cm_types/
lib.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! A crate containing common Component Manager types used in Component Manifests
6//! (`.cml` files and binary `.cm` files). These types come with `serde` serialization
7//! and deserialization implementations that perform the required validation.
8
9use 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    /// A default base URL from which to parse relative component URL
23    /// components.
24    static ref DEFAULT_BASE_URL: url::Url = url::Url::parse("relative:///").unwrap();
25}
26
27/// Generate `impl From` for two trivial enums with identical values, allowing
28/// converting to/from each other.
29/// This is useful if you have a FIDL-generated enum and a hand-rolled
30/// one that contain the same values.
31/// # Arguments
32///
33/// * `$a`, `$b` - The enums to generate `impl From` for. Order doesn't matter because
34///     implementation will be generated for both. Enums should be trivial.
35/// * `id` - Exhaustive list of all enum values.
36/// # Examples
37///
38/// ```
39/// mod a {
40///     #[derive(Debug, PartialEq, Eq)]
41///     pub enum Streetlight {
42///         Green,
43///         Yellow,
44///         Red,
45///     }
46/// }
47///
48/// mod b {
49///     #[derive(Debug, PartialEq, Eq)]
50///     pub enum Streetlight {
51///         Green,
52///         Yellow,
53///         Red,
54///     }
55/// }
56///
57/// symmetrical_enums!(a::Streetlight, b::Streetlight, Green, Yellow, Red);
58///
59/// assert_eq!(a::Streetlight::Green, b::Streetlight::Green.into());
60/// assert_eq!(b::Streetlight::Green, a::Streetlight::Green.into());
61/// ```
62#[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/// The error representing a failure to parse a type from string.
84#[derive(Serialize, Clone, Deserialize, Debug, Error, PartialEq, Eq)]
85pub enum ParseError {
86    /// The string did not match a valid value.
87    #[error("invalid value")]
88    InvalidValue,
89    /// The string did not match a valid absolute or relative component URL
90    #[error("invalid URL: {details}")]
91    InvalidComponentUrl { details: String },
92    /// The string was empty.
93    #[error("empty")]
94    Empty,
95    /// The string was too long.
96    #[error("too long")]
97    TooLong,
98    /// A required leading slash was missing.
99    #[error("no leading slash")]
100    NoLeadingSlash,
101    /// The path segment is invalid.
102    #[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
111/// This asks for the maximum possible rights that the parent connection will allow; this will
112/// include the writable and executable rights if the parent connection has them, but won't fail if
113/// it doesn't.
114pub 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
118/// A name that can refer to a component, collection, or other entity in the
119/// Component Manifest. Its length is bounded to `MAX_NAME_LENGTH`.
120pub type Name = BoundedName<MAX_NAME_LENGTH>;
121/// A `Name` with a higher string capacity of `MAX_LONG_NAME_LENGTH`.
122pub type LongName = BoundedName<MAX_LONG_NAME_LENGTH>;
123
124/// A `BoundedName` is a `Name` that can have a max length of `N` bytes.
125#[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    /// Creates a `BoundedName` from a `String`, returning an `Err` if the string
136    /// fails validation. The string must be non-empty, no more than `N`
137    /// characters in length, and consist of one or more of the
138    /// following characters: `A-Z`, `a-z`, `0-9`, `_`, `.`, `-`. It may not start
139    /// with `.` or `-`.
140    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/// [NamespacePath] is the same as [Path] but accepts `"/"` (which is also a valid namespace
292/// path).
293///
294/// Note that while `"/"` is accepted, `"."` (which is synonymous in fuchsia.io) is rejected.
295#[derive(Eq, Ord, PartialOrd, PartialEq, Hash, Clone)]
296pub struct NamespacePath(RelativePath);
297
298impl NamespacePath {
299    /// Like [Path::new] but `path` may be `/`.
300    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                // "/." is not a valid NamespacePath
320                return Err(ParseError::InvalidSegment);
321            }
322            Ok(Self(path))
323        }
324    }
325
326    /// Returns the [NamespacePath] for `"/"`.
327    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    /// Splits the path according to `"/"`.
336    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    /// Returns a path that represents the parent directory of this one, or None if this is a
345    /// root dir.
346    pub fn parent(&self) -> Option<Self> {
347        self.0.parent().map(|p| Self(p))
348    }
349
350    /// Returns whether `prefix` is a prefix of `self` in terms of path segments.
351    ///
352    /// For example:
353    /// ```
354    /// Path("/pkg/data").has_prefix("/pkg") == true
355    /// Path("/pkg_data").has_prefix("/pkg") == false
356    /// ```
357    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    /// The last path segment, or None.
364    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        // SAFETY: in `Path::new` we already verified that there are no
399        // embedded NULs.
400        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/// A path type used throughout Component Framework, along with its variants [NamespacePath] and
435/// [RelativePath]. Examples of use:
436///
437/// - [NamespacePath]: Namespace paths
438/// - [Path]: Outgoing paths and namespace paths that can't be "/"
439/// - [RelativePath]: Dictionary paths
440///
441/// [Path] obeys the following constraints:
442///
443/// - Is a [fuchsia.io.Path](https://fuchsia.dev/reference/fidl/fuchsia.io#Directory.Open).
444/// - Begins with `/`.
445/// - Is not `.`.
446/// - Contains at least one path segment (just `/` is disallowed).
447/// - Each path segment is a [Name]. (This is strictly more constrained than a fuchsia.io
448///   path segment.)
449#[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    /// Creates a [`Path`] from a [`String`], returning an `Err` if the string fails validation.
475    /// The string must be non-empty, no more than [`MAX_PATH_LENGTH`] bytes in length, start with
476    /// a leading `/`, not be exactly `/` or `.`, and each segment must be a valid [`Name`]. As a
477    /// result, [`Path`]s are always valid [`NamespacePath`]s.
478    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            // "/." is not a valid Path
495            return Err(ParseError::InvalidSegment);
496        }
497        Ok(Self(path))
498    }
499
500    /// Splits the path according to "/".
501    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    /// Returns a path that represents the parent directory of this one. Returns [NamespacePath]
510    /// instead of [Path] because the parent could be the root dir.
511    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        // SAFETY: in `Path::new` we already verified that there are no
560        // embedded NULs.
561        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/// Same as [Path] except the path does not begin with `/`.
618#[derive(Eq, Ord, PartialOrd, PartialEq, Hash, Clone, Default)]
619pub struct RelativePath {
620    segments: Vec<Name>,
621}
622
623impl RelativePath {
624    /// Like [Path::new] but `path` must not begin with `/` and may be `.`.
625    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/// Path that separates the dirname and basename as different variables
794/// (referencing type). Convenient for / path representations that split the
795/// dirname and basename, like Fuchsia component decl.
796#[derive(Debug, Clone, PartialEq, Eq)]
797pub struct BorrowedSeparatedPath<'a> {
798    pub dirname: &'a RelativePath,
799    pub basename: &'a Name,
800}
801
802impl BorrowedSeparatedPath<'_> {
803    /// Converts this [BorrowedSeparatedPath] to the owned type.
804    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/// Path that separates the dirname and basename as different variables (owned
826/// type). Convenient for path representations that split the dirname and
827/// basename, like Fuchsia component decl.
828#[derive(Debug, Clone, PartialEq, Eq)]
829pub struct SeparatedPath {
830    pub dirname: RelativePath,
831    pub basename: Name,
832}
833
834impl SeparatedPath {
835    /// Obtains a reference to this [SeparatedPath] as the borrowed type.
836    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
857/// Trait implemented by path types that provides an API to iterate over path segments.
858pub trait IterablePath: Clone + Send + Sync {
859    /// Returns a double-sided iterator over the segments in this path.
860    fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &Name> + Send;
861}
862
863/// A component URL. The URL is validated, but represented as a string to avoid
864/// normalization and retain the original representation.
865#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
866pub struct Url(FlyStr);
867
868impl Url {
869    /// Creates a `Url` from a `String`, returning an `Err` if the string fails
870    /// validation. The string must be non-empty, no more than 4096 characters
871    /// in length, and be a valid URL. See the [`url`](../../url/index.html) crate.
872    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    /// Verifies the given string is a valid absolute or relative component URL.
878    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                    // TODO(https://fxbug.dev/42070831): Fragments should be optional
899                    // for relative path URLs.
900                    //
901                    // Historically, a component URL string without a scheme
902                    // was considered invalid, unless it was only a fragment.
903                    // Subpackages allow a relative path URL, and by current
904                    // definition they require a fragment. By declaring a
905                    // relative path without a fragment "invalid", we can avoid
906                    // breaking tests that expect a path-only string to be
907                    // invalid. Sadly this appears to be a behavior of the
908                    // public API.
909                    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        // Use the unparsed URL string so that the original format is preserved.
930        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/// A URL scheme.
1028#[derive(Serialize, Clone, Debug, Eq, Hash, PartialEq)]
1029pub struct UrlScheme(FlyStr);
1030
1031impl UrlScheme {
1032    /// Creates a `UrlScheme` from a `String`, returning an `Err` if the string fails
1033    /// validation. The string must be non-empty and no more than 100 characters
1034    /// in length. It must start with a lowercase ASCII letter (a-z),
1035    /// and contain only lowercase ASCII letters, digits, `+`, `-`, and `.`.
1036    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    /// Validates `url_scheme` but does not construct a new `UrlScheme` object.
1042    /// See [`UrlScheme::new`] for validation details.
1043    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/// The duration of child components in a collection. See [`Durability`].
1125///
1126/// [`Durability`]: ../../fidl_fuchsia_sys2/enum.Durability.html
1127#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
1128#[serde(rename_all = "snake_case")]
1129pub enum Durability {
1130    Transient,
1131    /// An instance is started on creation and exists until it stops.
1132    SingleRun,
1133}
1134
1135symmetrical_enums!(Durability, fdecl::Durability, Transient, SingleRun);
1136
1137/// A component instance's startup mode. See [`StartupMode`].
1138///
1139/// [`StartupMode`]: ../../fidl_fuchsia_sys2/enum.StartupMode.html
1140#[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/// A component instance's recovery policy. See [`OnTerminate`].
1162///
1163/// [`OnTerminate`]: ../../fidl_fuchsia_sys2/enum.OnTerminate.html
1164#[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/// The kinds of offers that can target components in a given collection. See
1180/// [`AllowedOffers`].
1181///
1182/// [`AllowedOffers`]: ../../fidl_fuchsia_sys2/enum.AllowedOffers.html
1183#[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/// Offered dependency type. See [`DependencyType`].
1199///
1200/// [`DependencyType`]: ../../fidl_fuchsia_sys2/enum.DependencyType.html
1201#[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/// Capability availability. See [`Availability`].
1217///
1218/// [`Availability`]: ../../fidl_fuchsia_sys2/enum.Availability.html
1219#[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
1248// TODO(cgonyeo): remove this once we've soft migrated to the availability field being required.
1249impl 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/// Specifies when the framework will open the protocol from the provider
1276/// component's outgoing directory when someone requests the capability. See
1277/// [`DeliveryType`].
1278///
1279/// [`DeliveryType`]: ../../fidl_fuchsia_component_decl/enum.DeliveryType.html
1280#[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        // 2047 * 2 characters per repeat = 4094
1405        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        // 2048 * 2 characters per repeat = 4096
1426        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        // 2047 * 2 characters per repeat = 4094
1443        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        // 2048 * 2 characters per repeat = 4096
1466        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}