Skip to main content

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