1use crate::prelude::*;
5use crate::{Result, WasmFeatures};
6use core::borrow::Borrow;
7use core::cmp::Ordering;
8use core::fmt;
9use core::hash::{Hash, Hasher};
10use core::ops::Deref;
11use semver::Version;
12
13#[derive(Debug, Eq)]
22#[repr(transparent)]
23pub struct KebabStr(str);
24
25impl KebabStr {
26 pub fn new<'a>(s: impl AsRef<str> + 'a) -> Option<&'a Self> {
30 let s = Self::new_unchecked(s);
31 if s.is_kebab_case() {
32 Some(s)
33 } else {
34 None
35 }
36 }
37
38 pub(crate) fn new_unchecked<'a>(s: impl AsRef<str> + 'a) -> &'a Self {
39 #[allow(unsafe_code)]
42 unsafe {
43 core::mem::transmute::<_, &Self>(s.as_ref())
44 }
45 }
46
47 pub fn as_str(&self) -> &str {
49 &self.0
50 }
51
52 pub fn to_kebab_string(&self) -> KebabString {
54 KebabString(self.to_string())
55 }
56
57 fn is_kebab_case(&self) -> bool {
58 let mut lower = false;
59 let mut upper = false;
60 for c in self.chars() {
61 match c {
62 'a'..='z' if !lower && !upper => lower = true,
63 'A'..='Z' if !lower && !upper => upper = true,
64 'a'..='z' if lower => {}
65 'A'..='Z' if upper => {}
66 '0'..='9' if lower || upper => {}
67 '-' if lower || upper => {
68 lower = false;
69 upper = false;
70 }
71 _ => return false,
72 }
73 }
74
75 !self.is_empty() && !self.ends_with('-')
76 }
77}
78
79impl Deref for KebabStr {
80 type Target = str;
81
82 fn deref(&self) -> &str {
83 self.as_str()
84 }
85}
86
87impl PartialEq for KebabStr {
88 fn eq(&self, other: &Self) -> bool {
89 if self.len() != other.len() {
90 return false;
91 }
92
93 self.chars()
94 .zip(other.chars())
95 .all(|(a, b)| a.to_ascii_lowercase() == b.to_ascii_lowercase())
96 }
97}
98
99impl PartialEq<KebabString> for KebabStr {
100 fn eq(&self, other: &KebabString) -> bool {
101 self.eq(other.as_kebab_str())
102 }
103}
104
105impl Ord for KebabStr {
106 fn cmp(&self, other: &Self) -> Ordering {
107 let self_chars = self.chars().map(|c| c.to_ascii_lowercase());
108 let other_chars = other.chars().map(|c| c.to_ascii_lowercase());
109 self_chars.cmp(other_chars)
110 }
111}
112
113impl PartialOrd for KebabStr {
114 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
115 Some(self.cmp(other))
116 }
117}
118
119impl Hash for KebabStr {
120 fn hash<H: Hasher>(&self, state: &mut H) {
121 self.len().hash(state);
122
123 for b in self.chars() {
124 b.to_ascii_lowercase().hash(state);
125 }
126 }
127}
128
129impl fmt::Display for KebabStr {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 (self as &str).fmt(f)
132 }
133}
134
135impl ToOwned for KebabStr {
136 type Owned = KebabString;
137
138 fn to_owned(&self) -> Self::Owned {
139 self.to_kebab_string()
140 }
141}
142
143#[derive(Debug, Clone, Eq)]
152pub struct KebabString(String);
153
154impl KebabString {
155 pub fn new(s: impl Into<String>) -> Option<Self> {
159 let s = s.into();
160 if KebabStr::new(&s).is_some() {
161 Some(Self(s))
162 } else {
163 None
164 }
165 }
166
167 pub fn as_str(&self) -> &str {
169 self.0.as_str()
170 }
171
172 pub fn as_kebab_str(&self) -> &KebabStr {
174 KebabStr::new_unchecked(self.as_str())
176 }
177}
178
179impl Deref for KebabString {
180 type Target = KebabStr;
181
182 fn deref(&self) -> &Self::Target {
183 self.as_kebab_str()
184 }
185}
186
187impl Borrow<KebabStr> for KebabString {
188 fn borrow(&self) -> &KebabStr {
189 self.as_kebab_str()
190 }
191}
192
193impl Ord for KebabString {
194 fn cmp(&self, other: &Self) -> Ordering {
195 self.as_kebab_str().cmp(other.as_kebab_str())
196 }
197}
198
199impl PartialOrd for KebabString {
200 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
201 self.as_kebab_str().partial_cmp(other.as_kebab_str())
202 }
203}
204
205impl PartialEq for KebabString {
206 fn eq(&self, other: &Self) -> bool {
207 self.as_kebab_str().eq(other.as_kebab_str())
208 }
209}
210
211impl PartialEq<KebabStr> for KebabString {
212 fn eq(&self, other: &KebabStr) -> bool {
213 self.as_kebab_str().eq(other)
214 }
215}
216
217impl Hash for KebabString {
218 fn hash<H: Hasher>(&self, state: &mut H) {
219 self.as_kebab_str().hash(state)
220 }
221}
222
223impl fmt::Display for KebabString {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 self.as_kebab_str().fmt(f)
226 }
227}
228
229impl From<KebabString> for String {
230 fn from(s: KebabString) -> String {
231 s.0
232 }
233}
234
235#[derive(Clone)]
255pub struct ComponentName {
256 raw: String,
257 kind: ParsedComponentNameKind,
258}
259
260#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
261enum ParsedComponentNameKind {
262 Label,
263 Constructor,
264 Method,
265 Static,
266 Interface,
267 Dependency,
268 Url,
269 Hash,
270}
271
272#[derive(Debug, Clone)]
274pub enum ComponentNameKind<'a> {
275 Label(&'a KebabStr),
277 Constructor(&'a KebabStr),
279 #[allow(missing_docs)]
281 Method(ResourceFunc<'a>),
282 #[allow(missing_docs)]
284 Static(ResourceFunc<'a>),
285 #[allow(missing_docs)]
287 Interface(InterfaceName<'a>),
288 #[allow(missing_docs)]
290 Dependency(DependencyName<'a>),
291 #[allow(missing_docs)]
293 Url(UrlName<'a>),
294 #[allow(missing_docs)]
296 Hash(HashName<'a>),
297}
298
299const CONSTRUCTOR: &str = "[constructor]";
300const METHOD: &str = "[method]";
301const STATIC: &str = "[static]";
302
303impl ComponentName {
304 pub fn new(name: &str, offset: usize) -> Result<ComponentName> {
307 Self::new_with_features(name, offset, WasmFeatures::default())
308 }
309
310 pub fn new_with_features(name: &str, offset: usize, features: WasmFeatures) -> Result<Self> {
316 let mut parser = ComponentNameParser {
317 next: name,
318 offset,
319 features,
320 };
321 let kind = parser.parse()?;
322 if !parser.next.is_empty() {
323 bail!(offset, "trailing characters found: `{}`", parser.next);
324 }
325 Ok(ComponentName {
326 raw: name.to_string(),
327 kind,
328 })
329 }
330
331 pub fn kind(&self) -> ComponentNameKind<'_> {
333 use ComponentNameKind::*;
334 use ParsedComponentNameKind as PK;
335 match self.kind {
336 PK::Label => Label(KebabStr::new_unchecked(&self.raw)),
337 PK::Constructor => Constructor(KebabStr::new_unchecked(&self.raw[CONSTRUCTOR.len()..])),
338 PK::Method => Method(ResourceFunc(&self.raw[METHOD.len()..])),
339 PK::Static => Static(ResourceFunc(&self.raw[STATIC.len()..])),
340 PK::Interface => Interface(InterfaceName(&self.raw)),
341 PK::Dependency => Dependency(DependencyName(&self.raw)),
342 PK::Url => Url(UrlName(&self.raw)),
343 PK::Hash => Hash(HashName(&self.raw)),
344 }
345 }
346
347 pub fn as_str(&self) -> &str {
349 &self.raw
350 }
351}
352
353impl From<ComponentName> for String {
354 fn from(name: ComponentName) -> String {
355 name.raw
356 }
357}
358
359impl Hash for ComponentName {
360 fn hash<H: Hasher>(&self, hasher: &mut H) {
361 self.kind().hash(hasher)
362 }
363}
364
365impl PartialEq for ComponentName {
366 fn eq(&self, other: &ComponentName) -> bool {
367 self.kind().eq(&other.kind())
368 }
369}
370
371impl Eq for ComponentName {}
372
373impl Ord for ComponentName {
374 fn cmp(&self, other: &ComponentName) -> Ordering {
375 self.kind().cmp(&other.kind())
376 }
377}
378
379impl PartialOrd for ComponentName {
380 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
381 self.kind.partial_cmp(&other.kind)
382 }
383}
384
385impl fmt::Display for ComponentName {
386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387 self.raw.fmt(f)
388 }
389}
390
391impl fmt::Debug for ComponentName {
392 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393 self.raw.fmt(f)
394 }
395}
396
397impl ComponentNameKind<'_> {
398 fn kind(&self) -> ParsedComponentNameKind {
400 match self {
401 Self::Label(_) => ParsedComponentNameKind::Label,
402 Self::Constructor(_) => ParsedComponentNameKind::Constructor,
403 Self::Method(_) => ParsedComponentNameKind::Method,
404 Self::Static(_) => ParsedComponentNameKind::Static,
405 Self::Interface(_) => ParsedComponentNameKind::Interface,
406 Self::Dependency(_) => ParsedComponentNameKind::Dependency,
407 Self::Url(_) => ParsedComponentNameKind::Url,
408 Self::Hash(_) => ParsedComponentNameKind::Hash,
409 }
410 }
411}
412
413impl Ord for ComponentNameKind<'_> {
414 fn cmp(&self, other: &Self) -> Ordering {
415 match self.kind().cmp(&other.kind()) {
416 Ordering::Equal => (),
417 unequal => return unequal,
418 }
419 match (self, other) {
420 (ComponentNameKind::Label(lhs), ComponentNameKind::Label(rhs)) => lhs.cmp(rhs),
421 (ComponentNameKind::Constructor(lhs), ComponentNameKind::Constructor(rhs)) => {
422 lhs.cmp(rhs)
423 }
424 (ComponentNameKind::Method(lhs), ComponentNameKind::Method(rhs)) => lhs.cmp(rhs),
425 (ComponentNameKind::Method(lhs), ComponentNameKind::Static(rhs)) => lhs.cmp(rhs),
426 (ComponentNameKind::Static(lhs), ComponentNameKind::Method(rhs)) => lhs.cmp(rhs),
427 (ComponentNameKind::Static(lhs), ComponentNameKind::Static(rhs)) => lhs.cmp(rhs),
428 (ComponentNameKind::Interface(lhs), ComponentNameKind::Interface(rhs)) => lhs.cmp(rhs),
429 (ComponentNameKind::Dependency(lhs), ComponentNameKind::Dependency(rhs)) => {
430 lhs.cmp(rhs)
431 }
432 (ComponentNameKind::Url(lhs), ComponentNameKind::Url(rhs)) => lhs.cmp(rhs),
433 (ComponentNameKind::Hash(lhs), ComponentNameKind::Hash(rhs)) => lhs.cmp(rhs),
434 _ => unreachable!("already compared for different kinds above"),
435 }
436 }
437}
438
439impl PartialOrd for ComponentNameKind<'_> {
440 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
441 Some(self.cmp(other))
442 }
443}
444
445impl Hash for ComponentNameKind<'_> {
446 fn hash<H: Hasher>(&self, hasher: &mut H) {
447 use ComponentNameKind::*;
448 match self {
449 Label(name) => (0u8, name).hash(hasher),
450 Constructor(name) => (1u8, name).hash(hasher),
451 Method(name) | Static(name) => (2u8, name).hash(hasher),
453 Interface(name) => (3u8, name).hash(hasher),
454 Dependency(name) => (4u8, name).hash(hasher),
455 Url(name) => (5u8, name).hash(hasher),
456 Hash(name) => (6u8, name).hash(hasher),
457 }
458 }
459}
460
461impl PartialEq for ComponentNameKind<'_> {
462 fn eq(&self, other: &ComponentNameKind<'_>) -> bool {
463 use ComponentNameKind::*;
464 match (self, other) {
465 (Label(a), Label(b)) => a == b,
466 (Label(_), _) => false,
467 (Constructor(a), Constructor(b)) => a == b,
468 (Constructor(_), _) => false,
469
470 (Method(a), Method(b))
473 | (Static(a), Static(b))
474 | (Method(a), Static(b))
475 | (Static(a), Method(b)) => a == b,
476
477 (Method(_), _) => false,
478 (Static(_), _) => false,
479
480 (Interface(a), Interface(b)) => a == b,
481 (Interface(_), _) => false,
482 (Dependency(a), Dependency(b)) => a == b,
483 (Dependency(_), _) => false,
484 (Url(a), Url(b)) => a == b,
485 (Url(_), _) => false,
486 (Hash(a), Hash(b)) => a == b,
487 (Hash(_), _) => false,
488 }
489 }
490}
491
492impl Eq for ComponentNameKind<'_> {}
493
494#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
496pub struct ResourceFunc<'a>(&'a str);
497
498impl<'a> ResourceFunc<'a> {
499 pub fn as_str(&self) -> &'a str {
501 self.0
502 }
503
504 pub fn resource(&self) -> &'a KebabStr {
506 let dot = self.0.find('.').unwrap();
507 KebabStr::new_unchecked(&self.0[..dot])
508 }
509}
510
511#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
513pub struct InterfaceName<'a>(&'a str);
514
515impl<'a> InterfaceName<'a> {
516 pub fn as_str(&self) -> &'a str {
518 self.0
519 }
520
521 pub fn namespace(&self) -> &'a KebabStr {
523 let colon = self.0.rfind(':').unwrap();
524 KebabStr::new_unchecked(&self.0[..colon])
525 }
526
527 pub fn package(&self) -> &'a KebabStr {
529 let colon = self.0.rfind(':').unwrap();
530 let slash = self.0.find('/').unwrap();
531 KebabStr::new_unchecked(&self.0[colon + 1..slash])
532 }
533
534 pub fn interface(&self) -> &'a KebabStr {
536 let projection = self.projection();
537 let slash = projection.find('/').unwrap_or(projection.len());
538 KebabStr::new_unchecked(&projection[..slash])
539 }
540
541 pub fn projection(&self) -> &'a KebabStr {
543 let slash = self.0.find('/').unwrap();
544 let at = self.0.find('@').unwrap_or(self.0.len());
545 KebabStr::new_unchecked(&self.0[slash + 1..at])
546 }
547
548 pub fn version(&self) -> Option<Version> {
550 let at = self.0.find('@')?;
551 Some(Version::parse(&self.0[at + 1..]).unwrap())
552 }
553}
554
555#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
558pub struct DependencyName<'a>(&'a str);
559
560impl<'a> DependencyName<'a> {
561 pub fn as_str(&self) -> &'a str {
563 self.0
564 }
565}
566
567#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
569pub struct UrlName<'a>(&'a str);
570
571impl<'a> UrlName<'a> {
572 pub fn as_str(&self) -> &'a str {
574 self.0
575 }
576}
577
578#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
580pub struct HashName<'a>(&'a str);
581
582impl<'a> HashName<'a> {
583 pub fn as_str(&self) -> &'a str {
585 self.0
586 }
587}
588
589struct ComponentNameParser<'a> {
595 next: &'a str,
596 offset: usize,
597 features: WasmFeatures,
598}
599
600impl<'a> ComponentNameParser<'a> {
601 fn parse(&mut self) -> Result<ParsedComponentNameKind> {
602 if self.eat_str(CONSTRUCTOR) {
603 self.expect_kebab()?;
604 return Ok(ParsedComponentNameKind::Constructor);
605 }
606 if self.eat_str(METHOD) {
607 let resource = self.take_until('.')?;
608 self.kebab(resource)?;
609 self.expect_kebab()?;
610 return Ok(ParsedComponentNameKind::Method);
611 }
612 if self.eat_str(STATIC) {
613 let resource = self.take_until('.')?;
614 self.kebab(resource)?;
615 self.expect_kebab()?;
616 return Ok(ParsedComponentNameKind::Static);
617 }
618
619 if self.eat_str("unlocked-dep=") {
621 self.expect_str("<")?;
622 self.pkg_name_query()?;
623 self.expect_str(">")?;
624 return Ok(ParsedComponentNameKind::Dependency);
625 }
626
627 if self.eat_str("locked-dep=") {
629 self.expect_str("<")?;
630 self.pkg_name(false)?;
631 self.expect_str(">")?;
632 self.eat_optional_hash()?;
633 return Ok(ParsedComponentNameKind::Dependency);
634 }
635
636 if self.eat_str("url=") {
638 self.expect_str("<")?;
639 let url = self.take_up_to('>')?;
640 if url.contains('<') {
641 bail!(self.offset, "url cannot contain `<`");
642 }
643 self.expect_str(">")?;
644 self.eat_optional_hash()?;
645 return Ok(ParsedComponentNameKind::Url);
646 }
647
648 if self.eat_str("integrity=") {
650 self.expect_str("<")?;
651 let _hash = self.parse_hash()?;
652 self.expect_str(">")?;
653 return Ok(ParsedComponentNameKind::Hash);
654 }
655
656 if self.next.contains(':') {
657 self.pkg_name(true)?;
658 Ok(ParsedComponentNameKind::Interface)
659 } else {
660 self.expect_kebab()?;
661 Ok(ParsedComponentNameKind::Label)
662 }
663 }
664
665 fn pkg_name_query(&mut self) -> Result<()> {
667 self.pkg_path(false)?;
668
669 if self.eat_str("@") {
670 if self.eat_str("*") {
671 return Ok(());
672 }
673
674 self.expect_str("{")?;
675 let range = self.take_up_to('}')?;
676 self.expect_str("}")?;
677 self.semver_range(range)?;
678 }
679
680 Ok(())
681 }
682
683 fn pkg_name(&mut self, require_projection: bool) -> Result<()> {
685 self.pkg_path(require_projection)?;
686
687 if self.eat_str("@") {
688 let version = match self.eat_up_to('>') {
689 Some(version) => version,
690 None => self.take_rest(),
691 };
692
693 self.semver(version)?;
694 }
695
696 Ok(())
697 }
698
699 fn pkg_path(&mut self, require_projection: bool) -> Result<()> {
701 self.take_lowercase_kebab()?;
703 self.expect_str(":")?;
704 self.take_lowercase_kebab()?;
705
706 if self.features.component_model_nested_names() {
707 while self.next.starts_with(':') {
709 self.expect_str(":")?;
710 self.take_lowercase_kebab()?;
711 }
712 }
713
714 if self.next.starts_with('/') {
716 self.expect_str("/")?;
717 self.take_kebab()?;
718
719 if self.features.component_model_nested_names() {
720 while self.next.starts_with('/') {
721 self.expect_str("/")?;
722 self.take_kebab()?;
723 }
724 }
725 } else if require_projection {
726 bail!(self.offset, "expected `/` after package name");
727 }
728
729 Ok(())
730 }
731
732 fn semver_range(&self, range: &str) -> Result<()> {
739 if range == "*" {
740 return Ok(());
741 }
742
743 if let Some(range) = range.strip_prefix(">=") {
744 let (lower, upper) = range
745 .split_once(' ')
746 .map(|(l, u)| (l, Some(u)))
747 .unwrap_or((range, None));
748 self.semver(lower)?;
749
750 if let Some(upper) = upper {
751 match upper.strip_prefix('<') {
752 Some(upper) => {
753 self.semver(upper)?;
754 }
755 None => bail!(
756 self.offset,
757 "expected `<` at start of version range upper bounds"
758 ),
759 }
760 }
761 } else if let Some(upper) = range.strip_prefix('<') {
762 self.semver(upper)?;
763 } else {
764 bail!(
765 self.offset,
766 "expected `>=` or `<` at start of version range"
767 );
768 }
769
770 Ok(())
771 }
772
773 fn parse_hash(&mut self) -> Result<&'a str> {
774 let integrity = self.take_up_to('>')?;
775 let mut any = false;
776 for hash in integrity.split_whitespace() {
777 any = true;
778 let rest = hash
779 .strip_prefix("sha256")
780 .or_else(|| hash.strip_prefix("sha384"))
781 .or_else(|| hash.strip_prefix("sha512"));
782 let rest = match rest {
783 Some(s) => s,
784 None => bail!(self.offset, "unrecognized hash algorithm: `{hash}`"),
785 };
786 let rest = match rest.strip_prefix('-') {
787 Some(s) => s,
788 None => bail!(self.offset, "expected `-` after hash algorithm: {hash}"),
789 };
790 let (base64, _options) = match rest.find('?') {
791 Some(i) => (&rest[..i], Some(&rest[i + 1..])),
792 None => (rest, None),
793 };
794 if !is_base64(base64) {
795 bail!(self.offset, "not valid base64: `{base64}`");
796 }
797 }
798 if !any {
799 bail!(self.offset, "integrity hash cannot be empty");
800 }
801 Ok(integrity)
802 }
803
804 fn eat_optional_hash(&mut self) -> Result<Option<&'a str>> {
805 if !self.eat_str(",") {
806 return Ok(None);
807 }
808 self.expect_str("integrity=<")?;
809 let ret = self.parse_hash()?;
810 self.expect_str(">")?;
811 Ok(Some(ret))
812 }
813
814 fn eat_str(&mut self, prefix: &str) -> bool {
815 match self.next.strip_prefix(prefix) {
816 Some(rest) => {
817 self.next = rest;
818 true
819 }
820 None => false,
821 }
822 }
823
824 fn expect_str(&mut self, prefix: &str) -> Result<()> {
825 if self.eat_str(prefix) {
826 Ok(())
827 } else {
828 bail!(self.offset, "expected `{prefix}` at `{}`", self.next);
829 }
830 }
831
832 fn eat_until(&mut self, c: char) -> Option<&'a str> {
833 let ret = self.eat_up_to(c);
834 if ret.is_some() {
835 self.next = &self.next[c.len_utf8()..];
836 }
837 ret
838 }
839
840 fn eat_up_to(&mut self, c: char) -> Option<&'a str> {
841 let i = self.next.find(c)?;
842 let (a, b) = self.next.split_at(i);
843 self.next = b;
844 Some(a)
845 }
846
847 fn kebab(&self, s: &'a str) -> Result<&'a KebabStr> {
848 match KebabStr::new(s) {
849 Some(name) => Ok(name),
850 None => bail!(self.offset, "`{s}` is not in kebab case"),
851 }
852 }
853
854 fn semver(&self, s: &str) -> Result<Version> {
855 match Version::parse(s) {
856 Ok(v) => Ok(v),
857 Err(e) => bail!(self.offset, "`{s}` is not a valid semver: {e}"),
858 }
859 }
860
861 fn take_until(&mut self, c: char) -> Result<&'a str> {
862 match self.eat_until(c) {
863 Some(s) => Ok(s),
864 None => bail!(self.offset, "failed to find `{c}` character"),
865 }
866 }
867
868 fn take_up_to(&mut self, c: char) -> Result<&'a str> {
869 match self.eat_up_to(c) {
870 Some(s) => Ok(s),
871 None => bail!(self.offset, "failed to find `{c}` character"),
872 }
873 }
874
875 fn take_rest(&mut self) -> &'a str {
876 let ret = self.next;
877 self.next = "";
878 ret
879 }
880
881 fn take_kebab(&mut self) -> Result<&'a KebabStr> {
882 self.next
883 .find(|c| !matches!(c, 'a'..='z' | 'A'..='Z' | '0'..='9' | '-'))
884 .map(|i| {
885 let (kebab, next) = self.next.split_at(i);
886 self.next = next;
887 self.kebab(kebab)
888 })
889 .unwrap_or_else(|| self.expect_kebab())
890 }
891
892 fn take_lowercase_kebab(&mut self) -> Result<&'a KebabStr> {
893 let kebab = self.take_kebab()?;
894 if let Some(c) = kebab
895 .chars()
896 .find(|c| c.is_alphabetic() && !c.is_lowercase())
897 {
898 bail!(
899 self.offset,
900 "character `{c}` is not lowercase in package name/namespace"
901 );
902 }
903 Ok(kebab)
904 }
905
906 fn expect_kebab(&mut self) -> Result<&'a KebabStr> {
907 let s = self.take_rest();
908 self.kebab(s)
909 }
910}
911
912fn is_base64(s: &str) -> bool {
913 if s.is_empty() {
914 return false;
915 }
916 let mut equals = 0;
917 for (i, byte) in s.as_bytes().iter().enumerate() {
918 match byte {
919 b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'+' | b'/' if equals == 0 => {}
920 b'=' if i > 0 && equals < 2 => equals += 1,
921 _ => return false,
922 }
923 }
924 true
925}
926
927#[cfg(test)]
928mod tests {
929 use super::*;
930 use std::collections::HashSet;
931
932 fn parse_kebab_name(s: &str) -> Option<ComponentName> {
933 ComponentName::new(s, 0).ok()
934 }
935
936 #[test]
937 fn kebab_smoke() {
938 assert!(KebabStr::new("").is_none());
939 assert!(KebabStr::new("a").is_some());
940 assert!(KebabStr::new("aB").is_none());
941 assert!(KebabStr::new("a-B").is_some());
942 assert!(KebabStr::new("a-").is_none());
943 assert!(KebabStr::new("-").is_none());
944 assert!(KebabStr::new("ΒΆ").is_none());
945 assert!(KebabStr::new("0").is_none());
946 assert!(KebabStr::new("a0").is_some());
947 assert!(KebabStr::new("a-0").is_none());
948 }
949
950 #[test]
951 fn name_smoke() {
952 assert!(parse_kebab_name("a").is_some());
953 assert!(parse_kebab_name("[foo]a").is_none());
954 assert!(parse_kebab_name("[constructor]a").is_some());
955 assert!(parse_kebab_name("[method]a").is_none());
956 assert!(parse_kebab_name("[method]a.b").is_some());
957 assert!(parse_kebab_name("[method]a.b.c").is_none());
958 assert!(parse_kebab_name("[static]a.b").is_some());
959 assert!(parse_kebab_name("[static]a").is_none());
960 }
961
962 #[test]
963 fn name_equality() {
964 assert_eq!(parse_kebab_name("a"), parse_kebab_name("a"));
965 assert_ne!(parse_kebab_name("a"), parse_kebab_name("b"));
966 assert_eq!(
967 parse_kebab_name("[constructor]a"),
968 parse_kebab_name("[constructor]a")
969 );
970 assert_ne!(
971 parse_kebab_name("[constructor]a"),
972 parse_kebab_name("[constructor]b")
973 );
974 assert_eq!(
975 parse_kebab_name("[method]a.b"),
976 parse_kebab_name("[method]a.b")
977 );
978 assert_ne!(
979 parse_kebab_name("[method]a.b"),
980 parse_kebab_name("[method]b.b")
981 );
982 assert_eq!(
983 parse_kebab_name("[static]a.b"),
984 parse_kebab_name("[static]a.b")
985 );
986 assert_ne!(
987 parse_kebab_name("[static]a.b"),
988 parse_kebab_name("[static]b.b")
989 );
990
991 assert_eq!(
992 parse_kebab_name("[static]a.b"),
993 parse_kebab_name("[method]a.b")
994 );
995 assert_eq!(
996 parse_kebab_name("[method]a.b"),
997 parse_kebab_name("[static]a.b")
998 );
999
1000 assert_ne!(
1001 parse_kebab_name("[method]b.b"),
1002 parse_kebab_name("[static]a.b")
1003 );
1004
1005 let mut s = HashSet::new();
1006 assert!(s.insert(parse_kebab_name("a")));
1007 assert!(s.insert(parse_kebab_name("[constructor]a")));
1008 assert!(s.insert(parse_kebab_name("[method]a.b")));
1009 assert!(!s.insert(parse_kebab_name("[static]a.b")));
1010 assert!(s.insert(parse_kebab_name("[static]b.b")));
1011 }
1012}