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