blob: 69cc16ad01303317c7712e407d09142f009d32d4 [file] [log] [blame]
// 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(())
}
}