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