1use crate::component_instance::{ComponentInstanceInterface, ExtendedInstanceInterface};
6use crate::error::ComponentInstanceError;
7use anyhow::Error;
8use clonable_error::ClonableError;
9use lazy_static::lazy_static;
10use std::sync::Arc;
11use thiserror::Error;
12use url::Url;
13use version_history::AbiRevision;
14use {fidl_fuchsia_component_resolution as fresolution, fidl_fuchsia_io as fio, zx_status as zx};
15
16#[cfg(target_os = "fuchsia")]
17use cm_rust::{FidlIntoNative, NativeIntoFidl};
18
19const RELATIVE_URL_PREFIX: &str = "relative:///";
21lazy_static! {
22 static ref RELATIVE_URL_BASE: Url = Url::parse(RELATIVE_URL_PREFIX).unwrap();
25}
26
27#[derive(Debug)]
31pub struct ResolvedComponent {
32 pub resolved_url: String,
34 pub context_to_resolve_children: Option<ComponentResolutionContext>,
37 pub decl: cm_rust::ComponentDecl,
38 pub package: Option<ResolvedPackage>,
39 pub config_values: Option<cm_rust::ConfigValuesData>,
40 pub abi_revision: Option<AbiRevision>,
41}
42
43#[cfg(target_os = "fuchsia")]
47impl TryFrom<fresolution::Component> for ResolvedComponent {
48 type Error = ResolverError;
49
50 fn try_from(component: fresolution::Component) -> Result<Self, Self::Error> {
51 let decl_buffer: fidl_fuchsia_mem::Data =
52 component.decl.ok_or(ResolverError::RemoteInvalidData)?;
53 let decl = read_and_validate_manifest(&decl_buffer)?;
54 let config_values = match &decl.config {
55 Some(config) => match config.value_source {
56 cm_rust::ConfigValueSource::PackagePath(_) => {
57 Some(read_and_validate_config_values(
58 &component.config_values.ok_or(ResolverError::RemoteInvalidData)?,
59 )?)
60 }
61 cm_rust::ConfigValueSource::Capabilities(_) => None,
62 },
63 None => None,
64 };
65 let resolved_url = component.url.ok_or(ResolverError::RemoteInvalidData)?;
66 let context_to_resolve_children = component.resolution_context.map(Into::into);
67 let abi_revision = component.abi_revision.map(Into::into);
68 Ok(ResolvedComponent {
69 resolved_url,
70 context_to_resolve_children,
71 decl,
72 package: component.package.map(TryInto::try_into).transpose()?,
73 config_values,
74 abi_revision,
75 })
76 }
77}
78
79#[cfg(target_os = "fuchsia")]
80impl From<ResolvedComponent> for fresolution::Component {
81 fn from(component: ResolvedComponent) -> Self {
82 let ResolvedComponent {
83 resolved_url,
84 context_to_resolve_children,
85 decl,
86 package,
87 config_values,
88 abi_revision,
89 } = component;
90 let decl_bytes = fidl::persist(&decl.native_into_fidl())
91 .expect("failed to serialize validated manifest");
92 let decl_vmo = fidl::Vmo::create(decl_bytes.len() as u64).expect("failed to create VMO");
93 decl_vmo.write(&decl_bytes, 0).expect("failed to write to VMO");
94 fresolution::Component {
95 url: Some(resolved_url),
96 decl: Some(fidl_fuchsia_mem::Data::Buffer(fidl_fuchsia_mem::Buffer {
97 vmo: decl_vmo,
98 size: decl_bytes.len() as u64,
99 })),
100 package: package.map(|p| fresolution::Package {
101 url: Some(p.url),
102 directory: Some(p.directory),
103 ..Default::default()
104 }),
105 config_values: config_values.map(|config_values| {
106 let config_values_bytes = fidl::persist(&config_values.native_into_fidl())
107 .expect("failed to serialize config values");
108 let config_values_vmo = fidl::Vmo::create(config_values_bytes.len() as u64)
109 .expect("failed to create VMO");
110 config_values_vmo.write(&config_values_bytes, 0).expect("failed to write to VMO");
111 fidl_fuchsia_mem::Data::Buffer(fidl_fuchsia_mem::Buffer {
112 vmo: config_values_vmo,
113 size: config_values_bytes.len() as u64,
114 })
115 }),
116 resolution_context: context_to_resolve_children.map(Into::into),
117 abi_revision: abi_revision.map(Into::into),
118 ..Default::default()
119 }
120 }
121}
122
123#[cfg(target_os = "fuchsia")]
124pub fn read_and_validate_manifest(
125 data: &fidl_fuchsia_mem::Data,
126) -> Result<cm_rust::ComponentDecl, ResolverError> {
127 let bytes = mem_util::bytes_from_data(data).map_err(ResolverError::manifest_invalid)?;
128 read_and_validate_manifest_bytes(&bytes)
129}
130
131#[cfg(target_os = "fuchsia")]
132pub fn read_and_validate_manifest_bytes(
133 bytes: &[u8],
134) -> Result<cm_rust::ComponentDecl, ResolverError> {
135 let component_decl: fidl_fuchsia_component_decl::Component =
136 fidl::unpersist(bytes).map_err(ResolverError::manifest_invalid)?;
137 cm_fidl_validator::validate(&component_decl).map_err(ResolverError::manifest_invalid)?;
138 Ok(component_decl.fidl_into_native())
139}
140
141#[cfg(target_os = "fuchsia")]
142pub fn read_and_validate_config_values(
143 data: &fidl_fuchsia_mem::Data,
144) -> Result<cm_rust::ConfigValuesData, ResolverError> {
145 let bytes = mem_util::bytes_from_data(&data).map_err(ResolverError::config_values_invalid)?;
146 let values = fidl::unpersist(&bytes).map_err(ResolverError::fidl_error)?;
147 cm_fidl_validator::validate_values_data(&values)
148 .map_err(|e| ResolverError::config_values_invalid(e))?;
149 Ok(values.fidl_into_native())
150}
151
152#[derive(Debug)]
156pub struct ResolvedPackage {
157 pub url: String,
159 pub directory: fidl::endpoints::ClientEnd<fio::DirectoryMarker>,
161}
162
163impl TryFrom<fresolution::Package> for ResolvedPackage {
164 type Error = ResolverError;
165
166 fn try_from(package: fresolution::Package) -> Result<Self, Self::Error> {
167 Ok(ResolvedPackage {
168 url: package.url.ok_or(ResolverError::PackageUrlMissing)?,
169 directory: package.directory.ok_or(ResolverError::PackageDirectoryMissing)?,
170 })
171 }
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
177pub struct ComponentResolutionContext {
178 pub bytes: Vec<u8>,
179}
180
181impl ComponentResolutionContext {
182 pub fn new(bytes: Vec<u8>) -> Self {
183 ComponentResolutionContext { bytes }
184 }
185}
186
187impl From<fresolution::Context> for ComponentResolutionContext {
188 fn from(context: fresolution::Context) -> Self {
189 ComponentResolutionContext { bytes: context.bytes }
190 }
191}
192
193impl From<&fresolution::Context> for ComponentResolutionContext {
194 fn from(context: &fresolution::Context) -> ComponentResolutionContext {
195 ComponentResolutionContext { bytes: context.bytes.clone() }
196 }
197}
198
199impl From<ComponentResolutionContext> for fresolution::Context {
200 fn from(context: ComponentResolutionContext) -> Self {
201 Self { bytes: context.bytes }
202 }
203}
204
205impl From<&ComponentResolutionContext> for fresolution::Context {
206 fn from(context: &ComponentResolutionContext) -> fresolution::Context {
207 Self { bytes: context.bytes.clone() }
208 }
209}
210
211impl<'a> From<&'a ComponentResolutionContext> for &'a [u8] {
212 fn from(context: &'a ComponentResolutionContext) -> &'a [u8] {
213 &context.bytes
214 }
215}
216
217#[derive(Debug, Clone, PartialEq, Eq)]
220struct ResolvedAncestorComponent {
221 pub address: ComponentAddress,
225 pub context_to_resolve_children: Option<ComponentResolutionContext>,
228}
229
230impl ResolvedAncestorComponent {
231 pub async fn direct_parent_of<C: ComponentInstanceInterface>(
233 component: &Arc<C>,
234 ) -> Result<Self, ResolverError> {
235 let parent_component = get_parent(component).await?;
236 let resolved_parent = parent_component.lock_resolved_state().await?;
237 Ok(Self {
238 address: resolved_parent.address(),
239 context_to_resolve_children: resolved_parent.context_to_resolve_children(),
240 })
241 }
242
243 pub async fn first_packaged_ancestor_of<C: ComponentInstanceInterface>(
245 component: &Arc<C>,
246 ) -> Result<Self, ResolverError> {
247 let mut parent_component = get_parent(component).await?;
248 loop {
249 {
252 let resolved_parent = parent_component.lock_resolved_state().await?;
253 let address = resolved_parent.address();
254 if address.scheme() != "realm-builder" {
283 return Ok(Self {
284 address,
285 context_to_resolve_children: resolved_parent.context_to_resolve_children(),
286 });
287 }
288 }
289 parent_component = get_parent(&parent_component).await?;
290 }
291 }
292}
293
294async fn get_parent<C: ComponentInstanceInterface>(
295 component: &Arc<C>,
296) -> Result<Arc<C>, ResolverError> {
297 if let ExtendedInstanceInterface::Component(parent_component) =
298 component.try_get_parent().map_err(|err| {
299 ResolverError::no_parent_context(anyhow::format_err!(
300 "Component {} ({}) has no parent for context: {:?}.",
301 component.moniker(),
302 component.url(),
303 err,
304 ))
305 })?
306 {
307 Ok(parent_component)
308 } else {
309 Err(ResolverError::no_parent_context(anyhow::format_err!(
310 "Component {} ({}) has no parent for context.",
311 component.moniker(),
312 component.url(),
313 )))
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
323pub enum ComponentAddress {
324 Absolute { url: Url },
326
327 RelativePath {
330 scheme: String,
333
334 url: Url,
337
338 context: ComponentResolutionContext,
344 },
345}
346
347impl ComponentAddress {
348 fn new_absolute(url: Url) -> Self {
350 Self::Absolute { url }
351 }
352
353 fn new_relative_path(
355 path: &str,
356 some_resource: Option<&str>,
357 scheme: &str,
358 context: ComponentResolutionContext,
359 ) -> Result<Self, ResolverError> {
360 let mut url = RELATIVE_URL_BASE.clone();
361 url.set_path(path);
362 url.set_fragment(some_resource);
363 Self::check_relative_url(&url)?;
364 Ok(Self::RelativePath { url, context, scheme: scheme.into() })
365 }
366
367 pub fn from_absolute_url(component_url: &cm_types::Url) -> Result<Self, ResolverError> {
370 match Url::parse(component_url.as_str()) {
371 Ok(url) => Ok(Self::new_absolute(url)),
372 Err(url::ParseError::RelativeUrlWithoutBase) => {
373 Err(ResolverError::RelativeUrlNotExpected(component_url.to_string()))
374 }
375 Err(err) => Err(ResolverError::malformed_url(err)),
376 }
377 }
378
379 fn parse_relative_url(component_url: &cm_types::Url) -> Result<Url, ResolverError> {
382 let component_url = component_url.as_str();
383 match Url::parse(component_url) {
384 Ok(_) => Err(ResolverError::malformed_url(anyhow::format_err!(
385 "Error parsing a relative URL given absolute URL '{}'.",
386 component_url,
387 ))),
388 Err(url::ParseError::RelativeUrlWithoutBase) => {
389 RELATIVE_URL_BASE.join(component_url).map_err(|err| {
390 ResolverError::malformed_url(anyhow::format_err!(
391 "Error parsing a relative component URL '{}': {:?}.",
392 component_url,
393 err
394 ))
395 })
396 }
397 Err(err) => Err(ResolverError::malformed_url(anyhow::format_err!(
398 "Unexpected error while parsing a component URL '{}': {:?}.",
399 component_url,
400 err,
401 ))),
402 }
403 }
404
405 fn check_relative_url(url: &Url) -> Result<(), ResolverError> {
406 let truncated_url = url.as_str().strip_prefix(RELATIVE_URL_PREFIX).ok_or_else(|| {
407 ResolverError::malformed_url(anyhow::format_err!(
408 "Could not strip relative prefix from url. This is a bug. {}",
409 url
410 ))
411 })?;
412 let relative_url = RELATIVE_URL_BASE.make_relative(&url).ok_or_else(|| {
413 ResolverError::malformed_url(anyhow::format_err!(
414 "Could not make relative url. This is a bug. {}",
415 url
416 ))
417 })?;
418 if truncated_url != relative_url {
419 return Err(ResolverError::malformed_url(anyhow::format_err!(
420 "Relative url generated from url::Url did not match expectations. \
421 This is a bug. {}",
422 url
423 )));
424 }
425 Ok(())
426 }
427
428 fn relative_path(relative_url: &Url) -> &str {
433 let path = relative_url.path();
434 path.strip_prefix('/').unwrap_or(path)
435 }
436
437 pub async fn from_url<C: ComponentInstanceInterface>(
442 component_url: &cm_types::Url,
443 component: &Arc<C>,
444 ) -> Result<Self, ResolverError> {
445 Self::from(component_url, None, component).await
446 }
447
448 pub async fn from_url_and_context<C: ComponentInstanceInterface>(
452 component_url: &cm_types::Url,
453 context: ComponentResolutionContext,
454 component: &Arc<C>,
455 ) -> Result<Self, ResolverError> {
456 Self::from(component_url, Some(context), component).await
457 }
458
459 async fn from<C: ComponentInstanceInterface>(
464 component_url: &cm_types::Url,
465 context: Option<ComponentResolutionContext>,
466 component: &Arc<C>,
467 ) -> Result<Self, ResolverError> {
468 let result = Self::from_absolute_url(component_url);
469 if !matches!(result, Err(ResolverError::RelativeUrlNotExpected(_))) {
470 return result;
471 }
472 let relative_url = Self::parse_relative_url(component_url)?;
473 let relative_path = Self::relative_path(&relative_url);
474 if relative_url.fragment().is_none() && relative_path.is_empty() {
475 return Err(ResolverError::malformed_url(anyhow::format_err!("{}", component_url)));
476 }
477 if relative_url.query().is_some() {
478 return Err(ResolverError::malformed_url(anyhow::format_err!(
479 "Query strings are not allowed in relative component URLs: {}",
480 component_url
481 )));
482 }
483 if relative_path.is_empty() {
484 let resolved_parent = ResolvedAncestorComponent::direct_parent_of(component).await?;
487 resolved_parent.address.clone_with_new_resource(relative_url.fragment())
488 } else {
489 let resolved_ancestor =
498 ResolvedAncestorComponent::first_packaged_ancestor_of(component).await?;
499 let scheme = resolved_ancestor.address.scheme();
500 if let Some(context) = context {
501 Self::new_relative_path(relative_path, relative_url.fragment(), scheme, context)
502 } else {
503 let context = resolved_ancestor.context_to_resolve_children.clone().ok_or_else(|| {
504 ResolverError::RelativeUrlMissingContext(format!(
505 "Relative path component URL '{}' cannot be resolved because its ancestor did not provide a resolution context. The ancestor's component address is {:?}.",
506 component_url, resolved_ancestor.address
507 ))
508 })?;
509 Self::new_relative_path(relative_path, relative_url.fragment(), scheme, context)
510 }
511 }
512 }
513
514 pub fn clone_with_new_resource(
517 &self,
518 some_resource: Option<&str>,
519 ) -> Result<Self, ResolverError> {
520 let mut url = match &self {
521 Self::Absolute { url } => url.clone(),
522 Self::RelativePath { url, .. } => url.clone(),
523 };
524 url.set_fragment(some_resource);
525 match &self {
526 Self::Absolute { .. } => Ok(Self::Absolute { url }),
527 Self::RelativePath { context, scheme, .. } => {
528 Self::check_relative_url(&url)?;
529 Ok(Self::RelativePath { url, context: context.clone(), scheme: scheme.clone() })
530 }
531 }
532 }
533
534 pub fn is_absolute(&self) -> bool {
536 matches!(self, Self::Absolute { .. })
537 }
538
539 pub fn is_relative_path(&self) -> bool {
541 matches!(self, Self::RelativePath { .. })
542 }
543
544 pub fn context(&self) -> &ComponentResolutionContext {
549 if let Self::RelativePath { context, .. } = self {
550 &context
551 } else {
552 panic!("context() is only valid for `ComponentAddressKind::RelativePath");
553 }
554 }
555
556 pub fn scheme(&self) -> &str {
560 match self {
561 Self::Absolute { url } => url.scheme(),
562 Self::RelativePath { scheme, .. } => &scheme,
563 }
564 }
565
566 pub fn path(&self) -> &str {
568 match self {
569 Self::Absolute { url } => url.path(),
570 Self::RelativePath { url, .. } => Self::relative_path(&url),
571 }
572 }
573
574 pub fn query(&self) -> Option<&str> {
577 match self {
578 Self::Absolute { url } => url.query(),
579 Self::RelativePath { .. } => None,
580 }
581 }
582
583 pub fn resource(&self) -> Option<&str> {
585 match self {
586 Self::Absolute { url } => url.fragment(),
587 Self::RelativePath { url, .. } => url.fragment(),
588 }
589 }
590
591 pub fn url(&self) -> &str {
594 match self {
595 Self::Absolute { url } => url.as_str(),
596 Self::RelativePath { url, .. } => &url.as_str()[RELATIVE_URL_PREFIX.len()..],
597 }
598 }
599
600 pub fn to_url_and_context(&self) -> (&str, Option<&ComponentResolutionContext>) {
603 match self {
604 Self::Absolute { .. } => (self.url(), None),
605 Self::RelativePath { context, .. } => (self.url(), Some(context)),
606 }
607 }
608}
609
610#[derive(Debug, Error, Clone)]
612pub enum ResolverError {
613 #[error("an unexpected error occurred: {0}")]
614 Internal(#[source] ClonableError),
615 #[error("an IO error occurred: {0}")]
616 Io(#[source] ClonableError),
617 #[error("component manifest not found: {0}")]
618 ManifestNotFound(#[source] ClonableError),
619 #[error("package not found: {0}")]
620 PackageNotFound(#[source] ClonableError),
621 #[error("component manifest invalid: {0}")]
622 ManifestInvalid(#[source] ClonableError),
623 #[error("config values file invalid: {0}")]
624 ConfigValuesInvalid(#[source] ClonableError),
625 #[error("abi revision not found")]
626 AbiRevisionNotFound,
627 #[error("abi revision invalid: {0}")]
628 AbiRevisionInvalid(#[source] ClonableError),
629 #[error("failed to read config values: {0}")]
630 ConfigValuesIo(zx::Status),
631 #[error("scheme not registered")]
632 SchemeNotRegistered,
633 #[error("malformed url: {0}")]
634 MalformedUrl(#[source] ClonableError),
635 #[error("relative url requires a parent component with resolution context: {0}")]
636 NoParentContext(#[source] ClonableError),
637 #[error("package URL missing")]
638 PackageUrlMissing,
639 #[error("package directory handle missing")]
640 PackageDirectoryMissing,
641 #[error("a relative URL was not expected: {0}")]
642 RelativeUrlNotExpected(String),
643 #[error("failed to route resolver capability: {0}")]
644 RoutingError(#[source] ClonableError),
645 #[error("a context is required to resolve relative url: {0}")]
646 RelativeUrlMissingContext(String),
647 #[error("this component resolver does not resolve relative path component URLs: {0}")]
648 UnexpectedRelativePath(String),
649 #[error("the remote resolver returned invalid data")]
650 RemoteInvalidData,
651 #[error("an error occurred sending a FIDL request to the remote resolver: {0}")]
652 FidlError(#[source] ClonableError),
653}
654
655impl ResolverError {
656 pub fn as_zx_status(&self) -> zx::Status {
657 match self {
658 ResolverError::PackageNotFound(_)
659 | ResolverError::ManifestNotFound(_)
660 | ResolverError::ManifestInvalid(_)
661 | ResolverError::ConfigValuesInvalid(_)
662 | ResolverError::Io(_)
663 | ResolverError::ConfigValuesIo(_)
664 | ResolverError::AbiRevisionNotFound
665 | ResolverError::AbiRevisionInvalid(_)
666 | ResolverError::SchemeNotRegistered
667 | ResolverError::MalformedUrl(_)
668 | ResolverError::NoParentContext(_)
669 | ResolverError::RelativeUrlMissingContext(_)
670 | ResolverError::RemoteInvalidData
671 | ResolverError::PackageUrlMissing
672 | ResolverError::PackageDirectoryMissing
673 | ResolverError::UnexpectedRelativePath(_) => zx::Status::NOT_FOUND,
674
675 ResolverError::Internal(_)
676 | ResolverError::RelativeUrlNotExpected(_)
677 | ResolverError::RoutingError(_)
678 | ResolverError::FidlError(_) => zx::Status::INTERNAL,
679 }
680 }
681
682 pub fn internal(err: impl Into<Error>) -> Self {
683 Self::Internal(err.into().into())
684 }
685
686 pub fn io(err: impl Into<Error>) -> Self {
687 Self::Io(err.into().into())
688 }
689
690 pub fn manifest_not_found(err: impl Into<Error>) -> Self {
691 Self::ManifestNotFound(err.into().into())
692 }
693
694 pub fn package_not_found(err: impl Into<Error>) -> Self {
695 Self::PackageNotFound(err.into().into())
696 }
697
698 pub fn manifest_invalid(err: impl Into<Error>) -> Self {
699 Self::ManifestInvalid(err.into().into())
700 }
701
702 pub fn config_values_invalid(err: impl Into<Error>) -> Self {
703 Self::ConfigValuesInvalid(err.into().into())
704 }
705
706 pub fn abi_revision_invalid(err: impl Into<Error>) -> Self {
707 Self::AbiRevisionInvalid(err.into().into())
708 }
709
710 pub fn malformed_url(err: impl Into<Error>) -> Self {
711 Self::MalformedUrl(err.into().into())
712 }
713
714 pub fn no_parent_context(err: impl Into<Error>) -> Self {
715 Self::NoParentContext(err.into().into())
716 }
717
718 pub fn routing_error(err: impl Into<Error>) -> Self {
719 Self::RoutingError(err.into().into())
720 }
721
722 pub fn fidl_error(err: impl Into<Error>) -> Self {
723 Self::FidlError(err.into().into())
724 }
725}
726
727impl From<fresolution::ResolverError> for ResolverError {
728 fn from(err: fresolution::ResolverError) -> ResolverError {
729 match err {
730 fresolution::ResolverError::Internal => ResolverError::internal(RemoteError(err)),
731 fresolution::ResolverError::Io => ResolverError::io(RemoteError(err)),
732 fresolution::ResolverError::PackageNotFound
733 | fresolution::ResolverError::NoSpace
734 | fresolution::ResolverError::ResourceUnavailable
735 | fresolution::ResolverError::NotSupported => {
736 ResolverError::package_not_found(RemoteError(err))
737 }
738 fresolution::ResolverError::ManifestNotFound => {
739 ResolverError::manifest_not_found(RemoteError(err))
740 }
741 fresolution::ResolverError::InvalidArgs => {
742 ResolverError::malformed_url(RemoteError(err))
743 }
744 fresolution::ResolverError::InvalidManifest => {
745 ResolverError::ManifestInvalid(anyhow::Error::from(RemoteError(err)).into())
746 }
747 fresolution::ResolverError::ConfigValuesNotFound => {
748 ResolverError::ConfigValuesIo(zx::Status::NOT_FOUND)
749 }
750 fresolution::ResolverError::AbiRevisionNotFound => ResolverError::AbiRevisionNotFound,
751 fresolution::ResolverError::InvalidAbiRevision => {
752 ResolverError::abi_revision_invalid(RemoteError(err))
753 }
754 }
755 }
756}
757
758impl From<ResolverError> for fresolution::ResolverError {
759 fn from(err: ResolverError) -> fresolution::ResolverError {
760 match err {
761 ResolverError::Internal(_) => fresolution::ResolverError::Internal,
762 ResolverError::Io(_) => fresolution::ResolverError::Io,
763 ResolverError::ManifestNotFound(_) => fresolution::ResolverError::ManifestNotFound,
764 ResolverError::PackageNotFound(_) => fresolution::ResolverError::PackageNotFound,
765 ResolverError::ManifestInvalid(_) => fresolution::ResolverError::InvalidManifest,
766 ResolverError::ConfigValuesInvalid(_) => fresolution::ResolverError::InvalidManifest,
767 ResolverError::AbiRevisionNotFound => fresolution::ResolverError::AbiRevisionNotFound,
768 ResolverError::AbiRevisionInvalid(_) => fresolution::ResolverError::InvalidAbiRevision,
769 ResolverError::ConfigValuesIo(_) => fresolution::ResolverError::Io,
770 ResolverError::SchemeNotRegistered => fresolution::ResolverError::NotSupported,
771 ResolverError::MalformedUrl(_) => fresolution::ResolverError::InvalidArgs,
772 ResolverError::NoParentContext(_) => fresolution::ResolverError::Internal,
773 ResolverError::PackageUrlMissing => fresolution::ResolverError::PackageNotFound,
774 ResolverError::PackageDirectoryMissing => fresolution::ResolverError::PackageNotFound,
775 ResolverError::RelativeUrlNotExpected(_) => fresolution::ResolverError::InvalidArgs,
776 ResolverError::RoutingError(_) => fresolution::ResolverError::Internal,
777 ResolverError::RelativeUrlMissingContext(_) => fresolution::ResolverError::InvalidArgs,
778 ResolverError::UnexpectedRelativePath(_) => fresolution::ResolverError::InvalidArgs,
779 ResolverError::RemoteInvalidData => fresolution::ResolverError::InvalidManifest,
780 ResolverError::FidlError(_) => fresolution::ResolverError::Internal,
781 }
782 }
783}
784
785impl From<ComponentInstanceError> for ResolverError {
786 fn from(err: ComponentInstanceError) -> ResolverError {
787 use ComponentInstanceError::*;
788 match &err {
789 ComponentManagerInstanceUnavailable {}
790 | ComponentManagerInstanceUnexpected {}
791 | InstanceNotFound { .. }
792 | ResolveFailed { .. } => {
793 ResolverError::Internal(ClonableError::from(anyhow::format_err!("{:?}", err)))
794 }
795 NoAbsoluteUrl { .. } => ResolverError::NoParentContext(ClonableError::from(
796 anyhow::format_err!("{:?}", err),
797 )),
798 MalformedUrl { .. } => {
799 ResolverError::MalformedUrl(ClonableError::from(anyhow::format_err!("{:?}", err)))
800 }
801 }
802 }
803}
804
805#[derive(Error, Clone, Debug)]
806#[error("remote resolver responded with {0:?}")]
807struct RemoteError(fresolution::ResolverError);
808
809#[cfg(test)]
810mod tests {
811 use super::*;
812 use assert_matches::assert_matches;
813 use fidl::endpoints::create_endpoints;
814
815 fn from_absolute_url(url: &str) -> ComponentAddress {
816 ComponentAddress::from_absolute_url(&url.parse().unwrap()).unwrap()
817 }
818
819 fn parse_relative_url(url: &str) -> Url {
820 ComponentAddress::parse_relative_url(&url.parse().unwrap()).unwrap()
821 }
822
823 #[test]
824 fn test_resolved_package() {
825 let url = "some_url".to_string();
826 let (dir_client, _) = create_endpoints::<fio::DirectoryMarker>();
827 let fidl_package = fresolution::Package {
828 url: Some(url.clone()),
829 directory: Some(dir_client),
830 ..Default::default()
831 };
832 let resolved_package = ResolvedPackage::try_from(fidl_package).unwrap();
833 assert_eq!(resolved_package.url, url);
834 }
835
836 #[test]
837 fn test_component_address() {
838 let address = from_absolute_url("some-scheme://fuchsia.com/package#meta/comp.cm");
839 assert!(address.is_absolute());
840 assert_eq!(address.scheme(), "some-scheme");
841 assert_eq!(address.path(), "/package");
842 assert_eq!(address.query(), None);
843 assert_eq!(address.resource(), Some("meta/comp.cm"));
844 assert_eq!(address.url(), "some-scheme://fuchsia.com/package#meta/comp.cm");
845 assert_matches!(
846 address.to_url_and_context(),
847 ("some-scheme://fuchsia.com/package#meta/comp.cm", None)
848 );
849
850 let abs_address = ComponentAddress::new_absolute(
851 Url::parse("some-scheme://fuchsia.com/package#meta/comp.cm").unwrap(),
852 );
853 assert_eq!(abs_address, address);
854
855 assert_eq!(abs_address, address);
856 assert!(abs_address.is_absolute());
857 assert_eq!(abs_address.scheme(), "some-scheme");
858 assert_eq!(abs_address.path(), "/package");
859 assert_eq!(abs_address.query(), None);
860 assert_eq!(abs_address.resource(), Some("meta/comp.cm"));
861 assert_eq!(abs_address.url(), "some-scheme://fuchsia.com/package#meta/comp.cm");
862 assert_matches!(
863 abs_address.to_url_and_context(),
864 ("some-scheme://fuchsia.com/package#meta/comp.cm", None)
865 );
866
867 let cloned_address = abs_address.clone();
868 assert_eq!(abs_address, cloned_address);
869
870 let address2 = abs_address.clone_with_new_resource(Some("meta/other_comp.cm")).unwrap();
871 assert_ne!(address2, abs_address);
872 assert!(address2.is_absolute());
873 assert_eq!(address2.resource(), Some("meta/other_comp.cm"));
874 assert_eq!(address2.scheme(), "some-scheme");
875 assert_eq!(address2.path(), "/package");
876 assert_eq!(address2.query(), None);
877
878 let rel_address = ComponentAddress::new_relative_path(
879 "subpackage",
880 Some("meta/subcomp.cm"),
881 "some-scheme",
882 ComponentResolutionContext::new(vec![b'4', b'5', b'6']),
883 )
884 .unwrap();
885 if let ComponentAddress::RelativePath { ref context, .. } = rel_address {
886 assert_eq!(&context.bytes, &vec![b'4', b'5', b'6']);
887 }
888 assert!(rel_address.is_relative_path());
889 assert_eq!(rel_address.path(), "subpackage");
890 assert_eq!(rel_address.query(), None);
891 assert_eq!(rel_address.resource(), Some("meta/subcomp.cm"));
892 assert_eq!(&rel_address.context().bytes, &vec![b'4', b'5', b'6']);
893 assert_eq!(rel_address.url(), "subpackage#meta/subcomp.cm");
894 assert_eq!(
895 rel_address.to_url_and_context(),
896 (
897 "subpackage#meta/subcomp.cm",
898 Some(&ComponentResolutionContext::new(vec![b'4', b'5', b'6']))
899 )
900 );
901
902 let rel_address2 =
903 rel_address.clone_with_new_resource(Some("meta/other_subcomp.cm")).unwrap();
904 assert_ne!(rel_address2, rel_address);
905 assert!(rel_address2.is_relative_path());
906 assert_eq!(rel_address2.path(), "subpackage");
907 assert_eq!(rel_address2.query(), None);
908 assert_eq!(rel_address2.resource(), Some("meta/other_subcomp.cm"));
909 assert_eq!(&rel_address2.context().bytes, &vec![b'4', b'5', b'6']);
910 assert_eq!(rel_address2.url(), "subpackage#meta/other_subcomp.cm");
911 assert_eq!(
912 rel_address2.to_url_and_context(),
913 (
914 "subpackage#meta/other_subcomp.cm",
915 Some(&ComponentResolutionContext::new(vec![b'4', b'5', b'6']))
916 )
917 );
918
919 let address = from_absolute_url("base://b");
920 assert!(address.is_absolute());
921 assert_eq!(address.scheme(), "base");
922 assert_eq!(address.path(), "");
923 assert_eq!(address.query(), None);
924 assert_eq!(address.resource(), None);
925 assert_eq!(address.url(), "base://b");
926 assert_matches!(address.to_url_and_context(), ("base://b", None));
927
928 let address = from_absolute_url("fuchsia-boot:///#meta/root.cm");
929 assert!(address.is_absolute());
930 assert_eq!(address.scheme(), "fuchsia-boot");
931 assert_eq!(address.path(), "/");
932 assert_eq!(address.query(), None);
933 assert_eq!(address.resource(), Some("meta/root.cm"));
934 assert_eq!(address.url(), "fuchsia-boot:///#meta/root.cm");
935 assert_matches!(address.to_url_and_context(), ("fuchsia-boot:///#meta/root.cm", None));
936
937 let address = from_absolute_url("custom-resolver:my:special:path#meta/root.cm");
938 assert!(address.is_absolute());
939 assert_eq!(address.scheme(), "custom-resolver");
940 assert_eq!(address.path(), "my:special:path");
941 assert_eq!(address.query(), None);
942 assert_eq!(address.resource(), Some("meta/root.cm"));
943 assert_eq!(address.url(), "custom-resolver:my:special:path#meta/root.cm");
944 assert_matches!(
945 address.to_url_and_context(),
946 ("custom-resolver:my:special:path#meta/root.cm", None)
947 );
948
949 let address = from_absolute_url("cast:00000000");
950 assert!(address.is_absolute());
951 assert_eq!(address.scheme(), "cast");
952 assert_eq!(address.path(), "00000000");
953 assert_eq!(address.query(), None);
954 assert_eq!(address.resource(), None);
955 assert_eq!(address.url(), "cast:00000000");
956 assert_matches!(address.to_url_and_context(), ("cast:00000000", None));
957
958 let address = from_absolute_url("cast:00000000#meta/root.cm");
959 assert!(address.is_absolute());
960 assert_eq!(address.scheme(), "cast");
961 assert_eq!(address.path(), "00000000");
962 assert_eq!(address.query(), None);
963 assert_eq!(address.resource(), Some("meta/root.cm"));
964 assert_eq!(address.url(), "cast:00000000#meta/root.cm");
965 assert_matches!(address.to_url_and_context(), ("cast:00000000#meta/root.cm", None));
966
967 let address =
968 from_absolute_url("fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm");
969 assert!(address.is_absolute());
970 assert_eq!(address.scheme(), "fuchsia-pkg");
971 assert_eq!(address.path(), "/package");
972 assert_eq!(address.resource(), Some("meta/comp.cm"));
973 assert_eq!(address.query(), Some("hash=cafe0123"));
974 assert_eq!(address.url(), "fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm");
975 assert_matches!(
976 address.to_url_and_context(),
977 ("fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm", None)
978 );
979 }
980
981 #[test]
982 fn test_relative_path() {
983 let url = Url::parse("relative:///package#fragment").unwrap();
984 assert_eq!(url.path(), "/package");
985 assert_eq!(ComponentAddress::relative_path(&url), "package");
986
987 let url = Url::parse("cast:00000000#fragment").unwrap();
988 assert_eq!(url.path(), "00000000");
989 assert_eq!(ComponentAddress::relative_path(&url), "00000000");
990 }
991
992 #[test]
993 fn test_parse_relative_url() {
994 let relative_prefix_with_one_less_slash = Url::parse("relative://").unwrap();
995 assert_eq!(relative_prefix_with_one_less_slash.scheme(), "relative");
996 assert_eq!(relative_prefix_with_one_less_slash.host(), None);
997 assert_eq!(relative_prefix_with_one_less_slash.path(), "");
998
999 assert_eq!(RELATIVE_URL_BASE.scheme(), "relative");
1000 assert_eq!(RELATIVE_URL_BASE.host(), None);
1001 assert_eq!(RELATIVE_URL_BASE.path(), "/");
1002
1003 let mut clone_relative_base = RELATIVE_URL_BASE.clone();
1004 assert_eq!(clone_relative_base.path(), "/");
1005 clone_relative_base.set_path("");
1006 assert_eq!(clone_relative_base.path(), "");
1007
1008 let mut clone_relative_base = RELATIVE_URL_BASE.clone();
1009 assert_eq!(clone_relative_base.path(), "/");
1010 clone_relative_base.set_path("some_path_no_initial_slash");
1011 assert_eq!(clone_relative_base.path(), "/some_path_no_initial_slash");
1012
1013 let clone_relative_base = RELATIVE_URL_BASE.clone();
1014 let joined = clone_relative_base.join("some_path_no_initial_slash").unwrap();
1015 assert_eq!(joined.path(), "/some_path_no_initial_slash");
1016
1017 let clone_relative_base = relative_prefix_with_one_less_slash.clone();
1018 let joined = clone_relative_base.join("some_path_no_initial_slash").unwrap();
1019 assert_eq!(joined.path(), "/some_path_no_initial_slash");
1021
1022 let relative_url = parse_relative_url("subpackage#meta/subcomp.cm");
1023 assert_eq!(relative_url.path(), "/subpackage");
1024 assert_eq!(relative_url.query(), None);
1025 assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1026
1027 let relative_url = parse_relative_url("/subpackage#meta/subcomp.cm");
1028 assert_eq!(relative_url.path(), "/subpackage");
1029 assert_eq!(relative_url.query(), None);
1030 assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1031
1032 let relative_url = parse_relative_url("//subpackage#meta/subcomp.cm");
1033 assert_eq!(relative_url.path(), "");
1034 assert_eq!(relative_url.host_str(), Some("subpackage"));
1035 assert_eq!(relative_url.query(), None);
1036 assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1037
1038 let relative_url = parse_relative_url("///subpackage#meta/subcomp.cm");
1039 assert_eq!(relative_url.path(), "/subpackage");
1040 assert_eq!(relative_url.host_str(), None);
1041 assert_eq!(relative_url.query(), None);
1042 assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1043
1044 let relative_url = parse_relative_url("fuchsia.com/subpackage#meta/subcomp.cm");
1045 assert_eq!(relative_url.path(), "/fuchsia.com/subpackage");
1046 assert_eq!(relative_url.query(), None);
1047 assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1048
1049 let relative_url = parse_relative_url("//fuchsia.com/subpackage#meta/subcomp.cm");
1050 assert_eq!(relative_url.path(), "/subpackage");
1051 assert_eq!(relative_url.host_str(), Some("fuchsia.com"));
1052 assert_eq!(relative_url.query(), None);
1053 assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1054
1055 assert_matches!(
1056 ComponentAddress::parse_relative_url(
1057 &"fuchsia-pkg://fuchsia.com/subpackage#meta/subcomp.cm".parse().unwrap()
1058 ),
1059 Err(ResolverError::MalformedUrl(..))
1060 );
1061
1062 let relative_url = parse_relative_url("#meta/peercomp.cm");
1063 assert_eq!(relative_url.path(), "/");
1064 assert_eq!(relative_url.query(), None);
1065 assert_eq!(relative_url.fragment(), Some("meta/peercomp.cm"));
1066
1067 let address = from_absolute_url("some-scheme://fuchsia.com/package#meta/comp.cm")
1068 .clone_with_new_resource(relative_url.fragment())
1069 .unwrap();
1070
1071 assert!(address.is_absolute());
1072 assert_eq!(address.scheme(), "some-scheme");
1073 assert_eq!(address.path(), "/package");
1074 assert_eq!(address.query(), None);
1075 assert_eq!(address.resource(), Some("meta/peercomp.cm"));
1076 assert_eq!(address.url(), "some-scheme://fuchsia.com/package#meta/peercomp.cm");
1077
1078 let address = from_absolute_url("cast:00000000")
1079 .clone_with_new_resource(relative_url.fragment())
1080 .unwrap();
1081
1082 assert!(address.is_absolute());
1083 assert_eq!(address.scheme(), "cast");
1084 assert_eq!(address.path(), "00000000");
1085 assert_eq!(address.query(), None);
1086 assert_eq!(address.resource(), Some("meta/peercomp.cm"));
1087 assert_eq!(address.url(), "cast:00000000#meta/peercomp.cm");
1088 }
1089}