use crate::realm::{
get_all_instances, get_resolved_declaration, GetAllInstancesError, GetDeclarationError,
};
use cm_rust::{
CapabilityDecl, ComponentDecl, ExposeDecl, ExposeDeclCommon, OfferDecl, OfferDeclCommon,
SourceName, UseDecl, UseDeclCommon,
};
use fidl_fuchsia_sys2 as fsys;
use moniker::Moniker;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum FindInstancesError {
#[error("failed to get all instances: {0}")]
GetAllInstancesError(#[from] GetAllInstancesError),
#[error("failed to get manifest for {moniker}: {err}")]
GetDeclarationError {
moniker: Moniker,
#[source]
err: GetDeclarationError,
},
}
pub enum RouteSegment {
UseBy { moniker: Moniker, capability: UseDecl },
OfferBy { moniker: Moniker, capability: OfferDecl },
ExposeBy { moniker: Moniker, capability: ExposeDecl },
DeclareBy { moniker: Moniker, capability: CapabilityDecl },
}
impl std::fmt::Display for RouteSegment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UseBy { moniker, capability } => {
write!(
f,
"`{}` used `{}` from {}",
moniker,
capability.source_name(),
capability.source()
)
}
Self::OfferBy { moniker, capability } => {
write!(
f,
"`{}` offered `{}` from {} to {}",
moniker,
capability.source_name(),
capability.source(),
capability.target()
)
}
Self::ExposeBy { moniker, capability } => {
write!(
f,
"`{}` exposed `{}` from {} to {}",
moniker,
capability.source_name(),
capability.source(),
capability.target()
)
}
Self::DeclareBy { moniker, capability } => {
write!(f, "`{}` declared capability `{}`", moniker, capability.name())
}
}
}
}
pub async fn get_all_route_segments(
query: String,
realm_query: &fsys::RealmQueryProxy,
) -> Result<Vec<RouteSegment>, FindInstancesError> {
let instances = get_all_instances(realm_query).await?;
let mut segments = vec![];
for instance in instances {
match get_resolved_declaration(&instance.moniker, realm_query).await {
Ok(decl) => {
let mut component_segments = get_segments(&instance.moniker, decl, &query);
segments.append(&mut component_segments)
}
Err(
GetDeclarationError::InstanceNotResolved(_)
| GetDeclarationError::InstanceNotFound(_),
) => continue,
Err(err) => {
return Err(FindInstancesError::GetDeclarationError {
moniker: instance.moniker.clone(),
err,
})
}
}
}
Ok(segments)
}
fn get_segments(moniker: &Moniker, manifest: ComponentDecl, query: &str) -> Vec<RouteSegment> {
let mut segments = vec![];
for capability in manifest.capabilities {
if capability.name().to_string().contains(query) {
segments.push(RouteSegment::DeclareBy { moniker: moniker.clone(), capability });
}
}
for expose in manifest.exposes {
if expose.source_name().to_string().contains(query) {
segments.push(RouteSegment::ExposeBy { moniker: moniker.clone(), capability: expose });
}
}
for use_ in manifest.uses {
if use_.source_name().to_string().contains(query) {
segments.push(RouteSegment::UseBy { moniker: moniker.clone(), capability: use_ });
}
}
for offer in manifest.offers {
if offer.source_name().to_string().contains(query) {
segments.push(RouteSegment::OfferBy { moniker: moniker.clone(), capability: offer });
}
}
segments
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
use cm_rust::*;
use cm_rust_testing::*;
use std::collections::HashMap;
fn create_realm_query() -> fsys::RealmQueryProxy {
serve_realm_query(
vec![fsys::Instance {
moniker: Some("./my_foo".to_string()),
url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
instance_id: None,
resolved_info: Some(fsys::ResolvedInfo {
resolved_url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
execution_info: None,
..Default::default()
}),
..Default::default()
}],
HashMap::from([(
"./my_foo".to_string(),
ComponentDeclBuilder::new()
.child(
ChildBuilder::new()
.name("my_bar")
.url("fuchsia-pkg://fuchsia.com/bar#meta/bar.cm"),
)
.protocol_default("fuchsia.foo.bar")
.use_(UseBuilder::protocol().name("fuchsia.foo.bar"))
.expose(
ExposeBuilder::protocol()
.name("fuchsia.foo.bar")
.source(ExposeSource::Self_),
)
.offer(
OfferBuilder::protocol()
.name("fuchsia.foo.bar")
.source(OfferSource::Self_)
.target_static_child("my_bar"),
)
.build()
.native_into_fidl(),
)]),
HashMap::new(),
HashMap::new(),
)
}
#[fuchsia::test]
async fn segments() {
let realm_query = create_realm_query();
let segments =
get_all_route_segments("fuchsia.foo.bar".to_string(), &realm_query).await.unwrap();
assert_eq!(segments.len(), 4);
let mut found_use = false;
let mut found_offer = false;
let mut found_expose = false;
let mut found_declaration = false;
for segment in segments {
match segment {
RouteSegment::UseBy { moniker, capability } => {
found_use = true;
assert_eq!(moniker, "/my_foo".try_into().unwrap());
assert_eq!(
capability,
UseDecl::Protocol(UseProtocolDecl {
source: UseSource::Parent,
source_name: "fuchsia.foo.bar".parse().unwrap(),
source_dictionary: Default::default(),
target_path: "/svc/fuchsia.foo.bar".parse().unwrap(),
dependency_type: DependencyType::Strong,
availability: Availability::Required
})
);
}
RouteSegment::OfferBy { moniker, capability } => {
found_offer = true;
assert_eq!(moniker, "/my_foo".try_into().unwrap());
assert_eq!(
capability,
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferSource::Self_,
source_name: "fuchsia.foo.bar".parse().unwrap(),
source_dictionary: Default::default(),
target: OfferTarget::Child(ChildRef {
name: "my_bar".parse().unwrap(),
collection: None,
}),
target_name: "fuchsia.foo.bar".parse().unwrap(),
dependency_type: DependencyType::Strong,
availability: Availability::Required
})
);
}
RouteSegment::ExposeBy { moniker, capability } => {
found_expose = true;
assert_eq!(moniker, "/my_foo".try_into().unwrap());
assert_eq!(
capability,
ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_name: "fuchsia.foo.bar".parse().unwrap(),
source_dictionary: Default::default(),
target: ExposeTarget::Parent,
target_name: "fuchsia.foo.bar".parse().unwrap(),
availability: Availability::Required
})
);
}
RouteSegment::DeclareBy { moniker, capability } => {
found_declaration = true;
assert_eq!(moniker, "/my_foo".try_into().unwrap());
assert_eq!(
capability,
CapabilityDecl::Protocol(ProtocolDecl {
name: "fuchsia.foo.bar".parse().unwrap(),
source_path: Some("/svc/fuchsia.foo.bar".parse().unwrap()),
delivery: Default::default(),
})
);
}
}
}
assert!(found_use);
assert!(found_expose);
assert!(found_offer);
assert!(found_declaration);
}
}