blob: 8cc45cc79dc9deba4e16021f671dd33c540bad89 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Each routing method's name begins with `route_*`, and is an async function that returns
//! [Result<CapabilitySource<C>, RoutingError>], i.e. finds the capability source by walking the
//! route declarations and resolving components if necessary. Routing always walks in the direction
//! from the consuming side to the providing side.
//!
//! The most commonly used implementation is [route_from_use], which starts from a `Use`
//! declaration, walks along the chain of `Offer`s, and then the chain of `Expose`s. Similarly,
//! [route_from_registration] starts from a registration of a capability into an environment, then
//! walks the `Offer`s and `Expose`s.
//!
//! You can also start walking from halfway in this chain, e.g. [route_from_offer],
//! [route_from_expose].
use {
crate::{
capability_source::{
AggregateCapability, AggregateMember, CapabilitySource, ComponentCapability,
InternalCapability,
},
collection::{
AnonymizedAggregateServiceProvider, OfferAggregateServiceProvider,
OfferFilteredServiceProvider,
},
component_instance::{
ComponentInstanceInterface, ExtendedInstanceInterface, ResolvedInstanceInterface,
TopInstanceInterface,
},
error::RoutingError,
mapper::DebugRouteMapper,
RegistrationDecl,
},
cm_rust::{
Availability, CapabilityDecl, CapabilityTypeName, ExposeDecl, ExposeDeclCommon,
ExposeSource, ExposeTarget, OfferDecl, OfferDeclCommon, OfferServiceDecl, OfferSource,
OfferTarget, RegistrationDeclCommon, RegistrationSource, SourceName, SourcePath, UseDecl,
UseDeclCommon, UseSource,
},
cm_types::Name,
derivative::Derivative,
moniker::{ChildName, ChildNameBase, Moniker},
std::collections::HashSet,
std::{fmt, slice},
std::{marker::PhantomData, sync::Arc},
};
/// Routes a capability from its `Use` declaration to its source by following `Offer` and `Expose`
/// declarations.
///
/// `sources` defines what are the valid sources of the capability. See [`AllowedSourcesBuilder`].
/// `visitor` is invoked for each `Offer` and `Expose` declaration in the routing path, as well as
/// the final `Capability` declaration if `sources` permits.
pub async fn route_from_use<C, V>(
use_decl: UseDecl,
use_target: Arc<C>,
sources: Sources,
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<CapabilitySource<C>, RoutingError>
where
C: ComponentInstanceInterface + 'static,
V: OfferVisitor,
V: ExposeVisitor,
V: CapabilityVisitor,
V: Clone + Send + Sync + 'static,
{
let cap_type = CapabilityTypeName::from(&use_decl);
if !use_decl.source_path().dirname.is_dot() {
return Err(RoutingError::DictionariesNotSupported { cap_type });
}
match Use::route(use_decl, use_target.clone(), &sources, visitor, mapper).await? {
UseResult::Source(source) => return Ok(source),
UseResult::OfferFromParent(offer, component) => {
route_from_offer(offer, component, sources, visitor, mapper).await
}
UseResult::ExposeFromChild(use_decl, child_component) => {
let child_exposes = child_component.lock_resolved_state().await?.exposes();
let child_exposes =
find_matching_exposes(cap_type, use_decl.source_name(), &child_exposes)
.ok_or_else(|| {
let child_moniker =
child_component.child_moniker().expect("ChildName should exist");
<UseDecl as ErrorNotFoundInChild>::error_not_found_in_child(
use_target.moniker().clone(),
child_moniker.clone(),
use_decl.source_name().clone(),
)
})?;
route_from_expose(child_exposes, child_component, sources, visitor, mapper).await
}
}
}
/// Routes a capability from its environment `Registration` declaration to its source by following
/// `Offer` and `Expose` declarations.
///
/// `sources` defines what are the valid sources of the capability. See [`AllowedSourcesBuilder`].
/// `visitor` is invoked for each `Offer` and `Expose` declaration in the routing path, as well as
/// the final `Capability` declaration if `sources` permits.
pub async fn route_from_registration<R, C, V>(
registration_decl: R,
registration_target: Arc<C>,
sources: Sources,
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<CapabilitySource<C>, RoutingError>
where
R: RegistrationDeclCommon
+ ErrorNotFoundFromParent
+ ErrorNotFoundInChild
+ Into<RegistrationDecl>
+ Clone
+ 'static,
C: ComponentInstanceInterface + 'static,
V: OfferVisitor,
V: ExposeVisitor,
V: CapabilityVisitor,
V: Clone + Send + Sync + 'static,
{
match Registration::route(registration_decl, registration_target, &sources, visitor, mapper)
.await?
{
RegistrationResult::Source(source) => return Ok(source),
RegistrationResult::FromParent(offer, component) => {
route_from_offer(offer, component, sources, visitor, mapper).await
}
RegistrationResult::FromChild(expose, component) => {
route_from_expose(expose, component, sources, visitor, mapper).await
}
}
}
/// Routes a capability from its `Offer` declaration to its source by following `Offer` and `Expose`
/// declarations.
///
/// `sources` defines what are the valid sources of the capability. See [`AllowedSourcesBuilder`].
/// `visitor` is invoked for each `Offer` and `Expose` declaration in the routing path, as well as
/// the final `Capability` declaration if `sources` permits.
pub async fn route_from_offer<C, V>(
offer: RouteBundle<OfferDecl>,
offer_target: Arc<C>,
sources: Sources,
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<CapabilitySource<C>, RoutingError>
where
C: ComponentInstanceInterface + 'static,
V: OfferVisitor,
V: ExposeVisitor,
V: CapabilityVisitor,
V: Clone + Send + Sync + 'static,
{
let cap_type = CapabilityTypeName::from(offer.iter().next().unwrap());
for offer in offer.iter() {
if !offer.source_path().dirname.is_dot() {
return Err(RoutingError::DictionariesNotSupported { cap_type });
}
}
match Offer::route(offer, offer_target, &sources, visitor, mapper).await? {
OfferResult::Source(source) => return Ok(source),
OfferResult::OfferFromChild(offer, component) => {
let offer_decl: OfferDecl = offer.clone().into();
let (exposes, component) = change_directions::<C>(offer, component).await?;
let capability_source =
route_from_expose(exposes, component, sources, visitor, mapper).await?;
if let OfferDecl::Service(offer_service_decl) = offer_decl {
if offer_service_decl.source_instance_filter.is_some()
|| offer_service_decl.renamed_instances.is_some()
{
// TODO(https://fxbug.dev/42179343) support collection sources as well.
if let CapabilitySource::Component { capability, component } = capability_source
{
let source_name = offer_service_decl.source_name.clone();
let capability_provider = Box::new(OfferFilteredServiceProvider::new(
offer_service_decl,
component.clone(),
capability,
));
return Ok(CapabilitySource::<C>::FilteredAggregate {
capability: AggregateCapability::Service(source_name),
component,
capability_provider,
});
}
}
}
Ok(capability_source)
}
OfferResult::OfferFromAnonymizedAggregate(offers, aggregation_component) => {
let mut members = vec![];
for o in offers.iter() {
match o.source() {
OfferSource::Collection(n) => {
members.push(AggregateMember::Collection(n.clone()));
}
OfferSource::Child(c) => {
assert!(
c.collection.is_none(),
"Anonymized offer source contained a dynamic child"
);
members.push(AggregateMember::Child(
ChildName::try_new(c.name.clone(), None)
.expect("child source should be convertible to ChildName"),
));
}
OfferSource::Parent => {
members.push(AggregateMember::Parent);
}
OfferSource::Self_ => {
members.push(AggregateMember::Self_);
}
_ => unreachable!("impossible source"),
}
}
let first_offer = offers.iter().next().unwrap();
let capability_type = CapabilityTypeName::from(first_offer);
Ok(CapabilitySource::<C>::AnonymizedAggregate {
capability: AggregateCapability::Service(first_offer.source_name().clone()),
component: aggregation_component.as_weak(),
aggregate_capability_provider: Box::new(AnonymizedAggregateServiceProvider {
members: members.clone(),
containing_component: aggregation_component.as_weak(),
capability_name: first_offer.source_name().clone(),
capability_type,
sources: sources.clone(),
visitor: visitor.clone(),
}),
members,
})
}
OfferResult::OfferFromFilteredAggregate(offers, aggregation_component) => {
// Check that all of the service offers contain non-conflicting filter instances.
let mut seen_instances: HashSet<Name> = HashSet::new();
for o in offers.iter() {
if let OfferDecl::Service(offer_service_decl) = o.clone().into() {
match offer_service_decl.source_instance_filter {
None => {
return Err(RoutingError::unsupported_route_source(
"Aggregate offers must be of service capabilities \
with source_instance_filter set",
));
}
Some(allowed_instances) => {
for instance in allowed_instances.iter() {
if !seen_instances.insert(instance.clone()) {
return Err(RoutingError::unsupported_route_source(format!(
"Instance {} found in multiple offers \
of the same service.",
instance
)));
}
}
}
}
} else {
return Err(RoutingError::unsupported_route_source(
"Aggregate source must consist of only service capabilities",
));
}
}
// "_unused" is a placeholder value required by fold(). Since `offers` is guaranteed to
// be nonempty, it will always be overwritten by the actual source name.
let (source_name, offer_service_decls) = offers.iter().fold(
("_unused".parse().unwrap(), Vec::<OfferServiceDecl>::new()),
|(_, mut decls), o| {
if let OfferDecl::Service(offer_service_decl) = o.clone().into() {
decls.push(offer_service_decl);
}
(o.source_name().clone(), decls)
},
);
// TODO(https://fxbug.dev/42151281) Make the Collection CapabilitySource type generic
// for other types of aggregations.
Ok(CapabilitySource::<C>::FilteredAggregate {
capability: AggregateCapability::Service(source_name),
component: aggregation_component.as_weak(),
capability_provider: Box::new(OfferAggregateServiceProvider::new(
offer_service_decls,
aggregation_component.as_weak(),
sources.clone(),
visitor.clone(),
)),
})
}
}
}
/// Routes a capability from its `Expose` declaration to its source by following `Expose`
/// declarations.
///
/// `sources` defines what are the valid sources of the capability. See [`AllowedSourcesBuilder`].
/// `visitor` is invoked for each `Expose` declaration in the routing path, as well as the final
/// `Capability` declaration if `sources` permits.
pub async fn route_from_expose<C, V>(
expose: RouteBundle<ExposeDecl>,
expose_target: Arc<C>,
sources: Sources,
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<CapabilitySource<C>, RoutingError>
where
C: ComponentInstanceInterface + 'static,
V: OfferVisitor,
V: ExposeVisitor,
V: CapabilityVisitor,
V: Clone + Send + Sync + 'static,
{
let cap_type = CapabilityTypeName::from(expose.iter().next().unwrap());
for expose in expose.iter() {
if !expose.source_path().dirname.is_dot() {
return Err(RoutingError::DictionariesNotSupported { cap_type });
}
}
match Expose::route(expose, expose_target, &sources, visitor, mapper).await? {
ExposeResult::Source(source) => Ok(source),
ExposeResult::ExposeFromAnonymizedAggregate(expose, aggregation_component) => {
let mut members = vec![];
for e in expose.iter() {
match e.source() {
ExposeSource::Collection(n) => {
members.push(AggregateMember::Collection(n.clone()));
}
ExposeSource::Child(n) => {
members.push(AggregateMember::Child(
ChildName::try_new(n.clone(), None)
.expect("child source should be convertible to ChildName"),
));
}
ExposeSource::Self_ => {
members.push(AggregateMember::Self_);
}
_ => unreachable!("this was checked before"),
}
}
let first_expose = expose.iter().next().expect("empty bundle");
let capability_type = CapabilityTypeName::from(first_expose);
Ok(CapabilitySource::<C>::AnonymizedAggregate {
capability: AggregateCapability::Service(first_expose.source_name().clone()),
component: aggregation_component.as_weak(),
aggregate_capability_provider: Box::new(AnonymizedAggregateServiceProvider {
members: members.clone(),
containing_component: aggregation_component.as_weak(),
capability_name: first_expose.source_name().clone(),
capability_type,
sources: sources.clone(),
visitor: visitor.clone(),
}),
members,
})
}
}
}
/// Routes a capability from its `Use` declaration to its source by capabilities declarations, i.e.
/// whatever capabilities that this component itself provides.
///
/// `sources` defines what are the valid sources of the capability. See [`AllowedSourcesBuilder`].
/// `visitor` is invoked for each `Capability` declaration if `sources` permits.
pub async fn route_from_self<C, V>(
use_decl: UseDecl,
target: Arc<C>,
sources: Sources,
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<CapabilitySource<C>, RoutingError>
where
C: ComponentInstanceInterface + 'static,
V: CapabilityVisitor,
V: Clone + Send + Sync + 'static,
{
let cap_type = CapabilityTypeName::from(&use_decl);
if !use_decl.source_path().dirname.is_dot() {
return Err(RoutingError::DictionariesNotSupported { cap_type });
}
mapper.add_use(target.moniker().clone(), &use_decl.clone().into());
route_from_self_by_name(use_decl.source_name(), target, sources, visitor, mapper).await
}
/// Routes a capability from a capability name to its source by capabilities declarations, i.e.
/// whatever capabilities that this component itself provides.
///
/// `sources` defines what are the valid sources of the capability. See [`AllowedSourcesBuilder`].
/// `visitor` is invoked for each `Capability` declaration if `sources` permits.
pub async fn route_from_self_by_name<C, V>(
name: &Name,
target: Arc<C>,
sources: Sources,
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<CapabilitySource<C>, RoutingError>
where
C: ComponentInstanceInterface + 'static,
V: CapabilityVisitor,
V: Clone + Send + Sync + 'static,
{
let target_capabilities = target.lock_resolved_state().await?.capabilities();
Ok(CapabilitySource::<C>::Component {
capability: sources.find_component_source(
name,
target.moniker(),
&target_capabilities,
visitor,
mapper,
)?,
component: target.as_weak(),
})
}
/// Defines which capability source types are supported.
#[derive(Derivative)]
#[derivative(Clone(bound = ""))]
pub struct AllowedSourcesBuilder {
framework: Option<fn(Name) -> InternalCapability>,
builtin: bool,
capability: bool,
collection: bool,
namespace: bool,
component: bool,
capability_type: CapabilityTypeName,
}
impl AllowedSourcesBuilder {
/// Creates a new [`AllowedSourcesBuilder`] that does not allow any capability source types.
pub fn new(capability: CapabilityTypeName) -> Self {
Self {
framework: None,
builtin: false,
capability: false,
collection: false,
namespace: false,
component: false,
capability_type: capability,
}
}
/// Allows framework capability source types (`from: "framework"` in `CML`).
pub fn framework(self, builder: fn(Name) -> InternalCapability) -> Self {
Self { framework: Some(builder), ..self }
}
/// Allows capability source types that originate from other capabilities (`from: "#storage"` in
/// `CML`).
pub fn capability(self) -> Self {
Self { capability: true, ..self }
}
/// Allows capability sources to originate from a collection.
pub fn collection(self) -> Self {
Self { collection: true, ..self }
}
/// Allows namespace capability source types, which are capabilities that are installed in
/// component_manager's incoming namespace.
pub fn namespace(self) -> Self {
Self { namespace: true, ..self }
}
/// Allows component capability source types (`from: "self"` in `CML`).
pub fn component(self) -> Self {
Self { component: true, ..self }
}
/// Allows built-in capability source types (`from: "parent"` in `CML` where the parent component_instance is
/// component_manager).
pub fn builtin(self) -> Self {
Self { builtin: true, ..self }
}
pub fn build(self) -> Sources {
Sources::new(self)
}
}
#[derive(Clone)]
pub struct Sources(AllowedSourcesBuilder);
// Implementation of `Sources` that allows namespace, component, and/or built-in source
// types.
impl Sources {
pub fn new(builder: AllowedSourcesBuilder) -> Self {
Sources(builder)
}
/// Return the [`InternalCapability`] representing this framework capability source, or
/// [`RoutingError::UnsupportedRouteSource`] if unsupported.
pub fn framework_source(
&self,
name: Name,
mapper: &mut dyn DebugRouteMapper,
) -> Result<InternalCapability, RoutingError> {
let source = self
.0
.framework
.as_ref()
.map(|b| b(name.clone()))
.ok_or_else(|| RoutingError::unsupported_route_source("framework"));
mapper.add_framework_capability(name);
source
}
/// Checks whether capability sources are supported, returning [`RoutingError::UnsupportedRouteSource`]
/// if they are not.
// TODO(https://fxbug.dev/42140194): Add route mapping for capability sources.
pub fn capability_source(&self) -> Result<(), RoutingError> {
if self.0.capability {
Ok(())
} else {
Err(RoutingError::unsupported_route_source("capability"))
}
}
/// Checks whether namespace capability sources are supported.
pub fn is_namespace_supported(&self) -> bool {
self.0.namespace
}
/// Looks for a namespace capability in the list of capability sources.
/// If found, the declaration is visited by `visitor` and the declaration is wrapped
/// in a [`ComponentCapability`].
/// Returns [`RoutingError::UnsupportedRouteSource`] if namespace capabilities are unsupported.
pub fn find_namespace_source<V>(
&self,
name: &Name,
capabilities: &[CapabilityDecl],
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<Option<ComponentCapability>, RoutingError>
where
V: CapabilityVisitor,
{
if self.0.namespace {
if let Some(decl) = capabilities
.iter()
.find(|decl: &&CapabilityDecl| {
self.0.capability_type == CapabilityTypeName::from(*decl) && decl.name() == name
})
.cloned()
{
visitor.visit(&decl)?;
mapper.add_namespace_capability(&decl);
Ok(Some(decl.into()))
} else {
Ok(None)
}
} else {
Err(RoutingError::unsupported_route_source("namespace"))
}
}
/// Looks for a built-in capability in the list of capability sources.
/// If found, the capability's name is wrapped in an [`InternalCapability`].
/// Returns [`RoutingError::UnsupportedRouteSource`] if built-in capabilities are unsupported.
pub fn find_builtin_source<V>(
&self,
name: &Name,
capabilities: &[CapabilityDecl],
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<Option<InternalCapability>, RoutingError>
where
V: CapabilityVisitor,
{
if self.0.builtin {
if let Some(decl) = capabilities
.iter()
.find(|decl: &&CapabilityDecl| {
self.0.capability_type == CapabilityTypeName::from(*decl) && decl.name() == name
})
.cloned()
{
visitor.visit(&decl)?;
mapper.add_builtin_capability(&decl);
Ok(Some(decl.into()))
} else {
Ok(None)
}
} else {
Err(RoutingError::unsupported_route_source("built-in"))
}
}
/// Looks for a component capability in the list of capability sources for the component instance
/// with moniker `moniker`.
/// If found, the declaration is visited by `visitor` and the declaration is wrapped
/// in a [`ComponentCapability`].
/// Returns [`RoutingError::UnsupportedRouteSource`] if component capabilities are unsupported.
pub fn find_component_source<V>(
&self,
name: &Name,
moniker: &Moniker,
capabilities: &[CapabilityDecl],
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<ComponentCapability, RoutingError>
where
V: CapabilityVisitor,
{
if self.0.component {
let decl = capabilities
.iter()
.find(|decl: &&CapabilityDecl| {
self.0.capability_type == CapabilityTypeName::from(*decl) && decl.name() == name
})
.cloned()
.expect("CapabilityDecl missing, FIDL validation should catch this");
visitor.visit(&decl)?;
mapper.add_component_capability(moniker.clone(), &decl);
Ok(decl.into())
} else {
Err(RoutingError::unsupported_route_source("component"))
}
}
}
pub struct Use();
/// The result of routing a Use declaration to the next phase.
enum UseResult<C: ComponentInstanceInterface> {
/// The source of the Use was found (Framework, AboveRoot, etc.)
Source(CapabilitySource<C>),
/// The Use led to a parent offer.
OfferFromParent(RouteBundle<OfferDecl>, Arc<C>),
/// The Use led to a child Expose declaration.
/// Note: Instead of FromChild carrying an ExposeDecl of the matching child, it carries a
/// UseDecl. This is because some RoutingStrategy<> don't support Expose, but are still
/// required to enumerate over UseResult<>.
ExposeFromChild(UseDecl, Arc<C>),
}
impl Use {
/// Routes the capability starting from the `use_` declaration at `target` to either a valid
/// source (as defined by `sources`) or the Offer declaration that ends this phase of routing.
async fn route<C, V>(
use_: UseDecl,
target: Arc<C>,
sources: &Sources,
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<UseResult<C>, RoutingError>
where
C: ComponentInstanceInterface,
V: CapabilityVisitor,
{
mapper.add_use(target.moniker().clone(), &use_);
match use_.source() {
UseSource::Framework => Ok(UseResult::Source(CapabilitySource::<C>::Framework {
capability: sources.framework_source(use_.source_name().clone(), mapper)?,
component: target.as_weak(),
})),
UseSource::Capability(_) => {
sources.capability_source()?;
Ok(UseResult::Source(CapabilitySource::<C>::Capability {
component: target.as_weak(),
source_capability: ComponentCapability::Use(use_.into()),
}))
}
UseSource::Parent => match target.try_get_parent()? {
ExtendedInstanceInterface::<C>::AboveRoot(top_instance) => {
if sources.is_namespace_supported() {
if let Some(capability) = sources.find_namespace_source(
use_.source_name(),
top_instance.namespace_capabilities(),
visitor,
mapper,
)? {
return Ok(UseResult::Source(CapabilitySource::<C>::Namespace {
capability,
top_instance: Arc::downgrade(&top_instance),
}));
}
}
if let Some(capability) = sources.find_builtin_source(
use_.source_name(),
top_instance.builtin_capabilities(),
visitor,
mapper,
)? {
return Ok(UseResult::Source(CapabilitySource::<C>::Builtin {
capability,
top_instance: Arc::downgrade(&top_instance),
}));
}
Err(RoutingError::use_from_component_manager_not_found(
use_.source_name().to_string(),
))
}
ExtendedInstanceInterface::<C>::Component(parent_component) => {
let parent_offers = parent_component.lock_resolved_state().await?.offers();
let child_moniker = target.child_moniker().expect("ChildName should exist");
let parent_offers = find_matching_offers(
CapabilityTypeName::from(&use_),
use_.source_name(),
&child_moniker,
&parent_offers,
)
.ok_or_else(|| {
<UseDecl as ErrorNotFoundFromParent>::error_not_found_from_parent(
target.moniker().clone(),
use_.source_name().clone(),
)
})?;
Ok(UseResult::OfferFromParent(parent_offers, parent_component))
}
},
UseSource::Child(name) => {
let moniker = target.moniker();
let child_component = {
let child_moniker = ChildName::new(name.clone().into(), None);
target.lock_resolved_state().await?.get_child(&child_moniker).ok_or_else(
|| {
RoutingError::use_from_child_instance_not_found(
&child_moniker,
moniker,
name.clone(),
)
},
)?
};
Ok(UseResult::ExposeFromChild(use_, child_component))
}
UseSource::Debug => {
// This is not supported today. It might be worthwhile to support this if
// more than just protocol has a debug capability.
return Err(RoutingError::unsupported_route_source("debug capability"));
}
UseSource::Self_ => {
let use_: UseDecl = use_.into();
if let UseDecl::Config(config) = use_ {
sources.capability_source()?;
let target_capabilities = target.lock_resolved_state().await?.capabilities();
return Ok(UseResult::Source(CapabilitySource::<C>::Component {
capability: sources.find_component_source(
config.source_name(),
target.moniker(),
&target_capabilities,
visitor,
mapper,
)?,
component: target.as_weak(),
}));
}
return Err(RoutingError::unsupported_route_source("self"));
}
UseSource::Environment => {
// This is not supported today. It might be worthwhile to support this if
// capabilities other than runner can be used from environment.
return Err(RoutingError::unsupported_route_source("environment"));
}
}
}
}
/// The environment `Registration` phase of routing.
pub struct Registration<R>(PhantomData<R>);
/// The result of routing a Registration declaration to the next phase.
enum RegistrationResult<C: ComponentInstanceInterface, O: Clone + fmt::Debug> {
/// The source of the Registration was found (Framework, AboveRoot, etc.).
Source(CapabilitySource<C>),
/// The Registration led to a parent Offer declaration.
FromParent(RouteBundle<O>, Arc<C>),
/// The Registration led to a child Expose declaration.
FromChild(RouteBundle<ExposeDecl>, Arc<C>),
}
impl<R> Registration<R>
where
R: RegistrationDeclCommon
+ ErrorNotFoundFromParent
+ ErrorNotFoundInChild
+ Into<RegistrationDecl>
+ Clone,
{
/// Routes the capability starting from the `registration` declaration at `target` to either a
/// valid source (as defined by `sources`) or the Offer or Expose declaration that ends this
/// phase of routing.
async fn route<C, V>(
registration: R,
target: Arc<C>,
sources: &Sources,
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<RegistrationResult<C, OfferDecl>, RoutingError>
where
C: ComponentInstanceInterface,
V: CapabilityVisitor,
{
let registration_decl: RegistrationDecl = registration.clone().into();
mapper.add_registration(target.moniker().clone(), &registration_decl);
match registration.source() {
RegistrationSource::Self_ => {
let target_capabilities = target.lock_resolved_state().await?.capabilities();
Ok(RegistrationResult::Source(CapabilitySource::<C>::Component {
capability: sources.find_component_source(
registration.source_name(),
target.moniker(),
&target_capabilities,
visitor,
mapper,
)?,
component: target.as_weak(),
}))
}
RegistrationSource::Parent => match target.try_get_parent()? {
ExtendedInstanceInterface::<C>::AboveRoot(top_instance) => {
if sources.is_namespace_supported() {
if let Some(capability) = sources.find_namespace_source(
registration.source_name(),
top_instance.namespace_capabilities(),
visitor,
mapper,
)? {
return Ok(RegistrationResult::Source(
CapabilitySource::<C>::Namespace {
capability,
top_instance: Arc::downgrade(&top_instance),
},
));
}
}
if let Some(capability) = sources.find_builtin_source(
registration.source_name(),
top_instance.builtin_capabilities(),
visitor,
mapper,
)? {
return Ok(RegistrationResult::Source(CapabilitySource::<C>::Builtin {
capability,
top_instance: Arc::downgrade(&top_instance),
}));
}
Err(RoutingError::register_from_component_manager_not_found(
registration.source_name().to_string(),
))
}
ExtendedInstanceInterface::<C>::Component(parent_component) => {
let parent_offers = parent_component.lock_resolved_state().await?.offers();
let child_moniker = target.child_moniker().expect("ChildName should exist");
let parent_offers = find_matching_offers(
CapabilityTypeName::from(&registration_decl),
registration.source_name(),
&child_moniker,
&parent_offers,
)
.ok_or_else(|| {
<R as ErrorNotFoundFromParent>::error_not_found_from_parent(
target.moniker().clone(),
registration.source_name().clone(),
)
})?;
Ok(RegistrationResult::FromParent(parent_offers, parent_component))
}
},
RegistrationSource::Child(child) => {
let child_component = {
let child_moniker = ChildName::try_new(child, None)?;
target.lock_resolved_state().await?.get_child(&child_moniker).ok_or_else(
|| RoutingError::EnvironmentFromChildInstanceNotFound {
child_moniker,
moniker: target.moniker().clone(),
capability_name: registration.source_name().clone(),
capability_type: R::TYPE.to_string(),
},
)?
};
let child_exposes = child_component.lock_resolved_state().await?.exposes();
let capability_type = CapabilityTypeName::from(&registration.clone().into());
let child_exposes = find_matching_exposes(
capability_type,
registration.source_name(),
&child_exposes,
)
.ok_or_else(|| {
let child_moniker =
child_component.child_moniker().expect("ChildName should exist");
<R as ErrorNotFoundInChild>::error_not_found_in_child(
target.moniker().clone(),
child_moniker.clone(),
registration.source_name().clone(),
)
})?;
Ok(RegistrationResult::FromChild(child_exposes, child_component.clone()))
}
}
}
}
/// The `Offer` phase of routing.
pub struct Offer();
/// The result of routing an Offer declaration to the next phase.
enum OfferResult<C: ComponentInstanceInterface> {
/// The source of the Offer was found (Framework, AboveRoot, Component, etc.).
Source(CapabilitySource<C>),
/// The Offer led to an Offer-from-child declaration.
/// Not all capabilities can be exposed, so let the caller decide how to handle this.
OfferFromChild(OfferDecl, Arc<C>),
/// Offer from multiple static children, with filters.
OfferFromFilteredAggregate(RouteBundle<OfferDecl>, Arc<C>),
/// Offer from one or more collections and/or static children.
OfferFromAnonymizedAggregate(RouteBundle<OfferDecl>, Arc<C>),
}
enum OfferSegment<C: ComponentInstanceInterface> {
Done(OfferResult<C>),
Next(RouteBundle<OfferDecl>, Arc<C>),
}
impl Offer {
/// Routes the capability starting from the `offer` declaration at `target` to either a valid
/// source (as defined by `sources`) or the declaration that ends this phase of routing.
async fn route<C, V>(
mut offer_bundle: RouteBundle<OfferDecl>,
mut target: Arc<C>,
sources: &Sources,
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<OfferResult<C>, RoutingError>
where
C: ComponentInstanceInterface + 'static,
V: OfferVisitor,
V: CapabilityVisitor,
{
loop {
let visit_offer = match &offer_bundle {
RouteBundle::Single(offer) => Some(offer),
RouteBundle::Aggregate(offers) => {
// TODO(https://fxbug.dev/42124541): Visit routes in all aggregates.
if offers.len() == 1 {
Some(&offers[0])
} else {
None
}
}
};
if let Some(visit_offer) = visit_offer {
mapper.add_offer(target.moniker().clone(), &visit_offer);
OfferVisitor::visit(visitor, &visit_offer)?;
}
fn is_filtered_offer(o: &OfferDecl) -> bool {
if let OfferDecl::Service(offer_service) = o {
if let Some(f) = offer_service.source_instance_filter.as_ref() {
if !f.is_empty() {
return true;
}
}
if let Some(f) = offer_service.renamed_instances.as_ref() {
if !f.is_empty() {
return true;
}
}
}
false
}
// Make sure this isn't coming from a dictionary
for o in offer_bundle.iter() {
if !o.source_path().dirname.is_dot() {
return Err(RoutingError::DictionariesNotSupported {
cap_type: CapabilityTypeName::from(o),
});
}
}
match offer_bundle {
RouteBundle::Single(offer) => {
match Self::route_segment(offer, target, sources, visitor, mapper).await? {
OfferSegment::Done(r) => return Ok(r),
OfferSegment::Next(o, t) => {
offer_bundle = o;
target = t;
}
}
}
RouteBundle::Aggregate(_) => {
if offer_bundle.iter().all(|o| !is_filtered_offer(o)) {
return Ok(OfferResult::OfferFromAnonymizedAggregate(offer_bundle, target));
} else {
return Ok(OfferResult::OfferFromFilteredAggregate(offer_bundle, target));
}
}
}
}
}
async fn route_segment<C, V>(
offer: OfferDecl,
target: Arc<C>,
sources: &Sources,
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<OfferSegment<C>, RoutingError>
where
C: ComponentInstanceInterface + 'static,
V: OfferVisitor,
V: CapabilityVisitor,
{
let res = match offer.source() {
OfferSource::Void => {
OfferSegment::Done(OfferResult::Source(CapabilitySource::<C>::Void {
capability: InternalCapability::new(
(&offer).into(),
offer.source_name().clone(),
),
component: target.as_weak(),
}))
}
OfferSource::Self_ => {
let target_capabilities = target.lock_resolved_state().await?.capabilities();
let capability = sources.find_component_source(
offer.source_name(),
target.moniker(),
&target_capabilities,
visitor,
mapper,
)?;
// if offerdecl is for a filtered service return the associated filtered source.
let component = target.as_weak();
let res = match offer.into() {
OfferDecl::Service(offer_service_decl) => {
if offer_service_decl.source_instance_filter.is_some()
|| offer_service_decl.renamed_instances.is_some()
{
let source_name = offer_service_decl.source_name.clone();
let capability_provider = Box::new(OfferFilteredServiceProvider::new(
offer_service_decl,
component.clone(),
capability,
));
OfferResult::Source(CapabilitySource::<C>::FilteredAggregate {
capability: AggregateCapability::Service(source_name),
component,
capability_provider,
})
} else {
OfferResult::Source(CapabilitySource::<C>::Component {
capability,
component,
})
}
}
_ => OfferResult::Source(CapabilitySource::<C>::Component {
capability,
component,
}),
};
OfferSegment::Done(res)
}
OfferSource::Framework => {
OfferSegment::Done(OfferResult::Source(CapabilitySource::<C>::Framework {
capability: sources.framework_source(offer.source_name().clone(), mapper)?,
component: target.as_weak(),
}))
}
OfferSource::Capability(_) => {
sources.capability_source()?;
OfferSegment::Done(OfferResult::Source(CapabilitySource::<C>::Capability {
source_capability: ComponentCapability::Offer(offer.into()),
component: target.as_weak(),
}))
}
OfferSource::Parent => {
let parent_component = match target.try_get_parent()? {
ExtendedInstanceInterface::<C>::AboveRoot(top_instance) => {
if sources.is_namespace_supported() {
if let Some(capability) = sources.find_namespace_source(
offer.source_name(),
top_instance.namespace_capabilities(),
visitor,
mapper,
)? {
return Ok(OfferSegment::Done(OfferResult::Source(
CapabilitySource::<C>::Namespace {
capability,
top_instance: Arc::downgrade(&top_instance),
},
)));
}
}
if let Some(capability) = sources.find_builtin_source(
offer.source_name(),
top_instance.builtin_capabilities(),
visitor,
mapper,
)? {
return Ok(OfferSegment::Done(OfferResult::Source(
CapabilitySource::<C>::Builtin {
capability,
top_instance: Arc::downgrade(&top_instance),
},
)));
}
return Err(RoutingError::offer_from_component_manager_not_found(
offer.source_name().to_string(),
));
}
ExtendedInstanceInterface::<C>::Component(component) => component,
};
let child_moniker = target.child_moniker().expect("ChildName should exist");
let parent_offers = parent_component.lock_resolved_state().await?.offers();
let parent_offers = find_matching_offers(
CapabilityTypeName::from(&offer),
offer.source_name(),
&child_moniker,
&parent_offers,
)
.ok_or_else(|| {
<OfferDecl as ErrorNotFoundFromParent>::error_not_found_from_parent(
target.moniker().clone(),
offer.source_name().clone(),
)
})?;
OfferSegment::Next(parent_offers, parent_component)
}
OfferSource::Child(_) => OfferSegment::Done(OfferResult::OfferFromChild(offer, target)),
OfferSource::Collection(_) => OfferSegment::Done(
OfferResult::OfferFromAnonymizedAggregate(RouteBundle::from_offer(offer), target),
),
};
Ok(res)
}
}
/// Finds the matching Expose declaration for an Offer-from-child, changing the
/// direction in which the Component Tree is being navigated (from up to down).
async fn change_directions<C>(
offer: OfferDecl,
component: Arc<C>,
) -> Result<(RouteBundle<ExposeDecl>, Arc<C>), RoutingError>
where
C: ComponentInstanceInterface,
{
match offer.source() {
OfferSource::Child(child) => {
let child_component = {
let child_moniker = ChildName::try_new(
child.name.as_str(),
child.collection.as_ref().map(|s| s.as_str()),
)?;
component.lock_resolved_state().await?.get_child(&child_moniker).ok_or_else(
|| RoutingError::OfferFromChildInstanceNotFound {
child_moniker,
moniker: component.moniker().clone(),
capability_id: offer.source_name().clone().into(),
},
)?
};
let child_exposes = child_component.lock_resolved_state().await?.exposes();
let child_exposes = find_matching_exposes(
CapabilityTypeName::from(&offer),
offer.source_name(),
&child_exposes,
)
.ok_or_else(|| {
let child_moniker =
child_component.child_moniker().expect("ChildName should exist");
<OfferDecl as ErrorNotFoundInChild>::error_not_found_in_child(
component.moniker().clone(),
child_moniker.clone(),
offer.source_name().clone(),
)
})?;
Ok((child_exposes, child_component.clone()))
}
_ => panic!("change_direction called with offer that does not change direction"),
}
}
/// The `Expose` phase of routing.
#[derive(Debug)]
pub struct Expose();
/// The result of routing an Expose declaration to the next phase.
enum ExposeResult<C: ComponentInstanceInterface> {
/// The source of the Expose was found (Framework, Component, etc.).
Source(CapabilitySource<C>),
/// The source of the Expose comes from an aggregation of collections and/or static children
ExposeFromAnonymizedAggregate(RouteBundle<ExposeDecl>, Arc<C>),
}
/// A bundle of one or more routing declarations to route together, that share the same target_name
#[derive(Clone, Debug)]
pub enum RouteBundle<T>
where
T: Clone + fmt::Debug,
{
/// A single route from a unique source.
Single(T),
/// A bundle of routes representing an aggregated capability. This can be a vector of one,
/// e.g. exposing a service from a collection.
Aggregate(Vec<T>),
}
impl<T> RouteBundle<T>
where
T: Clone + fmt::Debug,
{
pub fn map<U: Clone + fmt::Debug>(self, mut f: impl FnMut(T) -> U) -> RouteBundle<U> {
match self {
RouteBundle::Single(r) => RouteBundle::Single(f(r)),
RouteBundle::Aggregate(r) => {
RouteBundle::Aggregate(r.into_iter().map(&mut f).collect())
}
}
}
/// Returns an iterator over the values of `OneOrMany<T>`.
pub fn iter(&self) -> RouteBundleIter<'_, T> {
match self {
Self::Single(item) => {
RouteBundleIter { inner_single: Some(item), inner_aggregate: None }
}
Self::Aggregate(items) => {
RouteBundleIter { inner_single: None, inner_aggregate: Some(items.iter()) }
}
}
}
/// Returns the number of routes in this bundle.
pub fn len(&self) -> usize {
match self {
Self::Single(_) => 1,
Self::Aggregate(v) => v.len(),
}
}
/// Creates a `RouteBundle` from of a single offer routing declaration.
pub fn from_offer(input: T) -> Self
where
T: OfferDeclCommon + Clone,
{
Self::from_offers(vec![input])
}
/// Creates a `RouteBundle` from of a list of offer routing declarations.
///
/// REQUIRES: `input` is nonempty.
/// REQUIRES: All elements of `input` share the same `target_name`.
pub fn from_offers(mut input: Vec<T>) -> Self
where
T: OfferDeclCommon + Clone,
{
match input.len() {
1 => {
let input = input.remove(0);
match input.source() {
OfferSource::Collection(_) => Self::Aggregate(vec![input]),
_ => Self::Single(input),
}
}
0 => panic!("empty bundles are not allowed"),
_ => Self::Aggregate(input),
}
}
/// Creates a `RouteBundle` from of a single expose routing declaration.
pub fn from_expose(input: T) -> Self
where
T: ExposeDeclCommon + Clone,
{
Self::from_exposes(vec![input])
}
/// Creates a `RouteBundle` from of a list of expose routing declarations.
///
/// REQUIRES: `input` is nonempty.
/// REQUIRES: All elements of `input` share the same `target_name`.
pub fn from_exposes(mut input: Vec<T>) -> Self
where
T: ExposeDeclCommon + Clone,
{
match input.len() {
1 => {
let input = input.remove(0);
match input.source() {
ExposeSource::Collection(_) => Self::Aggregate(vec![input]),
_ => Self::Single(input),
}
}
0 => panic!("empty bundles are not allowed"),
_ => Self::Aggregate(input),
}
}
}
impl<T> RouteBundle<T>
where
T: ExposeDeclCommon + Clone,
{
pub fn availability(&self) -> &Availability {
match self {
Self::Single(e) => e.availability(),
Self::Aggregate(v) => {
assert!(
v.iter().zip(v.iter().skip(1)).all(|(a, b)| a.availability() == b.availability()),
"CM validation should ensure all aggregated capabilities have the same availability");
v[0].availability()
}
}
}
}
/// Immutable iterator over a `RouteBundle`.
/// This `struct` is created by [`RouteBundle::iter`].
///
/// [`RouteBundle::iter`]: struct.RouteBundle.html#method.iter
pub struct RouteBundleIter<'a, T> {
inner_single: Option<&'a T>,
inner_aggregate: Option<slice::Iter<'a, T>>,
}
impl<'a, T> Iterator for RouteBundleIter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if let Some(item) = self.inner_single.take() {
Some(item)
} else if let Some(ref mut iter) = &mut self.inner_aggregate {
iter.next()
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
if let Some(_) = self.inner_single {
(1, Some(1))
} else if let Some(iter) = &self.inner_aggregate {
iter.size_hint()
} else {
(0, Some(0))
}
}
}
impl<'a, T> ExactSizeIterator for RouteBundleIter<'a, T> {}
enum ExposeSegment<C: ComponentInstanceInterface> {
Done(ExposeResult<C>),
Next(RouteBundle<ExposeDecl>, Arc<C>),
}
impl Expose {
/// Routes the capability starting from the `expose` declaration at `target` to a valid source
/// (as defined by `sources`).
async fn route<C, V>(
mut expose_bundle: RouteBundle<ExposeDecl>,
mut target: Arc<C>,
sources: &Sources,
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<ExposeResult<C>, RoutingError>
where
C: ComponentInstanceInterface,
V: ExposeVisitor,
V: CapabilityVisitor,
{
loop {
let visit_expose = match &expose_bundle {
RouteBundle::Single(expose) => Some(expose),
RouteBundle::Aggregate(exposes) => {
// TODO(https://fxbug.dev/42124541): Visit routes in all aggregates.
if exposes.len() == 1 {
Some(&exposes[0])
} else {
None
}
}
};
if let Some(visit_expose) = visit_expose {
mapper.add_expose(target.moniker().clone(), visit_expose.clone().into());
ExposeVisitor::visit(visitor, &visit_expose)?;
}
// Make sure this isn't coming from a dictionary
for e in expose_bundle.iter() {
if !e.source_path().dirname.is_dot() {
return Err(RoutingError::DictionariesNotSupported {
cap_type: CapabilityTypeName::from(e),
});
}
}
match expose_bundle {
RouteBundle::Single(expose) => {
match Self::route_segment(expose, target, sources, visitor, mapper).await? {
ExposeSegment::Done(r) => return Ok(r),
ExposeSegment::Next(e, t) => {
expose_bundle = e;
target = t;
}
}
}
RouteBundle::Aggregate(_) => {
return Ok(ExposeResult::ExposeFromAnonymizedAggregate(expose_bundle, target));
}
}
}
}
async fn route_segment<C, V>(
expose: ExposeDecl,
target: Arc<C>,
sources: &Sources,
visitor: &mut V,
mapper: &mut dyn DebugRouteMapper,
) -> Result<ExposeSegment<C>, RoutingError>
where
C: ComponentInstanceInterface,
V: ExposeVisitor,
V: CapabilityVisitor,
{
let res = match expose.source() {
ExposeSource::Void => {
ExposeSegment::Done(ExposeResult::Source(CapabilitySource::<C>::Void {
capability: InternalCapability::new(
(&expose).into(),
expose.source_name().clone(),
),
component: target.as_weak(),
}))
}
ExposeSource::Self_ => {
let target_capabilities = target.lock_resolved_state().await?.capabilities();
ExposeSegment::Done(ExposeResult::Source(CapabilitySource::<C>::Component {
capability: sources.find_component_source(
expose.source_name(),
target.moniker(),
&target_capabilities,
visitor,
mapper,
)?,
component: target.as_weak(),
}))
}
ExposeSource::Framework => {
ExposeSegment::Done(ExposeResult::Source(CapabilitySource::<C>::Framework {
capability: sources.framework_source(expose.source_name().clone(), mapper)?,
component: target.as_weak(),
}))
}
ExposeSource::Capability(_) => {
sources.capability_source()?;
ExposeSegment::Done(ExposeResult::Source(CapabilitySource::<C>::Capability {
source_capability: ComponentCapability::Expose(expose.into()),
component: target.as_weak(),
}))
}
ExposeSource::Child(child) => {
let child_component = {
let child_moniker = ChildName::new(child.clone().into(), None);
target.lock_resolved_state().await?.get_child(&child_moniker).ok_or_else(
|| RoutingError::ExposeFromChildInstanceNotFound {
child_moniker,
moniker: target.moniker().clone(),
capability_id: expose.source_name().clone().into(),
},
)?
};
let child_exposes = child_component.lock_resolved_state().await?.exposes();
let child_exposes = find_matching_exposes(
CapabilityTypeName::from(&expose),
expose.source_name(),
&child_exposes,
)
.ok_or_else(|| {
let child_moniker =
child_component.child_moniker().expect("ChildName should exist");
<ExposeDecl as ErrorNotFoundInChild>::error_not_found_in_child(
target.moniker().clone(),
child_moniker.clone(),
expose.source_name().clone(),
)
})?;
ExposeSegment::Next(child_exposes, child_component)
}
ExposeSource::Collection(_) => {
ExposeSegment::Done(ExposeResult::ExposeFromAnonymizedAggregate(
RouteBundle::from_expose(expose),
target,
))
}
};
Ok(res)
}
}
fn target_matches_moniker(target: &OfferTarget, child_moniker: &ChildName) -> bool {
match target {
OfferTarget::Child(target_ref) => {
&target_ref.name == child_moniker.name()
&& target_ref.collection.as_ref() == child_moniker.collection()
}
OfferTarget::Collection(target_collection) => {
Some(target_collection) == child_moniker.collection()
}
OfferTarget::Capability(_target_capability) => {
// TODO(https://fxbug.dev/301674053): Support dictionary targets.
false
}
}
}
/// Visitor pattern trait for visiting all [`OfferDecl`] during a route.
pub trait OfferVisitor {
fn visit(&mut self, offer: &OfferDecl) -> Result<(), RoutingError>;
}
/// Visitor pattern trait for visiting all [`ExposeDecl`] during a route.
pub trait ExposeVisitor {
/// Visit each [`ExposeDecl`] on the route.
/// Returning an `Err` cancels visitation.
fn visit(&mut self, expose: &ExposeDecl) -> Result<(), RoutingError>;
}
/// Visitor pattern trait for visiting all [`CapabilityDecl`] during a route.
pub trait CapabilityVisitor {
/// Visit each [`CapabilityDecl`] on the route.
/// Returning an `Err` cancels visitation.
fn visit(&mut self, capability: &CapabilityDecl) -> Result<(), RoutingError>;
}
pub fn find_matching_offers(
capability_type: CapabilityTypeName,
source_name: &Name,
child_moniker: &ChildName,
offers: &Vec<OfferDecl>,
) -> Option<RouteBundle<OfferDecl>> {
let offers: Vec<_> = offers
.iter()
.filter(|offer: &&OfferDecl| {
capability_type == CapabilityTypeName::from(*offer)
&& *offer.target_name() == *source_name
&& target_matches_moniker(offer.target(), &child_moniker)
})
.cloned()
.collect();
if offers.is_empty() {
return None;
}
Some(RouteBundle::from_offers(offers))
}
pub fn find_matching_exposes(
capability_type: CapabilityTypeName,
source_name: &Name,
exposes: &Vec<ExposeDecl>,
) -> Option<RouteBundle<ExposeDecl>> {
let exposes: Vec<_> = exposes
.iter()
.filter(|expose: &&ExposeDecl| {
capability_type == CapabilityTypeName::from(*expose)
&& *expose.target_name() == *source_name
&& *expose.target() == ExposeTarget::Parent
})
.cloned()
.collect();
if exposes.is_empty() {
return None;
}
Some(RouteBundle::from_exposes(exposes))
}
/// Implemented by declaration types to emit a proper error when a matching offer is not found in the parent.
pub trait ErrorNotFoundFromParent {
fn error_not_found_from_parent(
decl_site_moniker: Moniker,
capability_name: Name,
) -> RoutingError;
}
/// Implemented by declaration types to emit a proper error when a matching expose is not found in the child.
pub trait ErrorNotFoundInChild {
fn error_not_found_in_child(
decl_site_moniker: Moniker,
child_moniker: ChildName,
capability_name: Name,
) -> RoutingError;
}
#[derive(Clone)]
pub struct NoopVisitor {}
impl NoopVisitor {
pub fn new() -> NoopVisitor {
NoopVisitor {}
}
}
impl OfferVisitor for NoopVisitor {
fn visit(&mut self, _: &OfferDecl) -> Result<(), RoutingError> {
Ok(())
}
}
impl ExposeVisitor for NoopVisitor {
fn visit(&mut self, _: &ExposeDecl) -> Result<(), RoutingError> {
Ok(())
}
}
impl CapabilityVisitor for NoopVisitor {
fn visit(&mut self, _: &CapabilityDecl) -> Result<(), RoutingError> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use {
super::*, assert_matches::assert_matches, cm_rust::ExposeServiceDecl, cm_rust_testing::*,
};
#[test]
fn route_bundle_iter_single() {
let v = RouteBundle::Single(34);
let mut iter = v.iter();
assert_matches!(iter.next(), Some(&34));
assert_matches!(iter.next(), None);
}
#[test]
fn route_bundle_iter_many() {
let v = RouteBundle::Aggregate(vec![1, 2, 3]);
let mut iter = v.iter();
assert_matches!(iter.next(), Some(&1));
assert_matches!(iter.next(), Some(&2));
assert_matches!(iter.next(), Some(&3));
assert_matches!(iter.next(), None);
}
#[test]
fn route_bundle_from_offers() {
let parent_offers: Vec<_> = [1, 2, 3]
.into_iter()
.map(|i| OfferServiceDecl {
source: OfferSource::Parent,
source_name: format!("foo_source_{}", i).parse().unwrap(),
source_dictionary: Default::default(),
target: OfferTarget::Collection("coll".parse().unwrap()),
target_name: "foo_target".parse().unwrap(),
source_instance_filter: None,
renamed_instances: None,
availability: Availability::Required,
})
.collect();
let collection_offer = OfferServiceDecl {
source: OfferSource::Collection("coll".parse().unwrap()),
source_name: "foo_source".parse().unwrap(),
source_dictionary: Default::default(),
target: offer_target_static_child("target"),
target_name: "foo_target".parse().unwrap(),
source_instance_filter: None,
renamed_instances: None,
availability: Availability::Required,
};
assert_matches!(
RouteBundle::from_offer(parent_offers[0].clone()),
RouteBundle::Single(o) if o == parent_offers[0]
);
assert_matches!(
RouteBundle::from_offers(vec![parent_offers[0].clone()]),
RouteBundle::Single(o) if o == parent_offers[0]
);
assert_matches!(
RouteBundle::from_offers(parent_offers.clone()),
RouteBundle::Aggregate(v) if v == parent_offers
);
assert_matches!(
RouteBundle::from_offer(collection_offer.clone()),
RouteBundle::Aggregate(v) if v == vec![collection_offer.clone()]
);
}
#[test]
fn route_bundle_from_exposes() {
let child_exposes: Vec<_> = [1, 2, 3]
.into_iter()
.map(|i| ExposeServiceDecl {
source: ExposeSource::Child("source".parse().unwrap()),
source_name: format!("foo_source_{}", i).parse().unwrap(),
source_dictionary: Default::default(),
target: ExposeTarget::Parent,
target_name: "foo_target".parse().unwrap(),
availability: Availability::Required,
})
.collect();
let collection_expose = ExposeServiceDecl {
source: ExposeSource::Collection("coll".parse().unwrap()),
source_name: "foo_source".parse().unwrap(),
source_dictionary: Default::default(),
target: ExposeTarget::Parent,
target_name: "foo_target".parse().unwrap(),
availability: Availability::Required,
};
assert_matches!(
RouteBundle::from_expose(child_exposes[0].clone()),
RouteBundle::Single(o) if o == child_exposes[0]
);
assert_matches!(
RouteBundle::from_exposes(vec![child_exposes[0].clone()]),
RouteBundle::Single(o) if o == child_exposes[0]
);
assert_matches!(
RouteBundle::from_exposes(child_exposes.clone()),
RouteBundle::Aggregate(v) if v == child_exposes
);
assert_matches!(
RouteBundle::from_expose(collection_expose.clone()),
RouteBundle::Aggregate(v) if v == vec![collection_expose.clone()]
);
}
}