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::hash::{Hash, Hasher};
16use std::ops::Deref;
17use std::path::PathBuf;
18use std::str::FromStr;
19use std::{cmp, iter};
20use thiserror::Error;
21use {fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_io as fio};
22
23lazy_static! {
24    /// A default base URL from which to parse relative component URL
25    /// components.
26    static ref DEFAULT_BASE_URL: url::Url = url::Url::parse("relative:///").unwrap();
27}
28
29/// Generate `impl From` for two trivial enums with identical values, allowing
30/// converting to/from each other.
31/// This is useful if you have a FIDL-generated enum and a hand-rolled
32/// one that contain the same values.
33/// # Arguments
34///
35/// * `$a`, `$b` - The enums to generate `impl From` for. Order doesn't matter because
36///     implementation will be generated for both. Enums should be trivial.
37/// * `id` - Exhaustive list of all enum values.
38/// # Examples
39///
40/// ```
41/// mod a {
42///     #[derive(Debug, PartialEq, Eq)]
43///     pub enum Streetlight {
44///         Green,
45///         Yellow,
46///         Red,
47///     }
48/// }
49///
50/// mod b {
51///     #[derive(Debug, PartialEq, Eq)]
52///     pub enum Streetlight {
53///         Green,
54///         Yellow,
55///         Red,
56///     }
57/// }
58///
59/// symmetrical_enums!(a::Streetlight, b::Streetlight, Green, Yellow, Red);
60///
61/// assert_eq!(a::Streetlight::Green, b::Streetlight::Green.into());
62/// assert_eq!(b::Streetlight::Green, a::Streetlight::Green.into());
63/// ```
64#[macro_export]
65macro_rules! symmetrical_enums {
66    ($a:ty , $b:ty, $($id: ident),*) => {
67        impl From<$a> for $b {
68            fn from(input: $a) -> Self {
69                match input {
70                    $( <$a>::$id => <$b>::$id, )*
71                }
72            }
73        }
74
75        impl From<$b> for $a {
76            fn from(input: $b) -> Self {
77                match input {
78                    $( <$b>::$id => <$a>::$id, )*
79                }
80            }
81        }
82    };
83}
84
85/// The error representing a failure to parse a type from string.
86#[derive(Serialize, Clone, Deserialize, Debug, Error, PartialEq, Eq)]
87pub enum ParseError {
88    /// The string did not match a valid value.
89    #[error("invalid value")]
90    InvalidValue,
91    /// The string did not match a valid absolute or relative component URL
92    #[error("invalid URL: {details}")]
93    InvalidComponentUrl { details: String },
94    /// The string was empty.
95    #[error("empty")]
96    Empty,
97    /// The string was too long.
98    #[error("too long")]
99    TooLong,
100    /// A required leading slash was missing.
101    #[error("no leading slash")]
102    NoLeadingSlash,
103    /// The path segment is invalid.
104    #[error("invalid path segment")]
105    InvalidSegment,
106}
107
108pub const MAX_NAME_LENGTH: usize = name::MAX_NAME_LENGTH;
109pub const MAX_LONG_NAME_LENGTH: usize = 1024;
110pub const MAX_PATH_LENGTH: usize = fio::MAX_PATH_LENGTH as usize;
111pub const MAX_URL_LENGTH: usize = 4096;
112
113/// This asks for the maximum possible rights that the parent connection will allow; this will
114/// include the writable and executable rights if the parent connection has them, but won't fail if
115/// it doesn't.
116pub const FLAGS_MAX_POSSIBLE_RIGHTS: fio::Flags = fio::PERM_READABLE
117    .union(fio::Flags::PERM_INHERIT_WRITE)
118    .union(fio::Flags::PERM_INHERIT_EXECUTE);
119
120/// A name that can refer to a component, collection, or other entity in the
121/// Component Manifest. Its length is bounded to `MAX_NAME_LENGTH`.
122pub type Name = BoundedName<MAX_NAME_LENGTH>;
123/// A `Name` with a higher string capacity of `MAX_LONG_NAME_LENGTH`.
124pub type LongName = BoundedName<MAX_LONG_NAME_LENGTH>;
125
126/// A `BoundedName` is a `Name` that can have a max length of `N` bytes.
127#[derive(Serialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
128pub struct BoundedName<const N: usize>(FlyStr);
129
130impl Name {
131    #[inline]
132    pub fn to_long(self) -> LongName {
133        BoundedName(self.0)
134    }
135}
136
137impl<const N: usize> BoundedName<N> {
138    /// Creates a `BoundedName` from a `&str` slice, returning an `Err` if the string
139    /// fails validation. The string must be non-empty, no more than `N`
140    /// characters in length, and consist of one or more of the
141    /// following characters: `A-Z`, `a-z`, `0-9`, `_`, `.`, `-`. It may not start
142    /// with `.` or `-`.
143    pub fn new(s: impl AsRef<str>) -> Result<Self, ParseError> {
144        let s = s.as_ref();
145        validate_name::<N>(s)?;
146        Ok(Self(FlyStr::new(s)))
147    }
148
149    /// Private variant of [`BoundedName::new`] that does not perform correctness checks.
150    /// For efficiency when the caller is sure `s` is a valid [`BoundedName`].
151    fn new_unchecked<S: AsRef<str> + ?Sized>(s: &S) -> Self {
152        Self(FlyStr::new(s.as_ref()))
153    }
154
155    #[inline]
156    pub fn as_str(&self) -> &str {
157        &self.0
158    }
159
160    #[inline]
161    pub fn is_empty(&self) -> bool {
162        self.0.is_empty()
163    }
164
165    #[inline]
166    pub fn len(&self) -> usize {
167        self.0.len()
168    }
169}
170
171impl<const N: usize> AsRef<str> for BoundedName<N> {
172    #[inline]
173    fn as_ref(&self) -> &str {
174        self.as_str()
175    }
176}
177
178impl<const N: usize> AsRef<BoundedBorrowedName<N>> for BoundedName<N> {
179    #[inline]
180    fn as_ref(&self) -> &BoundedBorrowedName<N> {
181        BoundedBorrowedName::<N>::new_unchecked(self)
182    }
183}
184
185impl<const N: usize> AsRef<BoundedName<N>> for BoundedName<N> {
186    #[inline]
187    fn as_ref(&self) -> &BoundedName<N> {
188        self
189    }
190}
191
192impl<const N: usize> Borrow<str> for BoundedName<N> {
193    #[inline]
194    fn borrow(&self) -> &str {
195        &self.0
196    }
197}
198
199impl<const N: usize> Deref for BoundedName<N> {
200    type Target = BoundedBorrowedName<N>;
201
202    #[inline]
203    fn deref(&self) -> &BoundedBorrowedName<N> {
204        BoundedBorrowedName::new_unchecked(self.0.as_str())
205    }
206}
207
208impl<const N: usize> Borrow<BoundedBorrowedName<N>> for BoundedName<N> {
209    #[inline]
210    fn borrow(&self) -> &BoundedBorrowedName<N> {
211        self.deref()
212    }
213}
214
215impl<const N: usize> From<BoundedName<N>> for FlyStr {
216    #[inline]
217    fn from(o: BoundedName<N>) -> Self {
218        o.0
219    }
220}
221
222impl<'a, const N: usize> From<&'a BoundedName<N>> for &'a FlyStr {
223    #[inline]
224    fn from(o: &'a BoundedName<N>) -> Self {
225        &o.0
226    }
227}
228
229impl<const N: usize> PartialEq<&str> for BoundedName<N> {
230    #[inline]
231    fn eq(&self, o: &&str) -> bool {
232        &*self.0 == *o
233    }
234}
235
236impl<const N: usize> PartialEq<String> for BoundedName<N> {
237    #[inline]
238    fn eq(&self, o: &String) -> bool {
239        &*self.0 == *o
240    }
241}
242
243impl<const N: usize> PartialEq<BoundedBorrowedName<N>> for BoundedName<N> {
244    #[inline]
245    fn eq(&self, o: &BoundedBorrowedName<N>) -> bool {
246        &self.0 == &o.0
247    }
248}
249
250impl<const N: usize> Hash for BoundedName<N> {
251    #[inline]
252    fn hash<H: Hasher>(&self, state: &mut H) {
253        self.0.as_str().hash(state)
254    }
255}
256
257impl<const N: usize> fmt::Display for BoundedName<N> {
258    #[inline]
259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260        <FlyStr as fmt::Display>::fmt(&self.0, f)
261    }
262}
263
264impl<const N: usize> FromStr for BoundedName<N> {
265    type Err = ParseError;
266
267    #[inline]
268    fn from_str(name: &str) -> Result<Self, Self::Err> {
269        Self::new(name)
270    }
271}
272
273impl<const N: usize> From<&BoundedBorrowedName<N>> for BoundedName<N> {
274    #[inline]
275    fn from(o: &BoundedBorrowedName<N>) -> Self {
276        Self(o.0.into())
277    }
278}
279
280impl<const N: usize> From<BoundedName<N>> for String {
281    #[inline]
282    fn from(name: BoundedName<N>) -> String {
283        name.0.into()
284    }
285}
286
287impl From<Name> for LongName {
288    #[inline]
289    fn from(name: Name) -> Self {
290        Self(name.0)
291    }
292}
293
294/// Unowned variant of [`Name`]. [`Name`] for more details.
295pub type BorrowedName = BoundedBorrowedName<MAX_NAME_LENGTH>;
296/// Unowned variant of [`LongName`]. [`LongName`] for more details.
297pub type BorrowedLongName = BoundedBorrowedName<MAX_LONG_NAME_LENGTH>;
298
299/// Like [`BoundedName`], except it holds a string slice rather than an allocated string. For
300/// example, the [`Path`] API uses this to return path segments without making an allocation.
301#[derive(Serialize, Debug, PartialEq, Eq, PartialOrd, Ord)]
302#[repr(transparent)]
303pub struct BoundedBorrowedName<const N: usize>(str);
304
305impl BorrowedName {
306    #[inline]
307    pub fn to_long(&self) -> &BorrowedLongName {
308        // SAFETY: `BorrowedName` and `BorrowedLongName` share the same representation.
309        // Furthermore, every `BorrowedName` is a valid `BorrowedLongName`. Therefore, this
310        // typecast is safe.
311        unsafe { &*(self as *const BorrowedName as *const BorrowedLongName) }
312    }
313}
314
315impl<const N: usize> BoundedBorrowedName<N> {
316    /// Creates a `BoundedBorrowedName` from a `&str` slice, which obeys the same
317    /// rules as `BoundedName`.
318    pub fn new<S: AsRef<str> + ?Sized>(s: &S) -> Result<&Self, ParseError> {
319        validate_name::<N>(s.as_ref())?;
320        Ok(Self::new_unchecked(s))
321    }
322
323    /// Private variant of [`BoundedBorrowedName::new`] that does not perform correctness checks.
324    /// For efficiency when the caller is sure `s` is a valid [`BoundedName`].
325    fn new_unchecked<S: AsRef<str> + ?Sized>(s: &S) -> &Self {
326        // SAFETY: `&str` is the transparent representation of `BorrowedName`. This function is
327        // private, and it is only called from places that are certain the `&str` matches the
328        // `BorrowedName` requirements. Therefore, this typecast is safe.
329        unsafe { &*(s.as_ref() as *const str as *const Self) }
330    }
331
332    #[inline]
333    pub fn as_str(&self) -> &str {
334        &self.0
335    }
336
337    #[inline]
338    pub fn is_empty(&self) -> bool {
339        self.0.is_empty()
340    }
341
342    #[inline]
343    pub fn len(&self) -> usize {
344        self.0.len()
345    }
346}
347
348fn validate_name<const N: usize>(name: &str) -> Result<(), ParseError> {
349    if name.is_empty() {
350        return Err(ParseError::Empty);
351    }
352    if name.len() > N {
353        return Err(ParseError::TooLong);
354    }
355    let mut char_iter = name.chars();
356    let first_char = char_iter.next().unwrap();
357    if !first_char.is_ascii_alphanumeric() && first_char != '_' {
358        return Err(ParseError::InvalidValue);
359    }
360    let valid_fn = |c: char| c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.';
361    if !char_iter.all(valid_fn) {
362        return Err(ParseError::InvalidValue);
363    }
364    Ok(())
365}
366
367impl<const N: usize> AsRef<str> for BoundedBorrowedName<N> {
368    #[inline]
369    fn as_ref(&self) -> &str {
370        &self.0
371    }
372}
373
374impl<const N: usize> Borrow<str> for BoundedBorrowedName<N> {
375    #[inline]
376    fn borrow(&self) -> &str {
377        &self.0
378    }
379}
380
381impl<const N: usize> Borrow<str> for &BoundedBorrowedName<N> {
382    #[inline]
383    fn borrow(&self) -> &str {
384        &self.0
385    }
386}
387
388impl<'a, const N: usize> From<&'a BoundedBorrowedName<N>> for &'a str {
389    #[inline]
390    fn from(o: &'a BoundedBorrowedName<N>) -> Self {
391        &o.0
392    }
393}
394
395impl<const N: usize> PartialEq<&str> for BoundedBorrowedName<N> {
396    #[inline]
397    fn eq(&self, o: &&str) -> bool {
398        &self.0 == *o
399    }
400}
401
402impl<const N: usize> PartialEq<String> for BoundedBorrowedName<N> {
403    #[inline]
404    fn eq(&self, o: &String) -> bool {
405        &self.0 == &*o
406    }
407}
408
409impl<const N: usize> PartialEq<BoundedName<N>> for BoundedBorrowedName<N> {
410    #[inline]
411    fn eq(&self, o: &BoundedName<N>) -> bool {
412        self.0 == *o.0
413    }
414}
415
416impl<const N: usize> Hash for BoundedBorrowedName<N> {
417    #[inline]
418    fn hash<H: Hasher>(&self, state: &mut H) {
419        self.0.hash(state)
420    }
421}
422
423impl<const N: usize> fmt::Display for BoundedBorrowedName<N> {
424    #[inline]
425    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426        <str as fmt::Display>::fmt(&self.0, f)
427    }
428}
429
430impl<'a> From<&'a BorrowedName> for &'a BorrowedLongName {
431    #[inline]
432    fn from(name: &'a BorrowedName) -> Self {
433        name.to_long()
434    }
435}
436
437impl<'de, const N: usize> de::Deserialize<'de> for BoundedName<N> {
438    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
439    where
440        D: de::Deserializer<'de>,
441    {
442        struct Visitor<const N: usize>;
443
444        impl<'de, const N: usize> de::Visitor<'de> for Visitor<N> {
445            type Value = BoundedName<{ N }>;
446
447            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
448                f.write_str(&format!(
449                    "a non-empty string no more than {} characters in length, \
450                    consisting of [A-Za-z0-9_.-] and starting with [A-Za-z0-9_]",
451                    N
452                ))
453            }
454
455            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
456            where
457                E: de::Error,
458            {
459                s.parse().map_err(|err| match err {
460                    ParseError::InvalidValue => E::invalid_value(
461                        de::Unexpected::Str(s),
462                        &"a name that consists of [A-Za-z0-9_.-] and starts with [A-Za-z0-9_]",
463                    ),
464                    ParseError::TooLong | ParseError::Empty => E::invalid_length(
465                        s.len(),
466                        &format!("a non-empty name no more than {} characters in length", N)
467                            .as_str(),
468                    ),
469                    e => {
470                        panic!("unexpected parse error: {:?}", e);
471                    }
472                })
473            }
474        }
475        deserializer.deserialize_string(Visitor)
476    }
477}
478
479impl IterablePath for Name {
480    fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &BorrowedName> + Send {
481        iter::once(self as &BorrowedName)
482    }
483}
484
485impl IterablePath for &Name {
486    fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &BorrowedName> + Send {
487        iter::once(*self as &BorrowedName)
488    }
489}
490
491/// [NamespacePath] is the same as [Path] but accepts `"/"` (which is also a valid namespace
492/// path).
493///
494/// Note that while `"/"` is accepted, `"."` (which is synonymous in fuchsia.io) is rejected.
495#[derive(Eq, Ord, PartialOrd, PartialEq, Hash, Clone)]
496pub struct NamespacePath(RelativePath);
497
498impl NamespacePath {
499    /// Like [Path::new] but `path` may be `/`.
500    pub fn new(path: impl AsRef<str>) -> Result<Self, ParseError> {
501        let path = path.as_ref();
502        if path.is_empty() {
503            return Err(ParseError::Empty);
504        }
505        if path == "." {
506            return Err(ParseError::InvalidValue);
507        }
508        if !path.starts_with('/') {
509            return Err(ParseError::NoLeadingSlash);
510        }
511        if path.len() > MAX_PATH_LENGTH {
512            return Err(ParseError::TooLong);
513        }
514        if path == "/" {
515            Ok(Self(RelativePath::dot()))
516        } else {
517            let path: RelativePath = path[1..].parse()?;
518            if path.is_dot() {
519                // "/." is not a valid NamespacePath
520                return Err(ParseError::InvalidSegment);
521            }
522            Ok(Self(path))
523        }
524    }
525
526    /// Returns the [NamespacePath] for `"/"`.
527    pub fn root() -> Self {
528        Self(RelativePath::dot())
529    }
530
531    pub fn is_root(&self) -> bool {
532        self.0.is_dot()
533    }
534
535    /// Splits the path according to `"/"`.
536    pub fn split(&self) -> Vec<&BorrowedName> {
537        self.0.split()
538    }
539
540    pub fn to_path_buf(&self) -> PathBuf {
541        PathBuf::from(self.to_string())
542    }
543
544    /// Returns a path that represents the parent directory of this one, or None if this is a
545    /// root dir.
546    pub fn parent(&self) -> Option<Self> {
547        self.0.parent().map(|p| Self(p))
548    }
549
550    /// Returns whether `prefix` is a prefix of `self` in terms of path segments.
551    ///
552    /// For example:
553    /// ```
554    /// Path("/pkg/data").has_prefix("/pkg") == true
555    /// Path("/pkg_data").has_prefix("/pkg") == false
556    /// ```
557    pub fn has_prefix(&self, prefix: &Self) -> bool {
558        let my_segments = self.split();
559        let prefix_segments = prefix.split();
560        prefix_segments.into_iter().zip(my_segments.into_iter()).all(|(a, b)| a == b)
561    }
562
563    /// The last path segment, or None.
564    pub fn basename(&self) -> Option<&BorrowedName> {
565        self.0.basename()
566    }
567
568    pub fn pop_front(&mut self) -> Option<Name> {
569        self.0.pop_front()
570    }
571}
572
573impl IterablePath for NamespacePath {
574    fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &BorrowedName> + Send {
575        self.0.iter_segments()
576    }
577}
578
579impl serde::ser::Serialize for NamespacePath {
580    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
581    where
582        S: serde::ser::Serializer,
583    {
584        self.to_string().serialize(serializer)
585    }
586}
587
588impl TryFrom<CString> for NamespacePath {
589    type Error = ParseError;
590
591    fn try_from(path: CString) -> Result<Self, ParseError> {
592        Self::new(path.into_string().map_err(|_| ParseError::InvalidValue)?)
593    }
594}
595
596impl From<NamespacePath> for CString {
597    fn from(path: NamespacePath) -> Self {
598        // SAFETY: in `Path::new` we already verified that there are no
599        // embedded NULs.
600        unsafe { CString::from_vec_unchecked(path.to_string().as_bytes().to_owned()) }
601    }
602}
603
604impl From<NamespacePath> for String {
605    fn from(path: NamespacePath) -> Self {
606        path.to_string()
607    }
608}
609
610impl FromStr for NamespacePath {
611    type Err = ParseError;
612
613    fn from_str(path: &str) -> Result<Self, Self::Err> {
614        Self::new(path)
615    }
616}
617
618impl fmt::Debug for NamespacePath {
619    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
620        write!(f, "{}", self)
621    }
622}
623
624impl fmt::Display for NamespacePath {
625    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
626        if !self.0.is_dot() {
627            write!(f, "/{}", self.0)
628        } else {
629            write!(f, "/")
630        }
631    }
632}
633
634/// A path type used throughout Component Framework, along with its variants [NamespacePath] and
635/// [RelativePath]. Examples of use:
636///
637/// - [NamespacePath]: Namespace paths
638/// - [Path]: Outgoing paths and namespace paths that can't be "/"
639/// - [RelativePath]: Dictionary paths
640///
641/// [Path] obeys the following constraints:
642///
643/// - Is a [fuchsia.io.Path](https://fuchsia.dev/reference/fidl/fuchsia.io#Directory.Open).
644/// - Begins with `/`.
645/// - Is not `.`.
646/// - Contains at least one path segment (just `/` is disallowed).
647/// - Each path segment is a [Name]. (This is strictly more constrained than a fuchsia.io
648///   path segment.)
649#[derive(Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
650pub struct Path(RelativePath);
651
652impl fmt::Debug for Path {
653    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
654        write!(f, "{}", self)
655    }
656}
657
658impl fmt::Display for Path {
659    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
660        write!(f, "/{}", self.0)
661    }
662}
663
664impl ser::Serialize for Path {
665    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
666    where
667        S: serde::ser::Serializer,
668    {
669        self.to_string().serialize(serializer)
670    }
671}
672
673impl Path {
674    /// Creates a [`Path`] from a [`String`], returning an `Err` if the string fails validation.
675    /// The string must be non-empty, no more than [`MAX_PATH_LENGTH`] bytes in length, start with
676    /// a leading `/`, not be exactly `/` or `.`, and each segment must be a valid [`Name`]. As a
677    /// result, [`Path`]s are always valid [`NamespacePath`]s.
678    pub fn new(path: impl AsRef<str>) -> Result<Self, ParseError> {
679        let path = path.as_ref();
680        if path.is_empty() {
681            return Err(ParseError::Empty);
682        }
683        if path == "/" || path == "." {
684            return Err(ParseError::InvalidValue);
685        }
686        if !path.starts_with('/') {
687            return Err(ParseError::NoLeadingSlash);
688        }
689        if path.len() > MAX_PATH_LENGTH {
690            return Err(ParseError::TooLong);
691        }
692        let path: RelativePath = path[1..].parse()?;
693        if path.is_dot() {
694            // "/." is not a valid Path
695            return Err(ParseError::InvalidSegment);
696        }
697        Ok(Self(path))
698    }
699
700    /// Splits the path according to "/".
701    pub fn split(&self) -> Vec<&BorrowedName> {
702        self.0.split()
703    }
704
705    pub fn to_path_buf(&self) -> PathBuf {
706        PathBuf::from(self.to_string())
707    }
708
709    /// Returns a path that represents the parent directory of this one. Returns [NamespacePath]
710    /// instead of [Path] because the parent could be the root dir.
711    pub fn parent(&self) -> NamespacePath {
712        let p = self.0.parent().expect("can't be root");
713        NamespacePath(p)
714    }
715
716    pub fn basename(&self) -> &BorrowedName {
717        self.0.basename().expect("can't be root")
718    }
719
720    // Attaches the path `other` to the end of `self`. Returns `true` on success, and false
721    // if the resulting path's length would exceed `MAX_PATH_LENGTH`.
722    #[must_use]
723    pub fn extend(&mut self, other: RelativePath) -> bool {
724        let rep: FlyStr = if !other.is_dot() {
725            format!("{}/{}", self.0.rep, other.rep).into()
726        } else {
727            // Nothing to do.
728            return true;
729        };
730        // Account for leading /
731        if rep.len() > MAX_PATH_LENGTH - 1 {
732            return false;
733        }
734        self.0.rep = rep;
735        true
736    }
737
738    // Attaches `segment` to the end of `self`. Returns `true` on success, and false
739    // if the resulting path's length would exceed `MAX_PATH_LENGTH`.
740    #[must_use]
741    pub fn push(&mut self, segment: Name) -> bool {
742        let rep: FlyStr = format!("{}/{}", self.0.rep, segment).into();
743        // Account for leading /
744        if rep.len() > MAX_PATH_LENGTH - 1 {
745            return false;
746        }
747        self.0.rep = rep;
748        true
749    }
750}
751
752impl IterablePath for Path {
753    fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &BorrowedName> + Send {
754        Box::new(self.0.iter_segments())
755    }
756}
757
758impl From<Path> for NamespacePath {
759    fn from(value: Path) -> Self {
760        Self(value.0)
761    }
762}
763
764impl FromStr for Path {
765    type Err = ParseError;
766
767    fn from_str(path: &str) -> Result<Self, Self::Err> {
768        Self::new(path)
769    }
770}
771
772impl TryFrom<CString> for Path {
773    type Error = ParseError;
774
775    fn try_from(path: CString) -> Result<Self, ParseError> {
776        Self::new(path.into_string().map_err(|_| ParseError::InvalidValue)?)
777    }
778}
779
780impl From<Path> for CString {
781    fn from(path: Path) -> Self {
782        // SAFETY: in `Path::new` we already verified that there are no
783        // embedded NULs.
784        unsafe { CString::from_vec_unchecked(path.to_string().as_bytes().to_owned()) }
785    }
786}
787
788impl From<Path> for String {
789    fn from(path: Path) -> String {
790        path.to_string()
791    }
792}
793
794impl<'de> de::Deserialize<'de> for Path {
795    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
796    where
797        D: de::Deserializer<'de>,
798    {
799        struct Visitor;
800
801        impl<'de> de::Visitor<'de> for Visitor {
802            type Value = Path;
803
804            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
805                f.write_str(
806                    "a non-empty path no more than fuchsia.io/MAX_PATH_LENGTH characters \
807                     in length, with a leading `/`, and containing no \
808                     empty path segments",
809                )
810            }
811
812            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
813            where
814                E: de::Error,
815            {
816                s.parse().map_err(|err| {
817                    match err {
818                    ParseError::InvalidValue | ParseError::InvalidSegment | ParseError::NoLeadingSlash => E::invalid_value(
819                        de::Unexpected::Str(s),
820                        &"a path with leading `/` and non-empty segments, where each segment is no \
821                        more than fuchsia.io/MAX_NAME_LENGTH bytes in length, cannot be . or .., \
822                        and cannot contain embedded NULs",
823                    ),
824                    ParseError::TooLong | ParseError::Empty => E::invalid_length(
825                        s.len(),
826                        &"a non-empty path no more than fuchsia.io/MAX_PATH_LENGTH bytes \
827                        in length",
828                    ),
829                    e => {
830                        panic!("unexpected parse error: {:?}", e);
831                    }
832                }
833                })
834            }
835        }
836        deserializer.deserialize_string(Visitor)
837    }
838}
839
840/// Same as [Path] except the path does not begin with `/`.
841#[derive(Eq, Ord, PartialOrd, PartialEq, Hash, Clone)]
842pub struct RelativePath {
843    rep: FlyStr,
844}
845
846impl RelativePath {
847    /// Like [Path::new] but `path` must not begin with `/` and may be `.`.
848    pub fn new(path: impl AsRef<str>) -> Result<Self, ParseError> {
849        let path: &str = path.as_ref();
850        if path == "." {
851            return Ok(Self::dot());
852        }
853        if path.is_empty() {
854            return Err(ParseError::Empty);
855        }
856        if path.len() > MAX_PATH_LENGTH {
857            return Err(ParseError::TooLong);
858        }
859        path.split('/').try_for_each(|s| {
860            Name::new(s).map(|_| ()).map_err(|e| match e {
861                ParseError::Empty => ParseError::InvalidValue,
862                _ => ParseError::InvalidSegment,
863            })
864        })?;
865        Ok(Self { rep: path.into() })
866    }
867
868    pub fn dot() -> Self {
869        Self { rep: ".".into() }
870    }
871
872    pub fn is_dot(&self) -> bool {
873        self.rep == "."
874    }
875
876    pub fn parent(&self) -> Option<Self> {
877        if self.is_dot() {
878            None
879        } else {
880            match self.rep.rfind('/') {
881                Some(idx) => Some(Self::new(&self.rep[0..idx]).unwrap()),
882                None => Some(Self::dot()),
883            }
884        }
885    }
886
887    pub fn split(&self) -> Vec<&BorrowedName> {
888        if self.is_dot() {
889            vec![]
890        } else {
891            self.rep.split('/').map(|s| BorrowedName::new_unchecked(s)).collect()
892        }
893    }
894
895    pub fn basename(&self) -> Option<&BorrowedName> {
896        if self.is_dot() {
897            None
898        } else {
899            match self.rep.rfind('/') {
900                Some(idx) => Some(BorrowedName::new_unchecked(&self.rep[idx + 1..])),
901                None => Some(BorrowedName::new_unchecked(&self.rep)),
902            }
903        }
904    }
905
906    pub fn to_path_buf(&self) -> PathBuf {
907        if self.is_dot() {
908            PathBuf::new()
909        } else {
910            PathBuf::from(self.to_string())
911        }
912    }
913
914    // Attaches the path `other` to the end of `self`. Returns `true` on success, and false
915    // if the resulting path's length would exceed `MAX_PATH_LENGTH`.
916    #[must_use]
917    pub fn extend(&mut self, other: Self) -> bool {
918        let rep = if self.is_dot() {
919            other.rep
920        } else if !other.is_dot() {
921            format!("{}/{}", self.rep, other.rep).into()
922        } else {
923            // Nothing to do.
924            return true;
925        };
926        if rep.len() > MAX_PATH_LENGTH {
927            return false;
928        }
929        self.rep = rep;
930        true
931    }
932
933    // Attaches `segment` to the end of `self`. Returns `true` on success, and false
934    // if the resulting path's length would exceed `MAX_PATH_LENGTH`.
935    #[must_use]
936    pub fn push(&mut self, segment: Name) -> bool {
937        let rep: FlyStr = if self.is_dot() {
938            format!("{segment}").into()
939        } else {
940            format!("{}/{}", self.rep, segment).into()
941        };
942        if rep.len() > MAX_PATH_LENGTH {
943            return false;
944        }
945        self.rep = rep;
946        true
947    }
948
949    pub fn pop_front(&mut self) -> Option<Name> {
950        if self.is_dot() {
951            None
952        } else {
953            let (rep, front) = match self.rep.find('/') {
954                Some(idx) => {
955                    let rep = self.rep[idx + 1..].into();
956                    let front = Name::new_unchecked(&self.rep[0..idx]);
957                    (rep, front)
958                }
959                None => (".".into(), Name::new_unchecked(&self.rep)),
960            };
961            self.rep = rep;
962            Some(front)
963        }
964    }
965}
966
967impl Default for RelativePath {
968    fn default() -> Self {
969        Self::dot()
970    }
971}
972
973impl IterablePath for RelativePath {
974    fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &BorrowedName> + Send {
975        Box::new(self.split().into_iter())
976    }
977}
978
979impl FromStr for RelativePath {
980    type Err = ParseError;
981
982    fn from_str(path: &str) -> Result<Self, Self::Err> {
983        Self::new(path)
984    }
985}
986
987impl From<RelativePath> for String {
988    fn from(path: RelativePath) -> String {
989        path.to_string()
990    }
991}
992
993impl From<Vec<Name>> for RelativePath {
994    fn from(segments: Vec<Name>) -> Self {
995        if segments.is_empty() {
996            Self::dot()
997        } else {
998            Self { rep: segments.iter().map(|s| s.as_str()).collect::<Vec<_>>().join("/").into() }
999        }
1000    }
1001}
1002
1003impl From<Vec<&BorrowedName>> for RelativePath {
1004    fn from(segments: Vec<&BorrowedName>) -> Self {
1005        if segments.is_empty() {
1006            Self::dot()
1007        } else {
1008            Self {
1009                rep: segments.into_iter().map(|s| s.as_str()).collect::<Vec<_>>().join("/").into(),
1010            }
1011        }
1012    }
1013}
1014
1015impl fmt::Debug for RelativePath {
1016    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1017        write!(f, "{}", self)
1018    }
1019}
1020
1021impl fmt::Display for RelativePath {
1022    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1023        write!(f, "{}", self.rep)
1024    }
1025}
1026
1027impl ser::Serialize for RelativePath {
1028    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1029    where
1030        S: serde::ser::Serializer,
1031    {
1032        self.to_string().serialize(serializer)
1033    }
1034}
1035
1036impl<'de> de::Deserialize<'de> for RelativePath {
1037    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1038    where
1039        D: de::Deserializer<'de>,
1040    {
1041        struct Visitor;
1042
1043        impl<'de> de::Visitor<'de> for Visitor {
1044            type Value = RelativePath;
1045
1046            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1047                f.write_str(
1048                    "a non-empty path no more than fuchsia.io/MAX_PATH_LENGTH characters \
1049                     in length, not starting with `/`, and containing no empty path segments",
1050                )
1051            }
1052
1053            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
1054            where
1055                E: de::Error,
1056            {
1057                s.parse().map_err(|err| match err {
1058                    ParseError::InvalidValue
1059                    | ParseError::InvalidSegment
1060                    | ParseError::NoLeadingSlash => E::invalid_value(
1061                        de::Unexpected::Str(s),
1062                        &"a path with no leading `/` and non-empty segments",
1063                    ),
1064                    ParseError::TooLong | ParseError::Empty => E::invalid_length(
1065                        s.len(),
1066                        &"a non-empty path no more than fuchsia.io/MAX_PATH_LENGTH characters \
1067                        in length",
1068                    ),
1069                    e => {
1070                        panic!("unexpected parse error: {:?}", e);
1071                    }
1072                })
1073            }
1074        }
1075        deserializer.deserialize_string(Visitor)
1076    }
1077}
1078
1079/// Path that separates the dirname and basename as different variables
1080/// (referencing type). Convenient for / path representations that split the
1081/// dirname and basename, like Fuchsia component decl.
1082#[derive(Debug, Clone, PartialEq, Eq)]
1083pub struct BorrowedSeparatedPath<'a> {
1084    pub dirname: &'a RelativePath,
1085    pub basename: &'a Name,
1086}
1087
1088impl BorrowedSeparatedPath<'_> {
1089    /// Converts this [BorrowedSeparatedPath] to the owned type.
1090    pub fn to_owned(&self) -> SeparatedPath {
1091        SeparatedPath { dirname: self.dirname.clone(), basename: self.basename.clone() }
1092    }
1093}
1094
1095impl fmt::Display for BorrowedSeparatedPath<'_> {
1096    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1097        if !self.dirname.is_dot() {
1098            write!(f, "{}/{}", self.dirname, self.basename)
1099        } else {
1100            write!(f, "{}", self.basename)
1101        }
1102    }
1103}
1104
1105impl IterablePath for BorrowedSeparatedPath<'_> {
1106    fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &BorrowedName> + Send {
1107        Box::new(self.dirname.iter_segments().chain(iter::once(self.basename as &BorrowedName)))
1108    }
1109}
1110
1111/// Path that separates the dirname and basename as different variables (owned
1112/// type). Convenient for path representations that split the dirname and
1113/// basename, like Fuchsia component decl.
1114#[derive(Debug, Clone, PartialEq, Eq)]
1115pub struct SeparatedPath {
1116    pub dirname: RelativePath,
1117    pub basename: Name,
1118}
1119
1120impl SeparatedPath {
1121    /// Obtains a reference to this [SeparatedPath] as the borrowed type.
1122    pub fn as_ref(&self) -> BorrowedSeparatedPath<'_> {
1123        BorrowedSeparatedPath { dirname: &self.dirname, basename: &self.basename }
1124    }
1125}
1126
1127impl IterablePath for SeparatedPath {
1128    fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &BorrowedName> + Send {
1129        Box::new(self.dirname.iter_segments().chain(iter::once(&self.basename as &BorrowedName)))
1130    }
1131}
1132
1133impl fmt::Display for SeparatedPath {
1134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1135        if !self.dirname.is_dot() {
1136            write!(f, "{}/{}", self.dirname, self.basename)
1137        } else {
1138            write!(f, "{}", self.basename)
1139        }
1140    }
1141}
1142
1143/// Trait implemented by path types that provides an API to iterate over path segments.
1144pub trait IterablePath: Clone + Send + Sync {
1145    /// Returns a double-sided iterator over the segments in this path.
1146    fn iter_segments(&self) -> impl DoubleEndedIterator<Item = &BorrowedName> + Send;
1147}
1148
1149/// A component URL. The URL is validated, but represented as a string to avoid
1150/// normalization and retain the original representation.
1151#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
1152pub struct Url(FlyStr);
1153
1154impl Url {
1155    /// Creates a `Url` from a `&str` slice, returning an `Err` if the string fails
1156    /// validation. The string must be non-empty, no more than 4096 characters
1157    /// in length, and be a valid URL. See the [`url`](../../url/index.html) crate.
1158    pub fn new(url: impl AsRef<str> + Into<String>) -> Result<Self, ParseError> {
1159        Self::validate(url.as_ref())?;
1160        Ok(Self(FlyStr::new(url)))
1161    }
1162
1163    /// Verifies the given string is a valid absolute or relative component URL.
1164    pub fn validate(url_str: &str) -> Result<(), ParseError> {
1165        if url_str.is_empty() {
1166            return Err(ParseError::Empty);
1167        }
1168        if url_str.len() > MAX_URL_LENGTH {
1169            return Err(ParseError::TooLong);
1170        }
1171        match url::Url::parse(url_str).map(|url| (url, false)).or_else(|err| {
1172            if err == url::ParseError::RelativeUrlWithoutBase {
1173                DEFAULT_BASE_URL.join(url_str).map(|url| (url, true))
1174            } else {
1175                Err(err)
1176            }
1177        }) {
1178            Ok((url, is_relative)) => {
1179                let mut path = url.path();
1180                if path.starts_with('/') {
1181                    path = &path[1..];
1182                }
1183                if is_relative && url.fragment().is_none() {
1184                    // TODO(https://fxbug.dev/42070831): Fragments should be optional
1185                    // for relative path URLs.
1186                    //
1187                    // Historically, a component URL string without a scheme
1188                    // was considered invalid, unless it was only a fragment.
1189                    // Subpackages allow a relative path URL, and by current
1190                    // definition they require a fragment. By declaring a
1191                    // relative path without a fragment "invalid", we can avoid
1192                    // breaking tests that expect a path-only string to be
1193                    // invalid. Sadly this appears to be a behavior of the
1194                    // public API.
1195                    return Err(ParseError::InvalidComponentUrl {
1196                        details: "Relative URL has no resource fragment.".to_string(),
1197                    });
1198                }
1199                if url.host_str().unwrap_or("").is_empty()
1200                    && path.is_empty()
1201                    && url.fragment().is_none()
1202                {
1203                    return Err(ParseError::InvalidComponentUrl {
1204                        details: "URL is missing either `host`, `path`, and/or `resource`."
1205                            .to_string(),
1206                    });
1207                }
1208            }
1209            Err(err) => {
1210                return Err(ParseError::InvalidComponentUrl {
1211                    details: format!("Malformed URL: {err:?}."),
1212                });
1213            }
1214        }
1215        // Use the unparsed URL string so that the original format is preserved.
1216        Ok(())
1217    }
1218
1219    pub fn is_relative(&self) -> bool {
1220        matches!(url::Url::parse(&self.0), Err(url::ParseError::RelativeUrlWithoutBase))
1221    }
1222
1223    pub fn scheme(&self) -> Option<String> {
1224        url::Url::parse(&self.0).ok().map(|u| u.scheme().into())
1225    }
1226
1227    pub fn resource(&self) -> Option<String> {
1228        url::Url::parse(&self.0).ok().map(|u| u.fragment().map(str::to_string)).flatten()
1229    }
1230
1231    pub fn as_str(&self) -> &str {
1232        &*self.0
1233    }
1234}
1235
1236impl FromStr for Url {
1237    type Err = ParseError;
1238
1239    fn from_str(url: &str) -> Result<Self, Self::Err> {
1240        Self::new(url)
1241    }
1242}
1243
1244impl From<Url> for String {
1245    fn from(url: Url) -> String {
1246        url.0.into()
1247    }
1248}
1249
1250impl fmt::Display for Url {
1251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1252        fmt::Display::fmt(&self.0, f)
1253    }
1254}
1255
1256impl ser::Serialize for Url {
1257    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1258    where
1259        S: ser::Serializer,
1260    {
1261        self.to_string().serialize(serializer)
1262    }
1263}
1264
1265impl<'de> de::Deserialize<'de> for Url {
1266    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1267    where
1268        D: de::Deserializer<'de>,
1269    {
1270        struct Visitor;
1271
1272        impl<'de> de::Visitor<'de> for Visitor {
1273            type Value = Url;
1274
1275            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1276                f.write_str("a non-empty URL no more than 4096 characters in length")
1277            }
1278
1279            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
1280            where
1281                E: de::Error,
1282            {
1283                s.parse().map_err(|err| match err {
1284                    ParseError::InvalidComponentUrl { details: _ } => {
1285                        E::invalid_value(de::Unexpected::Str(s), &"a valid URL")
1286                    }
1287                    ParseError::TooLong | ParseError::Empty => E::invalid_length(
1288                        s.len(),
1289                        &"a non-empty URL no more than 4096 characters in length",
1290                    ),
1291                    e => {
1292                        panic!("unexpected parse error: {:?}", e);
1293                    }
1294                })
1295            }
1296        }
1297        deserializer.deserialize_string(Visitor)
1298    }
1299}
1300
1301impl PartialEq<&str> for Url {
1302    fn eq(&self, o: &&str) -> bool {
1303        &*self.0 == *o
1304    }
1305}
1306
1307impl PartialEq<String> for Url {
1308    fn eq(&self, o: &String) -> bool {
1309        &*self.0 == *o
1310    }
1311}
1312
1313/// A URL scheme.
1314#[derive(Serialize, Clone, Debug, Eq, Hash, PartialEq)]
1315pub struct UrlScheme(FlyStr);
1316
1317impl UrlScheme {
1318    /// Creates a `UrlScheme` from a `String`, returning an `Err` if the string fails
1319    /// validation. The string must be non-empty and no more than 100 characters
1320    /// in length. It must start with a lowercase ASCII letter (a-z),
1321    /// and contain only lowercase ASCII letters, digits, `+`, `-`, and `.`.
1322    pub fn new(url_scheme: impl AsRef<str> + Into<String>) -> Result<Self, ParseError> {
1323        Self::validate(url_scheme.as_ref())?;
1324        Ok(UrlScheme(FlyStr::new(url_scheme)))
1325    }
1326
1327    /// Validates `url_scheme` but does not construct a new `UrlScheme` object.
1328    /// See [`UrlScheme::new`] for validation details.
1329    pub fn validate(url_scheme: &str) -> Result<(), ParseError> {
1330        if url_scheme.is_empty() {
1331            return Err(ParseError::Empty);
1332        }
1333        if url_scheme.len() > MAX_NAME_LENGTH {
1334            return Err(ParseError::TooLong);
1335        }
1336        let mut iter = url_scheme.chars();
1337        let first_char = iter.next().unwrap();
1338        if !first_char.is_ascii_lowercase() {
1339            return Err(ParseError::InvalidValue);
1340        }
1341        if let Some(_) = iter.find(|&c| {
1342            !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '.' && c != '+' && c != '-'
1343        }) {
1344            return Err(ParseError::InvalidValue);
1345        }
1346        Ok(())
1347    }
1348
1349    pub fn as_str(&self) -> &str {
1350        &*self.0
1351    }
1352}
1353
1354impl fmt::Display for UrlScheme {
1355    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1356        fmt::Display::fmt(&self.0, f)
1357    }
1358}
1359
1360impl FromStr for UrlScheme {
1361    type Err = ParseError;
1362
1363    fn from_str(s: &str) -> Result<Self, Self::Err> {
1364        Self::new(s)
1365    }
1366}
1367
1368impl From<UrlScheme> for String {
1369    fn from(u: UrlScheme) -> String {
1370        u.0.into()
1371    }
1372}
1373
1374impl<'de> de::Deserialize<'de> for UrlScheme {
1375    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1376    where
1377        D: de::Deserializer<'de>,
1378    {
1379        struct Visitor;
1380
1381        impl<'de> de::Visitor<'de> for Visitor {
1382            type Value = UrlScheme;
1383
1384            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1385                f.write_str("a non-empty URL scheme no more than 100 characters in length")
1386            }
1387
1388            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
1389            where
1390                E: de::Error,
1391            {
1392                s.parse().map_err(|err| match err {
1393                    ParseError::InvalidValue => {
1394                        E::invalid_value(de::Unexpected::Str(s), &"a valid URL scheme")
1395                    }
1396                    ParseError::TooLong | ParseError::Empty => E::invalid_length(
1397                        s.len(),
1398                        &"a non-empty URL scheme no more than 100 characters in length",
1399                    ),
1400                    e => {
1401                        panic!("unexpected parse error: {:?}", e);
1402                    }
1403                })
1404            }
1405        }
1406        deserializer.deserialize_string(Visitor)
1407    }
1408}
1409
1410/// The duration of child components in a collection. See [`Durability`].
1411///
1412/// [`Durability`]: ../../fidl_fuchsia_sys2/enum.Durability.html
1413#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
1414#[serde(rename_all = "snake_case")]
1415pub enum Durability {
1416    Transient,
1417    /// An instance is started on creation and exists until it stops.
1418    SingleRun,
1419}
1420
1421symmetrical_enums!(Durability, fdecl::Durability, Transient, SingleRun);
1422
1423/// A component instance's startup mode. See [`StartupMode`].
1424///
1425/// [`StartupMode`]: ../../fidl_fuchsia_sys2/enum.StartupMode.html
1426#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
1427#[serde(rename_all = "snake_case")]
1428pub enum StartupMode {
1429    Lazy,
1430    Eager,
1431}
1432
1433impl StartupMode {
1434    pub fn is_lazy(&self) -> bool {
1435        matches!(self, StartupMode::Lazy)
1436    }
1437}
1438
1439symmetrical_enums!(StartupMode, fdecl::StartupMode, Lazy, Eager);
1440
1441impl Default for StartupMode {
1442    fn default() -> Self {
1443        Self::Lazy
1444    }
1445}
1446
1447/// A component instance's recovery policy. See [`OnTerminate`].
1448///
1449/// [`OnTerminate`]: ../../fidl_fuchsia_sys2/enum.OnTerminate.html
1450#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
1451#[serde(rename_all = "snake_case")]
1452pub enum OnTerminate {
1453    None,
1454    Reboot,
1455}
1456
1457symmetrical_enums!(OnTerminate, fdecl::OnTerminate, None, Reboot);
1458
1459impl Default for OnTerminate {
1460    fn default() -> Self {
1461        Self::None
1462    }
1463}
1464
1465/// The kinds of offers that can target components in a given collection. See
1466/// [`AllowedOffers`].
1467///
1468/// [`AllowedOffers`]: ../../fidl_fuchsia_sys2/enum.AllowedOffers.html
1469#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1470#[serde(rename_all = "snake_case")]
1471pub enum AllowedOffers {
1472    StaticOnly,
1473    StaticAndDynamic,
1474}
1475
1476symmetrical_enums!(AllowedOffers, fdecl::AllowedOffers, StaticOnly, StaticAndDynamic);
1477
1478impl Default for AllowedOffers {
1479    fn default() -> Self {
1480        Self::StaticOnly
1481    }
1482}
1483
1484/// Offered dependency type. See [`DependencyType`].
1485///
1486/// [`DependencyType`]: ../../fidl_fuchsia_sys2/enum.DependencyType.html
1487#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1488#[serde(rename_all = "snake_case")]
1489pub enum DependencyType {
1490    Strong,
1491    Weak,
1492}
1493
1494symmetrical_enums!(DependencyType, fdecl::DependencyType, Strong, Weak);
1495
1496impl Default for DependencyType {
1497    fn default() -> Self {
1498        Self::Strong
1499    }
1500}
1501
1502/// Capability availability. See [`Availability`].
1503///
1504/// [`Availability`]: ../../fidl_fuchsia_sys2/enum.Availability.html
1505#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Copy)]
1506#[serde(rename_all = "snake_case")]
1507pub enum Availability {
1508    Required,
1509    Optional,
1510    SameAsTarget,
1511    Transitional,
1512}
1513
1514symmetrical_enums!(
1515    Availability,
1516    fdecl::Availability,
1517    Required,
1518    Optional,
1519    SameAsTarget,
1520    Transitional
1521);
1522
1523impl Display for Availability {
1524    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1525        match self {
1526            Availability::Required => write!(f, "Required"),
1527            Availability::Optional => write!(f, "Optional"),
1528            Availability::SameAsTarget => write!(f, "SameAsTarget"),
1529            Availability::Transitional => write!(f, "Transitional"),
1530        }
1531    }
1532}
1533
1534// TODO(cgonyeo): remove this once we've soft migrated to the availability field being required.
1535impl Default for Availability {
1536    fn default() -> Self {
1537        Self::Required
1538    }
1539}
1540
1541impl PartialOrd for Availability {
1542    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
1543        match (*self, *other) {
1544            (Availability::Transitional, Availability::Optional)
1545            | (Availability::Transitional, Availability::Required)
1546            | (Availability::Optional, Availability::Required) => Some(cmp::Ordering::Less),
1547            (Availability::Optional, Availability::Transitional)
1548            | (Availability::Required, Availability::Transitional)
1549            | (Availability::Required, Availability::Optional) => Some(cmp::Ordering::Greater),
1550            (Availability::Required, Availability::Required)
1551            | (Availability::Optional, Availability::Optional)
1552            | (Availability::Transitional, Availability::Transitional)
1553            | (Availability::SameAsTarget, Availability::SameAsTarget) => {
1554                Some(cmp::Ordering::Equal)
1555            }
1556            (Availability::SameAsTarget, _) | (_, Availability::SameAsTarget) => None,
1557        }
1558    }
1559}
1560
1561/// Specifies when the framework will open the protocol from the provider
1562/// component's outgoing directory when someone requests the capability. See
1563/// [`DeliveryType`].
1564///
1565/// [`DeliveryType`]: ../../fidl_fuchsia_component_decl/enum.DeliveryType.html
1566#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Copy)]
1567#[serde(rename_all = "snake_case")]
1568pub enum DeliveryType {
1569    Immediate,
1570    OnReadable,
1571}
1572
1573#[cfg(fuchsia_api_level_at_least = "HEAD")]
1574impl TryFrom<fdecl::DeliveryType> for DeliveryType {
1575    type Error = fdecl::DeliveryType;
1576
1577    fn try_from(value: fdecl::DeliveryType) -> Result<Self, Self::Error> {
1578        match value {
1579            fdecl::DeliveryType::Immediate => Ok(DeliveryType::Immediate),
1580            fdecl::DeliveryType::OnReadable => Ok(DeliveryType::OnReadable),
1581            fdecl::DeliveryTypeUnknown!() => Err(value),
1582        }
1583    }
1584}
1585
1586#[cfg(fuchsia_api_level_at_least = "HEAD")]
1587impl From<DeliveryType> for fdecl::DeliveryType {
1588    fn from(value: DeliveryType) -> Self {
1589        match value {
1590            DeliveryType::Immediate => fdecl::DeliveryType::Immediate,
1591            DeliveryType::OnReadable => fdecl::DeliveryType::OnReadable,
1592        }
1593    }
1594}
1595
1596impl Display for DeliveryType {
1597    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1598        match self {
1599            DeliveryType::Immediate => write!(f, "Immediate"),
1600            DeliveryType::OnReadable => write!(f, "OnReadable"),
1601        }
1602    }
1603}
1604
1605impl Default for DeliveryType {
1606    fn default() -> Self {
1607        Self::Immediate
1608    }
1609}
1610
1611#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1612#[serde(rename_all = "snake_case")]
1613pub enum StorageId {
1614    StaticInstanceId,
1615    StaticInstanceIdOrMoniker,
1616}
1617
1618symmetrical_enums!(StorageId, fdecl::StorageId, StaticInstanceId, StaticInstanceIdOrMoniker);
1619
1620#[cfg(test)]
1621mod tests {
1622    use super::*;
1623    use assert_matches::assert_matches;
1624    use serde_json::json;
1625    use std::collections::HashSet;
1626    use std::iter::repeat;
1627
1628    macro_rules! expect_ok {
1629        ($type_:ty, $($input:tt)+) => {
1630            assert_matches!(
1631                serde_json::from_str::<$type_>(&json!($($input)*).to_string()),
1632                Ok(_)
1633            );
1634        };
1635    }
1636
1637    macro_rules! expect_ok_no_serialize {
1638        ($type_:ty, $($input:tt)+) => {
1639            assert_matches!(
1640                ($($input)*).parse::<$type_>(),
1641                Ok(_)
1642            );
1643        };
1644    }
1645
1646    macro_rules! expect_err_no_serialize {
1647        ($type_:ty, $err:pat, $($input:tt)+) => {
1648            assert_matches!(
1649                ($($input)*).parse::<$type_>(),
1650                Err($err)
1651            );
1652        };
1653    }
1654
1655    macro_rules! expect_err {
1656        ($type_:ty, $err:pat, $($input:tt)+) => {
1657            assert_matches!(
1658                ($($input)*).parse::<$type_>(),
1659                Err($err)
1660            );
1661            assert_matches!(
1662                serde_json::from_str::<$type_>(&json!($($input)*).to_string()),
1663                Err(_)
1664            );
1665        };
1666    }
1667
1668    #[test]
1669    fn test_valid_name() {
1670        expect_ok!(Name, "foo");
1671        expect_ok!(Name, "Foo");
1672        expect_ok!(Name, "O123._-");
1673        expect_ok!(Name, "_O123._-");
1674        expect_ok!(Name, repeat("x").take(255).collect::<String>());
1675    }
1676
1677    #[test]
1678    fn test_invalid_name() {
1679        expect_err!(Name, ParseError::Empty, "");
1680        expect_err!(Name, ParseError::InvalidValue, "-");
1681        expect_err!(Name, ParseError::InvalidValue, ".");
1682        expect_err!(Name, ParseError::InvalidValue, "@&%^");
1683        expect_err!(Name, ParseError::TooLong, repeat("x").take(256).collect::<String>());
1684    }
1685
1686    #[test]
1687    fn test_valid_path() {
1688        expect_ok!(Path, "/foo");
1689        expect_ok!(Path, "/foo/bar");
1690        expect_ok!(Path, format!("/{}", repeat("x").take(100).collect::<String>()).as_str());
1691        // 2047 * 2 characters per repeat = 4094
1692        expect_ok!(Path, repeat("/x").take(2047).collect::<String>().as_str());
1693    }
1694
1695    #[test]
1696    fn test_invalid_path() {
1697        expect_err!(Path, ParseError::Empty, "");
1698        expect_err!(Path, ParseError::InvalidValue, "/");
1699        expect_err!(Path, ParseError::InvalidValue, ".");
1700        expect_err!(Path, ParseError::NoLeadingSlash, "foo");
1701        expect_err!(Path, ParseError::NoLeadingSlash, "foo/");
1702        expect_err!(Path, ParseError::InvalidValue, "/foo/");
1703        expect_err!(Path, ParseError::InvalidValue, "/foo//bar");
1704        expect_err!(Path, ParseError::InvalidSegment, "/fo\0b/bar");
1705        expect_err!(Path, ParseError::InvalidSegment, "/.");
1706        expect_err!(Path, ParseError::InvalidSegment, "/foo/.");
1707        expect_err!(
1708            Path,
1709            ParseError::InvalidSegment,
1710            format!("/{}", repeat("x").take(256).collect::<String>()).as_str()
1711        );
1712        // 2048 * 2 characters per repeat = 4096
1713        expect_err!(
1714            Path,
1715            ParseError::TooLong,
1716            repeat("/x").take(2048).collect::<String>().as_str()
1717        );
1718    }
1719
1720    #[test]
1721    fn test_name_hash() {
1722        {
1723            let n1 = Name::new("a").unwrap();
1724            let s_b = repeat("b").take(255).collect::<String>();
1725            let n2 = Name::new(&s_b).unwrap();
1726            let b1 = BorrowedName::new("a").unwrap();
1727            let b2 = BorrowedName::new(&s_b).unwrap();
1728
1729            let mut set = HashSet::new();
1730            set.insert(n1.clone());
1731            assert!(set.contains(&n1));
1732            assert!(set.contains(b1));
1733            assert!(!set.contains(&n2));
1734            assert!(!set.contains(b2));
1735            set.insert(n2.clone());
1736            assert!(set.contains(&n1));
1737            assert!(set.contains(b1));
1738            assert!(set.contains(&n2));
1739            assert!(set.contains(b2));
1740        }
1741        {
1742            let n1 = LongName::new("a").unwrap();
1743            let s_b = repeat("b").take(1024).collect::<String>();
1744            let n2 = LongName::new(&s_b).unwrap();
1745            let b1 = BorrowedLongName::new("a").unwrap();
1746            let b2 = BorrowedLongName::new(&s_b).unwrap();
1747
1748            let mut set = HashSet::new();
1749            set.insert(n1.clone());
1750            assert!(set.contains(&n1));
1751            assert!(set.contains(b1));
1752            assert!(!set.contains(&n2));
1753            assert!(!set.contains(b2));
1754            set.insert(n2.clone());
1755            assert!(set.contains(&n1));
1756            assert!(set.contains(b1));
1757            assert!(set.contains(&n2));
1758            assert!(set.contains(b2));
1759        }
1760    }
1761
1762    // Keep in sync with test_relative_path_methods()
1763    #[test]
1764    fn test_path_methods() {
1765        let dot = RelativePath::dot();
1766        let prefix = Path::new("/some/path").unwrap();
1767        let suffix = RelativePath::new("another/path").unwrap();
1768        let segment = Name::new("segment").unwrap();
1769
1770        let mut path = prefix.clone();
1771        assert!(path.extend(suffix.clone()));
1772        assert_eq!(path, "/some/path/another/path".parse().unwrap());
1773        assert_eq!(
1774            path.split(),
1775            [
1776                BorrowedName::new("some").unwrap(),
1777                BorrowedName::new("path").unwrap(),
1778                BorrowedName::new("another").unwrap(),
1779                BorrowedName::new("path").unwrap(),
1780            ]
1781        );
1782
1783        let mut path = prefix.clone();
1784        assert!(path.extend(dot.clone()));
1785        assert_eq!(path, "/some/path".parse().unwrap());
1786
1787        let mut path = prefix.clone();
1788        assert!(path.push(segment.clone()));
1789        assert_eq!(path, "/some/path/segment".parse().unwrap());
1790        assert!(path.push(segment.clone()));
1791        assert_eq!(path, "/some/path/segment/segment".parse().unwrap());
1792        assert_eq!(
1793            path.split(),
1794            [
1795                BorrowedName::new("some").unwrap(),
1796                BorrowedName::new("path").unwrap(),
1797                BorrowedName::new("segment").unwrap(),
1798                BorrowedName::new("segment").unwrap(),
1799            ]
1800        );
1801
1802        let long_path =
1803            Path::new(format!("{}/xx", repeat("/x").take(4092 / 2).collect::<String>())).unwrap();
1804        let mut path = long_path.clone();
1805        // One more than the maximum size.
1806        assert!(!path.push("a".parse().unwrap()));
1807        assert_eq!(path, long_path);
1808        assert!(!path.extend("a".parse().unwrap()));
1809        assert_eq!(path, long_path);
1810    }
1811
1812    #[test]
1813    fn test_valid_namespace_path() {
1814        expect_ok_no_serialize!(NamespacePath, "/");
1815        expect_ok_no_serialize!(NamespacePath, "/foo");
1816        expect_ok_no_serialize!(NamespacePath, "/foo/bar");
1817        expect_ok_no_serialize!(
1818            NamespacePath,
1819            format!("/{}", repeat("x").take(100).collect::<String>()).as_str()
1820        );
1821        // 2047 * 2 characters per repeat = 4094
1822        expect_ok_no_serialize!(
1823            NamespacePath,
1824            repeat("/x").take(2047).collect::<String>().as_str()
1825        );
1826    }
1827
1828    #[test]
1829    fn test_invalid_namespace_path() {
1830        expect_err_no_serialize!(NamespacePath, ParseError::Empty, "");
1831        expect_err_no_serialize!(NamespacePath, ParseError::InvalidValue, ".");
1832        expect_err_no_serialize!(NamespacePath, ParseError::NoLeadingSlash, "foo");
1833        expect_err_no_serialize!(NamespacePath, ParseError::NoLeadingSlash, "foo/");
1834        expect_err_no_serialize!(NamespacePath, ParseError::InvalidValue, "/foo/");
1835        expect_err_no_serialize!(NamespacePath, ParseError::InvalidValue, "/foo//bar");
1836        expect_err_no_serialize!(NamespacePath, ParseError::InvalidSegment, "/fo\0b/bar");
1837        expect_err_no_serialize!(NamespacePath, ParseError::InvalidSegment, "/.");
1838        expect_err_no_serialize!(NamespacePath, ParseError::InvalidSegment, "/foo/.");
1839        expect_err_no_serialize!(
1840            NamespacePath,
1841            ParseError::InvalidSegment,
1842            format!("/{}", repeat("x").take(256).collect::<String>()).as_str()
1843        );
1844        // 2048 * 2 characters per repeat = 4096
1845        expect_err_no_serialize!(
1846            Path,
1847            ParseError::TooLong,
1848            repeat("/x").take(2048).collect::<String>().as_str()
1849        );
1850    }
1851
1852    #[test]
1853    fn test_path_parent_basename() {
1854        let path = Path::new("/foo").unwrap();
1855        assert_eq!((path.parent().to_string().as_str(), path.basename().as_str()), ("/", "foo"));
1856        let path = Path::new("/foo/bar").unwrap();
1857        assert_eq!((path.parent().to_string().as_str(), path.basename().as_str()), ("/foo", "bar"));
1858        let path = Path::new("/foo/bar/baz").unwrap();
1859        assert_eq!(
1860            (path.parent().to_string().as_str(), path.basename().as_str()),
1861            ("/foo/bar", "baz")
1862        );
1863    }
1864
1865    #[test]
1866    fn test_separated_path() {
1867        fn test_path(path: SeparatedPath, in_expected_segments: Vec<&str>) {
1868            let expected_segments: Vec<&BorrowedName> =
1869                in_expected_segments.iter().map(|s| BorrowedName::new(*s).unwrap()).collect();
1870            let segments: Vec<&BorrowedName> = path.iter_segments().collect();
1871            assert_eq!(segments, expected_segments);
1872            let borrowed_path = path.as_ref();
1873            let segments: Vec<&BorrowedName> = borrowed_path.iter_segments().collect();
1874            assert_eq!(segments, expected_segments);
1875            let owned_path = borrowed_path.to_owned();
1876            assert_eq!(path, owned_path);
1877            let expected_fmt = in_expected_segments.join("/");
1878            assert_eq!(format!("{path}"), expected_fmt);
1879            assert_eq!(format!("{owned_path}"), expected_fmt);
1880        }
1881        test_path(
1882            SeparatedPath { dirname: ".".parse().unwrap(), basename: "foo".parse().unwrap() },
1883            vec!["foo"],
1884        );
1885        test_path(
1886            SeparatedPath { dirname: "bar".parse().unwrap(), basename: "foo".parse().unwrap() },
1887            vec!["bar", "foo"],
1888        );
1889        test_path(
1890            SeparatedPath { dirname: "bar/baz".parse().unwrap(), basename: "foo".parse().unwrap() },
1891            vec!["bar", "baz", "foo"],
1892        );
1893    }
1894
1895    #[test]
1896    fn test_valid_relative_path() {
1897        expect_ok!(RelativePath, ".");
1898        expect_ok!(RelativePath, "foo");
1899        expect_ok!(RelativePath, "foo/bar");
1900        expect_ok!(RelativePath, &format!("x{}", repeat("/x").take(2047).collect::<String>()));
1901    }
1902
1903    #[test]
1904    fn test_invalid_relative_path() {
1905        expect_err!(RelativePath, ParseError::Empty, "");
1906        expect_err!(RelativePath, ParseError::InvalidValue, "/");
1907        expect_err!(RelativePath, ParseError::InvalidValue, "/foo");
1908        expect_err!(RelativePath, ParseError::InvalidValue, "foo/");
1909        expect_err!(RelativePath, ParseError::InvalidValue, "/foo/");
1910        expect_err!(RelativePath, ParseError::InvalidValue, "foo//bar");
1911        expect_err!(RelativePath, ParseError::InvalidSegment, "..");
1912        expect_err!(RelativePath, ParseError::InvalidSegment, "foo/..");
1913        expect_err!(
1914            RelativePath,
1915            ParseError::TooLong,
1916            &format!("x{}", repeat("/x").take(2048).collect::<String>())
1917        );
1918    }
1919
1920    // Keep in sync with test_path_methods()
1921    #[test]
1922    fn test_relative_path_methods() {
1923        let dot = RelativePath::dot();
1924        let prefix = RelativePath::new("some/path").unwrap();
1925        let suffix = RelativePath::new("another/path").unwrap();
1926        let segment = Name::new("segment").unwrap();
1927
1928        let mut path = prefix.clone();
1929        assert!(path.extend(suffix.clone()));
1930        assert_eq!(path, "some/path/another/path".parse().unwrap());
1931        assert_eq!(
1932            path.split(),
1933            [
1934                BorrowedName::new("some").unwrap(),
1935                BorrowedName::new("path").unwrap(),
1936                BorrowedName::new("another").unwrap(),
1937                BorrowedName::new("path").unwrap(),
1938            ]
1939        );
1940        assert_eq!(path.pop_front(), Some(Name::new("some").unwrap()));
1941        assert_eq!(path.pop_front(), Some(Name::new("path").unwrap()));
1942        assert_eq!(path.pop_front(), Some(Name::new("another").unwrap()));
1943        assert_eq!(path.pop_front(), Some(Name::new("path").unwrap()));
1944        assert_eq!(path.pop_front(), None);
1945
1946        let mut path = prefix.clone();
1947        assert!(path.extend(dot.clone()));
1948        assert_eq!(path, "some/path".parse().unwrap());
1949        let mut path = dot.clone();
1950        assert!(path.extend(suffix));
1951        assert_eq!(path, "another/path".parse().unwrap());
1952        let mut path = dot.clone();
1953        assert!(path.extend(dot.clone()));
1954        assert_eq!(path, RelativePath::dot());
1955
1956        let mut path = prefix.clone();
1957        assert!(path.push(segment.clone()));
1958        assert_eq!(path, "some/path/segment".parse().unwrap());
1959        assert!(path.push(segment.clone()));
1960        assert_eq!(path, "some/path/segment/segment".parse().unwrap());
1961        assert_eq!(
1962            path.split(),
1963            [
1964                BorrowedName::new("some").unwrap(),
1965                BorrowedName::new("path").unwrap(),
1966                BorrowedName::new("segment").unwrap(),
1967                BorrowedName::new("segment").unwrap(),
1968            ]
1969        );
1970
1971        let mut path = dot.clone();
1972        assert!(path.push(segment.clone()));
1973        assert_eq!(path, "segment".parse().unwrap());
1974
1975        let long_path =
1976            RelativePath::new(format!("{}x", repeat("x/").take(4094 / 2).collect::<String>()))
1977                .unwrap();
1978        let mut path = long_path.clone();
1979        // One more than the maximum size.
1980        assert!(!path.push("a".parse().unwrap()));
1981        assert_eq!(path, long_path);
1982        assert!(!path.extend("a".parse().unwrap()));
1983        assert_eq!(path, long_path);
1984    }
1985
1986    #[test]
1987    fn test_valid_url() {
1988        expect_ok!(Url, "a://foo");
1989        expect_ok!(Url, "#relative-url");
1990        expect_ok!(Url, &format!("a://{}", repeat("x").take(4092).collect::<String>()));
1991    }
1992
1993    #[test]
1994    fn test_invalid_url() {
1995        expect_err!(Url, ParseError::Empty, "");
1996        expect_err!(Url, ParseError::InvalidComponentUrl { .. }, "foo");
1997        expect_err!(
1998            Url,
1999            ParseError::TooLong,
2000            &format!("a://{}", repeat("x").take(4093).collect::<String>())
2001        );
2002    }
2003
2004    #[test]
2005    fn test_valid_url_scheme() {
2006        expect_ok!(UrlScheme, "fuch.sia-pkg+0");
2007        expect_ok!(UrlScheme, &format!("{}", repeat("f").take(255).collect::<String>()));
2008    }
2009
2010    #[test]
2011    fn test_invalid_url_scheme() {
2012        expect_err!(UrlScheme, ParseError::Empty, "");
2013        expect_err!(UrlScheme, ParseError::InvalidValue, "0fuch.sia-pkg+0");
2014        expect_err!(UrlScheme, ParseError::InvalidValue, "fuchsia_pkg");
2015        expect_err!(UrlScheme, ParseError::InvalidValue, "FUCHSIA-PKG");
2016        expect_err!(
2017            UrlScheme,
2018            ParseError::TooLong,
2019            &format!("{}", repeat("f").take(256).collect::<String>())
2020        );
2021    }
2022
2023    #[test]
2024    fn test_name_error_message() {
2025        let input = r#"
2026            "foo$"
2027        "#;
2028        let err = serde_json::from_str::<Name>(input).expect_err("must fail");
2029        assert_eq!(
2030            err.to_string(),
2031            "invalid value: string \"foo$\", expected a name \
2032            that consists of [A-Za-z0-9_.-] and starts with [A-Za-z0-9_] \
2033            at line 2 column 18"
2034        );
2035        assert_eq!(err.line(), 2);
2036        assert_eq!(err.column(), 18);
2037    }
2038
2039    #[test]
2040    fn test_path_error_message() {
2041        let input = r#"
2042            "foo";
2043        "#;
2044        let err = serde_json::from_str::<Path>(input).expect_err("must fail");
2045        assert_eq!(
2046            err.to_string(),
2047            "invalid value: string \"foo\", expected a path with leading `/` and non-empty \
2048            segments, where each segment is no \
2049            more than fuchsia.io/MAX_NAME_LENGTH bytes in length, cannot be . or .., \
2050            and cannot contain embedded NULs at line 2 column 17"
2051        );
2052
2053        assert_eq!(err.line(), 2);
2054        assert_eq!(err.column(), 17);
2055    }
2056
2057    #[test]
2058    fn test_url_error_message() {
2059        let input = r#"
2060            "foo";
2061        "#;
2062        let err = serde_json::from_str::<Url>(input).expect_err("must fail");
2063        assert_eq!(
2064            err.to_string(),
2065            "invalid value: string \"foo\", expected a valid URL at line 2 \
2066             column 17"
2067        );
2068        assert_eq!(err.line(), 2);
2069        assert_eq!(err.column(), 17);
2070    }
2071
2072    #[test]
2073    fn test_url_scheme_error_message() {
2074        let input = r#"
2075            "9fuchsia_pkg"
2076        "#;
2077        let err = serde_json::from_str::<UrlScheme>(input).expect_err("must fail");
2078        assert_eq!(
2079            err.to_string(),
2080            "invalid value: string \"9fuchsia_pkg\", expected a valid URL scheme at line 2 column 26"
2081        );
2082        assert_eq!(err.line(), 2);
2083        assert_eq!(err.column(), 26);
2084    }
2085
2086    #[test]
2087    fn test_symmetrical_enums() {
2088        mod a {
2089            #[derive(Debug, PartialEq, Eq)]
2090            pub enum Streetlight {
2091                Green,
2092                Yellow,
2093                Red,
2094            }
2095        }
2096
2097        mod b {
2098            #[derive(Debug, PartialEq, Eq)]
2099            pub enum Streetlight {
2100                Green,
2101                Yellow,
2102                Red,
2103            }
2104        }
2105
2106        symmetrical_enums!(a::Streetlight, b::Streetlight, Green, Yellow, Red);
2107
2108        assert_eq!(a::Streetlight::Green, b::Streetlight::Green.into());
2109        assert_eq!(a::Streetlight::Yellow, b::Streetlight::Yellow.into());
2110        assert_eq!(a::Streetlight::Red, b::Streetlight::Red.into());
2111        assert_eq!(b::Streetlight::Green, a::Streetlight::Green.into());
2112        assert_eq!(b::Streetlight::Yellow, a::Streetlight::Yellow.into());
2113        assert_eq!(b::Streetlight::Red, a::Streetlight::Red.into());
2114    }
2115}