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 fidl_fuchsia_component_resolution as fresolution;
12use fidl_fuchsia_io as fio;
13use std::sync::{Arc, LazyLock};
14use thiserror::Error;
15use url::Url;
16use version_history::AbiRevision;
17use zx_status as zx;
18
19#[cfg(target_os = "fuchsia")]
20use {
21 cm_rust::{FidlIntoNative, NativeIntoFidl},
22 fuchsia_trace as trace,
23};
24
25const RELATIVE_URL_PREFIX: &str = "relative:///";
27static RELATIVE_URL_BASE: LazyLock<Url> =
30 LazyLock::new(|| Url::parse(RELATIVE_URL_PREFIX).unwrap());
31
32#[derive(Debug)]
36pub struct ResolvedComponent {
37 pub context_to_resolve_children: Option<ComponentResolutionContext>,
40 pub decl: cm_rust::ComponentDecl,
41 pub package: Option<ResolvedPackage>,
42 pub config_values: Option<cm_rust::ConfigValuesData>,
43 pub abi_revision: Option<AbiRevision>,
44 pub dependencies: DirectedGraph<DependencyNode>,
45}
46
47#[cfg(target_os = "fuchsia")]
51impl TryFrom<fresolution::Component> for ResolvedComponent {
52 type Error = ResolverError;
53
54 fn try_from(component: fresolution::Component) -> Result<Self, Self::Error> {
55 let decl_buffer: fidl_fuchsia_mem::Data =
56 component.decl.ok_or(ResolverError::RemoteInvalidData)?;
57 let mut dependencies = DirectedGraph::new();
58 let decl = read_and_validate_manifest(&decl_buffer, &mut dependencies)?;
59 let config_values = match &decl.config {
60 Some(config) => match config.value_source {
61 cm_rust::ConfigValueSource::PackagePath(_) => {
62 Some(read_and_validate_config_values(
63 &component.config_values.ok_or(ResolverError::RemoteInvalidData)?,
64 )?)
65 }
66 cm_rust::ConfigValueSource::Capabilities(_) => None,
67 },
68 None => None,
69 };
70 let context_to_resolve_children = component.resolution_context.map(Into::into);
71 let abi_revision = component.abi_revision.map(Into::into);
72 Ok(ResolvedComponent {
73 context_to_resolve_children,
74 decl,
75 package: component.package.map(TryInto::try_into).transpose()?,
76 config_values,
77 abi_revision,
78 dependencies,
79 })
80 }
81}
82
83#[cfg(target_os = "fuchsia")]
84impl From<ResolvedComponent> for fresolution::Component {
85 fn from(component: ResolvedComponent) -> Self {
86 let ResolvedComponent {
87 context_to_resolve_children,
88 decl,
89 package,
90 config_values,
91 abi_revision,
92 dependencies: _,
93 } = component;
94 let decl_bytes = fidl::persist(&decl.native_into_fidl())
95 .expect("failed to serialize validated manifest");
96 let decl_vmo = fidl::Vmo::create(decl_bytes.len() as u64).expect("failed to create VMO");
97 decl_vmo.write(&decl_bytes, 0).expect("failed to write to VMO");
98 fresolution::Component {
99 decl: Some(fidl_fuchsia_mem::Data::Buffer(fidl_fuchsia_mem::Buffer {
100 vmo: decl_vmo,
101 size: decl_bytes.len() as u64,
102 })),
103 package: package.map(|p| fresolution::Package {
104 url: Some(p.url),
105 directory: Some(p.directory),
106 ..Default::default()
107 }),
108 config_values: config_values.map(|config_values| {
109 let config_values_bytes = fidl::persist(&config_values.native_into_fidl())
110 .expect("failed to serialize config values");
111 let config_values_vmo = fidl::Vmo::create(config_values_bytes.len() as u64)
112 .expect("failed to create VMO");
113 config_values_vmo.write(&config_values_bytes, 0).expect("failed to write to VMO");
114 fidl_fuchsia_mem::Data::Buffer(fidl_fuchsia_mem::Buffer {
115 vmo: config_values_vmo,
116 size: config_values_bytes.len() as u64,
117 })
118 }),
119 resolution_context: context_to_resolve_children.map(Into::into),
120 abi_revision: abi_revision.map(Into::into),
121 ..Default::default()
122 }
123 }
124}
125
126#[cfg(target_os = "fuchsia")]
127pub fn read_and_validate_manifest(
128 data: &fidl_fuchsia_mem::Data,
129 dependencies: &mut DirectedGraph<DependencyNode>,
130) -> Result<cm_rust::ComponentDecl, ResolverError> {
131 let bytes = mem_util::bytes_from_data(data).map_err(ResolverError::manifest_invalid)?;
132 read_and_validate_manifest_bytes(&bytes, dependencies)
133}
134
135#[cfg(target_os = "fuchsia")]
136pub fn read_and_validate_manifest_bytes(
137 bytes: &[u8],
138 dependencies: &mut DirectedGraph<DependencyNode>,
139) -> Result<cm_rust::ComponentDecl, ResolverError> {
140 trace::duration!("component_manager", "read_and_validate_manifest_bytes");
141 let component_decl: fidl_fuchsia_component_decl::Component =
142 fidl::unpersist(bytes).map_err(ResolverError::manifest_invalid)?;
143 cm_fidl_validator::validate(&component_decl, dependencies)
144 .map_err(ResolverError::manifest_invalid)?;
145 Ok(component_decl.fidl_into_native())
146}
147
148#[cfg(target_os = "fuchsia")]
149pub fn read_and_validate_config_values(
150 data: &fidl_fuchsia_mem::Data,
151) -> Result<cm_rust::ConfigValuesData, ResolverError> {
152 let bytes = mem_util::bytes_from_data(&data).map_err(ResolverError::config_values_invalid)?;
153 let values = fidl::unpersist(&bytes).map_err(ResolverError::fidl_error)?;
154 cm_fidl_validator::validate_values_data(&values)
155 .map_err(|e| ResolverError::config_values_invalid(e))?;
156 Ok(values.fidl_into_native())
157}
158
159#[derive(Debug)]
163pub struct ResolvedPackage {
164 pub url: String,
166 pub directory: fidl::endpoints::ClientEnd<fio::DirectoryMarker>,
168}
169
170impl TryFrom<fresolution::Package> for ResolvedPackage {
171 type Error = ResolverError;
172
173 fn try_from(package: fresolution::Package) -> Result<Self, Self::Error> {
174 Ok(ResolvedPackage {
175 url: package.url.ok_or(ResolverError::PackageUrlMissing)?,
176 directory: package.directory.ok_or(ResolverError::PackageDirectoryMissing)?,
177 })
178 }
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
184pub struct ComponentResolutionContext {
185 pub bytes: Vec<u8>,
186}
187
188impl ComponentResolutionContext {
189 pub fn new(bytes: Vec<u8>) -> Self {
190 ComponentResolutionContext { bytes }
191 }
192}
193
194impl From<fresolution::Context> for ComponentResolutionContext {
195 fn from(context: fresolution::Context) -> Self {
196 ComponentResolutionContext { bytes: context.bytes }
197 }
198}
199
200impl From<&fresolution::Context> for ComponentResolutionContext {
201 fn from(context: &fresolution::Context) -> ComponentResolutionContext {
202 ComponentResolutionContext { bytes: context.bytes.clone() }
203 }
204}
205
206impl From<ComponentResolutionContext> for fresolution::Context {
207 fn from(context: ComponentResolutionContext) -> Self {
208 Self { bytes: context.bytes }
209 }
210}
211
212impl From<&ComponentResolutionContext> for fresolution::Context {
213 fn from(context: &ComponentResolutionContext) -> fresolution::Context {
214 Self { bytes: context.bytes.clone() }
215 }
216}
217
218impl<'a> From<&'a ComponentResolutionContext> for &'a [u8] {
219 fn from(context: &'a ComponentResolutionContext) -> &'a [u8] {
220 &context.bytes
221 }
222}
223
224#[derive(Debug, Clone, PartialEq, Eq)]
227struct ResolvedAncestorComponent {
228 pub address: ComponentAddress,
232 pub context_to_resolve_children: Option<ComponentResolutionContext>,
235}
236
237impl ResolvedAncestorComponent {
238 pub async fn direct_parent_of<C: ComponentInstanceInterface>(
240 component: &Arc<C>,
241 ) -> Result<Self, ResolverError> {
242 let parent_component = get_parent(component).await?;
243 Ok(Self {
244 address: parent_component.address_maybe_resolve().await?,
245 context_to_resolve_children: parent_component.context_to_resolve_children().await?,
246 })
247 }
248
249 pub async fn first_packaged_ancestor_of<C: ComponentInstanceInterface>(
251 component: &Arc<C>,
252 ) -> Result<Self, ResolverError> {
253 let mut parent_component = get_parent(component).await?;
254 loop {
255 {
258 let address = parent_component.address_maybe_resolve().await?;
259 if address.scheme() != "realm-builder" {
288 return Ok(Self {
289 address,
290 context_to_resolve_children: parent_component
291 .context_to_resolve_children()
292 .await?,
293 });
294 }
295 }
296 parent_component = get_parent(&parent_component).await?;
297 }
298 }
299}
300
301async fn get_parent<C: ComponentInstanceInterface>(
302 component: &Arc<C>,
303) -> Result<Arc<C>, ResolverError> {
304 if let ExtendedInstanceInterface::Component(parent_component) =
305 component.try_get_parent().map_err(|err| {
306 ResolverError::no_parent_context(anyhow::format_err!(
307 "Component {} ({}) has no parent for context: {:?}.",
308 component.moniker(),
309 component.url(),
310 err,
311 ))
312 })?
313 {
314 Ok(parent_component)
315 } else {
316 Err(ResolverError::no_parent_context(anyhow::format_err!(
317 "Component {} ({}) has no parent for context.",
318 component.moniker(),
319 component.url(),
320 )))
321 }
322}
323
324#[derive(Debug, Clone, PartialEq, Eq)]
330pub enum ComponentAddress {
331 Absolute { url: Url },
333
334 RelativePath {
337 scheme: String,
340
341 url: Url,
344
345 context: ComponentResolutionContext,
351 },
352}
353
354impl ComponentAddress {
355 fn new_absolute(url: Url) -> Self {
357 Self::Absolute { url }
358 }
359
360 pub fn new_relative_path(
362 path: &str,
363 some_resource: Option<&str>,
364 scheme: &str,
365 context: ComponentResolutionContext,
366 ) -> Result<Self, ResolverError> {
367 let mut url = RELATIVE_URL_BASE.clone();
368 url.set_path(path);
369 url.set_fragment(some_resource);
370 Self::check_relative_url(&url)?;
371 Ok(Self::RelativePath { url, context, scheme: scheme.into() })
372 }
373
374 pub fn from_absolute_url(component_url: &cm_types::Url) -> Result<Self, ResolverError> {
377 match Url::parse(component_url.as_str()) {
378 Ok(url) => Ok(Self::new_absolute(url)),
379 Err(url::ParseError::RelativeUrlWithoutBase) => {
380 Err(ResolverError::RelativeUrlNotExpected(component_url.to_string()))
381 }
382 Err(err) => Err(ResolverError::malformed_url(err)),
383 }
384 }
385
386 fn parse_relative_url(component_url: &cm_types::Url) -> Result<Url, ResolverError> {
389 let component_url = component_url.as_str();
390 match Url::parse(component_url) {
391 Ok(_) => Err(ResolverError::malformed_url(anyhow::format_err!(
392 "Error parsing a relative URL given absolute URL '{}'.",
393 component_url,
394 ))),
395 Err(url::ParseError::RelativeUrlWithoutBase) => {
396 RELATIVE_URL_BASE.join(component_url).map_err(|err| {
397 ResolverError::malformed_url(anyhow::format_err!(
398 "Error parsing a relative component URL '{}': {:?}.",
399 component_url,
400 err
401 ))
402 })
403 }
404 Err(err) => Err(ResolverError::malformed_url(anyhow::format_err!(
405 "Unexpected error while parsing a component URL '{}': {:?}.",
406 component_url,
407 err,
408 ))),
409 }
410 }
411
412 fn check_relative_url(url: &Url) -> Result<(), ResolverError> {
413 let truncated_url = url.as_str().strip_prefix(RELATIVE_URL_PREFIX).ok_or_else(|| {
414 ResolverError::malformed_url(anyhow::format_err!(
415 "Could not strip relative prefix from url. This is a bug. {}",
416 url
417 ))
418 })?;
419 let relative_url = RELATIVE_URL_BASE.make_relative(&url).ok_or_else(|| {
420 ResolverError::malformed_url(anyhow::format_err!(
421 "Could not make relative url. This is a bug. {}",
422 url
423 ))
424 })?;
425 if truncated_url != relative_url {
426 return Err(ResolverError::malformed_url(anyhow::format_err!(
427 "Relative url generated from url::Url did not match expectations. \
428 This is a bug. {}",
429 url
430 )));
431 }
432 Ok(())
433 }
434
435 fn relative_path(relative_url: &Url) -> &str {
440 let path = relative_url.path();
441 path.strip_prefix('/').unwrap_or(path)
442 }
443
444 pub async fn from_url<C: ComponentInstanceInterface>(
449 component_url: &cm_types::Url,
450 component: &Arc<C>,
451 ) -> Result<Self, ResolverError> {
452 Self::from(component_url, None, component).await
453 }
454
455 pub async fn from_url_and_context<C: ComponentInstanceInterface>(
459 component_url: &cm_types::Url,
460 context: ComponentResolutionContext,
461 component: &Arc<C>,
462 ) -> Result<Self, ResolverError> {
463 Self::from(component_url, Some(context), component).await
464 }
465
466 pub async fn from<C: ComponentInstanceInterface>(
471 component_url: &cm_types::Url,
472 context: Option<ComponentResolutionContext>,
473 component: &Arc<C>,
474 ) -> Result<Self, ResolverError> {
475 let result = Self::from_absolute_url(component_url);
476 if !matches!(result, Err(ResolverError::RelativeUrlNotExpected(_))) {
477 return result;
478 }
479 let relative_url = Self::parse_relative_url(component_url)?;
480 let relative_path = Self::relative_path(&relative_url);
481 if relative_url.fragment().is_none() && relative_path.is_empty() {
482 return Err(ResolverError::malformed_url(anyhow::format_err!("{}", component_url)));
483 }
484 if relative_url.query().is_some() {
485 return Err(ResolverError::malformed_url(anyhow::format_err!(
486 "Query strings are not allowed in relative component URLs: {}",
487 component_url
488 )));
489 }
490 if relative_path.is_empty() {
491 let resolved_parent = ResolvedAncestorComponent::direct_parent_of(component).await?;
494 resolved_parent.address.clone_with_new_resource(relative_url.fragment())
495 } else {
496 let resolved_ancestor =
505 ResolvedAncestorComponent::first_packaged_ancestor_of(component).await?;
506 let scheme = resolved_ancestor.address.scheme();
507 if let Some(context) = context {
508 Self::new_relative_path(relative_path, relative_url.fragment(), scheme, context)
509 } else {
510 let context = resolved_ancestor.context_to_resolve_children.clone().ok_or_else(|| {
511 ResolverError::RelativeUrlMissingContext(format!(
512 "Relative path component URL '{}' cannot be resolved because its ancestor did not provide a resolution context. The ancestor's component address is {:?}.",
513 component_url, resolved_ancestor.address
514 ))
515 })?;
516 Self::new_relative_path(relative_path, relative_url.fragment(), scheme, context)
517 }
518 }
519 }
520
521 pub fn clone_with_new_resource(
524 &self,
525 some_resource: Option<&str>,
526 ) -> Result<Self, ResolverError> {
527 self.clone().consume_with_new_resource(some_resource)
528 }
529
530 pub fn consume_with_new_resource(
531 mut self,
532 some_resource: Option<&str>,
533 ) -> Result<Self, ResolverError> {
534 let url = match &mut self {
535 Self::Absolute { url } => url,
536 Self::RelativePath { url, .. } => url,
537 };
538 url.set_fragment(some_resource);
539 match self {
540 Self::Absolute { url } => Ok(Self::Absolute { url }),
541 Self::RelativePath { context, scheme, url } => {
542 Self::check_relative_url(&url)?;
543 Ok(Self::RelativePath { url, context, scheme })
544 }
545 }
546 }
547
548 pub fn is_absolute(&self) -> bool {
550 matches!(self, Self::Absolute { .. })
551 }
552
553 pub fn is_relative_path(&self) -> bool {
555 matches!(self, Self::RelativePath { .. })
556 }
557
558 pub fn context(&self) -> &ComponentResolutionContext {
563 if let Self::RelativePath { context, .. } = self {
564 &context
565 } else {
566 panic!("context() is only valid for `ComponentAddressKind::RelativePath");
567 }
568 }
569
570 pub fn scheme(&self) -> &str {
574 match self {
575 Self::Absolute { url } => url.scheme(),
576 Self::RelativePath { scheme, .. } => &scheme,
577 }
578 }
579
580 pub fn path(&self) -> &str {
582 match self {
583 Self::Absolute { url } => url.path(),
584 Self::RelativePath { url, .. } => Self::relative_path(&url),
585 }
586 }
587
588 pub fn query(&self) -> Option<&str> {
591 match self {
592 Self::Absolute { url } => url.query(),
593 Self::RelativePath { .. } => None,
594 }
595 }
596
597 pub fn resource(&self) -> Option<&str> {
599 match self {
600 Self::Absolute { url } => url.fragment(),
601 Self::RelativePath { url, .. } => url.fragment(),
602 }
603 }
604
605 pub fn url(&self) -> &str {
608 match self {
609 Self::Absolute { url } => url.as_str(),
610 Self::RelativePath { url, .. } => &url.as_str()[RELATIVE_URL_PREFIX.len()..],
611 }
612 }
613
614 pub fn to_url_and_context(&self) -> (&str, Option<&ComponentResolutionContext>) {
617 match self {
618 Self::Absolute { .. } => (self.url(), None),
619 Self::RelativePath { context, .. } => (self.url(), Some(context)),
620 }
621 }
622}
623
624#[derive(Debug, Error, Clone)]
626pub enum ResolverError {
627 #[error("an unexpected error occurred: {0}")]
628 Internal(#[source] ClonableError),
629 #[error("an IO error occurred: {0}")]
630 Io(#[source] ClonableError),
631 #[error("component manifest not found: {0}")]
632 ManifestNotFound(#[source] ClonableError),
633 #[error("package not found: {0}")]
634 PackageNotFound(#[source] ClonableError),
635 #[error("component manifest invalid: {0}")]
636 ManifestInvalid(#[source] ClonableError),
637 #[error("config values file invalid: {0}")]
638 ConfigValuesInvalid(#[source] ClonableError),
639 #[error("abi revision not found")]
640 AbiRevisionNotFound,
641 #[error("abi revision invalid: {0}")]
642 AbiRevisionInvalid(#[source] ClonableError),
643 #[error("failed to read config values: {0}")]
644 ConfigValuesIo(zx::Status),
645 #[error("scheme not registered")]
646 SchemeNotRegistered,
647 #[error("malformed url: {0}")]
648 MalformedUrl(#[source] ClonableError),
649 #[error("relative url requires a parent component with resolution context: {0}")]
650 NoParentContext(#[source] ClonableError),
651 #[error("package URL missing")]
652 PackageUrlMissing,
653 #[error("package directory handle missing")]
654 PackageDirectoryMissing,
655 #[error("a relative URL was not expected: {0}")]
656 RelativeUrlNotExpected(String),
657 #[error("failed to route resolver capability: {0}")]
658 RoutingError(#[source] ClonableError),
659 #[error("a context is required to resolve relative url: {0}")]
660 RelativeUrlMissingContext(String),
661 #[error("this component resolver does not resolve relative path component URLs: {0}")]
662 UnexpectedRelativePath(String),
663 #[error("the remote resolver returned invalid data")]
664 RemoteInvalidData,
665 #[error("an error occurred sending a FIDL request to the remote resolver: {0}")]
666 FidlError(#[source] ClonableError),
667}
668
669impl ResolverError {
670 pub fn as_zx_status(&self) -> zx::Status {
671 match self {
672 ResolverError::PackageNotFound(_)
673 | ResolverError::ManifestNotFound(_)
674 | ResolverError::ManifestInvalid(_)
675 | ResolverError::ConfigValuesInvalid(_)
676 | ResolverError::Io(_)
677 | ResolverError::ConfigValuesIo(_)
678 | ResolverError::AbiRevisionNotFound
679 | ResolverError::AbiRevisionInvalid(_)
680 | ResolverError::SchemeNotRegistered
681 | ResolverError::MalformedUrl(_)
682 | ResolverError::NoParentContext(_)
683 | ResolverError::RelativeUrlMissingContext(_)
684 | ResolverError::RemoteInvalidData
685 | ResolverError::PackageUrlMissing
686 | ResolverError::PackageDirectoryMissing
687 | ResolverError::UnexpectedRelativePath(_) => zx::Status::NOT_FOUND,
688
689 ResolverError::Internal(_)
690 | ResolverError::RelativeUrlNotExpected(_)
691 | ResolverError::RoutingError(_)
692 | ResolverError::FidlError(_) => zx::Status::INTERNAL,
693 }
694 }
695
696 pub fn internal(err: impl Into<Error>) -> Self {
697 Self::Internal(err.into().into())
698 }
699
700 pub fn io(err: impl Into<Error>) -> Self {
701 Self::Io(err.into().into())
702 }
703
704 pub fn manifest_not_found(err: impl Into<Error>) -> Self {
705 Self::ManifestNotFound(err.into().into())
706 }
707
708 pub fn package_not_found(err: impl Into<Error>) -> Self {
709 Self::PackageNotFound(err.into().into())
710 }
711
712 pub fn manifest_invalid(err: impl Into<Error>) -> Self {
713 Self::ManifestInvalid(err.into().into())
714 }
715
716 pub fn config_values_invalid(err: impl Into<Error>) -> Self {
717 Self::ConfigValuesInvalid(err.into().into())
718 }
719
720 pub fn abi_revision_invalid(err: impl Into<Error>) -> Self {
721 Self::AbiRevisionInvalid(err.into().into())
722 }
723
724 pub fn malformed_url(err: impl Into<Error>) -> Self {
725 Self::MalformedUrl(err.into().into())
726 }
727
728 pub fn no_parent_context(err: impl Into<Error>) -> Self {
729 Self::NoParentContext(err.into().into())
730 }
731
732 pub fn routing_error(err: impl Into<Error>) -> Self {
733 Self::RoutingError(err.into().into())
734 }
735
736 pub fn fidl_error(err: impl Into<Error>) -> Self {
737 Self::FidlError(err.into().into())
738 }
739}
740
741impl From<fresolution::ResolverError> for ResolverError {
742 fn from(err: fresolution::ResolverError) -> ResolverError {
743 match err {
744 fresolution::ResolverError::Internal => ResolverError::internal(RemoteError(err)),
745 fresolution::ResolverError::Io => ResolverError::io(RemoteError(err)),
746 fresolution::ResolverError::PackageNotFound
747 | fresolution::ResolverError::NoSpace
748 | fresolution::ResolverError::ResourceUnavailable
749 | fresolution::ResolverError::NotSupported => {
750 ResolverError::package_not_found(RemoteError(err))
751 }
752 fresolution::ResolverError::ManifestNotFound => {
753 ResolverError::manifest_not_found(RemoteError(err))
754 }
755 fresolution::ResolverError::InvalidArgs => {
756 ResolverError::malformed_url(RemoteError(err))
757 }
758 fresolution::ResolverError::InvalidManifest => {
759 ResolverError::ManifestInvalid(anyhow::Error::from(RemoteError(err)).into())
760 }
761 fresolution::ResolverError::ConfigValuesNotFound => {
762 ResolverError::ConfigValuesIo(zx::Status::NOT_FOUND)
763 }
764 fresolution::ResolverError::AbiRevisionNotFound => ResolverError::AbiRevisionNotFound,
765 fresolution::ResolverError::InvalidAbiRevision => {
766 ResolverError::abi_revision_invalid(RemoteError(err))
767 }
768 }
769 }
770}
771
772impl From<ResolverError> for fresolution::ResolverError {
773 fn from(err: ResolverError) -> fresolution::ResolverError {
774 match err {
775 ResolverError::Internal(_) => fresolution::ResolverError::Internal,
776 ResolverError::Io(_) => fresolution::ResolverError::Io,
777 ResolverError::ManifestNotFound(_) => fresolution::ResolverError::ManifestNotFound,
778 ResolverError::PackageNotFound(_) => fresolution::ResolverError::PackageNotFound,
779 ResolverError::ManifestInvalid(_) => fresolution::ResolverError::InvalidManifest,
780 ResolverError::ConfigValuesInvalid(_) => fresolution::ResolverError::InvalidManifest,
781 ResolverError::AbiRevisionNotFound => fresolution::ResolverError::AbiRevisionNotFound,
782 ResolverError::AbiRevisionInvalid(_) => fresolution::ResolverError::InvalidAbiRevision,
783 ResolverError::ConfigValuesIo(_) => fresolution::ResolverError::Io,
784 ResolverError::SchemeNotRegistered => fresolution::ResolverError::NotSupported,
785 ResolverError::MalformedUrl(_) => fresolution::ResolverError::InvalidArgs,
786 ResolverError::NoParentContext(_) => fresolution::ResolverError::Internal,
787 ResolverError::PackageUrlMissing => fresolution::ResolverError::PackageNotFound,
788 ResolverError::PackageDirectoryMissing => fresolution::ResolverError::PackageNotFound,
789 ResolverError::RelativeUrlNotExpected(_) => fresolution::ResolverError::InvalidArgs,
790 ResolverError::RoutingError(_) => fresolution::ResolverError::Internal,
791 ResolverError::RelativeUrlMissingContext(_) => fresolution::ResolverError::InvalidArgs,
792 ResolverError::UnexpectedRelativePath(_) => fresolution::ResolverError::InvalidArgs,
793 ResolverError::RemoteInvalidData => fresolution::ResolverError::InvalidManifest,
794 ResolverError::FidlError(_) => fresolution::ResolverError::Internal,
795 }
796 }
797}
798
799impl From<ComponentInstanceError> for ResolverError {
800 fn from(err: ComponentInstanceError) -> ResolverError {
801 use ComponentInstanceError::*;
802 match &err {
803 ComponentManagerInstanceUnavailable {}
804 | ComponentManagerInstanceUnexpected {}
805 | InstanceNotFound { .. }
806 | InstanceNotExecutable { .. }
807 | ResolveFailed { .. }
808 | StartFailed { .. }
809 | FailedToCreateStorage { .. } => {
810 ResolverError::Internal(ClonableError::from(anyhow::format_err!("{:?}", err)))
811 }
812 NoAbsoluteUrl { .. } => ResolverError::NoParentContext(ClonableError::from(
813 anyhow::format_err!("{:?}", err),
814 )),
815 MalformedUrl { .. } => {
816 ResolverError::MalformedUrl(ClonableError::from(anyhow::format_err!("{:?}", err)))
817 }
818 }
819 }
820}
821
822#[derive(Error, Clone, Debug)]
823#[error("remote resolver responded with {0:?}")]
824struct RemoteError(fresolution::ResolverError);
825
826#[cfg(test)]
827mod tests {
828 use super::*;
829 use crate::bedrock::sandbox_construction::ComponentSandbox;
830 use crate::component_instance::TopInstanceInterface;
831 use crate::policy::GlobalPolicyChecker;
832 use assert_matches::assert_matches;
833 use async_trait::async_trait;
834 use capability_source::{BuiltinCapabilities, NamespaceCapabilities};
835 use cm_rust_testing::new_decl_from_json;
836 use fidl::endpoints::create_endpoints;
837 use fidl_fuchsia_component_decl as fdecl;
838 use fidl_fuchsia_mem as fmem;
839 use moniker::{BorrowedChildName, Moniker};
840 use serde_json::json;
841
842 fn from_absolute_url(url: &str) -> ComponentAddress {
843 ComponentAddress::from_absolute_url(&url.parse().unwrap()).unwrap()
844 }
845
846 fn parse_relative_url(url: &str) -> Url {
847 ComponentAddress::parse_relative_url(&url.parse().unwrap()).unwrap()
848 }
849
850 #[test]
851 fn test_resolved_package() {
852 let url = "some_url".to_string();
853 let (dir_client, _) = create_endpoints::<fio::DirectoryMarker>();
854 let fidl_package = fresolution::Package {
855 url: Some(url.clone()),
856 directory: Some(dir_client),
857 ..Default::default()
858 };
859 let resolved_package = ResolvedPackage::try_from(fidl_package).unwrap();
860 assert_eq!(resolved_package.url, url);
861 }
862
863 #[test]
864 fn test_component_address() {
865 let address = from_absolute_url("some-scheme://fuchsia.com/package#meta/comp.cm");
866 assert!(address.is_absolute());
867 assert_eq!(address.scheme(), "some-scheme");
868 assert_eq!(address.path(), "/package");
869 assert_eq!(address.query(), None);
870 assert_eq!(address.resource(), Some("meta/comp.cm"));
871 assert_eq!(address.url(), "some-scheme://fuchsia.com/package#meta/comp.cm");
872 assert_matches!(
873 address.to_url_and_context(),
874 ("some-scheme://fuchsia.com/package#meta/comp.cm", None)
875 );
876
877 let abs_address = ComponentAddress::new_absolute(
878 Url::parse("some-scheme://fuchsia.com/package#meta/comp.cm").unwrap(),
879 );
880 assert_eq!(abs_address, address);
881
882 assert_eq!(abs_address, address);
883 assert!(abs_address.is_absolute());
884 assert_eq!(abs_address.scheme(), "some-scheme");
885 assert_eq!(abs_address.path(), "/package");
886 assert_eq!(abs_address.query(), None);
887 assert_eq!(abs_address.resource(), Some("meta/comp.cm"));
888 assert_eq!(abs_address.url(), "some-scheme://fuchsia.com/package#meta/comp.cm");
889 assert_matches!(
890 abs_address.to_url_and_context(),
891 ("some-scheme://fuchsia.com/package#meta/comp.cm", None)
892 );
893
894 let cloned_address = abs_address.clone();
895 assert_eq!(abs_address, cloned_address);
896
897 let address2 = abs_address.clone_with_new_resource(Some("meta/other_comp.cm")).unwrap();
898 assert_ne!(address2, abs_address);
899 assert!(address2.is_absolute());
900 assert_eq!(address2.resource(), Some("meta/other_comp.cm"));
901 assert_eq!(address2.scheme(), "some-scheme");
902 assert_eq!(address2.path(), "/package");
903 assert_eq!(address2.query(), None);
904
905 let rel_address = ComponentAddress::new_relative_path(
906 "subpackage",
907 Some("meta/subcomp.cm"),
908 "some-scheme",
909 ComponentResolutionContext::new(vec![b'4', b'5', b'6']),
910 )
911 .unwrap();
912 if let ComponentAddress::RelativePath { ref context, .. } = rel_address {
913 assert_eq!(&context.bytes, &vec![b'4', b'5', b'6']);
914 }
915 assert!(rel_address.is_relative_path());
916 assert_eq!(rel_address.path(), "subpackage");
917 assert_eq!(rel_address.query(), None);
918 assert_eq!(rel_address.resource(), Some("meta/subcomp.cm"));
919 assert_eq!(&rel_address.context().bytes, &vec![b'4', b'5', b'6']);
920 assert_eq!(rel_address.url(), "subpackage#meta/subcomp.cm");
921 assert_eq!(
922 rel_address.to_url_and_context(),
923 (
924 "subpackage#meta/subcomp.cm",
925 Some(&ComponentResolutionContext::new(vec![b'4', b'5', b'6']))
926 )
927 );
928
929 let rel_address2 =
930 rel_address.clone_with_new_resource(Some("meta/other_subcomp.cm")).unwrap();
931 assert_ne!(rel_address2, rel_address);
932 assert!(rel_address2.is_relative_path());
933 assert_eq!(rel_address2.path(), "subpackage");
934 assert_eq!(rel_address2.query(), None);
935 assert_eq!(rel_address2.resource(), Some("meta/other_subcomp.cm"));
936 assert_eq!(&rel_address2.context().bytes, &vec![b'4', b'5', b'6']);
937 assert_eq!(rel_address2.url(), "subpackage#meta/other_subcomp.cm");
938 assert_eq!(
939 rel_address2.to_url_and_context(),
940 (
941 "subpackage#meta/other_subcomp.cm",
942 Some(&ComponentResolutionContext::new(vec![b'4', b'5', b'6']))
943 )
944 );
945
946 let address = from_absolute_url("base://b");
947 assert!(address.is_absolute());
948 assert_eq!(address.scheme(), "base");
949 assert_eq!(address.path(), "");
950 assert_eq!(address.query(), None);
951 assert_eq!(address.resource(), None);
952 assert_eq!(address.url(), "base://b");
953 assert_matches!(address.to_url_and_context(), ("base://b", None));
954
955 let address = from_absolute_url("fuchsia-boot:///#meta/root.cm");
956 assert!(address.is_absolute());
957 assert_eq!(address.scheme(), "fuchsia-boot");
958 assert_eq!(address.path(), "/");
959 assert_eq!(address.query(), None);
960 assert_eq!(address.resource(), Some("meta/root.cm"));
961 assert_eq!(address.url(), "fuchsia-boot:///#meta/root.cm");
962 assert_matches!(address.to_url_and_context(), ("fuchsia-boot:///#meta/root.cm", None));
963
964 let address = from_absolute_url("custom-resolver:my:special:path#meta/root.cm");
965 assert!(address.is_absolute());
966 assert_eq!(address.scheme(), "custom-resolver");
967 assert_eq!(address.path(), "my:special:path");
968 assert_eq!(address.query(), None);
969 assert_eq!(address.resource(), Some("meta/root.cm"));
970 assert_eq!(address.url(), "custom-resolver:my:special:path#meta/root.cm");
971 assert_matches!(
972 address.to_url_and_context(),
973 ("custom-resolver:my:special:path#meta/root.cm", None)
974 );
975
976 let address = from_absolute_url("cast:00000000");
977 assert!(address.is_absolute());
978 assert_eq!(address.scheme(), "cast");
979 assert_eq!(address.path(), "00000000");
980 assert_eq!(address.query(), None);
981 assert_eq!(address.resource(), None);
982 assert_eq!(address.url(), "cast:00000000");
983 assert_matches!(address.to_url_and_context(), ("cast:00000000", None));
984
985 let address = from_absolute_url("cast:00000000#meta/root.cm");
986 assert!(address.is_absolute());
987 assert_eq!(address.scheme(), "cast");
988 assert_eq!(address.path(), "00000000");
989 assert_eq!(address.query(), None);
990 assert_eq!(address.resource(), Some("meta/root.cm"));
991 assert_eq!(address.url(), "cast:00000000#meta/root.cm");
992 assert_matches!(address.to_url_and_context(), ("cast:00000000#meta/root.cm", None));
993
994 let address =
995 from_absolute_url("fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm");
996 assert!(address.is_absolute());
997 assert_eq!(address.scheme(), "fuchsia-pkg");
998 assert_eq!(address.path(), "/package");
999 assert_eq!(address.resource(), Some("meta/comp.cm"));
1000 assert_eq!(address.query(), Some("hash=cafe0123"));
1001 assert_eq!(address.url(), "fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm");
1002 assert_matches!(
1003 address.to_url_and_context(),
1004 ("fuchsia-pkg://fuchsia.com/package?hash=cafe0123#meta/comp.cm", None)
1005 );
1006 }
1007
1008 #[test]
1009 fn test_relative_path() {
1010 let url = Url::parse("relative:///package#fragment").unwrap();
1011 assert_eq!(url.path(), "/package");
1012 assert_eq!(ComponentAddress::relative_path(&url), "package");
1013
1014 let url = Url::parse("cast:00000000#fragment").unwrap();
1015 assert_eq!(url.path(), "00000000");
1016 assert_eq!(ComponentAddress::relative_path(&url), "00000000");
1017 }
1018
1019 #[test]
1020 fn test_parse_relative_url() {
1021 let relative_prefix_with_one_less_slash = Url::parse("relative://").unwrap();
1022 assert_eq!(relative_prefix_with_one_less_slash.scheme(), "relative");
1023 assert_eq!(relative_prefix_with_one_less_slash.host(), None);
1024 assert_eq!(relative_prefix_with_one_less_slash.path(), "");
1025
1026 assert_eq!(RELATIVE_URL_BASE.scheme(), "relative");
1027 assert_eq!(RELATIVE_URL_BASE.host(), None);
1028 assert_eq!(RELATIVE_URL_BASE.path(), "/");
1029
1030 let mut clone_relative_base = RELATIVE_URL_BASE.clone();
1031 assert_eq!(clone_relative_base.path(), "/");
1032 clone_relative_base.set_path("");
1033 assert_eq!(clone_relative_base.path(), "");
1034
1035 let mut clone_relative_base = RELATIVE_URL_BASE.clone();
1036 assert_eq!(clone_relative_base.path(), "/");
1037 clone_relative_base.set_path("some_path_no_initial_slash");
1038 assert_eq!(clone_relative_base.path(), "/some_path_no_initial_slash");
1039
1040 let clone_relative_base = RELATIVE_URL_BASE.clone();
1041 let joined = clone_relative_base.join("some_path_no_initial_slash").unwrap();
1042 assert_eq!(joined.path(), "/some_path_no_initial_slash");
1043
1044 let clone_relative_base = relative_prefix_with_one_less_slash.clone();
1045 let joined = clone_relative_base.join("some_path_no_initial_slash").unwrap();
1046 assert_eq!(joined.path(), "/some_path_no_initial_slash");
1048
1049 let relative_url = parse_relative_url("subpackage#meta/subcomp.cm");
1050 assert_eq!(relative_url.path(), "/subpackage");
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("/subpackage#meta/subcomp.cm");
1055 assert_eq!(relative_url.path(), "/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("//subpackage#meta/subcomp.cm");
1060 assert_eq!(relative_url.path(), "");
1061 assert_eq!(relative_url.host_str(), Some("subpackage"));
1062 assert_eq!(relative_url.query(), None);
1063 assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1064
1065 let relative_url = parse_relative_url("///subpackage#meta/subcomp.cm");
1066 assert_eq!(relative_url.path(), "/subpackage");
1067 assert_eq!(relative_url.host_str(), None);
1068 assert_eq!(relative_url.query(), None);
1069 assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1070
1071 let relative_url = parse_relative_url("fuchsia.com/subpackage#meta/subcomp.cm");
1072 assert_eq!(relative_url.path(), "/fuchsia.com/subpackage");
1073 assert_eq!(relative_url.query(), None);
1074 assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1075
1076 let relative_url = parse_relative_url("//fuchsia.com/subpackage#meta/subcomp.cm");
1077 assert_eq!(relative_url.path(), "/subpackage");
1078 assert_eq!(relative_url.host_str(), Some("fuchsia.com"));
1079 assert_eq!(relative_url.query(), None);
1080 assert_eq!(relative_url.fragment(), Some("meta/subcomp.cm"));
1081
1082 assert_matches!(
1083 ComponentAddress::parse_relative_url(
1084 &"fuchsia-pkg://fuchsia.com/subpackage#meta/subcomp.cm".parse().unwrap()
1085 ),
1086 Err(ResolverError::MalformedUrl(..))
1087 );
1088
1089 let relative_url = parse_relative_url("#meta/peercomp.cm");
1090 assert_eq!(relative_url.path(), "/");
1091 assert_eq!(relative_url.query(), None);
1092 assert_eq!(relative_url.fragment(), Some("meta/peercomp.cm"));
1093
1094 let address = from_absolute_url("some-scheme://fuchsia.com/package#meta/comp.cm")
1095 .clone_with_new_resource(relative_url.fragment())
1096 .unwrap();
1097
1098 assert!(address.is_absolute());
1099 assert_eq!(address.scheme(), "some-scheme");
1100 assert_eq!(address.path(), "/package");
1101 assert_eq!(address.query(), None);
1102 assert_eq!(address.resource(), Some("meta/peercomp.cm"));
1103 assert_eq!(address.url(), "some-scheme://fuchsia.com/package#meta/peercomp.cm");
1104
1105 let address = from_absolute_url("cast:00000000")
1106 .clone_with_new_resource(relative_url.fragment())
1107 .unwrap();
1108
1109 assert!(address.is_absolute());
1110 assert_eq!(address.scheme(), "cast");
1111 assert_eq!(address.path(), "00000000");
1112 assert_eq!(address.query(), None);
1113 assert_eq!(address.resource(), Some("meta/peercomp.cm"));
1114 assert_eq!(address.url(), "cast:00000000#meta/peercomp.cm");
1115 }
1116
1117 static COMPONENT_DECL: LazyLock<cm_rust::ComponentDecl> = LazyLock::new(|| {
1118 new_decl_from_json(json!(
1119 {
1120 "include": [ "syslog/client.shard.cml" ],
1121 "program": {
1122 "runner": "elf",
1123 "binary": "bin/example",
1124 },
1125 "children": [
1126 {
1127 "name": "logger",
1128 "url": "fuchsia-pkg://fuchsia.com/logger/stable#meta/logger.cm",
1129 "environment": "#env_one",
1130 },
1131 ],
1132 "collections": [
1133 {
1134 "name": "modular",
1135 "durability": "transient",
1136 },
1137 ],
1138 "capabilities": [
1139 {
1140 "protocol": "fuchsia.logger.Log2",
1141 "path": "/svc/fuchsia.logger.Log2",
1142 },
1143 ],
1144 "use": [
1145 {
1146 "protocol": "fuchsia.fonts.LegacyProvider",
1147 },
1148 ],
1149 "environments": [
1150 {
1151 "name": "env_one",
1152 "extends": "none",
1153 "__stop_timeout_ms": 1337,
1154 },
1155 ],
1156 "facets": {
1157 "author": "Fuchsia",
1158 }}))
1159 .expect("failed to construct manifest")
1160 });
1161
1162 #[fuchsia::test]
1163 fn test_read_and_validate_manifest() {
1164 let manifest = fmem::Data::Bytes(
1165 fidl::persist(&COMPONENT_DECL.clone().native_into_fidl())
1166 .expect("failed to encode manifest"),
1167 );
1168 let actual = read_and_validate_manifest(&manifest, &mut DirectedGraph::new())
1169 .expect("failed to decode manifest");
1170 assert_eq!(actual, COMPONENT_DECL.clone());
1171 }
1172
1173 #[fuchsia::test]
1174 async fn test_read_and_validate_config_values() {
1175 let fidl_config_values = fdecl::ConfigValuesData {
1176 values: Some(vec![
1177 fdecl::ConfigValueSpec {
1178 value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Bool(false))),
1179 ..Default::default()
1180 },
1181 fdecl::ConfigValueSpec {
1182 value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::Uint8(5))),
1183 ..Default::default()
1184 },
1185 fdecl::ConfigValueSpec {
1186 value: Some(fdecl::ConfigValue::Single(fdecl::ConfigSingleValue::String(
1187 "hello!".to_string(),
1188 ))),
1189 ..Default::default()
1190 },
1191 fdecl::ConfigValueSpec {
1192 value: Some(fdecl::ConfigValue::Vector(fdecl::ConfigVectorValue::BoolVector(
1193 vec![true, false],
1194 ))),
1195 ..Default::default()
1196 },
1197 fdecl::ConfigValueSpec {
1198 value: Some(fdecl::ConfigValue::Vector(
1199 fdecl::ConfigVectorValue::StringVector(vec![
1200 "hello!".to_string(),
1201 "world!".to_string(),
1202 ]),
1203 )),
1204 ..Default::default()
1205 },
1206 ]),
1207 checksum: Some(fdecl::ConfigChecksum::Sha256([0; 32])),
1208 ..Default::default()
1209 };
1210 let config_values = cm_rust::ConfigValuesData {
1211 values: Box::from([
1212 cm_rust::ConfigValueSpec {
1213 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Bool(false)),
1214 },
1215 cm_rust::ConfigValueSpec {
1216 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::Uint8(5)),
1217 },
1218 cm_rust::ConfigValueSpec {
1219 value: cm_rust::ConfigValue::Single(cm_rust::ConfigSingleValue::String(
1220 "hello!".into(),
1221 )),
1222 },
1223 cm_rust::ConfigValueSpec {
1224 value: cm_rust::ConfigValue::Vector(cm_rust::ConfigVectorValue::BoolVector(
1225 Box::from([true, false]),
1226 )),
1227 },
1228 cm_rust::ConfigValueSpec {
1229 value: cm_rust::ConfigValue::Vector(cm_rust::ConfigVectorValue::StringVector(
1230 Box::from(["hello!".into(), "world!".into()]),
1231 )),
1232 },
1233 ]),
1234 checksum: cm_rust::ConfigChecksum::Sha256([0; 32]),
1235 };
1236 let data = fmem::Data::Bytes(
1237 fidl::persist(&fidl_config_values).expect("failed to encode config values"),
1238 );
1239 let actual =
1240 read_and_validate_config_values(&data).expect("failed to decode config values");
1241 assert_eq!(actual, config_values);
1242 }
1243
1244 #[derive(Debug, Default, Clone)]
1245 struct MockTopInstance {
1246 namespace_capabilities: NamespaceCapabilities,
1247 builtin_capabilities: BuiltinCapabilities,
1248 }
1249
1250 impl TopInstanceInterface for MockTopInstance {
1251 fn namespace_capabilities(&self) -> &NamespaceCapabilities {
1252 &self.namespace_capabilities
1253 }
1254 fn builtin_capabilities(&self) -> &BuiltinCapabilities {
1255 &self.builtin_capabilities
1256 }
1257 }
1258
1259 #[derive(Clone)]
1260 struct MockComponentInstance {
1261 parent: Option<Box<MockComponentInstance>>,
1262 resolved_state: Option<MockResolvedState>,
1263 moniker: Moniker,
1264 address: cm_types::Url,
1265 }
1266 #[async_trait]
1267 impl ComponentInstanceInterface for MockComponentInstance {
1268 type TopInstance = MockTopInstance;
1269 fn moniker(&self) -> &Moniker {
1270 &self.moniker
1271 }
1272 fn url(&self) -> &cm_types::Url {
1273 &self.address
1274 }
1275 fn config_parent_overrides(&self) -> Option<&[cm_rust::ConfigOverride]> {
1276 unimplemented!()
1277 }
1278 fn policy_checker(&self) -> &GlobalPolicyChecker {
1279 unimplemented!()
1280 }
1281 fn component_id_index(&self) -> &component_id_index::Index {
1282 unimplemented!()
1283 }
1284 fn try_get_parent(
1285 &self,
1286 ) -> Result<ExtendedInstanceInterface<Self>, ComponentInstanceError> {
1287 if let Some(parent) = self.parent.as_ref() {
1288 Ok(ExtendedInstanceInterface::Component(Arc::new((**parent).clone())))
1289 } else {
1290 Ok(ExtendedInstanceInterface::AboveRoot(Arc::new(MockTopInstance::default())))
1291 }
1292 }
1293 async fn component_sandbox(
1294 self: &Arc<Self>,
1295 ) -> Result<ComponentSandbox, ComponentInstanceError> {
1296 unimplemented!()
1297 }
1298 async fn get_child_maybe_resolve(
1299 self: &Arc<Self>,
1300 _moniker: &BorrowedChildName,
1301 ) -> Result<Option<Arc<Self>>, ComponentInstanceError> {
1302 unimplemented!()
1303 }
1304 async fn address_maybe_resolve(
1305 self: &Arc<Self>,
1306 ) -> Result<ComponentAddress, ResolverError> {
1307 self.resolved_state.as_ref().unwrap().address.clone()
1308 }
1309 async fn context_to_resolve_children(
1310 self: &Arc<Self>,
1311 ) -> Result<Option<ComponentResolutionContext>, ComponentInstanceError> {
1312 Ok(self.resolved_state.as_ref().unwrap().context_to_resolve_children.clone())
1313 }
1314 }
1315 impl MockComponentInstance {
1316 async fn component_address(self: &Arc<Self>) -> Result<ComponentAddress, ResolverError> {
1317 ComponentAddress::from_url(&self.address, self).await
1318 }
1319 }
1320
1321 #[derive(Clone)]
1322 struct MockResolvedState {
1323 address: Result<ComponentAddress, ResolverError>,
1324 context_to_resolve_children: Option<ComponentResolutionContext>,
1325 }
1326
1327 #[fuchsia::test]
1328 async fn test_from_absolute_component_url_with_component_instance() -> Result<(), Error> {
1329 let url_str = "fuchsia-pkg://fuchsia.com/package#meta/comp.cm";
1330 let root = Arc::new(MockComponentInstance {
1331 parent: None,
1332 resolved_state: Some(MockResolvedState {
1333 address: Ok(ComponentAddress::new_absolute(Url::parse(url_str).unwrap())),
1334 context_to_resolve_children: None,
1335 }),
1336 moniker: Moniker::root(),
1337 address: cm_types::Url::new(url_str).unwrap(),
1338 });
1339
1340 let abs = root.component_address().await.unwrap();
1341 assert_matches!(abs, ComponentAddress::Absolute { .. });
1342 assert_eq!(abs.scheme(), "fuchsia-pkg");
1343 assert_eq!(abs.path(), "/package");
1344 assert_eq!(abs.resource(), Some("meta/comp.cm"));
1345 Ok(())
1346 }
1347
1348 #[fuchsia::test]
1349 async fn test_from_relative_path_component_url_with_component_instance() -> Result<(), Error> {
1350 let root_url_str = "fuchsia-pkg://fuchsia.com/package#meta/comp.cm";
1351 let root = MockComponentInstance {
1352 parent: None,
1353 resolved_state: Some(MockResolvedState {
1354 address: Ok(ComponentAddress::new_absolute(Url::parse(root_url_str).unwrap())),
1355 context_to_resolve_children: Some(ComponentResolutionContext::new(
1356 "package_context".as_bytes().to_vec(),
1357 )),
1358 }),
1359 moniker: Moniker::root(),
1360 address: cm_types::Url::new(root_url_str).unwrap(),
1361 };
1362 let child = Arc::new(MockComponentInstance {
1363 parent: Some(Box::new(root)),
1364 resolved_state: None,
1365 moniker: "/child".try_into().unwrap(),
1366 address: cm_types::Url::new("subpackage#meta/subcomp.cm").unwrap(),
1367 });
1368
1369 let relpath = child.component_address().await.unwrap();
1370 assert_matches!(relpath, ComponentAddress::RelativePath { .. });
1371 assert_eq!(relpath.path(), "subpackage");
1372 assert_eq!(relpath.resource(), Some("meta/subcomp.cm"));
1373 assert_eq!(
1374 relpath.context(),
1375 &ComponentResolutionContext::new("package_context".as_bytes().to_vec())
1376 );
1377
1378 Ok(())
1379 }
1380
1381 #[fuchsia::test]
1382 async fn test_from_relative_path_component_url_with_cast_component_instance()
1383 -> Result<(), Error> {
1384 let root_url_str = "cast:00000000/package#meta/comp.cm";
1385 let root = MockComponentInstance {
1386 parent: None,
1387 resolved_state: Some(MockResolvedState {
1388 address: Ok(ComponentAddress::new_absolute(Url::parse(root_url_str).unwrap())),
1389 context_to_resolve_children: Some(ComponentResolutionContext::new(
1390 "package_context".as_bytes().to_vec(),
1391 )),
1392 }),
1393 moniker: Moniker::root(),
1394 address: cm_types::Url::new(root_url_str).unwrap(),
1395 };
1396 let child = Arc::new(MockComponentInstance {
1397 parent: Some(Box::new(root)),
1398 resolved_state: None,
1399 moniker: "/child".try_into().unwrap(),
1400 address: cm_types::Url::new("subpackage#meta/subcomp.cm").unwrap(),
1401 });
1402
1403 let relpath = child.component_address().await.unwrap();
1404 assert_matches!(relpath, ComponentAddress::RelativePath { .. });
1405 assert_eq!(relpath.path(), "subpackage");
1406 assert_eq!(relpath.resource(), Some("meta/subcomp.cm"));
1407 assert_eq!(
1408 relpath.context(),
1409 &ComponentResolutionContext::new("package_context".as_bytes().to_vec())
1410 );
1411
1412 Ok(())
1413 }
1414}