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