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