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