| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use { |
| crate::{ |
| component_instance::{ComponentInstanceInterface, ExtendedInstanceInterface}, |
| error::ComponentInstanceError, |
| }, |
| anyhow::Error, |
| clonable_error::ClonableError, |
| fidl_fuchsia_component_resolution as fresolution, fidl_fuchsia_io as fio, |
| fuchsia_zircon_status as zx, |
| lazy_static::lazy_static, |
| std::sync::Arc, |
| thiserror::Error, |
| url::Url, |
| version_history::AbiRevision, |
| }; |
| |
| /// The prefix for relative URLs internally represented as url::Url. |
| const RELATIVE_URL_PREFIX: &str = "relative:///"; |
| lazy_static! { |
| /// A default base URL from which to parse relative component URL |
| /// components. |
| static ref RELATIVE_URL_BASE: Url = Url::parse(RELATIVE_URL_PREFIX).unwrap(); |
| } |
| |
| /// The response returned from a Resolver. This struct is derived from the FIDL |
| /// [`fuchsia.component.resolution.Component`][fidl_fuchsia_component_resolution::Component] |
| /// table, except that the opaque binary ComponentDecl has been deserialized and validated. |
| #[derive(Debug)] |
| pub struct ResolvedComponent { |
| /// The url used to resolve this component. |
| pub resolved_url: String, |
| /// The package context, from the component resolution context returned by |
| /// the resolver. |
| pub context_to_resolve_children: Option<ComponentResolutionContext>, |
| pub decl: cm_rust::ComponentDecl, |
| pub package: Option<ResolvedPackage>, |
| pub config_values: Option<cm_rust::ConfigValuesData>, |
| pub abi_revision: Option<AbiRevision>, |
| } |
| |
| /// The response returned from a Resolver. This struct is derived from the FIDL |
| /// [`fuchsia.component.resolution.Package`][fidl_fuchsia_component_resolution::Package] |
| /// table. |
| #[derive(Debug)] |
| pub struct ResolvedPackage { |
| /// The package url. |
| pub url: String, |
| /// The package directory client proxy. |
| pub directory: fidl::endpoints::ClientEnd<fio::DirectoryMarker>, |
| } |
| |
| impl TryFrom<fresolution::Package> for ResolvedPackage { |
| type Error = ResolverError; |
| |
| fn try_from(package: fresolution::Package) -> Result<Self, Self::Error> { |
| Ok(ResolvedPackage { |
| url: package.url.ok_or(ResolverError::PackageUrlMissing)?, |
| directory: package.directory.ok_or(ResolverError::PackageDirectoryMissing)?, |
| }) |
| } |
| } |
| |
| /// Convenience wrapper type for the autogenerated FIDL |
| /// `fuchsia.component.resolution.Context`. |
| #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] |
| pub struct ComponentResolutionContext { |
| pub bytes: Vec<u8>, |
| } |
| |
| impl ComponentResolutionContext { |
| pub fn new(bytes: Vec<u8>) -> Self { |
| ComponentResolutionContext { bytes } |
| } |
| } |
| |
| impl From<fresolution::Context> for ComponentResolutionContext { |
| fn from(context: fresolution::Context) -> Self { |
| ComponentResolutionContext { bytes: context.bytes } |
| } |
| } |
| |
| impl From<&fresolution::Context> for ComponentResolutionContext { |
| fn from(context: &fresolution::Context) -> ComponentResolutionContext { |
| ComponentResolutionContext { bytes: context.bytes.clone() } |
| } |
| } |
| |
| impl From<ComponentResolutionContext> for fresolution::Context { |
| fn from(context: ComponentResolutionContext) -> Self { |
| Self { bytes: context.bytes } |
| } |
| } |
| |
| impl From<&ComponentResolutionContext> for fresolution::Context { |
| fn from(context: &ComponentResolutionContext) -> fresolution::Context { |
| Self { bytes: context.bytes.clone() } |
| } |
| } |
| |
| impl<'a> From<&'a ComponentResolutionContext> for &'a [u8] { |
| fn from(context: &'a ComponentResolutionContext) -> &'a [u8] { |
| &context.bytes |
| } |
| } |
| |
| /// Provides the `ComponentAddress` and context for resolving a child or |
| /// descendent component. |
| #[derive(Debug, Clone, PartialEq, Eq)] |
| struct ResolvedAncestorComponent { |
| /// The component address, needed for relative path URLs (to get the |
| /// scheme used to find the required `Resolver`), or for relative resource |
| /// URLs (which will clone the parent's address, but replace the resource). |
| pub address: ComponentAddress, |
| /// The component's resolution_context, required for resolving descendents |
| /// using a relative path component URLs. |
| pub context_to_resolve_children: Option<ComponentResolutionContext>, |
| } |
| |
| impl ResolvedAncestorComponent { |
| /// Creates a `ResolvedAncestorComponent` from one of its child components. |
| pub async fn direct_parent_of<C: ComponentInstanceInterface>( |
| component: &Arc<C>, |
| ) -> Result<Self, ResolverError> { |
| let parent_component = get_parent(component).await?; |
| let resolved_parent = parent_component.lock_resolved_state().await?; |
| Ok(Self { |
| address: resolved_parent.address(), |
| context_to_resolve_children: resolved_parent.context_to_resolve_children(), |
| }) |
| } |
| |
| /// Creates a `ResolvedAncestorComponent` from one of its child components. |
| pub async fn first_packaged_ancestor_of<C: ComponentInstanceInterface>( |
| component: &Arc<C>, |
| ) -> Result<Self, ResolverError> { |
| let mut parent_component = get_parent(component).await?; |
| loop { |
| // Loop until the parent has a valid context_to_resolve_children, |
| // or an error getting the next parent, or its resolved state. |
| { |
| let resolved_parent = parent_component.lock_resolved_state().await?; |
| let address = resolved_parent.address(); |
| // TODO(https://fxbug.dev/42053123): change this test to something more |
| // explicit, that is, return the parent's address and context if |
| // the component address is a packaged component (determined in |
| // some way). |
| // |
| // The issue being addressed here is, when resolving a relative |
| // subpackaged component URL, component manager MUST resolve the |
| // component using a "resolution context" _AND_ the resolver |
| // that provided that context. Typically these are provided by |
| // the parent component, but in the case of a RealmBuilder child |
| // component, its parent is the built "realm" (which was |
| // resolved by the realm_builder_resolver, using URL scheme |
| // "realm-builder://"). The child component's URL is supposed to |
| // be relative to the test component (the parent of the realm), |
| // which was probably resolved by the full-resolver (scheme |
| // "fuchsia-pkg://"). Knowing this expected topology, we can |
| // skip "realm-builder" components when searching for the |
| // required ancestor's URL scheme (to get the right resolver) |
| // and context. This is a brittle workaround that will be |
| // replaced. |
| // |
| // Some alternatives are under discussion, but the leading |
| // candidate, for now, is to allow a resolver to return a flag |
| // (with the resolved Component; perhaps `is_packaged()`) to |
| // indicate that descendents should (if true) use this component |
| // to get scheme and context for resolving relative path URLs |
| // (for example, subpackages). If false, get the parent's parent |
| // and so on. |
| if address.scheme() != "realm-builder" { |
| return Ok(Self { |
| address, |
| context_to_resolve_children: resolved_parent.context_to_resolve_children(), |
| }); |
| } |
| } |
| parent_component = get_parent(&parent_component).await?; |
| } |
| } |
| } |
| |
| async fn get_parent<C: ComponentInstanceInterface>( |
| component: &Arc<C>, |
| ) -> Result<Arc<C>, ResolverError> { |
| if let ExtendedInstanceInterface::Component(parent_component) = |
| component.try_get_parent().map_err(|err| { |
| ResolverError::no_parent_context(anyhow::format_err!( |
| "Component {} ({}) has no parent for context: {:?}.", |
| component.moniker(), |
| component.url(), |
| err, |
| )) |
| })? |
| { |
| Ok(parent_component.clone()) |
| } else { |
| Err(ResolverError::no_parent_context(anyhow::format_err!( |
| "Component {} ({}) has no parent for context.", |
| component.moniker(), |
| component.url(), |
| ))) |
| } |
| } |
| |
| /// Indicates the kind of `ComponentAddress`, and holds `ComponentAddress` |
| /// properties specific to its kind. Note that there is no kind for a relative |
| /// resource component URL (a URL that only contains a resource fragment, such |
| /// as `#meta/comp.cm`) because `ComponentAddress::from()` will translate a |
| /// resource fragment component URL into one of the fully-resolvable |
| /// `ComponentAddress`s. |
| #[derive(Debug, Clone, PartialEq, Eq)] |
| pub enum ComponentAddress { |
| /// A fully-qualified component URL. |
| Absolute { url: Url }, |
| |
| /// A relative Component URL, starting with the package path; for example a |
| /// subpackage relative URL such as "needed_package#meta/dep_component.cm". |
| RelativePath { |
| /// This is the scheme of the ancestor component's absolute URL, used to identify the |
| /// `Resolver` in a `ResolverRegistry`. |
| scheme: String, |
| |
| /// The relative URL, represented as a `url::Url` with the `relative:///` base prefix. |
| /// `url::Url` cannot represent relative urls directly. |
| url: Url, |
| |
| /// An opaque value (from the perspective of component resolution) |
| /// required by the resolver when resolving a relative package path. |
| /// For a given child component, this property is populated from a |
| /// parent component's `resolution_context`, as returned by the parent |
| /// component's resolver. |
| context: ComponentResolutionContext, |
| }, |
| } |
| |
| impl ComponentAddress { |
| /// Creates a new `ComponentAddress` of the `Absolute` kind. |
| fn new_absolute(url: Url) -> Self { |
| Self::Absolute { url } |
| } |
| |
| /// Creates a new `ComponentAddress` of the `RelativePath` kind. |
| fn new_relative_path( |
| path: &str, |
| some_resource: Option<&str>, |
| scheme: &str, |
| context: ComponentResolutionContext, |
| ) -> Result<Self, ResolverError> { |
| let mut url = RELATIVE_URL_BASE.clone(); |
| url.set_path(path); |
| url.set_fragment(some_resource); |
| Self::check_relative_url(&url)?; |
| Ok(Self::RelativePath { url, context, scheme: scheme.into() }) |
| } |
| |
| /// Parse the given absolute `component_url` and create a `ComponentAddress` |
| /// with kind `Absolute`. |
| pub fn from_absolute_url(component_url: &str) -> Result<Self, ResolverError> { |
| match Url::parse(component_url) { |
| Ok(url) => Ok(Self::new_absolute(url)), |
| Err(url::ParseError::RelativeUrlWithoutBase) => { |
| Err(ResolverError::RelativeUrlNotExpected(component_url.to_string())) |
| } |
| Err(err) => Err(ResolverError::malformed_url(err)), |
| } |
| } |
| |
| /// Assumes the given string is a relative URL, and converts this string into |
| /// a `url::Url` by attaching a known, internal absolute URL prefix. |
| fn parse_relative_url(component_url: &str) -> Result<Url, ResolverError> { |
| match Url::parse(component_url) { |
| Ok(_) => Err(ResolverError::malformed_url(anyhow::format_err!( |
| "Error parsing a relative URL given absolute URL '{}'.", |
| component_url, |
| ))), |
| Err(url::ParseError::RelativeUrlWithoutBase) => { |
| RELATIVE_URL_BASE.join(component_url).map_err(|err| { |
| ResolverError::malformed_url(anyhow::format_err!( |
| "Error parsing a relative component URL '{}': {:?}.", |
| component_url, |
| err |
| )) |
| }) |
| } |
| Err(err) => Err(ResolverError::malformed_url(anyhow::format_err!( |
| "Unexpected error while parsing a component URL '{}': {:?}.", |
| component_url, |
| err, |
| ))), |
| } |
| } |
| |
| fn check_relative_url(url: &Url) -> Result<(), ResolverError> { |
| let truncated_url = url.as_str().strip_prefix(RELATIVE_URL_PREFIX).ok_or_else(|| { |
| ResolverError::malformed_url(anyhow::format_err!( |
| "Could not strip relative prefix from url. This is a bug. {}", |
| url |
| )) |
| })?; |
| let relative_url = RELATIVE_URL_BASE.make_relative(&url).ok_or_else(|| { |
| ResolverError::malformed_url(anyhow::format_err!( |
| "Could not make relative url. This is a bug. {}", |
| url |
| )) |
| })?; |
| if truncated_url != relative_url { |
| return Err(ResolverError::malformed_url(anyhow::format_err!( |
| "Relative url generated from url::Url did not match expectations. \ |
| This is a bug. {}", |
| url |
| ))); |
| } |
| Ok(()) |
| } |
| |
| /// For `Url`s constructed from `parse_relative_url()`, the `path()` is expected |
| /// to include a slash prefix, but a String representation of a relative path |
| /// URL should not include the slash prefix. This convenience API assumes the |
| /// given `Url` is a relative URL, and strips the slash prefix, if present. |
| fn relative_path(relative_url: &Url) -> &str { |
| let path = relative_url.path(); |
| path.strip_prefix('/').unwrap_or(path) |
| } |
| |
| /// Parse the given `component_url` to determine if it is an absolute URL, |
| /// a relative subpackage URL, or a relative resource URL, and return the |
| /// corresponding `ComponentAddress` enum variant and value. If the URL is |
| /// relative, use the component instance to get the required resolution |
| /// context from the component's parent. |
| pub async fn from<C: ComponentInstanceInterface>( |
| component_url: &str, |
| component: &Arc<C>, |
| ) -> Result<Self, ResolverError> { |
| let result = Self::from_absolute_url(component_url); |
| if !matches!(result, Err(ResolverError::RelativeUrlNotExpected(_))) { |
| return result; |
| } |
| let relative_url = Self::parse_relative_url(component_url)?; |
| let relative_path = Self::relative_path(&relative_url); |
| if relative_url.fragment().is_none() && relative_path.is_empty() { |
| return Err(ResolverError::malformed_url(anyhow::format_err!("{}", component_url))); |
| } |
| if relative_url.query().is_some() { |
| return Err(ResolverError::malformed_url(anyhow::format_err!( |
| "Query strings are not allowed in relative component URLs: {}", |
| component_url |
| ))); |
| } |
| if relative_path.is_empty() { |
| // The `component_url` had only a fragment, so the new address will |
| // be the same as its parent (for example, the same package), except |
| // for its resource. |
| let resolved_parent = ResolvedAncestorComponent::direct_parent_of(component).await?; |
| resolved_parent.address.clone_with_new_resource(relative_url.fragment()) |
| } else { |
| // The `component_url` starts with a relative path (for example, a |
| // subpackage name). Create a `RelativePath` address, and resolve it |
| // using the `context_to_resolve_children`, from this component's |
| // parent, or the first ancestor that is from a "package". (Note |
| // that Realm Builder realms are synthesized, and not from a |
| // package. A test component using Realm Builder will build a realm |
| // and may add child components using subpackage references. Those |
| // child components should get resolved using the context of the |
| // test package, not the intermediate realm created via |
| // RealmBuilder.) |
| let resolved_ancestor = |
| ResolvedAncestorComponent::first_packaged_ancestor_of(component).await?; |
| let scheme = resolved_ancestor.address.scheme(); |
| let context = resolved_ancestor.context_to_resolve_children.clone().ok_or_else(|| { |
| ResolverError::RelativeUrlMissingContext(format!( |
| "Relative path component URL '{}' cannot be resolved because its ancestor did not provide a resolution context. The ancestor's component address is {:?}.", |
| component_url, resolved_ancestor.address |
| )) |
| })?; |
| Self::new_relative_path(relative_path, relative_url.fragment(), scheme, context) |
| } |
| } |
| |
| /// Creates a new `ComponentAddress` from `self` by replacing only the |
| /// component URL resource. |
| pub fn clone_with_new_resource( |
| &self, |
| some_resource: Option<&str>, |
| ) -> Result<Self, ResolverError> { |
| let mut url = match &self { |
| Self::Absolute { url } => url.clone(), |
| Self::RelativePath { url, .. } => url.clone(), |
| }; |
| url.set_fragment(some_resource); |
| match &self { |
| Self::Absolute { .. } => Ok(Self::Absolute { url }), |
| Self::RelativePath { context, scheme, .. } => { |
| Self::check_relative_url(&url)?; |
| Ok(Self::RelativePath { url, context: context.clone(), scheme: scheme.clone() }) |
| } |
| } |
| } |
| |
| /// True if the address is `Absolute`. |
| pub fn is_absolute(&self) -> bool { |
| matches!(self, Self::Absolute { .. }) |
| } |
| |
| /// True if the address is `RelativePath`. |
| pub fn is_relative_path(&self) -> bool { |
| matches!(self, Self::RelativePath { .. }) |
| } |
| |
| /// Returns the `ComponentResolutionContext` value required to resolve for a |
| /// `RelativePath` component URL. |
| /// |
| /// Panics if called for an `Absolute` component address. |
| pub fn context(&self) -> &ComponentResolutionContext { |
| if let Self::RelativePath { context, .. } = self { |
| &context |
| } else { |
| panic!("context() is only valid for `ComponentAddressKind::RelativePath"); |
| } |
| } |
| |
| /// Returns the URL scheme either provided for an `Absolute` URL or derived |
| /// from the component's parent. The scheme is used to look up a registered |
| /// resolver, when resolving the component. |
| pub fn scheme(&self) -> &str { |
| match self { |
| Self::Absolute { url } => url.scheme(), |
| Self::RelativePath { scheme, .. } => &scheme, |
| } |
| } |
| |
| /// Returns the URL path. |
| pub fn path(&self) -> &str { |
| match self { |
| Self::Absolute { url } => url.path(), |
| Self::RelativePath { url, .. } => Self::relative_path(&url), |
| } |
| } |
| |
| /// Returns the optional query value for an `Absolute` component URL. |
| /// Always returns `None` for `Relative` component URLs. |
| pub fn query(&self) -> Option<&str> { |
| match self { |
| Self::Absolute { url } => url.query(), |
| Self::RelativePath { .. } => None, |
| } |
| } |
| |
| /// Returns the optional component resource, from the URL fragment. |
| pub fn resource(&self) -> Option<&str> { |
| match self { |
| Self::Absolute { url } => url.fragment(), |
| Self::RelativePath { url, .. } => url.fragment(), |
| } |
| } |
| |
| /// Returns the resolver-ready URL string and, if it is a `RelativePath`, |
| /// `Some(context)`, or `None` for an `Absolute` address. |
| pub fn url(&self) -> &str { |
| match self { |
| Self::Absolute { url } => url.as_str(), |
| Self::RelativePath { url, .. } => &url.as_str()[RELATIVE_URL_PREFIX.len()..], |
| } |
| } |
| |
| /// Returns the `url()` and `Some(context)` for resolving the URL, |
| /// if the kind is `RelativePath` (or `None` if `Absolute`). |
| pub fn to_url_and_context(&self) -> (&str, Option<&ComponentResolutionContext>) { |
| match self { |
| Self::Absolute { .. } => (self.url(), None), |
| Self::RelativePath { context, .. } => (self.url(), Some(context)), |
| } |
| } |
| } |
| |
| /// Errors produced by built-in `Resolver`s and `resolving` APIs. |
| #[derive(Debug, Error, Clone)] |
| pub enum ResolverError { |
| #[error("an unexpected error occurred: {0}")] |
| Internal(#[source] ClonableError), |
| #[error("an IO error occurred: {0}")] |
| Io(#[source] ClonableError), |
| #[error("component manifest not found: {0}")] |
| ManifestNotFound(#[source] ClonableError), |
| #[error("package not found: {0}")] |
| PackageNotFound(#[source] ClonableError), |
| #[error("component manifest invalid: {0}")] |
| ManifestInvalid(#[source] ClonableError), |
| #[error("config values file invalid: {0}")] |
| ConfigValuesInvalid(#[source] ClonableError), |
| #[error("abi revision not found")] |
| AbiRevisionNotFound, |
| #[error("abi revision invalid: {0}")] |
| AbiRevisionInvalid(#[source] ClonableError), |
| #[error("failed to read config values: {0}")] |
| ConfigValuesIo(zx::Status), |
| #[error("scheme not registered")] |
| SchemeNotRegistered, |
| #[error("malformed url: {0}")] |
| MalformedUrl(#[source] ClonableError), |
| #[error("relative url requires a parent component with resolution context: {0}")] |
| NoParentContext(#[source] ClonableError), |
| #[error("package URL missing")] |
| PackageUrlMissing, |
| #[error("package directory handle missing")] |
| PackageDirectoryMissing, |
| #[error("a relative URL was not expected: {0}")] |
| RelativeUrlNotExpected(String), |
| #[error("failed to route resolver capability: {0}")] |
| RoutingError(#[source] ClonableError), |
| #[error("a context is required to resolve relative url: {0}")] |
| RelativeUrlMissingContext(String), |
| #[error("this component resolver does not resolve relative path component URLs: {0}")] |
| UnexpectedRelativePath(String), |
| #[error("the remote resolver returned invalid data")] |
| RemoteInvalidData, |
| #[error("an error occurred sending a FIDL request to the remote resolver: {0}")] |
| FidlError(#[source] ClonableError), |
| } |
| |
| impl ResolverError { |
| pub fn as_zx_status(&self) -> zx::Status { |
| match self { |
| ResolverError::PackageNotFound(_) |
| | ResolverError::ManifestNotFound(_) |
| | ResolverError::ManifestInvalid(_) |
| | ResolverError::ConfigValuesInvalid(_) |
| | ResolverError::Io(_) |
| | ResolverError::ConfigValuesIo(_) |
| | ResolverError::AbiRevisionNotFound |
| | ResolverError::AbiRevisionInvalid(_) |
| | ResolverError::SchemeNotRegistered |
| | ResolverError::MalformedUrl(_) |
| | ResolverError::NoParentContext(_) |
| | ResolverError::RelativeUrlMissingContext(_) |
| | ResolverError::RemoteInvalidData |
| | ResolverError::PackageUrlMissing |
| | ResolverError::PackageDirectoryMissing |
| | ResolverError::UnexpectedRelativePath(_) => zx::Status::NOT_FOUND, |
| |
| ResolverError::Internal(_) |
| | ResolverError::RelativeUrlNotExpected(_) |
| | ResolverError::RoutingError(_) |
| | ResolverError::FidlError(_) => zx::Status::INTERNAL, |
| } |
| } |
| |
| pub fn internal(err: impl Into<Error>) -> Self { |
| Self::Internal(err.into().into()) |
| } |
| |
| pub fn io(err: impl Into<Error>) -> Self { |
| Self::Io(err.into().into()) |
| } |
| |
| pub fn manifest_not_found(err: impl Into<Error>) -> Self { |
| Self::ManifestNotFound(err.into().into()) |
| } |
| |
| pub fn package_not_found(err: impl Into<Error>) -> Self { |
| Self::PackageNotFound(err.into().into()) |
| } |
| |
| pub fn manifest_invalid(err: impl Into<Error>) -> Self { |
| Self::ManifestInvalid(err.into().into()) |
| } |
| |
| pub fn config_values_invalid(err: impl Into<Error>) -> Self { |
| Self::ConfigValuesInvalid(err.into().into()) |
| } |
| |
| pub fn abi_revision_invalid(err: impl Into<Error>) -> Self { |
| Self::AbiRevisionInvalid(err.into().into()) |
| } |
| |
| pub fn malformed_url(err: impl Into<Error>) -> Self { |
| Self::MalformedUrl(err.into().into()) |
| } |
| |
| pub fn no_parent_context(err: impl Into<Error>) -> Self { |
| Self::NoParentContext(err.into().into()) |
| } |
| |
| pub fn routing_error(err: impl Into<Error>) -> Self { |
| Self::RoutingError(err.into().into()) |
| } |
| |
| pub fn fidl_error(err: impl Into<Error>) -> Self { |
| Self::FidlError(err.into().into()) |
| } |
| } |
| |
| impl From<fresolution::ResolverError> for ResolverError { |
| fn from(err: fresolution::ResolverError) -> ResolverError { |
| match err { |
| fresolution::ResolverError::Internal => ResolverError::internal(RemoteError(err)), |
| fresolution::ResolverError::Io => ResolverError::io(RemoteError(err)), |
| fresolution::ResolverError::PackageNotFound |
| | fresolution::ResolverError::NoSpace |
| | fresolution::ResolverError::ResourceUnavailable |
| | fresolution::ResolverError::NotSupported => { |
| ResolverError::package_not_found(RemoteError(err)) |
| } |
| fresolution::ResolverError::ManifestNotFound => { |
| ResolverError::manifest_not_found(RemoteError(err)) |
| } |
| fresolution::ResolverError::InvalidArgs => { |
| ResolverError::malformed_url(RemoteError(err)) |
| } |
| fresolution::ResolverError::InvalidManifest => { |
| ResolverError::ManifestInvalid(anyhow::Error::from(RemoteError(err)).into()) |
| } |
| fresolution::ResolverError::ConfigValuesNotFound => { |
| ResolverError::ConfigValuesIo(zx::Status::NOT_FOUND) |
| } |
| fresolution::ResolverError::AbiRevisionNotFound => ResolverError::AbiRevisionNotFound, |
| fresolution::ResolverError::InvalidAbiRevision => { |
| ResolverError::abi_revision_invalid(RemoteError(err)) |
| } |
| } |
| } |
| } |
| |
| impl From<ComponentInstanceError> for ResolverError { |
| fn from(err: ComponentInstanceError) -> ResolverError { |
| use ComponentInstanceError::*; |
| match &err { |
| ComponentManagerInstanceUnavailable {} |
| | InstanceNotFound { .. } |
| | ResolveFailed { .. } => { |
| ResolverError::Internal(ClonableError::from(anyhow::format_err!("{:?}", err))) |
| } |
| NoAbsoluteUrl { .. } => ResolverError::NoParentContext(ClonableError::from( |
| anyhow::format_err!("{:?}", err), |
| )), |
| MalformedUrl { .. } => { |
| ResolverError::MalformedUrl(ClonableError::from(anyhow::format_err!("{:?}", err))) |
| } |
| } |
| } |
| } |
| |
| #[derive(Error, Clone, Debug)] |
| #[error("remote resolver responded with {0:?}")] |
| struct RemoteError(fresolution::ResolverError); |
| |
| #[cfg(test)] |
| mod tests { |
| use {super::*, assert_matches::assert_matches, fidl::endpoints::create_endpoints}; |
| |
| #[test] |
| fn test_resolved_package() { |
| let url = "some_url".to_string(); |
| let (dir_client, _) = create_endpoints::<fio::DirectoryMarker>(); |
| let fidl_package = fresolution::Package { |
| url: Some(url.clone()), |
| directory: Some(dir_client), |
| ..Default::default() |
| }; |
| let resolved_package = ResolvedPackage::try_from(fidl_package).unwrap(); |
| assert_eq!(resolved_package.url, url); |
| } |
| |
| #[test] |
| fn test_component_address() { |
| let address = |
| ComponentAddress::from_absolute_url("some-scheme://fuchsia.com/package#meta/comp.cm") |
| .unwrap(); |
| assert!(address.is_absolute()); |
| assert_eq!(address.scheme(), "some-scheme"); |
| assert_eq!(address.path(), "/package"); |
| assert_eq!(address.query(), None); |
| assert_eq!(address.resource(), Some("meta/comp.cm")); |
| assert_eq!(address.url(), "some-scheme://fuchsia.com/package#meta/comp.cm"); |
| assert_matches!( |
| address.to_url_and_context(), |
| ("some-scheme://fuchsia.com/package#meta/comp.cm", None) |
| ); |
| |
| let abs_address = ComponentAddress::new_absolute( |
| Url::parse("some-scheme://fuchsia.com/package#meta/comp.cm").unwrap(), |
| ); |
| assert_eq!(abs_address, address); |
| |
| assert_eq!(abs_address, address); |
| assert!(abs_address.is_absolute()); |
| assert_eq!(abs_address.scheme(), "some-scheme"); |
| assert_eq!(abs_address.path(), "/package"); |
| assert_eq!(abs_address.query(), None); |
| assert_eq!(abs_address.resource(), Some("meta/comp.cm")); |
| assert_eq!(abs_address.url(), "some-scheme://fuchsia.com/package#meta/comp.cm"); |
| assert_matches!( |
| abs_address.to_url_and_context(), |
| ("some-scheme://fuchsia.com/package#meta/comp.cm", None) |
| ); |
| |
| let cloned_address = abs_address.clone(); |
| assert_eq!(abs_address, cloned_address); |
| |
| let address2 = abs_address.clone_with_new_resource(Some("meta/other_comp.cm")).unwrap(); |
| assert_ne!(address2, abs_address); |
| assert!(address2.is_absolute()); |
| assert_eq!(address2.resource(), Some("meta/other_comp.cm")); |
| assert_eq!(address2.scheme(), "some-scheme"); |
| assert_eq!(address2.path(), "/package"); |
| assert_eq!(address2.query(), None); |
| |
| let rel_address = ComponentAddress::new_relative_path( |
| "subpackage", |
| Some("meta/subcomp.cm"), |
| "some-scheme", |
| ComponentResolutionContext::new(vec![b'4', b'5', b'6']), |
| ) |
| .unwrap(); |
| if let ComponentAddress::RelativePath { ref context, .. } = rel_address { |
| assert_eq!(&context.bytes, &vec![b'4', b'5', b'6']); |
| } |
| assert!(rel_address.is_relative_path()); |
| assert_eq!(rel_address.path(), "subpackage"); |
| assert_eq!(rel_address.query(), None); |
| assert_eq!(rel_address.resource(), Some("meta/subcomp.cm")); |
| assert_eq!(&rel_address.context().bytes, &vec![b'4', b'5', b'6']); |
| assert_eq!(rel_address.url(), "subpackage#meta/subcomp.cm"); |
| assert_eq!( |
| rel_address.to_url_and_context(), |
| ( |
| "subpackage#meta/subcomp.cm", |
| Some(&ComponentResolutionContext::new(vec![b'4', b'5', b'6'])) |
| ) |
| ); |
| |
| let rel_address2 = |
| rel_address.clone_with_new_resource(Some("meta/other_subcomp.cm")).unwrap(); |
| assert_ne!(rel_address2, rel_address); |
| assert!(rel_address2.is_relative_path()); |
| assert_eq!(rel_address2.path(), "subpackage"); |
| assert_eq!(rel_address2.query(), None); |
| assert_eq!(rel_address2.resource(), Some("meta/other_subcomp.cm")); |
| assert_eq!(&rel_address2.context().bytes, &vec![b'4', b'5', b'6']); |
| assert_eq!(rel_address2.url(), "subpackage#meta/other_subcomp.cm"); |
| assert_eq!( |
| rel_address2.to_url_and_context(), |
| ( |
| "subpackage#meta/other_subcomp.cm", |
| Some(&ComponentResolutionContext::new(vec![b'4', b'5', b'6'])) |
| ) |
| ); |
| |
| let address = ComponentAddress::from_absolute_url("base://b").unwrap(); |
| assert!(address.is_absolute()); |
| assert_eq!(address.scheme(), "base"); |
| assert_eq!(address.path(), ""); |
| assert_eq!(address.query(), None); |
| assert_eq!(address.resource(), None); |
| assert_eq!(address.url(), "base://b"); |
| assert_matches!(address.to_url_and_context(), ("base://b", None)); |
| |
| let address = ComponentAddress::from_absolute_url("fuchsia-boot:///#meta/root.cm").unwrap(); |
| assert!(address.is_absolute()); |
| assert_eq!(address.scheme(), "fuchsia-boot"); |
| assert_eq!(address.path(), "/"); |
| assert_eq!(address.query(), None); |
| assert_eq!(address.resource(), Some("meta/root.cm")); |
| assert_eq!(address.url(), "fuchsia-boot:///#meta/root.cm"); |
| assert_matches!(address.to_url_and_context(), ("fuchsia-boot:///#meta/root.cm", None)); |
| |
| let address = |
| ComponentAddress::from_absolute_url("custom-resolver:my:special:path#meta/root.cm") |
| .unwrap(); |
| assert!(address.is_absolute()); |
| assert_eq!(address.scheme(), "custom-resolver"); |
| assert_eq!(address.path(), "my:special:path"); |
| assert_eq!(address.query(), None); |
| assert_eq!(address.resource(), Some("meta/root.cm")); |
| assert_eq!(address.url(), "custom-resolver:my:special:path#meta/root.cm"); |
| assert_matches!( |
| address.to_url_and_context(), |
| ("custom-resolver:my:special:path#meta/root.cm", None) |
| ); |
| |
| let address = ComponentAddress::from_absolute_url("cast:00000000").unwrap(); |
| assert!(address.is_absolute()); |
| assert_eq!(address.scheme(), "cast"); |
| assert_eq!(address.path(), "00000000"); |
| assert_eq!(address.query(), None); |
| assert_eq!(address.resource(), None); |
| assert_eq!(address.url(), "cast:00000000"); |
| assert_matches!(address.to_url_and_context(), ("cast:00000000", None)); |
| |
| let address = ComponentAddress::from_absolute_url("cast:00000000#meta/root.cm").unwrap(); |
| assert!(address.is_absolute()); |
| assert_eq!(address.scheme(), "cast"); |
| assert_eq!(address.path(), "00000000"); |
| assert_eq!(address.query(), None); |
| assert_eq!(address.resource(), Some("meta/root.cm")); |
| assert_eq!(address.url(), "cast:00000000#meta/root.cm"); |
| assert_matches!(address.to_url_and_context(), ("cast:00000000#meta/root.cm", None)); |
| |
| let address = ComponentAddress::from_absolute_url( |
| "fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm", |
| ) |
| .unwrap(); |
| assert!(address.is_absolute()); |
| assert_eq!(address.scheme(), "fuchsia-pkg"); |
| assert_eq!(address.path(), "/package"); |
| assert_eq!(address.resource(), Some("meta/comp.cm")); |
| assert_eq!(address.query(), Some("hash=cafe0123")); |
| assert_eq!(address.url(), "fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm"); |
| assert_matches!( |
| address.to_url_and_context(), |
| ("fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm", None) |
| ); |
| } |
| |
| #[test] |
| fn test_relative_path() { |
| let url = Url::parse("relative:///package#fragment").unwrap(); |
| assert_eq!(url.path(), "/package"); |
| assert_eq!(ComponentAddress::relative_path(&url), "package"); |
| |
| let url = Url::parse("cast:00000000#fragment").unwrap(); |
| assert_eq!(url.path(), "00000000"); |
| assert_eq!(ComponentAddress::relative_path(&url), "00000000"); |
| } |
| |
| #[test] |
| fn test_parse_relative_url() { |
| let relative_prefix_with_one_less_slash = Url::parse("relative://").unwrap(); |
| assert_eq!(relative_prefix_with_one_less_slash.scheme(), "relative"); |
| assert_eq!(relative_prefix_with_one_less_slash.host(), None); |
| assert_eq!(relative_prefix_with_one_less_slash.path(), ""); |
| |
| assert_eq!(RELATIVE_URL_BASE.scheme(), "relative"); |
| assert_eq!(RELATIVE_URL_BASE.host(), None); |
| assert_eq!(RELATIVE_URL_BASE.path(), "/"); |
| |
| let mut clone_relative_base = RELATIVE_URL_BASE.clone(); |
| assert_eq!(clone_relative_base.path(), "/"); |
| clone_relative_base.set_path(""); |
| assert_eq!(clone_relative_base.path(), ""); |
| |
| let mut clone_relative_base = RELATIVE_URL_BASE.clone(); |
| assert_eq!(clone_relative_base.path(), "/"); |
| clone_relative_base.set_path("some_path_no_initial_slash"); |
| assert_eq!(clone_relative_base.path(), "/some_path_no_initial_slash"); |
| |
| let clone_relative_base = RELATIVE_URL_BASE.clone(); |
| let joined = clone_relative_base.join("some_path_no_initial_slash").unwrap(); |
| assert_eq!(joined.path(), "/some_path_no_initial_slash"); |
| |
| let clone_relative_base = relative_prefix_with_one_less_slash.clone(); |
| let joined = clone_relative_base.join("some_path_no_initial_slash").unwrap(); |
| // Same result as with three slashes |
| assert_eq!(joined.path(), "/some_path_no_initial_slash"); |
| |
| let relative_url = |
| ComponentAddress::parse_relative_url("subpackage#meta/subcomp.cm").unwrap(); |
| assert_eq!(relative_url.path(), "/subpackage"); |
| assert_eq!(relative_url.query(), None); |
| assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm")); |
| |
| let relative_url = ComponentAddress::parse_relative_url("subpackage").unwrap(); |
| assert_eq!(relative_url.path(), "/subpackage"); |
| assert_eq!(relative_url.query(), None); |
| assert_eq!(relative_url.fragment(), None); |
| |
| let relative_url = ComponentAddress::parse_relative_url("/subpackage").unwrap(); |
| assert_eq!(relative_url.path(), "/subpackage"); |
| assert_eq!(relative_url.query(), None); |
| assert_eq!(relative_url.fragment(), None); |
| |
| let relative_url = ComponentAddress::parse_relative_url("//subpackage").unwrap(); |
| assert_eq!(relative_url.path(), ""); |
| assert_eq!(relative_url.host_str(), Some("subpackage")); |
| assert_eq!(relative_url.query(), None); |
| assert_eq!(relative_url.fragment(), None); |
| |
| let relative_url = ComponentAddress::parse_relative_url("///subpackage").unwrap(); |
| assert_eq!(relative_url.path(), "/subpackage"); |
| assert_eq!(relative_url.host_str(), None); |
| assert_eq!(relative_url.query(), None); |
| assert_eq!(relative_url.fragment(), None); |
| |
| let relative_url = ComponentAddress::parse_relative_url("fuchsia.com/subpackage").unwrap(); |
| assert_eq!(relative_url.path(), "/fuchsia.com/subpackage"); |
| assert_eq!(relative_url.query(), None); |
| assert_eq!(relative_url.fragment(), None); |
| |
| let relative_url = |
| ComponentAddress::parse_relative_url("//fuchsia.com/subpackage").unwrap(); |
| assert_eq!(relative_url.path(), "/subpackage"); |
| assert_eq!(relative_url.host_str(), Some("fuchsia.com")); |
| assert_eq!(relative_url.query(), None); |
| assert_eq!(relative_url.fragment(), None); |
| |
| assert_matches!( |
| ComponentAddress::parse_relative_url("fuchsia-pkg://fuchsia.com/subpackage"), |
| Err(ResolverError::MalformedUrl(..)) |
| ); |
| |
| let relative_url = |
| ComponentAddress::parse_relative_url("//fuchsia.com/subpackage").unwrap(); |
| assert_eq!(relative_url.path(), "/subpackage"); |
| assert_eq!(relative_url.query(), None); |
| assert_eq!(relative_url.fragment(), None); |
| |
| let relative_url = ComponentAddress::parse_relative_url("#meta/peercomp.cm").unwrap(); |
| assert_eq!(relative_url.path(), "/"); |
| assert_eq!(relative_url.query(), None); |
| assert_eq!(relative_url.fragment(), Some("meta/peercomp.cm")); |
| |
| let address = |
| ComponentAddress::from_absolute_url("some-scheme://fuchsia.com/package#meta/comp.cm") |
| .unwrap() |
| .clone_with_new_resource(relative_url.fragment()) |
| .unwrap(); |
| |
| assert!(address.is_absolute()); |
| assert_eq!(address.scheme(), "some-scheme"); |
| assert_eq!(address.path(), "/package"); |
| assert_eq!(address.query(), None); |
| assert_eq!(address.resource(), Some("meta/peercomp.cm")); |
| assert_eq!(address.url(), "some-scheme://fuchsia.com/package#meta/peercomp.cm"); |
| |
| let address = ComponentAddress::from_absolute_url("cast:00000000") |
| .unwrap() |
| .clone_with_new_resource(relative_url.fragment()) |
| .unwrap(); |
| |
| assert!(address.is_absolute()); |
| assert_eq!(address.scheme(), "cast"); |
| assert_eq!(address.path(), "00000000"); |
| assert_eq!(address.query(), None); |
| assert_eq!(address.resource(), Some("meta/peercomp.cm")); |
| assert_eq!(address.url(), "cast:00000000#meta/peercomp.cm"); |
| } |
| } |