1use flyweights::FlyStr;
10use lazy_static::lazy_static;
11use serde::{de, ser, Deserialize, Serialize};
12use std::borrow::Borrow;
13use std::ffi::CString;
14use std::fmt::{self, Display};
15use std::path::PathBuf;
16use std::str::FromStr;
17use std::{cmp, iter};
18use thiserror::Error;
19use {fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_io as fio};
20
21lazy_static! {
22 static ref DEFAULT_BASE_URL: url::Url = url::Url::parse("relative:///").unwrap();
25}
26
27#[macro_export]
63macro_rules! symmetrical_enums {
64 ($a:ty , $b:ty, $($id: ident),*) => {
65 impl From<$a> for $b {
66 fn from(input: $a) -> Self {
67 match input {
68 $( <$a>::$id => <$b>::$id, )*
69 }
70 }
71 }
72
73 impl From<$b> for $a {
74 fn from(input: $b) -> Self {
75 match input {
76 $( <$b>::$id => <$a>::$id, )*
77 }
78 }
79 }
80 };
81}
82
83#[derive(Serialize, Clone, Deserialize, Debug, Error, PartialEq, Eq)]
85pub enum ParseError {
86 #[error("invalid value")]
88 InvalidValue,
89 #[error("invalid URL: {details}")]
91 InvalidComponentUrl { details: String },
92 #[error("empty")]
94 Empty,
95 #[error("too long")]
97 TooLong,
98 #[error("no leading slash")]
100 NoLeadingSlash,
101 #[error("invalid path segment")]
103 InvalidSegment,
104}
105
106pub const MAX_NAME_LENGTH: usize = name::MAX_NAME_LENGTH;
107pub const MAX_LONG_NAME_LENGTH: usize = 1024;
108pub const MAX_PATH_LENGTH: usize = fio::MAX_PATH_LENGTH as usize;
109pub const MAX_URL_LENGTH: usize = 4096;
110
111pub const FLAGS_MAX_POSSIBLE_RIGHTS: fio::Flags = fio::PERM_READABLE
115 .union(fio::Flags::PERM_INHERIT_WRITE)
116 .union(fio::Flags::PERM_INHERIT_EXECUTE);
117
118pub type Name = BoundedName<MAX_NAME_LENGTH>;
121pub type LongName = BoundedName<MAX_LONG_NAME_LENGTH>;
123
124#[derive(Serialize, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
126pub struct BoundedName<const N: usize>(FlyStr);
127
128impl Name {
129 pub fn to_long(self) -> LongName {
130 BoundedName(self.0)
131 }
132}
133
134impl<const N: usize> BoundedName<N> {
135 pub fn new(name: impl AsRef<str> + Into<String>) -> Result<Self, ParseError> {
141 {
142 let name = name.as_ref();
143 if name.is_empty() {
144 return Err(ParseError::Empty);
145 }
146 if name.len() > N {
147 return Err(ParseError::TooLong);
148 }
149 let mut char_iter = name.chars();
150 let first_char = char_iter.next().unwrap();
151 if !first_char.is_ascii_alphanumeric() && first_char != '_' {
152 return Err(ParseError::InvalidValue);
153 }
154 let valid_fn = |c: char| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.';
155 if !char_iter.all(valid_fn) {
156 return Err(ParseError::InvalidValue);
157 }
158 }
159 Ok(Self(FlyStr::new(name)))
160 }
161
162 pub fn as_str(&self) -> &str {
163 &self.0
164 }
165
166 pub fn is_empty(&self) -> bool {
167 self.0.is_empty()
168 }
169
170 pub fn len(&self) -> usize {
171 self.0.len()
172 }
173}
174
175impl<const N: usize> AsRef<str> for BoundedName<N> {
176 fn as_ref(&self) -> &str {
177 self.as_str()
178 }
179}
180
181impl<const N: usize> Borrow<FlyStr> for BoundedName<N> {
182 fn borrow(&self) -> &FlyStr {
183 &self.0
184 }
185}
186
187impl<'a, const N: usize> From<BoundedName<N>> for FlyStr {
188 fn from(o: BoundedName<N>) -> Self {
189 o.0
190 }
191}
192
193impl<'a, const N: usize> From<&'a BoundedName<N>> for &'a FlyStr {
194 fn from(o: &'a BoundedName<N>) -> Self {
195 &o.0
196 }
197}
198
199impl<const N: usize> PartialEq<&str> for BoundedName<N> {
200 fn eq(&self, o: &&str) -> bool {
201 &*self.0 == *o
202 }
203}
204
205impl<const N: usize> PartialEq<String> for BoundedName<N> {
206 fn eq(&self, o: &String) -> bool {
207 &*self.0 == *o
208 }
209}
210
211impl<const N: usize> fmt::Display for BoundedName<N> {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 <FlyStr as fmt::Display>::fmt(&self.0, f)
214 }
215}
216
217impl<const N: usize> FromStr for BoundedName<N> {
218 type Err = ParseError;
219
220 fn from_str(name: &str) -> Result<Self, Self::Err> {
221 Self::new(name)
222 }
223}
224
225impl<const N: usize> From<BoundedName<N>> for String {
226 fn from(name: BoundedName<N>) -> String {
227 name.0.into()
228 }
229}
230
231impl From<Name> for LongName {
232 fn from(name: Name) -> Self {
233 Self(name.0)
234 }
235}
236
237impl<'de, const N: usize> de::Deserialize<'de> for BoundedName<N> {
238 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
239 where
240 D: de::Deserializer<'de>,
241 {
242 struct Visitor<const N: usize>;
243
244 impl<'de, const N: usize> de::Visitor<'de> for Visitor<N> {
245 type Value = BoundedName<{ N }>;
246
247 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 f.write_str(&format!(
249 "a non-empty string no more than {} characters in length, \
250 consisting of [A-Za-z0-9_.-] and starting with [A-Za-z0-9_]",
251 N
252 ))
253 }
254
255 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
256 where
257 E: de::Error,
258 {
259 s.parse().map_err(|err| match err {
260 ParseError::InvalidValue => E::invalid_value(
261 de::Unexpected::Str(s),
262 &"a name that consists of [A-Za-z0-9_.-] and starts with [A-Za-z0-9_]",
263 ),
264 ParseError::TooLong | ParseError::Empty => E::invalid_length(
265 s.len(),
266 &format!("a non-empty name no more than {} characters in length", N)
267 .as_str(),
268 ),
269 e => {
270 panic!("unexpected parse error: {:?}", e);
271 }
272 })
273 }
274 }
275 deserializer.deserialize_string(Visitor)
276 }
277}
278
279impl IterablePath for Name {
280 fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &Name> + Send {
281 iter::once(self)
282 }
283}
284
285impl IterablePath for &Name {
286 fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &Name> + Send {
287 iter::once(*self)
288 }
289}
290
291#[derive(Eq, Ord, PartialOrd, PartialEq, Hash, Clone)]
296pub struct NamespacePath(RelativePath);
297
298impl NamespacePath {
299 pub fn new(path: impl AsRef<str>) -> Result<Self, ParseError> {
301 let path = path.as_ref();
302 if path.is_empty() {
303 return Err(ParseError::Empty);
304 }
305 if path == "." {
306 return Err(ParseError::InvalidValue);
307 }
308 if !path.starts_with('/') {
309 return Err(ParseError::NoLeadingSlash);
310 }
311 if path.len() > MAX_PATH_LENGTH {
312 return Err(ParseError::TooLong);
313 }
314 if path == "/" {
315 Ok(Self(RelativePath::dot()))
316 } else {
317 let path: RelativePath = path[1..].parse()?;
318 if path.is_dot() {
319 return Err(ParseError::InvalidSegment);
321 }
322 Ok(Self(path))
323 }
324 }
325
326 pub fn root() -> Self {
328 Self(RelativePath::dot())
329 }
330
331 pub fn is_root(&self) -> bool {
332 self.0.is_dot()
333 }
334
335 pub fn split(&self) -> Vec<Name> {
337 self.0.split()
338 }
339
340 pub fn to_path_buf(&self) -> PathBuf {
341 PathBuf::from(self.to_string())
342 }
343
344 pub fn parent(&self) -> Option<Self> {
347 self.0.parent().map(|p| Self(p))
348 }
349
350 pub fn has_prefix(&self, prefix: &Self) -> bool {
358 let my_segments = self.split();
359 let prefix_segments = prefix.split();
360 prefix_segments.into_iter().zip(my_segments.into_iter()).all(|(a, b)| a == b)
361 }
362
363 pub fn basename(&self) -> Option<&Name> {
365 self.0.basename()
366 }
367
368 pub fn pop_front(&mut self) -> Option<Name> {
369 self.0.pop_front()
370 }
371}
372
373impl IterablePath for NamespacePath {
374 fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &Name> + Send {
375 self.0.iter_segments()
376 }
377}
378
379impl serde::ser::Serialize for NamespacePath {
380 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
381 where
382 S: serde::ser::Serializer,
383 {
384 self.to_string().serialize(serializer)
385 }
386}
387
388impl TryFrom<CString> for NamespacePath {
389 type Error = ParseError;
390
391 fn try_from(path: CString) -> Result<Self, ParseError> {
392 Self::new(path.into_string().map_err(|_| ParseError::InvalidValue)?)
393 }
394}
395
396impl From<NamespacePath> for CString {
397 fn from(path: NamespacePath) -> Self {
398 unsafe { CString::from_vec_unchecked(path.to_string().as_bytes().to_owned()) }
401 }
402}
403
404impl From<NamespacePath> for String {
405 fn from(path: NamespacePath) -> Self {
406 path.to_string()
407 }
408}
409
410impl FromStr for NamespacePath {
411 type Err = ParseError;
412
413 fn from_str(path: &str) -> Result<Self, Self::Err> {
414 Self::new(path)
415 }
416}
417
418impl fmt::Debug for NamespacePath {
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420 write!(f, "{}", self)
421 }
422}
423
424impl fmt::Display for NamespacePath {
425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426 if !self.0.is_dot() {
427 write!(f, "/{}", self.0)
428 } else {
429 write!(f, "/")
430 }
431 }
432}
433
434#[derive(Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
450pub struct Path(RelativePath);
451
452impl fmt::Debug for Path {
453 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
454 write!(f, "{}", self)
455 }
456}
457
458impl fmt::Display for Path {
459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460 write!(f, "/{}", self.0)
461 }
462}
463
464impl ser::Serialize for Path {
465 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
466 where
467 S: serde::ser::Serializer,
468 {
469 self.to_string().serialize(serializer)
470 }
471}
472
473impl Path {
474 pub fn new(path: impl AsRef<str>) -> Result<Self, ParseError> {
479 let path = path.as_ref();
480 if path.is_empty() {
481 return Err(ParseError::Empty);
482 }
483 if path == "/" || path == "." {
484 return Err(ParseError::InvalidValue);
485 }
486 if !path.starts_with('/') {
487 return Err(ParseError::NoLeadingSlash);
488 }
489 if path.len() > MAX_PATH_LENGTH {
490 return Err(ParseError::TooLong);
491 }
492 let path: RelativePath = path[1..].parse()?;
493 if path.is_dot() {
494 return Err(ParseError::InvalidSegment);
496 }
497 Ok(Self(path))
498 }
499
500 pub fn split(&self) -> Vec<Name> {
502 self.0.split()
503 }
504
505 pub fn to_path_buf(&self) -> PathBuf {
506 PathBuf::from(self.to_string())
507 }
508
509 pub fn parent(&self) -> NamespacePath {
512 let p = self.0.parent().expect("can't be root");
513 NamespacePath(p)
514 }
515
516 pub fn basename(&self) -> &Name {
517 self.0.basename().expect("can't be root")
518 }
519
520 pub fn extend(&mut self, other: RelativePath) {
521 self.0.extend(other);
522 }
523
524 pub fn push(&mut self, other: Name) {
525 self.0.push(other);
526 }
527}
528
529impl IterablePath for Path {
530 fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &Name> + Send {
531 Box::new(self.0.iter_segments())
532 }
533}
534
535impl From<Path> for NamespacePath {
536 fn from(value: Path) -> Self {
537 Self(value.0)
538 }
539}
540
541impl FromStr for Path {
542 type Err = ParseError;
543
544 fn from_str(path: &str) -> Result<Self, Self::Err> {
545 Self::new(path)
546 }
547}
548
549impl TryFrom<CString> for Path {
550 type Error = ParseError;
551
552 fn try_from(path: CString) -> Result<Self, ParseError> {
553 Self::new(path.into_string().map_err(|_| ParseError::InvalidValue)?)
554 }
555}
556
557impl From<Path> for CString {
558 fn from(path: Path) -> Self {
559 unsafe { CString::from_vec_unchecked(path.to_string().as_bytes().to_owned()) }
562 }
563}
564
565impl From<Path> for String {
566 fn from(path: Path) -> String {
567 path.to_string()
568 }
569}
570
571impl<'de> de::Deserialize<'de> for Path {
572 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
573 where
574 D: de::Deserializer<'de>,
575 {
576 struct Visitor;
577
578 impl<'de> de::Visitor<'de> for Visitor {
579 type Value = Path;
580
581 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582 f.write_str(
583 "a non-empty path no more than fuchsia.io/MAX_PATH_LENGTH characters \
584 in length, with a leading `/`, and containing no \
585 empty path segments",
586 )
587 }
588
589 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
590 where
591 E: de::Error,
592 {
593 s.parse().map_err(|err| {
594 match err {
595 ParseError::InvalidValue | ParseError::InvalidSegment | ParseError::NoLeadingSlash => E::invalid_value(
596 de::Unexpected::Str(s),
597 &"a path with leading `/` and non-empty segments, where each segment is no \
598 more than fuchsia.io/MAX_NAME_LENGTH bytes in length, cannot be . or .., \
599 and cannot contain embedded NULs",
600 ),
601 ParseError::TooLong | ParseError::Empty => E::invalid_length(
602 s.len(),
603 &"a non-empty path no more than fuchsia.io/MAX_PATH_LENGTH bytes \
604 in length",
605 ),
606 e => {
607 panic!("unexpected parse error: {:?}", e);
608 }
609 }
610 })
611 }
612 }
613 deserializer.deserialize_string(Visitor)
614 }
615}
616
617#[derive(Eq, Ord, PartialOrd, PartialEq, Hash, Clone, Default)]
619pub struct RelativePath {
620 segments: Vec<Name>,
621}
622
623impl RelativePath {
624 pub fn new(path: impl AsRef<str>) -> Result<Self, ParseError> {
626 let path: &str = path.as_ref();
627 if path == "." {
628 return Ok(Self::dot());
629 }
630 if path.is_empty() {
631 return Err(ParseError::Empty);
632 }
633 if path.len() > MAX_PATH_LENGTH {
634 return Err(ParseError::TooLong);
635 }
636 let segments = path
637 .split('/')
638 .map(|s| {
639 Name::new(s).map_err(|e| match e {
640 ParseError::Empty => ParseError::InvalidValue,
641 _ => ParseError::InvalidSegment,
642 })
643 })
644 .collect::<Result<Vec<_>, _>>()?;
645 Ok(Self { segments })
646 }
647
648 pub fn dot() -> Self {
649 Self { segments: vec![] }
650 }
651
652 pub fn is_dot(&self) -> bool {
653 self.segments.is_empty()
654 }
655
656 pub fn parent(&self) -> Option<Self> {
657 if self.segments.is_empty() {
658 None
659 } else {
660 let segments: Vec<_> =
661 self.segments[0..self.segments.len() - 1].into_iter().map(Clone::clone).collect();
662 Some(Self { segments })
663 }
664 }
665
666 pub fn split(&self) -> Vec<Name> {
667 self.segments.clone()
668 }
669
670 pub fn basename(&self) -> Option<&Name> {
671 self.segments.last()
672 }
673
674 pub fn to_path_buf(&self) -> PathBuf {
675 if self.is_dot() {
676 PathBuf::new()
677 } else {
678 PathBuf::from(self.to_string())
679 }
680 }
681
682 pub fn extend(&mut self, other: Self) {
683 self.segments.extend(other.segments);
684 }
685
686 pub fn push(&mut self, segment: Name) {
687 self.segments.push(segment);
688 }
689
690 pub fn pop_front(&mut self) -> Option<Name> {
691 if self.segments.is_empty() {
692 None
693 } else {
694 Some(self.segments.remove(0))
695 }
696 }
697}
698
699impl IterablePath for RelativePath {
700 fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &Name> + Send {
701 Box::new(self.segments.iter())
702 }
703}
704
705impl FromStr for RelativePath {
706 type Err = ParseError;
707
708 fn from_str(path: &str) -> Result<Self, Self::Err> {
709 Self::new(path)
710 }
711}
712
713impl From<RelativePath> for String {
714 fn from(path: RelativePath) -> String {
715 path.to_string()
716 }
717}
718
719impl From<Vec<Name>> for RelativePath {
720 fn from(segments: Vec<Name>) -> Self {
721 Self { segments }
722 }
723}
724
725impl fmt::Debug for RelativePath {
726 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
727 write!(f, "{}", self)
728 }
729}
730
731impl fmt::Display for RelativePath {
732 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
733 if self.is_dot() {
734 write!(f, ".")
735 } else {
736 write!(f, "{}", self.segments.iter().map(|s| s.as_str()).collect::<Vec<_>>().join("/"))
737 }
738 }
739}
740
741impl ser::Serialize for RelativePath {
742 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
743 where
744 S: serde::ser::Serializer,
745 {
746 self.to_string().serialize(serializer)
747 }
748}
749
750impl<'de> de::Deserialize<'de> for RelativePath {
751 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
752 where
753 D: de::Deserializer<'de>,
754 {
755 struct Visitor;
756
757 impl<'de> de::Visitor<'de> for Visitor {
758 type Value = RelativePath;
759
760 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
761 f.write_str(
762 "a non-empty path no more than fuchsia.io/MAX_PATH_LENGTH characters \
763 in length, not starting with `/`, and containing no empty path segments",
764 )
765 }
766
767 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
768 where
769 E: de::Error,
770 {
771 s.parse().map_err(|err| match err {
772 ParseError::InvalidValue
773 | ParseError::InvalidSegment
774 | ParseError::NoLeadingSlash => E::invalid_value(
775 de::Unexpected::Str(s),
776 &"a path with no leading `/` and non-empty segments",
777 ),
778 ParseError::TooLong | ParseError::Empty => E::invalid_length(
779 s.len(),
780 &"a non-empty path no more than fuchsia.io/MAX_PATH_LENGTH characters \
781 in length",
782 ),
783 e => {
784 panic!("unexpected parse error: {:?}", e);
785 }
786 })
787 }
788 }
789 deserializer.deserialize_string(Visitor)
790 }
791}
792
793#[derive(Debug, Clone, PartialEq, Eq)]
797pub struct BorrowedSeparatedPath<'a> {
798 pub dirname: &'a RelativePath,
799 pub basename: &'a Name,
800}
801
802impl BorrowedSeparatedPath<'_> {
803 pub fn to_owned(&self) -> SeparatedPath {
805 SeparatedPath { dirname: self.dirname.clone(), basename: self.basename.clone() }
806 }
807}
808
809impl fmt::Display for BorrowedSeparatedPath<'_> {
810 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
811 if !self.dirname.is_dot() {
812 write!(f, "{}/{}", self.dirname, self.basename)
813 } else {
814 write!(f, "{}", self.basename)
815 }
816 }
817}
818
819impl IterablePath for BorrowedSeparatedPath<'_> {
820 fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &Name> + Send {
821 Box::new(self.dirname.iter_segments().chain(iter::once(self.basename)))
822 }
823}
824
825#[derive(Debug, Clone, PartialEq, Eq)]
829pub struct SeparatedPath {
830 pub dirname: RelativePath,
831 pub basename: Name,
832}
833
834impl SeparatedPath {
835 pub fn as_ref(&self) -> BorrowedSeparatedPath<'_> {
837 BorrowedSeparatedPath { dirname: &self.dirname, basename: &self.basename }
838 }
839}
840
841impl IterablePath for SeparatedPath {
842 fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &Name> + Send {
843 Box::new(self.dirname.iter_segments().chain(iter::once(&self.basename)))
844 }
845}
846
847impl fmt::Display for SeparatedPath {
848 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
849 if !self.dirname.is_dot() {
850 write!(f, "{}/{}", self.dirname, self.basename)
851 } else {
852 write!(f, "{}", self.basename)
853 }
854 }
855}
856
857pub trait IterablePath: Clone + Send + Sync {
859 fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &Name> + Send;
861}
862
863#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
866pub struct Url(FlyStr);
867
868impl Url {
869 pub fn new(url: impl AsRef<str> + Into<String>) -> Result<Self, ParseError> {
873 Self::validate(url.as_ref())?;
874 Ok(Self(FlyStr::new(url)))
875 }
876
877 pub fn validate(url_str: &str) -> Result<(), ParseError> {
879 if url_str.is_empty() {
880 return Err(ParseError::Empty);
881 }
882 if url_str.len() > MAX_URL_LENGTH {
883 return Err(ParseError::TooLong);
884 }
885 match url::Url::parse(url_str).map(|url| (url, false)).or_else(|err| {
886 if err == url::ParseError::RelativeUrlWithoutBase {
887 DEFAULT_BASE_URL.join(url_str).map(|url| (url, true))
888 } else {
889 Err(err)
890 }
891 }) {
892 Ok((url, is_relative)) => {
893 let mut path = url.path();
894 if path.starts_with('/') {
895 path = &path[1..];
896 }
897 if is_relative && url.fragment().is_none() {
898 return Err(ParseError::InvalidComponentUrl {
910 details: "Relative URL has no resource fragment.".to_string(),
911 });
912 }
913 if url.host_str().unwrap_or("").is_empty()
914 && path.is_empty()
915 && url.fragment().is_none()
916 {
917 return Err(ParseError::InvalidComponentUrl {
918 details: "URL is missing either `host`, `path`, and/or `resource`."
919 .to_string(),
920 });
921 }
922 }
923 Err(err) => {
924 return Err(ParseError::InvalidComponentUrl {
925 details: format!("Malformed URL: {err:?}."),
926 });
927 }
928 }
929 Ok(())
931 }
932
933 pub fn is_relative(&self) -> bool {
934 matches!(url::Url::parse(&self.0), Err(url::ParseError::RelativeUrlWithoutBase))
935 }
936
937 pub fn scheme(&self) -> Option<String> {
938 url::Url::parse(&self.0).ok().map(|u| u.scheme().into())
939 }
940
941 pub fn resource(&self) -> Option<String> {
942 url::Url::parse(&self.0).ok().map(|u| u.fragment().map(str::to_string)).flatten()
943 }
944
945 pub fn as_str(&self) -> &str {
946 &*self.0
947 }
948}
949
950impl FromStr for Url {
951 type Err = ParseError;
952
953 fn from_str(url: &str) -> Result<Self, Self::Err> {
954 Self::new(url)
955 }
956}
957
958impl From<Url> for String {
959 fn from(url: Url) -> String {
960 url.0.into()
961 }
962}
963
964impl fmt::Display for Url {
965 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
966 fmt::Display::fmt(&self.0, f)
967 }
968}
969
970impl ser::Serialize for Url {
971 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
972 where
973 S: ser::Serializer,
974 {
975 self.to_string().serialize(serializer)
976 }
977}
978
979impl<'de> de::Deserialize<'de> for Url {
980 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
981 where
982 D: de::Deserializer<'de>,
983 {
984 struct Visitor;
985
986 impl<'de> de::Visitor<'de> for Visitor {
987 type Value = Url;
988
989 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
990 f.write_str("a non-empty URL no more than 4096 characters in length")
991 }
992
993 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
994 where
995 E: de::Error,
996 {
997 s.parse().map_err(|err| match err {
998 ParseError::InvalidComponentUrl { details: _ } => {
999 E::invalid_value(de::Unexpected::Str(s), &"a valid URL")
1000 }
1001 ParseError::TooLong | ParseError::Empty => E::invalid_length(
1002 s.len(),
1003 &"a non-empty URL no more than 4096 characters in length",
1004 ),
1005 e => {
1006 panic!("unexpected parse error: {:?}", e);
1007 }
1008 })
1009 }
1010 }
1011 deserializer.deserialize_string(Visitor)
1012 }
1013}
1014
1015impl PartialEq<&str> for Url {
1016 fn eq(&self, o: &&str) -> bool {
1017 &*self.0 == *o
1018 }
1019}
1020
1021impl PartialEq<String> for Url {
1022 fn eq(&self, o: &String) -> bool {
1023 &*self.0 == *o
1024 }
1025}
1026
1027#[derive(Serialize, Clone, Debug, Eq, Hash, PartialEq)]
1029pub struct UrlScheme(FlyStr);
1030
1031impl UrlScheme {
1032 pub fn new(url_scheme: impl AsRef<str> + Into<String>) -> Result<Self, ParseError> {
1037 Self::validate(url_scheme.as_ref())?;
1038 Ok(UrlScheme(FlyStr::new(url_scheme)))
1039 }
1040
1041 pub fn validate(url_scheme: &str) -> Result<(), ParseError> {
1044 if url_scheme.is_empty() {
1045 return Err(ParseError::Empty);
1046 }
1047 if url_scheme.len() > MAX_NAME_LENGTH {
1048 return Err(ParseError::TooLong);
1049 }
1050 let mut iter = url_scheme.chars();
1051 let first_char = iter.next().unwrap();
1052 if !first_char.is_ascii_lowercase() {
1053 return Err(ParseError::InvalidValue);
1054 }
1055 if let Some(_) = iter.find(|&c| {
1056 !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '.' && c != '+' && c != '-'
1057 }) {
1058 return Err(ParseError::InvalidValue);
1059 }
1060 Ok(())
1061 }
1062
1063 pub fn as_str(&self) -> &str {
1064 &*self.0
1065 }
1066}
1067
1068impl fmt::Display for UrlScheme {
1069 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1070 fmt::Display::fmt(&self.0, f)
1071 }
1072}
1073
1074impl FromStr for UrlScheme {
1075 type Err = ParseError;
1076
1077 fn from_str(s: &str) -> Result<Self, Self::Err> {
1078 Self::new(s)
1079 }
1080}
1081
1082impl From<UrlScheme> for String {
1083 fn from(u: UrlScheme) -> String {
1084 u.0.into()
1085 }
1086}
1087
1088impl<'de> de::Deserialize<'de> for UrlScheme {
1089 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1090 where
1091 D: de::Deserializer<'de>,
1092 {
1093 struct Visitor;
1094
1095 impl<'de> de::Visitor<'de> for Visitor {
1096 type Value = UrlScheme;
1097
1098 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1099 f.write_str("a non-empty URL scheme no more than 100 characters in length")
1100 }
1101
1102 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
1103 where
1104 E: de::Error,
1105 {
1106 s.parse().map_err(|err| match err {
1107 ParseError::InvalidValue => {
1108 E::invalid_value(de::Unexpected::Str(s), &"a valid URL scheme")
1109 }
1110 ParseError::TooLong | ParseError::Empty => E::invalid_length(
1111 s.len(),
1112 &"a non-empty URL scheme no more than 100 characters in length",
1113 ),
1114 e => {
1115 panic!("unexpected parse error: {:?}", e);
1116 }
1117 })
1118 }
1119 }
1120 deserializer.deserialize_string(Visitor)
1121 }
1122}
1123
1124#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
1128#[serde(rename_all = "snake_case")]
1129pub enum Durability {
1130 Transient,
1131 SingleRun,
1133}
1134
1135symmetrical_enums!(Durability, fdecl::Durability, Transient, SingleRun);
1136
1137#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
1141#[serde(rename_all = "snake_case")]
1142pub enum StartupMode {
1143 Lazy,
1144 Eager,
1145}
1146
1147impl StartupMode {
1148 pub fn is_lazy(&self) -> bool {
1149 matches!(self, StartupMode::Lazy)
1150 }
1151}
1152
1153symmetrical_enums!(StartupMode, fdecl::StartupMode, Lazy, Eager);
1154
1155impl Default for StartupMode {
1156 fn default() -> Self {
1157 Self::Lazy
1158 }
1159}
1160
1161#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
1165#[serde(rename_all = "snake_case")]
1166pub enum OnTerminate {
1167 None,
1168 Reboot,
1169}
1170
1171symmetrical_enums!(OnTerminate, fdecl::OnTerminate, None, Reboot);
1172
1173impl Default for OnTerminate {
1174 fn default() -> Self {
1175 Self::None
1176 }
1177}
1178
1179#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1184#[serde(rename_all = "snake_case")]
1185pub enum AllowedOffers {
1186 StaticOnly,
1187 StaticAndDynamic,
1188}
1189
1190symmetrical_enums!(AllowedOffers, fdecl::AllowedOffers, StaticOnly, StaticAndDynamic);
1191
1192impl Default for AllowedOffers {
1193 fn default() -> Self {
1194 Self::StaticOnly
1195 }
1196}
1197
1198#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1202#[serde(rename_all = "snake_case")]
1203pub enum DependencyType {
1204 Strong,
1205 Weak,
1206}
1207
1208symmetrical_enums!(DependencyType, fdecl::DependencyType, Strong, Weak);
1209
1210impl Default for DependencyType {
1211 fn default() -> Self {
1212 Self::Strong
1213 }
1214}
1215
1216#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Copy)]
1220#[serde(rename_all = "snake_case")]
1221pub enum Availability {
1222 Required,
1223 Optional,
1224 SameAsTarget,
1225 Transitional,
1226}
1227
1228symmetrical_enums!(
1229 Availability,
1230 fdecl::Availability,
1231 Required,
1232 Optional,
1233 SameAsTarget,
1234 Transitional
1235);
1236
1237impl Display for Availability {
1238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1239 match self {
1240 Availability::Required => write!(f, "Required"),
1241 Availability::Optional => write!(f, "Optional"),
1242 Availability::SameAsTarget => write!(f, "SameAsTarget"),
1243 Availability::Transitional => write!(f, "Transitional"),
1244 }
1245 }
1246}
1247
1248impl Default for Availability {
1250 fn default() -> Self {
1251 Self::Required
1252 }
1253}
1254
1255impl PartialOrd for Availability {
1256 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
1257 match (*self, *other) {
1258 (Availability::Transitional, Availability::Optional)
1259 | (Availability::Transitional, Availability::Required)
1260 | (Availability::Optional, Availability::Required) => Some(cmp::Ordering::Less),
1261 (Availability::Optional, Availability::Transitional)
1262 | (Availability::Required, Availability::Transitional)
1263 | (Availability::Required, Availability::Optional) => Some(cmp::Ordering::Greater),
1264 (Availability::Required, Availability::Required)
1265 | (Availability::Optional, Availability::Optional)
1266 | (Availability::Transitional, Availability::Transitional)
1267 | (Availability::SameAsTarget, Availability::SameAsTarget) => {
1268 Some(cmp::Ordering::Equal)
1269 }
1270 (Availability::SameAsTarget, _) | (_, Availability::SameAsTarget) => None,
1271 }
1272 }
1273}
1274
1275#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Copy)]
1281#[serde(rename_all = "snake_case")]
1282pub enum DeliveryType {
1283 Immediate,
1284 OnReadable,
1285}
1286
1287#[cfg(fuchsia_api_level_at_least = "HEAD")]
1288impl TryFrom<fdecl::DeliveryType> for DeliveryType {
1289 type Error = fdecl::DeliveryType;
1290
1291 fn try_from(value: fdecl::DeliveryType) -> Result<Self, Self::Error> {
1292 match value {
1293 fdecl::DeliveryType::Immediate => Ok(DeliveryType::Immediate),
1294 fdecl::DeliveryType::OnReadable => Ok(DeliveryType::OnReadable),
1295 fdecl::DeliveryTypeUnknown!() => Err(value),
1296 }
1297 }
1298}
1299
1300#[cfg(fuchsia_api_level_at_least = "HEAD")]
1301impl From<DeliveryType> for fdecl::DeliveryType {
1302 fn from(value: DeliveryType) -> Self {
1303 match value {
1304 DeliveryType::Immediate => fdecl::DeliveryType::Immediate,
1305 DeliveryType::OnReadable => fdecl::DeliveryType::OnReadable,
1306 }
1307 }
1308}
1309
1310impl Display for DeliveryType {
1311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1312 match self {
1313 DeliveryType::Immediate => write!(f, "Immediate"),
1314 DeliveryType::OnReadable => write!(f, "OnReadable"),
1315 }
1316 }
1317}
1318
1319impl Default for DeliveryType {
1320 fn default() -> Self {
1321 Self::Immediate
1322 }
1323}
1324
1325#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1326#[serde(rename_all = "snake_case")]
1327pub enum StorageId {
1328 StaticInstanceId,
1329 StaticInstanceIdOrMoniker,
1330}
1331
1332symmetrical_enums!(StorageId, fdecl::StorageId, StaticInstanceId, StaticInstanceIdOrMoniker);
1333
1334#[cfg(test)]
1335mod tests {
1336 use super::*;
1337 use assert_matches::assert_matches;
1338 use serde_json::json;
1339 use std::iter::repeat;
1340
1341 macro_rules! expect_ok {
1342 ($type_:ty, $($input:tt)+) => {
1343 assert_matches!(
1344 serde_json::from_str::<$type_>(&json!($($input)*).to_string()),
1345 Ok(_)
1346 );
1347 };
1348 }
1349
1350 macro_rules! expect_ok_no_serialize {
1351 ($type_:ty, $($input:tt)+) => {
1352 assert_matches!(
1353 ($($input)*).parse::<$type_>(),
1354 Ok(_)
1355 );
1356 };
1357 }
1358
1359 macro_rules! expect_err_no_serialize {
1360 ($type_:ty, $err:pat, $($input:tt)+) => {
1361 assert_matches!(
1362 ($($input)*).parse::<$type_>(),
1363 Err($err)
1364 );
1365 };
1366 }
1367
1368 macro_rules! expect_err {
1369 ($type_:ty, $err:pat, $($input:tt)+) => {
1370 assert_matches!(
1371 ($($input)*).parse::<$type_>(),
1372 Err($err)
1373 );
1374 assert_matches!(
1375 serde_json::from_str::<$type_>(&json!($($input)*).to_string()),
1376 Err(_)
1377 );
1378 };
1379 }
1380
1381 #[test]
1382 fn test_valid_name() {
1383 expect_ok!(Name, "foo");
1384 expect_ok!(Name, "Foo");
1385 expect_ok!(Name, "O123._-");
1386 expect_ok!(Name, "_O123._-");
1387 expect_ok!(Name, repeat("x").take(255).collect::<String>());
1388 }
1389
1390 #[test]
1391 fn test_invalid_name() {
1392 expect_err!(Name, ParseError::Empty, "");
1393 expect_err!(Name, ParseError::InvalidValue, "-");
1394 expect_err!(Name, ParseError::InvalidValue, ".");
1395 expect_err!(Name, ParseError::InvalidValue, "@&%^");
1396 expect_err!(Name, ParseError::TooLong, repeat("x").take(256).collect::<String>());
1397 }
1398
1399 #[test]
1400 fn test_valid_path() {
1401 expect_ok!(Path, "/foo");
1402 expect_ok!(Path, "/foo/bar");
1403 expect_ok!(Path, format!("/{}", repeat("x").take(100).collect::<String>()).as_str());
1404 expect_ok!(Path, repeat("/x").take(2047).collect::<String>().as_str());
1406 }
1407
1408 #[test]
1409 fn test_invalid_path() {
1410 expect_err!(Path, ParseError::Empty, "");
1411 expect_err!(Path, ParseError::InvalidValue, "/");
1412 expect_err!(Path, ParseError::InvalidValue, ".");
1413 expect_err!(Path, ParseError::NoLeadingSlash, "foo");
1414 expect_err!(Path, ParseError::NoLeadingSlash, "foo/");
1415 expect_err!(Path, ParseError::InvalidValue, "/foo/");
1416 expect_err!(Path, ParseError::InvalidValue, "/foo//bar");
1417 expect_err!(Path, ParseError::InvalidSegment, "/fo\0b/bar");
1418 expect_err!(Path, ParseError::InvalidSegment, "/.");
1419 expect_err!(Path, ParseError::InvalidSegment, "/foo/.");
1420 expect_err!(
1421 Path,
1422 ParseError::InvalidSegment,
1423 format!("/{}", repeat("x").take(256).collect::<String>()).as_str()
1424 );
1425 expect_err!(
1427 Path,
1428 ParseError::TooLong,
1429 repeat("/x").take(2048).collect::<String>().as_str()
1430 );
1431 }
1432
1433 #[test]
1434 fn test_valid_namespace_path() {
1435 expect_ok_no_serialize!(NamespacePath, "/");
1436 expect_ok_no_serialize!(NamespacePath, "/foo");
1437 expect_ok_no_serialize!(NamespacePath, "/foo/bar");
1438 expect_ok_no_serialize!(
1439 NamespacePath,
1440 format!("/{}", repeat("x").take(100).collect::<String>()).as_str()
1441 );
1442 expect_ok_no_serialize!(
1444 NamespacePath,
1445 repeat("/x").take(2047).collect::<String>().as_str()
1446 );
1447 }
1448
1449 #[test]
1450 fn test_invalid_namespace_path() {
1451 expect_err_no_serialize!(NamespacePath, ParseError::Empty, "");
1452 expect_err_no_serialize!(NamespacePath, ParseError::InvalidValue, ".");
1453 expect_err_no_serialize!(NamespacePath, ParseError::NoLeadingSlash, "foo");
1454 expect_err_no_serialize!(NamespacePath, ParseError::NoLeadingSlash, "foo/");
1455 expect_err_no_serialize!(NamespacePath, ParseError::InvalidValue, "/foo/");
1456 expect_err_no_serialize!(NamespacePath, ParseError::InvalidValue, "/foo//bar");
1457 expect_err_no_serialize!(NamespacePath, ParseError::InvalidSegment, "/fo\0b/bar");
1458 expect_err_no_serialize!(NamespacePath, ParseError::InvalidSegment, "/.");
1459 expect_err_no_serialize!(NamespacePath, ParseError::InvalidSegment, "/foo/.");
1460 expect_err_no_serialize!(
1461 NamespacePath,
1462 ParseError::InvalidSegment,
1463 format!("/{}", repeat("x").take(256).collect::<String>()).as_str()
1464 );
1465 expect_err_no_serialize!(
1467 Path,
1468 ParseError::TooLong,
1469 repeat("/x").take(2048).collect::<String>().as_str()
1470 );
1471 }
1472
1473 #[test]
1474 fn test_path_parent_basename() {
1475 let path = Path::new("/foo").unwrap();
1476 assert_eq!(
1477 (path.parent(), path.basename()),
1478 ("/".parse().unwrap(), &"foo".parse().unwrap())
1479 );
1480 let path = Path::new("/foo/bar").unwrap();
1481 assert_eq!(
1482 (path.parent(), path.basename()),
1483 ("/foo".parse().unwrap(), &"bar".parse().unwrap())
1484 );
1485 let path = Path::new("/foo/bar/baz").unwrap();
1486 assert_eq!(
1487 (path.parent(), path.basename()),
1488 ("/foo/bar".parse().unwrap(), &"baz".parse().unwrap())
1489 );
1490 }
1491
1492 #[test]
1493 fn test_separated_path() {
1494 fn test_path(path: SeparatedPath, in_expected_segments: Vec<&str>) {
1495 let expected_segments: Vec<_> =
1496 in_expected_segments.iter().map(|s| Name::new(*s).unwrap()).collect();
1497 let expected_segments: Vec<_> = expected_segments.iter().collect();
1498 let segments: Vec<_> = path.iter_segments().collect();
1499 assert_eq!(segments, expected_segments);
1500 let borrowed_path = path.as_ref();
1501 let segments: Vec<_> = borrowed_path.iter_segments().collect();
1502 assert_eq!(segments, expected_segments);
1503 let owned_path = borrowed_path.to_owned();
1504 assert_eq!(path, owned_path);
1505 let expected_fmt = in_expected_segments.join("/");
1506 assert_eq!(format!("{path}"), expected_fmt);
1507 assert_eq!(format!("{owned_path}"), expected_fmt);
1508 }
1509 test_path(
1510 SeparatedPath { dirname: ".".parse().unwrap(), basename: "foo".parse().unwrap() },
1511 vec!["foo"],
1512 );
1513 test_path(
1514 SeparatedPath { dirname: "bar".parse().unwrap(), basename: "foo".parse().unwrap() },
1515 vec!["bar", "foo"],
1516 );
1517 test_path(
1518 SeparatedPath { dirname: "bar/baz".parse().unwrap(), basename: "foo".parse().unwrap() },
1519 vec!["bar", "baz", "foo"],
1520 );
1521 }
1522
1523 #[test]
1524 fn test_valid_relative_path() {
1525 expect_ok!(RelativePath, ".");
1526 expect_ok!(RelativePath, "foo");
1527 expect_ok!(RelativePath, "foo/bar");
1528 expect_ok!(RelativePath, &format!("x{}", repeat("/x").take(2047).collect::<String>()));
1529 }
1530
1531 #[test]
1532 fn test_invalid_relative_path() {
1533 expect_err!(RelativePath, ParseError::Empty, "");
1534 expect_err!(RelativePath, ParseError::InvalidValue, "/");
1535 expect_err!(RelativePath, ParseError::InvalidValue, "/foo");
1536 expect_err!(RelativePath, ParseError::InvalidValue, "foo/");
1537 expect_err!(RelativePath, ParseError::InvalidValue, "/foo/");
1538 expect_err!(RelativePath, ParseError::InvalidValue, "foo//bar");
1539 expect_err!(RelativePath, ParseError::InvalidSegment, "..");
1540 expect_err!(RelativePath, ParseError::InvalidSegment, "foo/..");
1541 expect_err!(
1542 RelativePath,
1543 ParseError::TooLong,
1544 &format!("x{}", repeat("/x").take(2048).collect::<String>())
1545 );
1546 }
1547
1548 #[test]
1549 fn test_valid_url() {
1550 expect_ok!(Url, "a://foo");
1551 expect_ok!(Url, "#relative-url");
1552 expect_ok!(Url, &format!("a://{}", repeat("x").take(4092).collect::<String>()));
1553 }
1554
1555 #[test]
1556 fn test_invalid_url() {
1557 expect_err!(Url, ParseError::Empty, "");
1558 expect_err!(Url, ParseError::InvalidComponentUrl { .. }, "foo");
1559 expect_err!(
1560 Url,
1561 ParseError::TooLong,
1562 &format!("a://{}", repeat("x").take(4093).collect::<String>())
1563 );
1564 }
1565
1566 #[test]
1567 fn test_valid_url_scheme() {
1568 expect_ok!(UrlScheme, "fuch.sia-pkg+0");
1569 expect_ok!(UrlScheme, &format!("{}", repeat("f").take(255).collect::<String>()));
1570 }
1571
1572 #[test]
1573 fn test_invalid_url_scheme() {
1574 expect_err!(UrlScheme, ParseError::Empty, "");
1575 expect_err!(UrlScheme, ParseError::InvalidValue, "0fuch.sia-pkg+0");
1576 expect_err!(UrlScheme, ParseError::InvalidValue, "fuchsia_pkg");
1577 expect_err!(UrlScheme, ParseError::InvalidValue, "FUCHSIA-PKG");
1578 expect_err!(
1579 UrlScheme,
1580 ParseError::TooLong,
1581 &format!("{}", repeat("f").take(256).collect::<String>())
1582 );
1583 }
1584
1585 #[test]
1586 fn test_name_error_message() {
1587 let input = r#"
1588 "foo$"
1589 "#;
1590 let err = serde_json::from_str::<Name>(input).expect_err("must fail");
1591 assert_eq!(
1592 err.to_string(),
1593 "invalid value: string \"foo$\", expected a name \
1594 that consists of [A-Za-z0-9_.-] and starts with [A-Za-z0-9_] \
1595 at line 2 column 18"
1596 );
1597 assert_eq!(err.line(), 2);
1598 assert_eq!(err.column(), 18);
1599 }
1600
1601 #[test]
1602 fn test_path_error_message() {
1603 let input = r#"
1604 "foo";
1605 "#;
1606 let err = serde_json::from_str::<Path>(input).expect_err("must fail");
1607 assert_eq!(
1608 err.to_string(),
1609 "invalid value: string \"foo\", expected a path with leading `/` and non-empty \
1610 segments, where each segment is no \
1611 more than fuchsia.io/MAX_NAME_LENGTH bytes in length, cannot be . or .., \
1612 and cannot contain embedded NULs at line 2 column 17"
1613 );
1614
1615 assert_eq!(err.line(), 2);
1616 assert_eq!(err.column(), 17);
1617 }
1618
1619 #[test]
1620 fn test_url_error_message() {
1621 let input = r#"
1622 "foo";
1623 "#;
1624 let err = serde_json::from_str::<Url>(input).expect_err("must fail");
1625 assert_eq!(
1626 err.to_string(),
1627 "invalid value: string \"foo\", expected a valid URL at line 2 \
1628 column 17"
1629 );
1630 assert_eq!(err.line(), 2);
1631 assert_eq!(err.column(), 17);
1632 }
1633
1634 #[test]
1635 fn test_url_scheme_error_message() {
1636 let input = r#"
1637 "9fuchsia_pkg"
1638 "#;
1639 let err = serde_json::from_str::<UrlScheme>(input).expect_err("must fail");
1640 assert_eq!(
1641 err.to_string(),
1642 "invalid value: string \"9fuchsia_pkg\", expected a valid URL scheme at line 2 column 26"
1643 );
1644 assert_eq!(err.line(), 2);
1645 assert_eq!(err.column(), 26);
1646 }
1647
1648 #[test]
1649 fn test_symmetrical_enums() {
1650 mod a {
1651 #[derive(Debug, PartialEq, Eq)]
1652 pub enum Streetlight {
1653 Green,
1654 Yellow,
1655 Red,
1656 }
1657 }
1658
1659 mod b {
1660 #[derive(Debug, PartialEq, Eq)]
1661 pub enum Streetlight {
1662 Green,
1663 Yellow,
1664 Red,
1665 }
1666 }
1667
1668 symmetrical_enums!(a::Streetlight, b::Streetlight, Green, Yellow, Red);
1669
1670 assert_eq!(a::Streetlight::Green, b::Streetlight::Green.into());
1671 assert_eq!(a::Streetlight::Yellow, b::Streetlight::Yellow.into());
1672 assert_eq!(a::Streetlight::Red, b::Streetlight::Red.into());
1673 assert_eq!(b::Streetlight::Green, a::Streetlight::Green.into());
1674 assert_eq!(b::Streetlight::Yellow, a::Streetlight::Yellow.into());
1675 assert_eq!(b::Streetlight::Red, a::Streetlight::Red.into());
1676 }
1677}