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