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