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