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