| // 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, |
| log::error, |
| once_cell::sync::OnceCell, |
| std::convert::TryFrom, |
| std::sync::Arc, |
| thiserror::Error, |
| url::Url, |
| }; |
| |
| lazy_static! { |
| /// A default base URL from which to parse relative component URL |
| /// components. |
| static ref A_BASE_URL: Url = Url::parse("relative:///").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 { |
| /// A string indicating which resolver resolved this component (for log |
| /// messages and debugging only). |
| pub resolved_by: String, |
| /// 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::ValuesData>, |
| } |
| |
| /// 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)?, |
| }) |
| } |
| } |
| |
| /// When resolving a component, the resolver returns a PackageContext, which it |
| /// may use to resolve relative path URLs. The value is resolver-specific, so |
| /// a resolver that doesn't support relative path resolution can return any |
| /// value (typically an empty `Vec`). |
| #[derive(Debug, Clone, PartialEq, Eq)] |
| pub struct ComponentResolutionContext(pub Vec<u8>); |
| |
| impl ComponentResolutionContext { |
| /// Converts this ComponentResolutionContext into a byte vector, which can be passed |
| /// to a `Resolver::ResolveWithContext()` request with a relative component |
| /// URL. |
| pub fn into_bytes(self) -> Vec<u8> { |
| self.0 |
| } |
| |
| /// Returns a reference to the underlying byte slice. |
| pub fn as_bytes(&self) -> &[u8] { |
| &self.0 |
| } |
| } |
| |
| impl From<Vec<u8>> for ComponentResolutionContext { |
| fn from(context: Vec<u8>) -> ComponentResolutionContext { |
| ComponentResolutionContext(context) |
| } |
| } |
| |
| impl From<&[u8]> for ComponentResolutionContext { |
| fn from(context: &[u8]) -> ComponentResolutionContext { |
| ComponentResolutionContext(Vec::from(context)) |
| } |
| } |
| |
| impl From<ComponentResolutionContext> for Vec<u8> { |
| fn from(context: ComponentResolutionContext) -> Vec<u8> { |
| context.into_bytes() |
| } |
| } |
| |
| impl From<&ComponentResolutionContext> for Vec<u8> { |
| fn from(context: &ComponentResolutionContext) -> Vec<u8> { |
| context.clone().into_bytes() |
| } |
| } |
| |
| impl<'a> From<&'a ComponentResolutionContext> for &'a [u8] { |
| fn from(context: &'a ComponentResolutionContext) -> &'a [u8] { |
| context.as_bytes() |
| } |
| } |
| |
| /// Provides the `ComponentAddress` and context for resolving the child that was |
| /// passed to `from_child()` to create this `ResolvedParentComponent`. |
| #[derive(Debug, Clone, PartialEq, Eq)] |
| struct ResolvedParentComponent { |
| /// The parent 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 parent's resolution_context, required for resolving children using |
| /// a relative path component URLs. |
| pub context_to_resolve_children: Option<ComponentResolutionContext>, |
| } |
| |
| impl ResolvedParentComponent { |
| /// Creates a `ResolvedParentComponent` from one of its child components. |
| pub async fn from_child<C: ComponentInstanceInterface>( |
| component: &Arc<C>, |
| ) -> Result<Self, ResolverError> { |
| if let ExtendedInstanceInterface::Component(parent_component) = |
| component.try_get_parent().map_err(|err| { |
| ResolverError::no_parent_context(anyhow::format_err!( |
| "component {} at {} has no parent for context: {:?}", |
| component.url(), |
| component.abs_moniker(), |
| err |
| )) |
| })? |
| { |
| 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(), |
| }) |
| } else { |
| Err(ResolverError::no_parent_context(anyhow::format_err!( |
| "component {} has no parent for context: {}", |
| component.url(), |
| component.abs_moniker() |
| ))) |
| } |
| } |
| } |
| |
| /// 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 |
| /// `ComponentAddressKind`s. |
| #[derive(Debug, Clone, PartialEq, Eq)] |
| pub enum ComponentAddressKind { |
| /// A fully-qualified component URL, including scheme and host; for example, |
| /// "fuchsia-pkg://fuchsia.com/some_package#meta/my_component.cm". Host may |
| /// be empty. The query string (excluding the question mark (`?`) prefix) |
| /// is optional, and only an absolute component URL may include a query. |
| /// `ComponentAddress` does not constrain the value of the query string. |
| /// They may be invalid for some resolvers. Some resolvers may support a |
| /// query string of the form `hash=<hex-package-merkle>`. |
| Absolute { host: String, some_query: Option<String> }, |
| |
| /// A relative Component URL, starting with the package path; for example a |
| /// subpackage relative URL such as "needed_package#meta/dep_component.cm". |
| RelativePath { |
| /// 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, |
| }, |
| } |
| |
| #[derive(Debug, Clone, Eq)] |
| pub struct ComponentAddress { |
| /// The kind of `ComponentAddress` (`Absolute` or `RelativePath`) with |
| /// kind-specific additional properties. |
| kind: ComponentAddressKind, |
| |
| /// The scheme of an ancestor component's URL, used to identify the |
| /// `Resolver` in a `ResolverRegistry`. |
| scheme: String, |
| |
| /// The path part of the component URL. For `RelativePath`, this path MUST |
| /// NOT start with a slash (`/`). For subpackages, the path MUST contain |
| /// exactly one path segment (no slashes). |
| path: String, |
| |
| /// The URI fragment used to identify a resource in a package. For |
| /// components, this is the component manifest path. For component URLs, the |
| /// path MUST NOT start with a pound sign (`#`) or slash (`/`). |
| some_resource: Option<String>, |
| |
| /// The given URL used to compute the ComponentAddress, which may have |
| /// required additional information from its parent component. |
| some_original_url: Option<String>, |
| |
| /// Holds a resolver-ready computed URL string. |
| url: OnceCell<String>, |
| } |
| |
| /// Ignore `some_original_url` and `url` when comparing `ComponentAddress` |
| /// instances. |
| impl PartialEq for ComponentAddress { |
| fn eq(&self, other: &Self) -> bool { |
| let ComponentAddress { kind, scheme, path, some_resource, some_original_url: _, url: _ } = |
| other; |
| &self.kind == kind |
| && &self.scheme == scheme |
| && &self.path == path |
| && &self.some_resource == some_resource |
| } |
| } |
| |
| impl ComponentAddress { |
| /// Creates a new `ComponentAddress` of the `Absolute` kind. |
| pub fn new_absolute( |
| scheme: &str, |
| host: &str, |
| path: &str, |
| some_query: Option<&str>, |
| some_resource: Option<&str>, |
| ) -> Self { |
| Self { |
| kind: ComponentAddressKind::Absolute { |
| host: host.to_owned(), |
| some_query: some_query.map(str::to_string), |
| }, |
| scheme: scheme.to_owned(), |
| path: path.to_owned(), |
| some_resource: some_resource.map(str::to_string), |
| some_original_url: None, |
| url: OnceCell::new(), |
| } |
| } |
| |
| /// Creates a new `ComponentAddress` of the `RelativePath` kind. |
| pub fn new_relative_path( |
| path: &str, |
| some_resource: Option<&str>, |
| scheme: &str, |
| context: ComponentResolutionContext, |
| ) -> Self { |
| Self { |
| kind: ComponentAddressKind::RelativePath { context }, |
| scheme: scheme.to_owned(), |
| path: path.to_owned(), |
| some_resource: some_resource.map(str::to_string), |
| some_original_url: None, |
| url: OnceCell::new(), |
| } |
| } |
| |
| /// Parse the given absolute `component_url` and create a `ComponentAddress` |
| /// with kind `Absolute`. |
| pub fn from_absolute_url(component_url: &str) -> Result<Self, ResolverError> { |
| let url = match Url::parse(component_url) { |
| Ok(url) => url, |
| Err(url::ParseError::RelativeUrlWithoutBase) => { |
| return Err(ResolverError::RelativeUrlNotExpected(component_url.to_string())) |
| } |
| Err(err) => return Err(ResolverError::malformed_url(err)), |
| }; |
| let path = &url.path()[1..]; // skip leading "/" |
| let host = url.host_str().ok_or_else(|| { |
| ResolverError::malformed_url(anyhow::format_err!( |
| "parsed `host` was invalid (`None`) from: {}", |
| component_url |
| )) |
| })?; |
| let mut address = Self::new_absolute(url.scheme(), host, path, url.query(), url.fragment()); |
| address.some_original_url = Some(component_url.to_owned()); |
| Ok(address) |
| } |
| |
| /// 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 url = parse_relative_url(component_url)?; |
| let path = &url.path()[1..]; // skip leading "/" |
| if url.fragment().is_none() && path.is_empty() { |
| return Err(ResolverError::malformed_url(anyhow::format_err!("{}", component_url))); |
| } |
| if url.query().is_some() { |
| return Err(ResolverError::malformed_url(anyhow::format_err!( |
| "Query strings are not allowed in relative component URLs: {}", |
| component_url |
| ))); |
| } |
| let resolved_parent = ResolvedParentComponent::from_child(component).await?; |
| let mut address = if path.is_empty() { |
| // The `component_url` had only a fragment, so the new |
| // address will be the same as it's parent (for example, the |
| // same package), except for its resource. |
| resolved_parent.address.clone_with_new_resource(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. |
| Self::new_relative_path( |
| path, |
| url.fragment(), |
| resolved_parent.address.scheme(), |
| resolved_parent.context_to_resolve_children.clone().ok_or_else(|| { |
| ResolverError::RelativeUrlMissingContext(format!( |
| "relative path component URL '{}' cannot be resolved because it's parent/ancestor did not provide a resolution context: {:?}", |
| component_url, resolved_parent.address |
| )) |
| })?, |
| ) |
| }; |
| address.some_original_url = Some(component_url.to_owned()); |
| Ok(address) |
| } |
| |
| /// Creates a new `ComponentAddress` from `self` by replacing only the |
| /// component URL resource. |
| pub fn clone_with_new_resource(&self, some_resource: Option<&str>) -> Self { |
| Self { |
| kind: self.kind.clone(), |
| scheme: self.scheme.clone(), |
| path: self.path.clone(), |
| some_resource: some_resource.map(str::to_string), |
| some_original_url: None, |
| url: OnceCell::new(), |
| } |
| } |
| |
| /// Returns the variant, which is `Absolute` or `RelativePath`. |
| pub fn kind(&self) -> &ComponentAddressKind { |
| &self.kind |
| } |
| |
| /// True if the `kind()` is `Absolute`. |
| pub fn is_absolute(&self) -> bool { |
| matches!(self.kind, ComponentAddressKind::Absolute { .. }) |
| } |
| |
| /// True if the `kind()` is `RelativePath`. |
| pub fn is_relative_path(&self) -> bool { |
| matches!(self.kind, ComponentAddressKind::RelativePath { .. }) |
| } |
| |
| /// Returns the optional host value for an `Absolute` component URL. |
| /// |
| /// Panics if called for a `RelativePath` component address. |
| pub fn host(&self) -> &str { |
| if let ComponentAddressKind::Absolute { host, .. } = &self.kind { |
| host |
| } else { |
| panic!("host() is only valid for `ComponentAddressKind::Absolute"); |
| } |
| } |
| |
| /// 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 ComponentAddressKind::RelativePath { context } = &self.kind { |
| &context |
| } else { |
| panic!("host() is only valid for `ComponentAddressKind::Absolute"); |
| } |
| } |
| |
| /// 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 { |
| &self.scheme |
| } |
| |
| /// Returns the URL path. |
| pub fn path(&self) -> &str { |
| &self.path |
| } |
| |
| /// Returns the optional query value for an `Absolute` component URL. |
| /// Always returns `None` for `Relative` component URLs. |
| pub fn query(&self) -> Option<&str> { |
| if let ComponentAddressKind::Absolute { some_query, .. } = &self.kind { |
| some_query.as_deref() |
| } else { |
| None |
| } |
| } |
| |
| /// Returns the optional component resource, from the URL fragment. |
| pub fn resource(&self) -> Option<&str> { |
| self.some_resource.as_deref() |
| } |
| |
| /// Returns the original URL, if this `ComponentAddress` was created by |
| /// calling `ComponentAddress::from(<original_url>, ...)`. |
| pub fn original_url(&self) -> Option<&str> { |
| self.some_original_url.as_deref() |
| } |
| |
| /// 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 { |
| let url = self.url.get_or_init(|| match &self.kind { |
| ComponentAddressKind::Absolute { host, some_query } => { |
| let path = if self.path.is_empty() && !host.is_empty() { |
| "".to_string() |
| } else { |
| format!("/{}", self.path) |
| }; |
| format!( |
| "{}://{}{}{}{}", |
| self.scheme, |
| host, |
| path, |
| if let Some(query) = some_query { |
| format!("?{}", query) |
| } else { |
| "".to_string() |
| }, |
| if let Some(resource) = &self.some_resource { |
| format!("#{}", resource) |
| } else { |
| "".to_string() |
| }, |
| ) |
| } |
| ComponentAddressKind::RelativePath { .. } => { |
| if let Some(resource) = &self.some_resource { |
| format!("{}#{}", self.path, resource) |
| } else { |
| format!("{}", self.path) |
| } |
| } |
| }); |
| url |
| } |
| |
| /// 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>) { |
| let some_context = match &self.kind { |
| ComponentAddressKind::Absolute { .. } => None, |
| ComponentAddressKind::RelativePath { context } => Some(context), |
| }; |
| (self.url(), some_context) |
| } |
| } |
| |
| 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) => { |
| A_BASE_URL.join(component_url).map_err(|err| { |
| ResolverError::malformed_url(anyhow::format_err!( |
| "Error parsing relative component URL {}: {:?}", |
| component_url, |
| err |
| )) |
| }) |
| } |
| Err(err) => Err(ResolverError::malformed_url(anyhow::format_err!( |
| "Unexpected error while parsing component_url {}: {:?}", |
| component_url, |
| err, |
| ))), |
| } |
| } |
| |
| /// 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("failed to read manifest: {0}")] |
| ManifestIo(zx::Status), |
| #[error("failed to read config values: {0}")] |
| ConfigValuesIo(zx::Status), |
| #[error("Model not available")] |
| ModelNotAvailable, |
| #[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("url missing resource")] |
| UrlMissingResource, |
| #[error("a relative URL was not expected: {0}")] |
| RelativeUrlNotExpected(String), |
| #[error("failed to route resolver capability: {0}")] |
| RoutingError(#[source] ClonableError), |
| #[error("a resolver resolved a component but did not return its required context")] |
| ResolveMustReturnContext, |
| #[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("error creating a resolution context: {0}")] |
| CreatingContext(String), |
| #[error("error reading a resolution context: {0}")] |
| ReadingContext(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 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 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) |
| } |
| } |
| } |
| } |
| |
| impl From<ComponentInstanceError> for ResolverError { |
| fn from(err: ComponentInstanceError) -> ResolverError { |
| use ComponentInstanceError::*; |
| match &err { |
| ComponentManagerInstanceUnavailable {} |
| | InstanceNotFound { .. } |
| | PolicyCheckerNotFound { .. } |
| | ComponentIdIndexNotFound { .. } |
| | ResolveFailed { .. } |
| | UnresolveFailed { .. } => { |
| 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() -> anyhow::Result<()> { |
| let url = "some_url".to_string(); |
| let (dir_client, _) = |
| create_endpoints::<fio::DirectoryMarker>().expect("failed to create Directory proxy"); |
| let fidl_package = fresolution::Package { |
| url: Some(url.clone()), |
| directory: Some(dir_client), |
| ..fresolution::Package::EMPTY |
| }; |
| let resolved_package = ResolvedPackage::try_from(fidl_package)?; |
| assert_eq!(resolved_package.url, url); |
| Ok(()) |
| } |
| |
| #[test] |
| fn test_component_address() -> anyhow::Result<()> { |
| let address = |
| ComponentAddress::from_absolute_url("some-scheme://fuchsia.com/package#meta/comp.cm")?; |
| if let ComponentAddressKind::Absolute { host, some_query } = address.kind() { |
| assert_eq!(host.as_str(), "fuchsia.com"); |
| assert_eq!(some_query, &None); |
| } |
| assert!(address.is_absolute()); |
| assert_eq!(address.host(), "fuchsia.com"); |
| 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.original_url(), Some("some-scheme://fuchsia.com/package#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( |
| "some-scheme", |
| "fuchsia.com", |
| "package", |
| None, |
| Some("meta/comp.cm"), |
| ); |
| // Note that `url()` has been called on `address` but not on |
| // `abs_address`, and `original_url()` differences should be ignored |
| // when comparing. |
| assert_eq!(abs_address, address); |
| |
| assert_eq!(abs_address, address); |
| if let ComponentAddressKind::Absolute { host, .. } = abs_address.kind() { |
| assert_eq!(host.as_str(), "fuchsia.com"); |
| } |
| assert!(abs_address.is_absolute()); |
| assert_eq!(abs_address.host(), "fuchsia.com"); |
| 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")); |
| 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.host(), "fuchsia.com"); |
| 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(vec![b'4', b'5', b'6']), |
| ); |
| if let ComponentAddressKind::RelativePath { context } = rel_address.kind() { |
| assert_eq!(context.as_bytes(), &vec![b'4', b'5', b'6']); |
| } |
| assert!(rel_address.is_relative_path()); |
| assert_eq!(rel_address.context().as_bytes(), &vec![b'4', b'5', b'6']); |
| assert_eq!(rel_address.original_url(), None); |
| assert_eq!(rel_address.url(), "subpackage#meta/subcomp.cm"); |
| assert_eq!( |
| rel_address.to_url_and_context(), |
| ( |
| "subpackage#meta/subcomp.cm", |
| Some(&ComponentResolutionContext(vec![b'4', b'5', b'6'])) |
| ) |
| ); |
| |
| let rel_address2 = rel_address.clone_with_new_resource(Some("meta/other_subcomp.cm")); |
| assert_ne!(rel_address2, rel_address); |
| assert!(rel_address2.is_relative_path()); |
| assert_eq!(rel_address2.context().as_bytes(), &vec![b'4', b'5', b'6']); |
| assert_eq!(rel_address2.original_url(), None); |
| 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(vec![b'4', b'5', b'6'])) |
| ) |
| ); |
| |
| let address = ComponentAddress::from_absolute_url("fuchsia-boot:///#meta/root.cm")?; |
| if let ComponentAddressKind::Absolute { host, .. } = address.kind() { |
| assert_eq!(host.as_str(), ""); |
| } |
| assert!(address.is_absolute()); |
| assert_eq!(address.host(), ""); |
| 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.original_url(), Some("fuchsia-boot:///#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( |
| "fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm", |
| )?; |
| if let ComponentAddressKind::Absolute { host, some_query } = address.kind() { |
| assert_eq!(host.as_str(), "fuchsia.com"); |
| assert_eq!(some_query.as_deref(), Some("hash=cafe0123")); |
| } |
| assert!(address.is_absolute()); |
| assert_eq!(address.host(), "fuchsia.com"); |
| 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.original_url(), |
| Some("fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm") |
| ); |
| 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) |
| ); |
| |
| Ok(()) |
| } |
| } |