Skip to main content

routing/
resolving.rs

1// Copyright 2022 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
5use crate::component_instance::{ComponentInstanceInterface, ExtendedInstanceInterface};
6use crate::error::ComponentInstanceError;
7use anyhow::Error;
8use clonable_error::ClonableError;
9use cm_graph::DependencyNode;
10use directed_graph::DirectedGraph;
11use fidl_fuchsia_component_resolution as fresolution;
12use fidl_fuchsia_io as fio;
13use std::sync::{Arc, LazyLock};
14use thiserror::Error;
15use url::Url;
16use version_history::AbiRevision;
17use zx_status as zx;
18
19#[cfg(target_os = "fuchsia")]
20use {
21    cm_rust::{FidlIntoNative, NativeIntoFidl},
22    fuchsia_trace as trace,
23};
24
25/// The prefix for relative URLs internally represented as url::Url.
26const RELATIVE_URL_PREFIX: &str = "relative:///";
27/// A default base URL from which to parse relative component URL
28/// components.
29static RELATIVE_URL_BASE: LazyLock<Url> =
30    LazyLock::new(|| Url::parse(RELATIVE_URL_PREFIX).unwrap());
31
32/// The response returned from a Resolver. This struct is derived from the FIDL
33/// [`fuchsia.component.resolution.Component`][fidl_fuchsia_component_resolution::Component]
34/// table, except that the opaque binary ComponentDecl has been deserialized and validated.
35#[derive(Debug)]
36pub struct ResolvedComponent {
37    /// The package context, from the component resolution context returned by
38    /// the resolver.
39    pub context_to_resolve_children: Option<ComponentResolutionContext>,
40    pub decl: cm_rust::ComponentDecl,
41    pub package: Option<ResolvedPackage>,
42    pub config_values: Option<cm_rust::ConfigValuesData>,
43    pub abi_revision: Option<AbiRevision>,
44    pub dependencies: DirectedGraph<DependencyNode>,
45}
46
47// This block and others in this file only build on target, because these functions rely on
48// mem_util which has a test dependency on `vfs`, which isn't typically allowed to be built on
49// host.
50#[cfg(target_os = "fuchsia")]
51impl TryFrom<fresolution::Component> for ResolvedComponent {
52    type Error = ResolverError;
53
54    fn try_from(component: fresolution::Component) -> Result<Self, Self::Error> {
55        let decl_buffer: fidl_fuchsia_mem::Data =
56            component.decl.ok_or(ResolverError::RemoteInvalidData)?;
57        let mut dependencies = DirectedGraph::new();
58        let decl = read_and_validate_manifest(&decl_buffer, &mut dependencies)?;
59        let config_values = match &decl.config {
60            Some(config) => match config.value_source {
61                cm_rust::ConfigValueSource::PackagePath(_) => {
62                    Some(read_and_validate_config_values(
63                        &component.config_values.ok_or(ResolverError::RemoteInvalidData)?,
64                    )?)
65                }
66                cm_rust::ConfigValueSource::Capabilities(_) => None,
67            },
68            None => None,
69        };
70        let context_to_resolve_children = component.resolution_context.map(Into::into);
71        let abi_revision = component.abi_revision.map(Into::into);
72        Ok(ResolvedComponent {
73            context_to_resolve_children,
74            decl,
75            package: component.package.map(TryInto::try_into).transpose()?,
76            config_values,
77            abi_revision,
78            dependencies,
79        })
80    }
81}
82
83#[cfg(target_os = "fuchsia")]
84impl From<ResolvedComponent> for fresolution::Component {
85    fn from(component: ResolvedComponent) -> Self {
86        let ResolvedComponent {
87            context_to_resolve_children,
88            decl,
89            package,
90            config_values,
91            abi_revision,
92            dependencies: _,
93        } = component;
94        let decl_bytes = fidl::persist(&decl.native_into_fidl())
95            .expect("failed to serialize validated manifest");
96        let decl_vmo = fidl::Vmo::create(decl_bytes.len() as u64).expect("failed to create VMO");
97        decl_vmo.write(&decl_bytes, 0).expect("failed to write to VMO");
98        fresolution::Component {
99            url: None,
100            decl: Some(fidl_fuchsia_mem::Data::Buffer(fidl_fuchsia_mem::Buffer {
101                vmo: decl_vmo,
102                size: decl_bytes.len() as u64,
103            })),
104            package: package.map(|p| fresolution::Package {
105                url: Some(p.url),
106                directory: Some(p.directory),
107                ..Default::default()
108            }),
109            config_values: config_values.map(|config_values| {
110                let config_values_bytes = fidl::persist(&config_values.native_into_fidl())
111                    .expect("failed to serialize config values");
112                let config_values_vmo = fidl::Vmo::create(config_values_bytes.len() as u64)
113                    .expect("failed to create VMO");
114                config_values_vmo.write(&config_values_bytes, 0).expect("failed to write to VMO");
115                fidl_fuchsia_mem::Data::Buffer(fidl_fuchsia_mem::Buffer {
116                    vmo: config_values_vmo,
117                    size: config_values_bytes.len() as u64,
118                })
119            }),
120            resolution_context: context_to_resolve_children.map(Into::into),
121            abi_revision: abi_revision.map(Into::into),
122            ..Default::default()
123        }
124    }
125}
126
127#[cfg(target_os = "fuchsia")]
128pub fn read_and_validate_manifest(
129    data: &fidl_fuchsia_mem::Data,
130    dependencies: &mut DirectedGraph<DependencyNode>,
131) -> Result<cm_rust::ComponentDecl, ResolverError> {
132    let bytes = mem_util::bytes_from_data(data).map_err(ResolverError::manifest_invalid)?;
133    read_and_validate_manifest_bytes(&bytes, dependencies)
134}
135
136#[cfg(target_os = "fuchsia")]
137pub fn read_and_validate_manifest_bytes(
138    bytes: &[u8],
139    dependencies: &mut DirectedGraph<DependencyNode>,
140) -> Result<cm_rust::ComponentDecl, ResolverError> {
141    trace::duration!("component_manager", "read_and_validate_manifest_bytes");
142    let component_decl: fidl_fuchsia_component_decl::Component =
143        fidl::unpersist(bytes).map_err(ResolverError::manifest_invalid)?;
144    cm_fidl_validator::validate(&component_decl, dependencies)
145        .map_err(ResolverError::manifest_invalid)?;
146    Ok(component_decl.fidl_into_native())
147}
148
149#[cfg(target_os = "fuchsia")]
150pub fn read_and_validate_config_values(
151    data: &fidl_fuchsia_mem::Data,
152) -> Result<cm_rust::ConfigValuesData, ResolverError> {
153    let bytes = mem_util::bytes_from_data(&data).map_err(ResolverError::config_values_invalid)?;
154    let values = fidl::unpersist(&bytes).map_err(ResolverError::fidl_error)?;
155    cm_fidl_validator::validate_values_data(&values)
156        .map_err(|e| ResolverError::config_values_invalid(e))?;
157    Ok(values.fidl_into_native())
158}
159
160/// The response returned from a Resolver. This struct is derived from the FIDL
161/// [`fuchsia.component.resolution.Package`][fidl_fuchsia_component_resolution::Package]
162/// table.
163#[derive(Debug)]
164pub struct ResolvedPackage {
165    /// The package url.
166    pub url: String,
167    /// The package directory client proxy.
168    pub directory: fidl::endpoints::ClientEnd<fio::DirectoryMarker>,
169}
170
171impl TryFrom<fresolution::Package> for ResolvedPackage {
172    type Error = ResolverError;
173
174    fn try_from(package: fresolution::Package) -> Result<Self, Self::Error> {
175        Ok(ResolvedPackage {
176            url: package.url.ok_or(ResolverError::PackageUrlMissing)?,
177            directory: package.directory.ok_or(ResolverError::PackageDirectoryMissing)?,
178        })
179    }
180}
181
182/// Convenience wrapper type for the autogenerated FIDL
183/// `fuchsia.component.resolution.Context`.
184#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
185pub struct ComponentResolutionContext {
186    pub bytes: Vec<u8>,
187}
188
189impl ComponentResolutionContext {
190    pub fn new(bytes: Vec<u8>) -> Self {
191        ComponentResolutionContext { bytes }
192    }
193}
194
195impl From<fresolution::Context> for ComponentResolutionContext {
196    fn from(context: fresolution::Context) -> Self {
197        ComponentResolutionContext { bytes: context.bytes }
198    }
199}
200
201impl From<&fresolution::Context> for ComponentResolutionContext {
202    fn from(context: &fresolution::Context) -> ComponentResolutionContext {
203        ComponentResolutionContext { bytes: context.bytes.clone() }
204    }
205}
206
207impl From<ComponentResolutionContext> for fresolution::Context {
208    fn from(context: ComponentResolutionContext) -> Self {
209        Self { bytes: context.bytes }
210    }
211}
212
213impl From<&ComponentResolutionContext> for fresolution::Context {
214    fn from(context: &ComponentResolutionContext) -> fresolution::Context {
215        Self { bytes: context.bytes.clone() }
216    }
217}
218
219impl<'a> From<&'a ComponentResolutionContext> for &'a [u8] {
220    fn from(context: &'a ComponentResolutionContext) -> &'a [u8] {
221        &context.bytes
222    }
223}
224
225/// Provides the `ComponentAddress` and context for resolving a child or
226/// descendent component.
227#[derive(Debug, Clone, PartialEq, Eq)]
228struct ResolvedAncestorComponent {
229    /// The component address, needed for relative path URLs (to get the
230    /// scheme used to find the required `Resolver`), or for relative resource
231    /// URLs (which will clone the parent's address, but replace the resource).
232    pub address: ComponentAddress,
233    /// The component's resolution_context, required for resolving descendents
234    /// using a relative path component URLs.
235    pub context_to_resolve_children: Option<ComponentResolutionContext>,
236}
237
238impl ResolvedAncestorComponent {
239    /// Creates a `ResolvedAncestorComponent` from one of its child components.
240    pub async fn direct_parent_of<C: ComponentInstanceInterface>(
241        component: &Arc<C>,
242    ) -> Result<Self, ResolverError> {
243        let parent_component = get_parent(component).await?;
244        Ok(Self {
245            address: parent_component.address_maybe_resolve().await?,
246            context_to_resolve_children: parent_component.context_to_resolve_children().await?,
247        })
248    }
249
250    /// Creates a `ResolvedAncestorComponent` from one of its child components.
251    pub async fn first_packaged_ancestor_of<C: ComponentInstanceInterface>(
252        component: &Arc<C>,
253    ) -> Result<Self, ResolverError> {
254        let mut parent_component = get_parent(component).await?;
255        loop {
256            // Loop until the parent has a valid context_to_resolve_children,
257            // or an error getting the next parent, or its resolved state.
258            {
259                let address = parent_component.address_maybe_resolve().await?;
260                // TODO(https://fxbug.dev/42053123): change this test to something more
261                // explicit, that is, return the parent's address and context if
262                // the component address is a packaged component (determined in
263                // some way).
264                //
265                // The issue being addressed here is, when resolving a relative
266                // subpackaged component URL, component manager MUST resolve the
267                // component using a "resolution context" _AND_ the resolver
268                // that provided that context. Typically these are provided by
269                // the parent component, but in the case of a RealmBuilder child
270                // component, its parent is the built "realm" (which was
271                // resolved by the realm_builder_resolver, using URL scheme
272                // "realm-builder://"). The child component's URL is supposed to
273                // be relative to the test component (the parent of the realm),
274                // which was probably resolved by the full-resolver (scheme
275                // "fuchsia-pkg://"). Knowing this expected topology, we can
276                // skip "realm-builder" components when searching for the
277                // required ancestor's URL scheme (to get the right resolver)
278                // and context. This is a brittle workaround that will be
279                // replaced.
280                //
281                // Some alternatives are under discussion, but the leading
282                // candidate, for now, is to allow a resolver to return a flag
283                // (with the resolved Component; perhaps `is_packaged()`) to
284                // indicate that descendents should (if true) use this component
285                // to get scheme and context for resolving relative path URLs
286                // (for example, subpackages). If false, get the parent's parent
287                // and so on.
288                if address.scheme() != "realm-builder" {
289                    return Ok(Self {
290                        address,
291                        context_to_resolve_children: parent_component
292                            .context_to_resolve_children()
293                            .await?,
294                    });
295                }
296            }
297            parent_component = get_parent(&parent_component).await?;
298        }
299    }
300}
301
302async fn get_parent<C: ComponentInstanceInterface>(
303    component: &Arc<C>,
304) -> Result<Arc<C>, ResolverError> {
305    if let ExtendedInstanceInterface::Component(parent_component) =
306        component.try_get_parent().map_err(|err| {
307            ResolverError::no_parent_context(anyhow::format_err!(
308                "Component {} ({}) has no parent for context: {:?}.",
309                component.moniker(),
310                component.url(),
311                err,
312            ))
313        })?
314    {
315        Ok(parent_component)
316    } else {
317        Err(ResolverError::no_parent_context(anyhow::format_err!(
318            "Component {} ({}) has no parent for context.",
319            component.moniker(),
320            component.url(),
321        )))
322    }
323}
324
325/// Indicates the kind of `ComponentAddress`, and holds `ComponentAddress` properties specific to
326/// its kind. Note that there is no kind for a relative resource component URL (a URL that only
327/// contains a resource fragment, such as `#meta/comp.cm`) because `ComponentAddress::from_url()`
328/// and `ComponentAddress::from_url_and_context()` will translate a resource fragment component URL
329/// into one of the fully-resolvable `ComponentAddress`s.
330#[derive(Debug, Clone, PartialEq, Eq)]
331pub enum ComponentAddress {
332    /// A fully-qualified component URL.
333    Absolute { url: Url },
334
335    /// A relative Component URL, starting with the package path; for example a
336    /// subpackage relative URL such as "needed_package#meta/dep_component.cm".
337    RelativePath {
338        /// This is the scheme of the ancestor component's absolute URL, used to identify the
339        /// `Resolver` in a `ResolverRegistry`.
340        scheme: String,
341
342        /// The relative URL, represented as a `url::Url` with the `relative:///` base prefix.
343        /// `url::Url` cannot represent relative urls directly.
344        url: Url,
345
346        /// An opaque value (from the perspective of component resolution)
347        /// required by the resolver when resolving a relative package path.
348        /// For a given child component, this property is populated from a
349        /// parent component's `resolution_context`, as returned by the parent
350        /// component's resolver.
351        context: ComponentResolutionContext,
352    },
353}
354
355impl ComponentAddress {
356    /// Creates a new `ComponentAddress` of the `Absolute` kind.
357    fn new_absolute(url: Url) -> Self {
358        Self::Absolute { url }
359    }
360
361    /// Creates a new `ComponentAddress` of the `RelativePath` kind.
362    pub fn new_relative_path(
363        path: &str,
364        some_resource: Option<&str>,
365        scheme: &str,
366        context: ComponentResolutionContext,
367    ) -> Result<Self, ResolverError> {
368        let mut url = RELATIVE_URL_BASE.clone();
369        url.set_path(path);
370        url.set_fragment(some_resource);
371        Self::check_relative_url(&url)?;
372        Ok(Self::RelativePath { url, context, scheme: scheme.into() })
373    }
374
375    /// Parse the given absolute `component_url` and create a `ComponentAddress`
376    /// with kind `Absolute`.
377    pub fn from_absolute_url(component_url: &cm_types::Url) -> Result<Self, ResolverError> {
378        match Url::parse(component_url.as_str()) {
379            Ok(url) => Ok(Self::new_absolute(url)),
380            Err(url::ParseError::RelativeUrlWithoutBase) => {
381                Err(ResolverError::RelativeUrlNotExpected(component_url.to_string()))
382            }
383            Err(err) => Err(ResolverError::malformed_url(err)),
384        }
385    }
386
387    /// Assumes the given string is a relative URL, and converts this string into
388    /// a `url::Url` by attaching a known, internal absolute URL prefix.
389    fn parse_relative_url(component_url: &cm_types::Url) -> Result<Url, ResolverError> {
390        let component_url = component_url.as_str();
391        match Url::parse(component_url) {
392            Ok(_) => Err(ResolverError::malformed_url(anyhow::format_err!(
393                "Error parsing a relative URL given absolute URL '{}'.",
394                component_url,
395            ))),
396            Err(url::ParseError::RelativeUrlWithoutBase) => {
397                RELATIVE_URL_BASE.join(component_url).map_err(|err| {
398                    ResolverError::malformed_url(anyhow::format_err!(
399                        "Error parsing a relative component URL '{}': {:?}.",
400                        component_url,
401                        err
402                    ))
403                })
404            }
405            Err(err) => Err(ResolverError::malformed_url(anyhow::format_err!(
406                "Unexpected error while parsing a component URL '{}': {:?}.",
407                component_url,
408                err,
409            ))),
410        }
411    }
412
413    fn check_relative_url(url: &Url) -> Result<(), ResolverError> {
414        let truncated_url = url.as_str().strip_prefix(RELATIVE_URL_PREFIX).ok_or_else(|| {
415            ResolverError::malformed_url(anyhow::format_err!(
416                "Could not strip relative prefix from url. This is a bug. {}",
417                url
418            ))
419        })?;
420        let relative_url = RELATIVE_URL_BASE.make_relative(&url).ok_or_else(|| {
421            ResolverError::malformed_url(anyhow::format_err!(
422                "Could not make relative url. This is a bug. {}",
423                url
424            ))
425        })?;
426        if truncated_url != relative_url {
427            return Err(ResolverError::malformed_url(anyhow::format_err!(
428                "Relative url generated from url::Url did not match expectations. \
429                This is a bug. {}",
430                url
431            )));
432        }
433        Ok(())
434    }
435
436    /// For `Url`s constructed from `parse_relative_url()`, the `path()` is expected
437    /// to include a slash prefix, but a String representation of a relative path
438    /// URL should not include the slash prefix. This convenience API assumes the
439    /// given `Url` is a relative URL, and strips the slash prefix, if present.
440    fn relative_path(relative_url: &Url) -> &str {
441        let path = relative_url.path();
442        path.strip_prefix('/').unwrap_or(path)
443    }
444
445    /// Parse the given `component_url` to determine if it is an absolute URL, a relative
446    /// subpackage URL, or a relative resource URL, and return the corresponding `ComponentAddress`
447    /// enum variant and value. If the URL is relative, use the component instance to get the
448    /// required resolution context from the component's parent.
449    pub async fn from_url<C: ComponentInstanceInterface>(
450        component_url: &cm_types::Url,
451        component: &Arc<C>,
452    ) -> Result<Self, ResolverError> {
453        Self::from(component_url, None, component).await
454    }
455
456    /// Parse the given `component_url` to determine if it is an absolute URL, a relative
457    /// subpackage URL, or a relative resource URL, and return the corresponding `ComponentAddress`
458    /// enum variant and value. If the URL is relative, the provided context is used.
459    pub async fn from_url_and_context<C: ComponentInstanceInterface>(
460        component_url: &cm_types::Url,
461        context: ComponentResolutionContext,
462        component: &Arc<C>,
463    ) -> Result<Self, ResolverError> {
464        Self::from(component_url, Some(context), component).await
465    }
466
467    /// This function is the helper for `from_url` and `from_url_and_context`. It assembles a new
468    /// `ComponentAddress`, using the provided resolution context. If a resolution context is not
469    /// provided, and the URL is a relative URL, the component's parent will be used to create a
470    /// context.
471    pub async fn from<C: ComponentInstanceInterface>(
472        component_url: &cm_types::Url,
473        context: Option<ComponentResolutionContext>,
474        component: &Arc<C>,
475    ) -> Result<Self, ResolverError> {
476        let result = Self::from_absolute_url(component_url);
477        if !matches!(result, Err(ResolverError::RelativeUrlNotExpected(_))) {
478            return result;
479        }
480        let relative_url = Self::parse_relative_url(component_url)?;
481        let relative_path = Self::relative_path(&relative_url);
482        if relative_url.fragment().is_none() && relative_path.is_empty() {
483            return Err(ResolverError::malformed_url(anyhow::format_err!("{}", component_url)));
484        }
485        if relative_url.query().is_some() {
486            return Err(ResolverError::malformed_url(anyhow::format_err!(
487                "Query strings are not allowed in relative component URLs: {}",
488                component_url
489            )));
490        }
491        if relative_path.is_empty() {
492            // The `component_url` had only a fragment, so the new address will be the same as its
493            // parent (for example, the same package), except for its resource.
494            let resolved_parent = ResolvedAncestorComponent::direct_parent_of(component).await?;
495            resolved_parent.address.clone_with_new_resource(relative_url.fragment())
496        } else {
497            // The `component_url` starts with a relative path (for example, a subpackage name).
498            // Create a `RelativePath` address, and resolve it using the
499            // `context_to_resolve_children`, from this component's parent, or the first ancestor
500            // that is from a "package". (Note that Realm Builder realms are synthesized, and not
501            // from a package. A test component using Realm Builder will build a realm and may add
502            // child components using subpackage references. Those child components should get
503            // resolved using the context of the test package, not the intermediate realm created
504            // via RealmBuilder.)
505            let resolved_ancestor =
506                ResolvedAncestorComponent::first_packaged_ancestor_of(component).await?;
507            let scheme = resolved_ancestor.address.scheme();
508            if let Some(context) = context {
509                Self::new_relative_path(relative_path, relative_url.fragment(), scheme, context)
510            } else {
511                let context = resolved_ancestor.context_to_resolve_children.clone().ok_or_else(|| {
512                        ResolverError::RelativeUrlMissingContext(format!(
513                            "Relative path component URL '{}' cannot be resolved because its ancestor did not provide a resolution context. The ancestor's component address is {:?}.",
514                             component_url, resolved_ancestor.address
515                        ))
516                    })?;
517                Self::new_relative_path(relative_path, relative_url.fragment(), scheme, context)
518            }
519        }
520    }
521
522    /// Creates a new `ComponentAddress` from `self` by replacing only the
523    /// component URL resource.
524    pub fn clone_with_new_resource(
525        &self,
526        some_resource: Option<&str>,
527    ) -> Result<Self, ResolverError> {
528        self.clone().consume_with_new_resource(some_resource)
529    }
530
531    pub fn consume_with_new_resource(
532        mut self,
533        some_resource: Option<&str>,
534    ) -> Result<Self, ResolverError> {
535        let url = match &mut self {
536            Self::Absolute { url } => url,
537            Self::RelativePath { url, .. } => url,
538        };
539        url.set_fragment(some_resource);
540        match self {
541            Self::Absolute { url } => Ok(Self::Absolute { url }),
542            Self::RelativePath { context, scheme, url } => {
543                Self::check_relative_url(&url)?;
544                Ok(Self::RelativePath { url, context, scheme })
545            }
546        }
547    }
548
549    /// True if the address is `Absolute`.
550    pub fn is_absolute(&self) -> bool {
551        matches!(self, Self::Absolute { .. })
552    }
553
554    /// True if the address is `RelativePath`.
555    pub fn is_relative_path(&self) -> bool {
556        matches!(self, Self::RelativePath { .. })
557    }
558
559    /// Returns the `ComponentResolutionContext` value required to resolve for a
560    /// `RelativePath` component URL.
561    ///
562    /// Panics if called for an `Absolute` component address.
563    pub fn context(&self) -> &ComponentResolutionContext {
564        if let Self::RelativePath { context, .. } = self {
565            &context
566        } else {
567            panic!("context() is only valid for `ComponentAddressKind::RelativePath");
568        }
569    }
570
571    /// Returns the URL scheme either provided for an `Absolute` URL or derived
572    /// from the component's parent. The scheme is used to look up a registered
573    /// resolver, when resolving the component.
574    pub fn scheme(&self) -> &str {
575        match self {
576            Self::Absolute { url } => url.scheme(),
577            Self::RelativePath { scheme, .. } => &scheme,
578        }
579    }
580
581    /// Returns the URL path.
582    pub fn path(&self) -> &str {
583        match self {
584            Self::Absolute { url } => url.path(),
585            Self::RelativePath { url, .. } => Self::relative_path(&url),
586        }
587    }
588
589    /// Returns the optional query value for an `Absolute` component URL.
590    /// Always returns `None` for `Relative` component URLs.
591    pub fn query(&self) -> Option<&str> {
592        match self {
593            Self::Absolute { url } => url.query(),
594            Self::RelativePath { .. } => None,
595        }
596    }
597
598    /// Returns the optional component resource, from the URL fragment.
599    pub fn resource(&self) -> Option<&str> {
600        match self {
601            Self::Absolute { url } => url.fragment(),
602            Self::RelativePath { url, .. } => url.fragment(),
603        }
604    }
605
606    /// Returns the resolver-ready URL string and, if it is a `RelativePath`,
607    /// `Some(context)`, or `None` for an `Absolute` address.
608    pub fn url(&self) -> &str {
609        match self {
610            Self::Absolute { url } => url.as_str(),
611            Self::RelativePath { url, .. } => &url.as_str()[RELATIVE_URL_PREFIX.len()..],
612        }
613    }
614
615    /// Returns the `url()` and `Some(context)` for resolving the URL,
616    /// if the kind is `RelativePath` (or `None` if `Absolute`).
617    pub fn to_url_and_context(&self) -> (&str, Option<&ComponentResolutionContext>) {
618        match self {
619            Self::Absolute { .. } => (self.url(), None),
620            Self::RelativePath { context, .. } => (self.url(), Some(context)),
621        }
622    }
623}
624
625/// Errors produced by built-in `Resolver`s and `resolving` APIs.
626#[derive(Debug, Error, Clone)]
627pub enum ResolverError {
628    #[error("an unexpected error occurred: {0}")]
629    Internal(#[source] ClonableError),
630    #[error("an IO error occurred: {0}")]
631    Io(#[source] ClonableError),
632    #[error("component manifest not found: {0}")]
633    ManifestNotFound(#[source] ClonableError),
634    #[error("package not found: {0}")]
635    PackageNotFound(#[source] ClonableError),
636    #[error("component manifest invalid: {0}")]
637    ManifestInvalid(#[source] ClonableError),
638    #[error("config values file invalid: {0}")]
639    ConfigValuesInvalid(#[source] ClonableError),
640    #[error("abi revision not found")]
641    AbiRevisionNotFound,
642    #[error("abi revision invalid: {0}")]
643    AbiRevisionInvalid(#[source] ClonableError),
644    #[error("failed to read config values: {0}")]
645    ConfigValuesIo(zx::Status),
646    #[error("scheme not registered")]
647    SchemeNotRegistered,
648    #[error("malformed url: {0}")]
649    MalformedUrl(#[source] ClonableError),
650    #[error("relative url requires a parent component with resolution context: {0}")]
651    NoParentContext(#[source] ClonableError),
652    #[error("package URL missing")]
653    PackageUrlMissing,
654    #[error("package directory handle missing")]
655    PackageDirectoryMissing,
656    #[error("a relative URL was not expected: {0}")]
657    RelativeUrlNotExpected(String),
658    #[error("failed to route resolver capability: {0}")]
659    RoutingError(#[source] ClonableError),
660    #[error("a context is required to resolve relative url: {0}")]
661    RelativeUrlMissingContext(String),
662    #[error("this component resolver does not resolve relative path component URLs: {0}")]
663    UnexpectedRelativePath(String),
664    #[error("the remote resolver returned invalid data")]
665    RemoteInvalidData,
666    #[error("an error occurred sending a FIDL request to the remote resolver: {0}")]
667    FidlError(#[source] ClonableError),
668}
669
670impl ResolverError {
671    pub fn as_zx_status(&self) -> zx::Status {
672        match self {
673            ResolverError::PackageNotFound(_)
674            | ResolverError::ManifestNotFound(_)
675            | ResolverError::ManifestInvalid(_)
676            | ResolverError::ConfigValuesInvalid(_)
677            | ResolverError::Io(_)
678            | ResolverError::ConfigValuesIo(_)
679            | ResolverError::AbiRevisionNotFound
680            | ResolverError::AbiRevisionInvalid(_)
681            | ResolverError::SchemeNotRegistered
682            | ResolverError::MalformedUrl(_)
683            | ResolverError::NoParentContext(_)
684            | ResolverError::RelativeUrlMissingContext(_)
685            | ResolverError::RemoteInvalidData
686            | ResolverError::PackageUrlMissing
687            | ResolverError::PackageDirectoryMissing
688            | ResolverError::UnexpectedRelativePath(_) => zx::Status::NOT_FOUND,
689
690            ResolverError::Internal(_)
691            | ResolverError::RelativeUrlNotExpected(_)
692            | ResolverError::RoutingError(_)
693            | ResolverError::FidlError(_) => zx::Status::INTERNAL,
694        }
695    }
696
697    pub fn internal(err: impl Into<Error>) -> Self {
698        Self::Internal(err.into().into())
699    }
700
701    pub fn io(err: impl Into<Error>) -> Self {
702        Self::Io(err.into().into())
703    }
704
705    pub fn manifest_not_found(err: impl Into<Error>) -> Self {
706        Self::ManifestNotFound(err.into().into())
707    }
708
709    pub fn package_not_found(err: impl Into<Error>) -> Self {
710        Self::PackageNotFound(err.into().into())
711    }
712
713    pub fn manifest_invalid(err: impl Into<Error>) -> Self {
714        Self::ManifestInvalid(err.into().into())
715    }
716
717    pub fn config_values_invalid(err: impl Into<Error>) -> Self {
718        Self::ConfigValuesInvalid(err.into().into())
719    }
720
721    pub fn abi_revision_invalid(err: impl Into<Error>) -> Self {
722        Self::AbiRevisionInvalid(err.into().into())
723    }
724
725    pub fn malformed_url(err: impl Into<Error>) -> Self {
726        Self::MalformedUrl(err.into().into())
727    }
728
729    pub fn no_parent_context(err: impl Into<Error>) -> Self {
730        Self::NoParentContext(err.into().into())
731    }
732
733    pub fn routing_error(err: impl Into<Error>) -> Self {
734        Self::RoutingError(err.into().into())
735    }
736
737    pub fn fidl_error(err: impl Into<Error>) -> Self {
738        Self::FidlError(err.into().into())
739    }
740}
741
742impl From<fresolution::ResolverError> for ResolverError {
743    fn from(err: fresolution::ResolverError) -> ResolverError {
744        match err {
745            fresolution::ResolverError::Internal => ResolverError::internal(RemoteError(err)),
746            fresolution::ResolverError::Io => ResolverError::io(RemoteError(err)),
747            fresolution::ResolverError::PackageNotFound
748            | fresolution::ResolverError::NoSpace
749            | fresolution::ResolverError::ResourceUnavailable
750            | fresolution::ResolverError::NotSupported => {
751                ResolverError::package_not_found(RemoteError(err))
752            }
753            fresolution::ResolverError::ManifestNotFound => {
754                ResolverError::manifest_not_found(RemoteError(err))
755            }
756            fresolution::ResolverError::InvalidArgs => {
757                ResolverError::malformed_url(RemoteError(err))
758            }
759            fresolution::ResolverError::InvalidManifest => {
760                ResolverError::ManifestInvalid(anyhow::Error::from(RemoteError(err)).into())
761            }
762            fresolution::ResolverError::ConfigValuesNotFound => {
763                ResolverError::ConfigValuesIo(zx::Status::NOT_FOUND)
764            }
765            fresolution::ResolverError::AbiRevisionNotFound => ResolverError::AbiRevisionNotFound,
766            fresolution::ResolverError::InvalidAbiRevision => {
767                ResolverError::abi_revision_invalid(RemoteError(err))
768            }
769        }
770    }
771}
772
773impl From<ResolverError> for fresolution::ResolverError {
774    fn from(err: ResolverError) -> fresolution::ResolverError {
775        match err {
776            ResolverError::Internal(_) => fresolution::ResolverError::Internal,
777            ResolverError::Io(_) => fresolution::ResolverError::Io,
778            ResolverError::ManifestNotFound(_) => fresolution::ResolverError::ManifestNotFound,
779            ResolverError::PackageNotFound(_) => fresolution::ResolverError::PackageNotFound,
780            ResolverError::ManifestInvalid(_) => fresolution::ResolverError::InvalidManifest,
781            ResolverError::ConfigValuesInvalid(_) => fresolution::ResolverError::InvalidManifest,
782            ResolverError::AbiRevisionNotFound => fresolution::ResolverError::AbiRevisionNotFound,
783            ResolverError::AbiRevisionInvalid(_) => fresolution::ResolverError::InvalidAbiRevision,
784            ResolverError::ConfigValuesIo(_) => fresolution::ResolverError::Io,
785            ResolverError::SchemeNotRegistered => fresolution::ResolverError::NotSupported,
786            ResolverError::MalformedUrl(_) => fresolution::ResolverError::InvalidArgs,
787            ResolverError::NoParentContext(_) => fresolution::ResolverError::Internal,
788            ResolverError::PackageUrlMissing => fresolution::ResolverError::PackageNotFound,
789            ResolverError::PackageDirectoryMissing => fresolution::ResolverError::PackageNotFound,
790            ResolverError::RelativeUrlNotExpected(_) => fresolution::ResolverError::InvalidArgs,
791            ResolverError::RoutingError(_) => fresolution::ResolverError::Internal,
792            ResolverError::RelativeUrlMissingContext(_) => fresolution::ResolverError::InvalidArgs,
793            ResolverError::UnexpectedRelativePath(_) => fresolution::ResolverError::InvalidArgs,
794            ResolverError::RemoteInvalidData => fresolution::ResolverError::InvalidManifest,
795            ResolverError::FidlError(_) => fresolution::ResolverError::Internal,
796        }
797    }
798}
799
800impl From<ComponentInstanceError> for ResolverError {
801    fn from(err: ComponentInstanceError) -> ResolverError {
802        use ComponentInstanceError::*;
803        match &err {
804            ComponentManagerInstanceUnavailable {}
805            | ComponentManagerInstanceUnexpected {}
806            | InstanceNotFound { .. }
807            | InstanceNotExecutable { .. }
808            | ResolveFailed { .. }
809            | StartFailed { .. }
810            | FailedToCreateStorage { .. } => {
811                ResolverError::Internal(ClonableError::from(anyhow::format_err!("{:?}", err)))
812            }
813            NoAbsoluteUrl { .. } => ResolverError::NoParentContext(ClonableError::from(
814                anyhow::format_err!("{:?}", err),
815            )),
816            MalformedUrl { .. } => {
817                ResolverError::MalformedUrl(ClonableError::from(anyhow::format_err!("{:?}", err)))
818            }
819        }
820    }
821}
822
823#[derive(Error, Clone, Debug)]
824#[error("remote resolver responded with {0:?}")]
825struct RemoteError(fresolution::ResolverError);
826
827#[cfg(test)]
828mod tests {
829    use super::*;
830    use crate::bedrock::sandbox_construction::ComponentSandbox;
831    use crate::component_instance::TopInstanceInterface;
832    use crate::policy::GlobalPolicyChecker;
833    use assert_matches::assert_matches;
834    use async_trait::async_trait;
835    use capability_source::{BuiltinCapabilities, NamespaceCapabilities};
836    use cm_rust_testing::new_decl_from_json;
837    use fidl::endpoints::create_endpoints;
838    use fidl_fuchsia_component_decl as fdecl;
839    use fidl_fuchsia_mem as fmem;
840    use moniker::{BorrowedChildName, Moniker};
841    use serde_json::json;
842
843    fn from_absolute_url(url: &str) -> ComponentAddress {
844        ComponentAddress::from_absolute_url(&url.parse().unwrap()).unwrap()
845    }
846
847    fn parse_relative_url(url: &str) -> Url {
848        ComponentAddress::parse_relative_url(&url.parse().unwrap()).unwrap()
849    }
850
851    #[test]
852    fn test_resolved_package() {
853        let url = "some_url".to_string();
854        let (dir_client, _) = create_endpoints::<fio::DirectoryMarker>();
855        let fidl_package = fresolution::Package {
856            url: Some(url.clone()),
857            directory: Some(dir_client),
858            ..Default::default()
859        };
860        let resolved_package = ResolvedPackage::try_from(fidl_package).unwrap();
861        assert_eq!(resolved_package.url, url);
862    }
863
864    #[test]
865    fn test_component_address() {
866        let address = from_absolute_url("some-scheme://fuchsia.com/package#meta/comp.cm");
867        assert!(address.is_absolute());
868        assert_eq!(address.scheme(), "some-scheme");
869        assert_eq!(address.path(), "/package");
870        assert_eq!(address.query(), None);
871        assert_eq!(address.resource(), Some("meta/comp.cm"));
872        assert_eq!(address.url(), "some-scheme://fuchsia.com/package#meta/comp.cm");
873        assert_matches!(
874            address.to_url_and_context(),
875            ("some-scheme://fuchsia.com/package#meta/comp.cm", None)
876        );
877
878        let abs_address = ComponentAddress::new_absolute(
879            Url::parse("some-scheme://fuchsia.com/package#meta/comp.cm").unwrap(),
880        );
881        assert_eq!(abs_address, address);
882
883        assert_eq!(abs_address, address);
884        assert!(abs_address.is_absolute());
885        assert_eq!(abs_address.scheme(), "some-scheme");
886        assert_eq!(abs_address.path(), "/package");
887        assert_eq!(abs_address.query(), None);
888        assert_eq!(abs_address.resource(), Some("meta/comp.cm"));
889        assert_eq!(abs_address.url(), "some-scheme://fuchsia.com/package#meta/comp.cm");
890        assert_matches!(
891            abs_address.to_url_and_context(),
892            ("some-scheme://fuchsia.com/package#meta/comp.cm", None)
893        );
894
895        let cloned_address = abs_address.clone();
896        assert_eq!(abs_address, cloned_address);
897
898        let address2 = abs_address.clone_with_new_resource(Some("meta/other_comp.cm")).unwrap();
899        assert_ne!(address2, abs_address);
900        assert!(address2.is_absolute());
901        assert_eq!(address2.resource(), Some("meta/other_comp.cm"));
902        assert_eq!(address2.scheme(), "some-scheme");
903        assert_eq!(address2.path(), "/package");
904        assert_eq!(address2.query(), None);
905
906        let rel_address = ComponentAddress::new_relative_path(
907            "subpackage",
908            Some("meta/subcomp.cm"),
909            "some-scheme",
910            ComponentResolutionContext::new(vec![b'4', b'5', b'6']),
911        )
912        .unwrap();
913        if let ComponentAddress::RelativePath { ref context, .. } = rel_address {
914            assert_eq!(&context.bytes, &vec![b'4', b'5', b'6']);
915        }
916        assert!(rel_address.is_relative_path());
917        assert_eq!(rel_address.path(), "subpackage");
918        assert_eq!(rel_address.query(), None);
919        assert_eq!(rel_address.resource(), Some("meta/subcomp.cm"));
920        assert_eq!(&rel_address.context().bytes, &vec![b'4', b'5', b'6']);
921        assert_eq!(rel_address.url(), "subpackage#meta/subcomp.cm");
922        assert_eq!(
923            rel_address.to_url_and_context(),
924            (
925                "subpackage#meta/subcomp.cm",
926                Some(&ComponentResolutionContext::new(vec![b'4', b'5', b'6']))
927            )
928        );
929
930        let rel_address2 =
931            rel_address.clone_with_new_resource(Some("meta/other_subcomp.cm")).unwrap();
932        assert_ne!(rel_address2, rel_address);
933        assert!(rel_address2.is_relative_path());
934        assert_eq!(rel_address2.path(), "subpackage");
935        assert_eq!(rel_address2.query(), None);
936        assert_eq!(rel_address2.resource(), Some("meta/other_subcomp.cm"));
937        assert_eq!(&rel_address2.context().bytes, &vec![b'4', b'5', b'6']);
938        assert_eq!(rel_address2.url(), "subpackage#meta/other_subcomp.cm");
939        assert_eq!(
940            rel_address2.to_url_and_context(),
941            (
942                "subpackage#meta/other_subcomp.cm",
943                Some(&ComponentResolutionContext::new(vec![b'4', b'5', b'6']))
944            )
945        );
946
947        let address = from_absolute_url("base://b");
948        assert!(address.is_absolute());
949        assert_eq!(address.scheme(), "base");
950        assert_eq!(address.path(), "");
951        assert_eq!(address.query(), None);
952        assert_eq!(address.resource(), None);
953        assert_eq!(address.url(), "base://b");
954        assert_matches!(address.to_url_and_context(), ("base://b", None));
955
956        let address = from_absolute_url("fuchsia-boot:///#meta/root.cm");
957        assert!(address.is_absolute());
958        assert_eq!(address.scheme(), "fuchsia-boot");
959        assert_eq!(address.path(), "/");
960        assert_eq!(address.query(), None);
961        assert_eq!(address.resource(), Some("meta/root.cm"));
962        assert_eq!(address.url(), "fuchsia-boot:///#meta/root.cm");
963        assert_matches!(address.to_url_and_context(), ("fuchsia-boot:///#meta/root.cm", None));
964
965        let address = from_absolute_url("custom-resolver:my:special:path#meta/root.cm");
966        assert!(address.is_absolute());
967        assert_eq!(address.scheme(), "custom-resolver");
968        assert_eq!(address.path(), "my:special:path");
969        assert_eq!(address.query(), None);
970        assert_eq!(address.resource(), Some("meta/root.cm"));
971        assert_eq!(address.url(), "custom-resolver:my:special:path#meta/root.cm");
972        assert_matches!(
973            address.to_url_and_context(),
974            ("custom-resolver:my:special:path#meta/root.cm", None)
975        );
976
977        let address = from_absolute_url("cast:00000000");
978        assert!(address.is_absolute());
979        assert_eq!(address.scheme(), "cast");
980        assert_eq!(address.path(), "00000000");
981        assert_eq!(address.query(), None);
982        assert_eq!(address.resource(), None);
983        assert_eq!(address.url(), "cast:00000000");
984        assert_matches!(address.to_url_and_context(), ("cast:00000000", None));
985
986        let address = from_absolute_url("cast:00000000#meta/root.cm");
987        assert!(address.is_absolute());
988        assert_eq!(address.scheme(), "cast");
989        assert_eq!(address.path(), "00000000");
990        assert_eq!(address.query(), None);
991        assert_eq!(address.resource(), Some("meta/root.cm"));
992        assert_eq!(address.url(), "cast:00000000#meta/root.cm");
993        assert_matches!(address.to_url_and_context(), ("cast:00000000#meta/root.cm", None));
994
995        let address =
996            from_absolute_url("fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm");
997        assert!(address.is_absolute());
998        assert_eq!(address.scheme(), "fuchsia-pkg");
999        assert_eq!(address.path(), "/package");
1000        assert_eq!(address.resource(), Some("meta/comp.cm"));
1001        assert_eq!(address.query(), Some("hash=cafe0123"));
1002        assert_eq!(address.url(), "fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm");
1003        assert_matches!(
1004            address.to_url_and_context(),
1005            ("fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm", None)
1006        );
1007    }
1008
1009    #[test]
1010    fn test_relative_path() {
1011        let url = Url::parse("relative:///package#fragment").unwrap();
1012        assert_eq!(url.path(), "/package");
1013        assert_eq!(ComponentAddress::relative_path(&url), "package");
1014
1015        let url = Url::parse("cast:00000000#fragment").unwrap();
1016        assert_eq!(url.path(), "00000000");
1017        assert_eq!(ComponentAddress::relative_path(&url), "00000000");
1018    }
1019
1020    #[test]
1021    fn test_parse_relative_url() {
1022        let relative_prefix_with_one_less_slash = Url::parse("relative://").unwrap();
1023        assert_eq!(relative_prefix_with_one_less_slash.scheme(), "relative");
1024        assert_eq!(relative_prefix_with_one_less_slash.host(), None);
1025        assert_eq!(relative_prefix_with_one_less_slash.path(), "");
1026
1027        assert_eq!(RELATIVE_URL_BASE.scheme(), "relative");
1028        assert_eq!(RELATIVE_URL_BASE.host(), None);
1029        assert_eq!(RELATIVE_URL_BASE.path(), "/");
1030
1031        let mut clone_relative_base = RELATIVE_URL_BASE.clone();
1032        assert_eq!(clone_relative_base.path(), "/");
1033        clone_relative_base.set_path("");
1034        assert_eq!(clone_relative_base.path(), "");
1035
1036        let mut clone_relative_base = RELATIVE_URL_BASE.clone();
1037        assert_eq!(clone_relative_base.path(), "/");
1038        clone_relative_base.set_path("some_path_no_initial_slash");
1039        assert_eq!(clone_relative_base.path(), "/some_path_no_initial_slash");
1040
1041        let clone_relative_base = RELATIVE_URL_BASE.clone();
1042        let joined = clone_relative_base.join("some_path_no_initial_slash").unwrap();
1043        assert_eq!(joined.path(), "/some_path_no_initial_slash");
1044
1045        let clone_relative_base = relative_prefix_with_one_less_slash.clone();
1046        let joined = clone_relative_base.join("some_path_no_initial_slash").unwrap();
1047        // Same result as with three slashes
1048        assert_eq!(joined.path(), "/some_path_no_initial_slash");
1049
1050        let relative_url = parse_relative_url("subpackage#meta/subcomp.cm");
1051        assert_eq!(relative_url.path(), "/subpackage");
1052        assert_eq!(relative_url.query(), None);
1053        assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1054
1055        let relative_url = parse_relative_url("/subpackage#meta/subcomp.cm");
1056        assert_eq!(relative_url.path(), "/subpackage");
1057        assert_eq!(relative_url.query(), None);
1058        assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1059
1060        let relative_url = parse_relative_url("//subpackage#meta/subcomp.cm");
1061        assert_eq!(relative_url.path(), "");
1062        assert_eq!(relative_url.host_str(), Some("subpackage"));
1063        assert_eq!(relative_url.query(), None);
1064        assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1065
1066        let relative_url = parse_relative_url("///subpackage#meta/subcomp.cm");
1067        assert_eq!(relative_url.path(), "/subpackage");
1068        assert_eq!(relative_url.host_str(), None);
1069        assert_eq!(relative_url.query(), None);
1070        assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1071
1072        let relative_url = parse_relative_url("fuchsia.com/subpackage#meta/subcomp.cm");
1073        assert_eq!(relative_url.path(), "/fuchsia.com/subpackage");
1074        assert_eq!(relative_url.query(), None);
1075        assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1076
1077        let relative_url = parse_relative_url("//fuchsia.com/subpackage#meta/subcomp.cm");
1078        assert_eq!(relative_url.path(), "/subpackage");
1079        assert_eq!(relative_url.host_str(), Some("fuchsia.com"));
1080        assert_eq!(relative_url.query(), None);
1081        assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1082
1083        assert_matches!(
1084            ComponentAddress::parse_relative_url(
1085                &"fuchsia-pkg://fuchsia.com/subpackage#meta/subcomp.cm".parse().unwrap()
1086            ),
1087            Err(ResolverError::MalformedUrl(..))
1088        );
1089
1090        let relative_url = parse_relative_url("#meta/peercomp.cm");
1091        assert_eq!(relative_url.path(), "/");
1092        assert_eq!(relative_url.query(), None);
1093        assert_eq!(relative_url.fragment(), Some("meta/peercomp.cm"));
1094
1095        let address = from_absolute_url("some-scheme://fuchsia.com/package#meta/comp.cm")
1096            .clone_with_new_resource(relative_url.fragment())
1097            .unwrap();
1098
1099        assert!(address.is_absolute());
1100        assert_eq!(address.scheme(), "some-scheme");
1101        assert_eq!(address.path(), "/package");
1102        assert_eq!(address.query(), None);
1103        assert_eq!(address.resource(), Some("meta/peercomp.cm"));
1104        assert_eq!(address.url(), "some-scheme://fuchsia.com/package#meta/peercomp.cm");
1105
1106        let address = from_absolute_url("cast:00000000")
1107            .clone_with_new_resource(relative_url.fragment())
1108            .unwrap();
1109
1110        assert!(address.is_absolute());
1111        assert_eq!(address.scheme(), "cast");
1112        assert_eq!(address.path(), "00000000");
1113        assert_eq!(address.query(), None);
1114        assert_eq!(address.resource(), Some("meta/peercomp.cm"));
1115        assert_eq!(address.url(), "cast:00000000#meta/peercomp.cm");
1116    }
1117
1118    static COMPONENT_DECL: LazyLock<cm_rust::ComponentDecl> = LazyLock::new(|| {
1119        new_decl_from_json(json!(
1120        {
1121            "include": [ "syslog/client.shard.cml" ],
1122            "program": {
1123                "runner": "elf",
1124                "binary": "bin/example",
1125            },
1126            "children": [
1127                {
1128                    "name": "logger",
1129                    "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
1130                    "environment": "#env_one",
1131                },
1132            ],
1133            "collections": [
1134                {
1135                    "name": "modular",
1136                    "durability": "transient",
1137                },
1138            ],
1139            "capabilities": [
1140                {
1141                    "protocol": "fuchsia.logger.Log2",
1142                    "path": "/svc/fuchsia.logger.Log2",
1143                },
1144            ],
1145            "use": [
1146                {
1147                    "protocol": "fuchsia.fonts.LegacyProvider",
1148                },
1149            ],
1150            "environments": [
1151                {
1152                    "name": "env_one",
1153                    "extends": "none",
1154                    "__stop_timeout_ms": 1337,
1155                },
1156            ],
1157            "facets": {
1158                "author": "Fuchsia",
1159            }}))
1160        .expect("failed to construct manifest")
1161    });
1162
1163    #[fuchsia::test]
1164    fn test_read_and_validate_manifest() {
1165        let manifest = fmem::Data::Bytes(
1166            fidl::persist(&COMPONENT_DECL.clone().native_into_fidl())
1167                .expect("failed to encode manifest"),
1168        );
1169        let actual = read_and_validate_manifest(&manifest, &mut DirectedGraph::new())
1170            .expect("failed to decode manifest");
1171        assert_eq!(actual, COMPONENT_DECL.clone());
1172    }
1173
1174    #[fuchsia::test]
1175    async fn test_read_and_validate_config_values() {
1176        let fidl_config_values = fdecl::ConfigValuesData {
1177            values: Some(vec![
1178                fdecl::ConfigValueSpec {
1179                    value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(false))),
1180                    ..Default::default()
1181                },
1182                fdecl::ConfigValueSpec {
1183                    value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Uint8(5))),
1184                    ..Default::default()
1185                },
1186                fdecl::ConfigValueSpec {
1187                    value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::String(
1188                        "hello!".to_string(),
1189                    ))),
1190                    ..Default::default()
1191                },
1192                fdecl::ConfigValueSpec {
1193                    value: Some(fdecl::ConfigValue::Vector(fdecl::ConfigVectorValue::BoolVector(
1194                        vec![true, false],
1195                    ))),
1196                    ..Default::default()
1197                },
1198                fdecl::ConfigValueSpec {
1199                    value: Some(fdecl::ConfigValue::Vector(
1200                        fdecl::ConfigVectorValue::StringVector(vec![
1201                            "hello!".to_string(),
1202                            "world!".to_string(),
1203                        ]),
1204                    )),
1205                    ..Default::default()
1206                },
1207            ]),
1208            checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
1209            ..Default::default()
1210        };
1211        let config_values = cm_rust::ConfigValuesData {
1212            values: Box::from([
1213                cm_rust::ConfigValueSpec {
1214                    value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
1215                },
1216                cm_rust::ConfigValueSpec {
1217                    value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint8(5)),
1218                },
1219                cm_rust::ConfigValueSpec {
1220                    value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
1221                        "hello!".into(),
1222                    )),
1223                },
1224                cm_rust::ConfigValueSpec {
1225                    value: cm_rust::ConfigValue::Vector(cm_rust::ConfigVectorValue::BoolVector(
1226                        Box::from([true, false]),
1227                    )),
1228                },
1229                cm_rust::ConfigValueSpec {
1230                    value: cm_rust::ConfigValue::Vector(cm_rust::ConfigVectorValue::StringVector(
1231                        Box::from(["hello!".into(), "world!".into()]),
1232                    )),
1233                },
1234            ]),
1235            checksum: cm_rust::ConfigChecksum::Sha256([0; 32]),
1236        };
1237        let data = fmem::Data::Bytes(
1238            fidl::persist(&fidl_config_values).expect("failed to encode config values"),
1239        );
1240        let actual =
1241            read_and_validate_config_values(&data).expect("failed to decode config values");
1242        assert_eq!(actual, config_values);
1243    }
1244
1245    #[derive(Debug, Default, Clone)]
1246    struct MockTopInstance {
1247        namespace_capabilities: NamespaceCapabilities,
1248        builtin_capabilities: BuiltinCapabilities,
1249    }
1250
1251    impl TopInstanceInterface for MockTopInstance {
1252        fn namespace_capabilities(&self) -> &NamespaceCapabilities {
1253            &self.namespace_capabilities
1254        }
1255        fn builtin_capabilities(&self) -> &BuiltinCapabilities {
1256            &self.builtin_capabilities
1257        }
1258    }
1259
1260    #[derive(Clone)]
1261    struct MockComponentInstance {
1262        parent: Option<Box<MockComponentInstance>>,
1263        resolved_state: Option<MockResolvedState>,
1264        moniker: Moniker,
1265        address: cm_types::Url,
1266    }
1267    #[async_trait]
1268    impl ComponentInstanceInterface for MockComponentInstance {
1269        type TopInstance = MockTopInstance;
1270        fn moniker(&self) -> &Moniker {
1271            &self.moniker
1272        }
1273        fn url(&self) -> &cm_types::Url {
1274            &self.address
1275        }
1276        fn config_parent_overrides(&self) -> Option<&[cm_rust::ConfigOverride]> {
1277            unimplemented!()
1278        }
1279        fn policy_checker(&self) -> &GlobalPolicyChecker {
1280            unimplemented!()
1281        }
1282        fn component_id_index(&self) -> &component_id_index::Index {
1283            unimplemented!()
1284        }
1285        fn try_get_parent(
1286            &self,
1287        ) -> Result<ExtendedInstanceInterface<Self>, ComponentInstanceError> {
1288            if let Some(parent) = self.parent.as_ref() {
1289                Ok(ExtendedInstanceInterface::Component(Arc::new((**parent).clone())))
1290            } else {
1291                Ok(ExtendedInstanceInterface::AboveRoot(Arc::new(MockTopInstance::default())))
1292            }
1293        }
1294        async fn component_sandbox(
1295            self: &Arc<Self>,
1296        ) -> Result<ComponentSandbox, ComponentInstanceError> {
1297            unimplemented!()
1298        }
1299        async fn get_child_maybe_resolve(
1300            self: &Arc<Self>,
1301            _moniker: &BorrowedChildName,
1302        ) -> Result<Option<Arc<Self>>, ComponentInstanceError> {
1303            unimplemented!()
1304        }
1305        async fn address_maybe_resolve(
1306            self: &Arc<Self>,
1307        ) -> Result<ComponentAddress, ResolverError> {
1308            self.resolved_state.as_ref().unwrap().address.clone()
1309        }
1310        async fn context_to_resolve_children(
1311            self: &Arc<Self>,
1312        ) -> Result<Option<ComponentResolutionContext>, ComponentInstanceError> {
1313            Ok(self.resolved_state.as_ref().unwrap().context_to_resolve_children.clone())
1314        }
1315    }
1316    impl MockComponentInstance {
1317        async fn component_address(self: &Arc<Self>) -> Result<ComponentAddress, ResolverError> {
1318            ComponentAddress::from_url(&self.address, self).await
1319        }
1320    }
1321
1322    #[derive(Clone)]
1323    struct MockResolvedState {
1324        address: Result<ComponentAddress, ResolverError>,
1325        context_to_resolve_children: Option<ComponentResolutionContext>,
1326    }
1327
1328    #[fuchsia::test]
1329    async fn test_from_absolute_component_url_with_component_instance() -> Result<(), Error> {
1330        let url_str = "fuchsia-pkg://fuchsia.com/package#meta/comp.cm";
1331        let root = Arc::new(MockComponentInstance {
1332            parent: None,
1333            resolved_state: Some(MockResolvedState {
1334                address: Ok(ComponentAddress::new_absolute(Url::parse(url_str).unwrap())),
1335                context_to_resolve_children: None,
1336            }),
1337            moniker: Moniker::root(),
1338            address: cm_types::Url::new(url_str).unwrap(),
1339        });
1340
1341        let abs = root.component_address().await.unwrap();
1342        assert_matches!(abs, ComponentAddress::Absolute { .. });
1343        assert_eq!(abs.scheme(), "fuchsia-pkg");
1344        assert_eq!(abs.path(), "/package");
1345        assert_eq!(abs.resource(), Some("meta/comp.cm"));
1346        Ok(())
1347    }
1348
1349    #[fuchsia::test]
1350    async fn test_from_relative_path_component_url_with_component_instance() -> Result<(), Error> {
1351        let root_url_str = "fuchsia-pkg://fuchsia.com/package#meta/comp.cm";
1352        let root = MockComponentInstance {
1353            parent: None,
1354            resolved_state: Some(MockResolvedState {
1355                address: Ok(ComponentAddress::new_absolute(Url::parse(root_url_str).unwrap())),
1356                context_to_resolve_children: Some(ComponentResolutionContext::new(
1357                    "package_context".as_bytes().to_vec(),
1358                )),
1359            }),
1360            moniker: Moniker::root(),
1361            address: cm_types::Url::new(root_url_str).unwrap(),
1362        };
1363        let child = Arc::new(MockComponentInstance {
1364            parent: Some(Box::new(root)),
1365            resolved_state: None,
1366            moniker: "/child".try_into().unwrap(),
1367            address: cm_types::Url::new("subpackage#meta/subcomp.cm").unwrap(),
1368        });
1369
1370        let relpath = child.component_address().await.unwrap();
1371        assert_matches!(relpath, ComponentAddress::RelativePath { .. });
1372        assert_eq!(relpath.path(), "subpackage");
1373        assert_eq!(relpath.resource(), Some("meta/subcomp.cm"));
1374        assert_eq!(
1375            relpath.context(),
1376            &ComponentResolutionContext::new("package_context".as_bytes().to_vec())
1377        );
1378
1379        Ok(())
1380    }
1381
1382    #[fuchsia::test]
1383    async fn test_from_relative_path_component_url_with_cast_component_instance()
1384    -> Result<(), Error> {
1385        let root_url_str = "cast:00000000/package#meta/comp.cm";
1386        let root = MockComponentInstance {
1387            parent: None,
1388            resolved_state: Some(MockResolvedState {
1389                address: Ok(ComponentAddress::new_absolute(Url::parse(root_url_str).unwrap())),
1390                context_to_resolve_children: Some(ComponentResolutionContext::new(
1391                    "package_context".as_bytes().to_vec(),
1392                )),
1393            }),
1394            moniker: Moniker::root(),
1395            address: cm_types::Url::new(root_url_str).unwrap(),
1396        };
1397        let child = Arc::new(MockComponentInstance {
1398            parent: Some(Box::new(root)),
1399            resolved_state: None,
1400            moniker: "/child".try_into().unwrap(),
1401            address: cm_types::Url::new("subpackage#meta/subcomp.cm").unwrap(),
1402        });
1403
1404        let relpath = child.component_address().await.unwrap();
1405        assert_matches!(relpath, ComponentAddress::RelativePath { .. });
1406        assert_eq!(relpath.path(), "subpackage");
1407        assert_eq!(relpath.resource(), Some("meta/subcomp.cm"));
1408        assert_eq!(
1409            relpath.context(),
1410            &ComponentResolutionContext::new("package_context".as_bytes().to_vec())
1411        );
1412
1413        Ok(())
1414    }
1415}