use crate::component_instance::{ComponentInstanceInterface, ExtendedInstanceInterface};
use crate::error::ComponentInstanceError;
use anyhow::Error;
use clonable_error::ClonableError;
use lazy_static::lazy_static;
use std::sync::Arc;
use thiserror::Error;
use url::Url;
use version_history::AbiRevision;
use {fidl_fuchsia_component_resolution as fresolution, fidl_fuchsia_io as fio, zx_status as zx};
#[cfg(target_os = "fuchsia")]
use cm_rust::{FidlIntoNative, NativeIntoFidl};
const RELATIVE_URL_PREFIX: &str = "relative:///";
lazy_static! {
static ref RELATIVE_URL_BASE: Url = Url::parse(RELATIVE_URL_PREFIX).unwrap();
}
#[derive(Debug)]
pub struct ResolvedComponent {
pub resolved_url: String,
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>,
}
#[cfg(target_os = "fuchsia")]
impl TryFrom<fresolution::Component> for ResolvedComponent {
type Error = ResolverError;
fn try_from(component: fresolution::Component) -> Result<Self, Self::Error> {
let decl_buffer: fidl_fuchsia_mem::Data =
component.decl.ok_or(ResolverError::RemoteInvalidData)?;
let decl = read_and_validate_manifest(&decl_buffer)?;
let config_values = match &decl.config {
Some(config) => match config.value_source {
cm_rust::ConfigValueSource::PackagePath(_) => {
Some(read_and_validate_config_values(
&component.config_values.ok_or(ResolverError::RemoteInvalidData)?,
)?)
}
cm_rust::ConfigValueSource::Capabilities(_) => None,
},
None => None,
};
let resolved_url = component.url.ok_or(ResolverError::RemoteInvalidData)?;
let context_to_resolve_children = component.resolution_context.map(Into::into);
let abi_revision = component.abi_revision.map(Into::into);
Ok(ResolvedComponent {
resolved_url,
context_to_resolve_children,
decl,
package: component.package.map(TryInto::try_into).transpose()?,
config_values,
abi_revision,
})
}
}
#[cfg(target_os = "fuchsia")]
impl From<ResolvedComponent> for fresolution::Component {
fn from(component: ResolvedComponent) -> Self {
let ResolvedComponent {
resolved_url,
context_to_resolve_children,
decl,
package,
config_values,
abi_revision,
} = component;
let decl_bytes = fidl::persist(&decl.native_into_fidl())
.expect("failed to serialize validated manifest");
let decl_vmo = fidl::Vmo::create(decl_bytes.len() as u64).expect("failed to create VMO");
decl_vmo.write(&decl_bytes, 0).expect("failed to write to VMO");
fresolution::Component {
url: Some(resolved_url),
decl: Some(fidl_fuchsia_mem::Data::Buffer(fidl_fuchsia_mem::Buffer {
vmo: decl_vmo,
size: decl_bytes.len() as u64,
})),
package: package.map(|p| fresolution::Package {
url: Some(p.url),
directory: Some(p.directory),
..Default::default()
}),
config_values: config_values.map(|config_values| {
let config_values_bytes = fidl::persist(&config_values.native_into_fidl())
.expect("failed to serialize config values");
let config_values_vmo = fidl::Vmo::create(config_values_bytes.len() as u64)
.expect("failed to create VMO");
config_values_vmo.write(&config_values_bytes, 0).expect("failed to write to VMO");
fidl_fuchsia_mem::Data::Buffer(fidl_fuchsia_mem::Buffer {
vmo: config_values_vmo,
size: config_values_bytes.len() as u64,
})
}),
resolution_context: context_to_resolve_children.map(Into::into),
abi_revision: abi_revision.map(Into::into),
..Default::default()
}
}
}
#[cfg(target_os = "fuchsia")]
pub fn read_and_validate_manifest(
data: &fidl_fuchsia_mem::Data,
) -> Result<cm_rust::ComponentDecl, ResolverError> {
let bytes = mem_util::bytes_from_data(data).map_err(ResolverError::manifest_invalid)?;
read_and_validate_manifest_bytes(&bytes)
}
#[cfg(target_os = "fuchsia")]
pub fn read_and_validate_manifest_bytes(
bytes: &[u8],
) -> Result<cm_rust::ComponentDecl, ResolverError> {
let component_decl: fidl_fuchsia_component_decl::Component =
fidl::unpersist(bytes).map_err(ResolverError::manifest_invalid)?;
cm_fidl_validator::validate(&component_decl).map_err(ResolverError::manifest_invalid)?;
Ok(component_decl.fidl_into_native())
}
#[cfg(target_os = "fuchsia")]
pub fn read_and_validate_config_values(
data: &fidl_fuchsia_mem::Data,
) -> Result<cm_rust::ConfigValuesData, ResolverError> {
let bytes = mem_util::bytes_from_data(&data).map_err(ResolverError::config_values_invalid)?;
let values = fidl::unpersist(&bytes).map_err(ResolverError::fidl_error)?;
cm_fidl_validator::validate_values_data(&values)
.map_err(|e| ResolverError::config_values_invalid(e))?;
Ok(values.fidl_into_native())
}
#[derive(Debug)]
pub struct ResolvedPackage {
pub url: String,
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)?,
})
}
}
#[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
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct ResolvedAncestorComponent {
pub address: ComponentAddress,
pub context_to_resolve_children: Option<ComponentResolutionContext>,
}
impl ResolvedAncestorComponent {
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(),
})
}
pub async fn first_packaged_ancestor_of<C: ComponentInstanceInterface>(
component: &Arc<C>,
) -> Result<Self, ResolverError> {
let mut parent_component = get_parent(component).await?;
loop {
{
let resolved_parent = parent_component.lock_resolved_state().await?;
let address = resolved_parent.address();
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)
} else {
Err(ResolverError::no_parent_context(anyhow::format_err!(
"Component {} ({}) has no parent for context.",
component.moniker(),
component.url(),
)))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ComponentAddress {
Absolute { url: Url },
RelativePath {
scheme: String,
url: Url,
context: ComponentResolutionContext,
},
}
impl ComponentAddress {
fn new_absolute(url: Url) -> Self {
Self::Absolute { url }
}
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() })
}
pub fn from_absolute_url(component_url: &cm_types::Url) -> Result<Self, ResolverError> {
match Url::parse(component_url.as_str()) {
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)),
}
}
fn parse_relative_url(component_url: &cm_types::Url) -> Result<Url, ResolverError> {
let component_url = component_url.as_str();
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(())
}
fn relative_path(relative_url: &Url) -> &str {
let path = relative_url.path();
path.strip_prefix('/').unwrap_or(path)
}
pub async fn from_url<C: ComponentInstanceInterface>(
component_url: &cm_types::Url,
component: &Arc<C>,
) -> Result<Self, ResolverError> {
Self::from(component_url, None, component).await
}
pub async fn from_url_and_context<C: ComponentInstanceInterface>(
component_url: &cm_types::Url,
context: ComponentResolutionContext,
component: &Arc<C>,
) -> Result<Self, ResolverError> {
Self::from(component_url, Some(context), component).await
}
async fn from<C: ComponentInstanceInterface>(
component_url: &cm_types::Url,
context: Option<ComponentResolutionContext>,
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() {
let resolved_parent = ResolvedAncestorComponent::direct_parent_of(component).await?;
resolved_parent.address.clone_with_new_resource(relative_url.fragment())
} else {
let resolved_ancestor =
ResolvedAncestorComponent::first_packaged_ancestor_of(component).await?;
let scheme = resolved_ancestor.address.scheme();
if let Some(context) = context {
Self::new_relative_path(relative_path, relative_url.fragment(), scheme, context)
} else {
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)
}
}
}
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() })
}
}
}
pub fn is_absolute(&self) -> bool {
matches!(self, Self::Absolute { .. })
}
pub fn is_relative_path(&self) -> bool {
matches!(self, Self::RelativePath { .. })
}
pub fn context(&self) -> &ComponentResolutionContext {
if let Self::RelativePath { context, .. } = self {
&context
} else {
panic!("context() is only valid for `ComponentAddressKind::RelativePath");
}
}
pub fn scheme(&self) -> &str {
match self {
Self::Absolute { url } => url.scheme(),
Self::RelativePath { scheme, .. } => &scheme,
}
}
pub fn path(&self) -> &str {
match self {
Self::Absolute { url } => url.path(),
Self::RelativePath { url, .. } => Self::relative_path(&url),
}
}
pub fn query(&self) -> Option<&str> {
match self {
Self::Absolute { url } => url.query(),
Self::RelativePath { .. } => None,
}
}
pub fn resource(&self) -> Option<&str> {
match self {
Self::Absolute { url } => url.fragment(),
Self::RelativePath { url, .. } => url.fragment(),
}
}
pub fn url(&self) -> &str {
match self {
Self::Absolute { url } => url.as_str(),
Self::RelativePath { url, .. } => &url.as_str()[RELATIVE_URL_PREFIX.len()..],
}
}
pub fn to_url_and_context(&self) -> (&str, Option<&ComponentResolutionContext>) {
match self {
Self::Absolute { .. } => (self.url(), None),
Self::RelativePath { context, .. } => (self.url(), Some(context)),
}
}
}
#[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<ResolverError> for fresolution::ResolverError {
fn from(err: ResolverError) -> fresolution::ResolverError {
match err {
ResolverError::Internal(_) => fresolution::ResolverError::Internal,
ResolverError::Io(_) => fresolution::ResolverError::Io,
ResolverError::ManifestNotFound(_) => fresolution::ResolverError::ManifestNotFound,
ResolverError::PackageNotFound(_) => fresolution::ResolverError::PackageNotFound,
ResolverError::ManifestInvalid(_) => fresolution::ResolverError::InvalidManifest,
ResolverError::ConfigValuesInvalid(_) => fresolution::ResolverError::InvalidManifest,
ResolverError::AbiRevisionNotFound => fresolution::ResolverError::AbiRevisionNotFound,
ResolverError::AbiRevisionInvalid(_) => fresolution::ResolverError::InvalidAbiRevision,
ResolverError::ConfigValuesIo(_) => fresolution::ResolverError::Io,
ResolverError::SchemeNotRegistered => fresolution::ResolverError::NotSupported,
ResolverError::MalformedUrl(_) => fresolution::ResolverError::InvalidArgs,
ResolverError::NoParentContext(_) => fresolution::ResolverError::Internal,
ResolverError::PackageUrlMissing => fresolution::ResolverError::PackageNotFound,
ResolverError::PackageDirectoryMissing => fresolution::ResolverError::PackageNotFound,
ResolverError::RelativeUrlNotExpected(_) => fresolution::ResolverError::InvalidArgs,
ResolverError::RoutingError(_) => fresolution::ResolverError::Internal,
ResolverError::RelativeUrlMissingContext(_) => fresolution::ResolverError::InvalidArgs,
ResolverError::UnexpectedRelativePath(_) => fresolution::ResolverError::InvalidArgs,
ResolverError::RemoteInvalidData => fresolution::ResolverError::InvalidManifest,
ResolverError::FidlError(_) => fresolution::ResolverError::Internal,
}
}
}
impl From<ComponentInstanceError> for ResolverError {
fn from(err: ComponentInstanceError) -> ResolverError {
use ComponentInstanceError::*;
match &err {
ComponentManagerInstanceUnavailable {}
| ComponentManagerInstanceUnexpected {}
| 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::*;
use assert_matches::assert_matches;
use fidl::endpoints::create_endpoints;
fn from_absolute_url(url: &str) -> ComponentAddress {
ComponentAddress::from_absolute_url(&url.parse().unwrap()).unwrap()
}
fn parse_relative_url(url: &str) -> Url {
ComponentAddress::parse_relative_url(&url.parse().unwrap()).unwrap()
}
#[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 = from_absolute_url("some-scheme://fuchsia.com/package#meta/comp.cm");
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 = from_absolute_url("base://b");
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 = from_absolute_url("fuchsia-boot:///#meta/root.cm");
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 = from_absolute_url("custom-resolver:my:special:path#meta/root.cm");
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 = from_absolute_url("cast:00000000");
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 = from_absolute_url("cast:00000000#meta/root.cm");
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 =
from_absolute_url("fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm");
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();
assert_eq!(joined.path(), "/some_path_no_initial_slash");
let relative_url = parse_relative_url("subpackage#meta/subcomp.cm");
assert_eq!(relative_url.path(), "/subpackage");
assert_eq!(relative_url.query(), None);
assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
let relative_url = parse_relative_url("/subpackage#meta/subcomp.cm");
assert_eq!(relative_url.path(), "/subpackage");
assert_eq!(relative_url.query(), None);
assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
let relative_url = parse_relative_url("//subpackage#meta/subcomp.cm");
assert_eq!(relative_url.path(), "");
assert_eq!(relative_url.host_str(), Some("subpackage"));
assert_eq!(relative_url.query(), None);
assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
let relative_url = parse_relative_url("///subpackage#meta/subcomp.cm");
assert_eq!(relative_url.path(), "/subpackage");
assert_eq!(relative_url.host_str(), None);
assert_eq!(relative_url.query(), None);
assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
let relative_url = parse_relative_url("fuchsia.com/subpackage#meta/subcomp.cm");
assert_eq!(relative_url.path(), "/fuchsia.com/subpackage");
assert_eq!(relative_url.query(), None);
assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
let relative_url = parse_relative_url("//fuchsia.com/subpackage#meta/subcomp.cm");
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(), Some("meta/subcomp.cm"));
assert_matches!(
ComponentAddress::parse_relative_url(
&"fuchsia-pkg://fuchsia.com/subpackage#meta/subcomp.cm".parse().unwrap()
),
Err(ResolverError::MalformedUrl(..))
);
let relative_url = parse_relative_url("#meta/peercomp.cm");
assert_eq!(relative_url.path(), "/");
assert_eq!(relative_url.query(), None);
assert_eq!(relative_url.fragment(), Some("meta/peercomp.cm"));
let address = from_absolute_url("some-scheme://fuchsia.com/package#meta/comp.cm")
.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 = from_absolute_url("cast:00000000")
.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");
}
}