1use crate::realm::{
6 get_all_instances, get_resolved_declaration, GetAllInstancesError, GetDeclarationError,
7};
8use cm_rust::{
9 CapabilityDecl, ComponentDecl, ExposeDecl, ExposeDeclCommon, OfferDecl, OfferDeclCommon,
10 SourceName, UseDecl, UseDeclCommon,
11};
12use fidl_fuchsia_sys2 as fsys;
13use moniker::Moniker;
14use thiserror::Error;
15
16#[derive(Debug, Error)]
17pub enum FindInstancesError {
18 #[error("failed to get all instances: {0}")]
19 GetAllInstancesError(#[from] GetAllInstancesError),
20
21 #[error("failed to get manifest for {moniker}: {err}")]
22 GetDeclarationError {
23 moniker: Moniker,
24 #[source]
25 err: GetDeclarationError,
26 },
27}
28
29pub enum RouteSegment {
30 UseBy { moniker: Moniker, capability: UseDecl },
32
33 OfferBy { moniker: Moniker, capability: OfferDecl },
35
36 ExposeBy { moniker: Moniker, capability: ExposeDecl },
38
39 DeclareBy { moniker: Moniker, capability: CapabilityDecl },
41}
42
43impl std::fmt::Display for RouteSegment {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match self {
46 Self::UseBy { moniker, capability } => {
47 write!(
48 f,
49 "`{}` used `{}` from {}",
50 moniker,
51 capability.source_name(),
52 capability.source()
53 )
54 }
55 Self::OfferBy { moniker, capability } => {
56 write!(
57 f,
58 "`{}` offered `{}` from {} to {}",
59 moniker,
60 capability.source_name(),
61 capability.source(),
62 capability.target()
63 )
64 }
65 Self::ExposeBy { moniker, capability } => {
66 write!(
67 f,
68 "`{}` exposed `{}` from {} to {}",
69 moniker,
70 capability.source_name(),
71 capability.source(),
72 capability.target()
73 )
74 }
75 Self::DeclareBy { moniker, capability } => {
76 write!(f, "`{}` declared capability `{}`", moniker, capability.name())
77 }
78 }
79 }
80}
81pub async fn get_all_route_segments(
83 query: String,
84 realm_query: &fsys::RealmQueryProxy,
85) -> Result<Vec<RouteSegment>, FindInstancesError> {
86 let instances = get_all_instances(realm_query).await?;
87 let mut segments = vec![];
88
89 for instance in instances {
90 match get_resolved_declaration(&instance.moniker, realm_query).await {
91 Ok(decl) => {
92 let mut component_segments = get_segments(&instance.moniker, decl, &query);
93 segments.append(&mut component_segments)
94 }
95 Err(
100 GetDeclarationError::InstanceNotResolved(_)
101 | GetDeclarationError::InstanceNotFound(_),
102 ) => continue,
103 Err(err) => {
104 return Err(FindInstancesError::GetDeclarationError {
105 moniker: instance.moniker.clone(),
106 err,
107 })
108 }
109 }
110 }
111
112 Ok(segments)
113}
114
115fn get_segments(moniker: &Moniker, manifest: ComponentDecl, query: &str) -> Vec<RouteSegment> {
118 let mut segments = vec![];
119
120 for capability in manifest.capabilities {
121 if capability.name().to_string().contains(query) {
122 segments.push(RouteSegment::DeclareBy { moniker: moniker.clone(), capability });
123 }
124 }
125
126 for expose in manifest.exposes {
127 if expose.source_name().to_string().contains(query) {
128 segments.push(RouteSegment::ExposeBy { moniker: moniker.clone(), capability: expose });
129 }
130 }
131
132 for use_ in manifest.uses {
133 if use_.source_name().to_string().contains(query) {
134 segments.push(RouteSegment::UseBy { moniker: moniker.clone(), capability: use_ });
135 }
136 }
137
138 for offer in manifest.offers {
139 if offer.source_name().to_string().contains(query) {
140 segments.push(RouteSegment::OfferBy { moniker: moniker.clone(), capability: offer });
141 }
142 }
143
144 segments
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::test_utils::*;
151 use cm_rust::*;
152 use cm_rust_testing::*;
153 use std::collections::HashMap;
154
155 fn create_realm_query() -> fsys::RealmQueryProxy {
156 serve_realm_query(
157 vec![fsys::Instance {
158 moniker: Some("./my_foo".to_string()),
159 url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
160 instance_id: None,
161 resolved_info: Some(fsys::ResolvedInfo {
162 resolved_url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
163 execution_info: None,
164 ..Default::default()
165 }),
166 ..Default::default()
167 }],
168 HashMap::from([(
169 "./my_foo".to_string(),
170 ComponentDeclBuilder::new()
171 .child(
172 ChildBuilder::new()
173 .name("my_bar")
174 .url("fuchsia-pkg://fuchsia.com/bar#meta/bar.cm"),
175 )
176 .protocol_default("fuchsia.foo.bar")
177 .use_(UseBuilder::protocol().name("fuchsia.foo.bar"))
178 .expose(
179 ExposeBuilder::protocol()
180 .name("fuchsia.foo.bar")
181 .source(ExposeSource::Self_),
182 )
183 .offer(
184 OfferBuilder::protocol()
185 .name("fuchsia.foo.bar")
186 .source(OfferSource::Self_)
187 .target_static_child("my_bar"),
188 )
189 .build()
190 .native_into_fidl(),
191 )]),
192 HashMap::new(),
193 HashMap::new(),
194 )
195 }
196
197 #[fuchsia::test]
198 async fn segments() {
199 let realm_query = create_realm_query();
200
201 let segments =
202 get_all_route_segments("fuchsia.foo.bar".to_string(), &realm_query).await.unwrap();
203
204 assert_eq!(segments.len(), 4);
205
206 let mut found_use = false;
207 let mut found_offer = false;
208 let mut found_expose = false;
209 let mut found_declaration = false;
210
211 for segment in segments {
212 match segment {
213 RouteSegment::UseBy { moniker, capability } => {
214 found_use = true;
215 assert_eq!(moniker, "/my_foo".try_into().unwrap());
216 assert_eq!(
217 capability,
218 UseDecl::Protocol(UseProtocolDecl {
219 source: UseSource::Parent,
220 source_name: "fuchsia.foo.bar".parse().unwrap(),
221 source_dictionary: Default::default(),
222 target_path: "/svc/fuchsia.foo.bar".parse().unwrap(),
223 dependency_type: DependencyType::Strong,
224 availability: Availability::Required
225 })
226 );
227 }
228 RouteSegment::OfferBy { moniker, capability } => {
229 found_offer = true;
230 assert_eq!(moniker, "/my_foo".try_into().unwrap());
231 assert_eq!(
232 capability,
233 OfferDecl::Protocol(OfferProtocolDecl {
234 source: OfferSource::Self_,
235 source_name: "fuchsia.foo.bar".parse().unwrap(),
236 source_dictionary: Default::default(),
237 target: OfferTarget::Child(ChildRef {
238 name: "my_bar".parse().unwrap(),
239 collection: None,
240 }),
241 target_name: "fuchsia.foo.bar".parse().unwrap(),
242 dependency_type: DependencyType::Strong,
243 availability: Availability::Required
244 })
245 );
246 }
247 RouteSegment::ExposeBy { moniker, capability } => {
248 found_expose = true;
249 assert_eq!(moniker, "/my_foo".try_into().unwrap());
250 assert_eq!(
251 capability,
252 ExposeDecl::Protocol(ExposeProtocolDecl {
253 source: ExposeSource::Self_,
254 source_name: "fuchsia.foo.bar".parse().unwrap(),
255 source_dictionary: Default::default(),
256 target: ExposeTarget::Parent,
257 target_name: "fuchsia.foo.bar".parse().unwrap(),
258 availability: Availability::Required
259 })
260 );
261 }
262 RouteSegment::DeclareBy { moniker, capability } => {
263 found_declaration = true;
264 assert_eq!(moniker, "/my_foo".try_into().unwrap());
265 assert_eq!(
266 capability,
267 CapabilityDecl::Protocol(ProtocolDecl {
268 name: "fuchsia.foo.bar".parse().unwrap(),
269 source_path: Some("/svc/fuchsia.foo.bar".parse().unwrap()),
270 delivery: Default::default(),
271 })
272 );
273 }
274 }
275 }
276
277 assert!(found_use);
278 assert!(found_expose);
279 assert!(found_offer);
280 assert!(found_declaration);
281 }
282}