1use crate::realm::{
6 GetAllInstancesError, GetDeclarationError, get_all_instances, get_resolved_declaration,
7};
8use cm_rust::offer::{OfferDecl, OfferDeclCommon};
9use cm_rust::{
10 CapabilityDecl, ComponentDecl, ExposeDecl, ExposeDeclCommon, SourceName, UseDecl, UseDeclCommon,
11};
12use flex_fuchsia_sys2 as fsys;
13use futures::StreamExt;
14use futures::stream::FuturesUnordered;
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::offer::{OfferProtocolDecl, OfferSource, OfferTarget};
162 use cm_rust::*;
163 use cm_rust_testing::*;
164 use std::collections::HashMap;
165
166 fn create_realm_query() -> fsys::RealmQueryProxy {
167 serve_realm_query(
168 vec![fsys::Instance {
169 moniker: Some("./my_foo".to_string()),
170 url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
171 instance_id: None,
172 resolved_info: Some(fsys::ResolvedInfo {
173 resolved_url: Some("fuchsia-pkg://fuchsia.com/foo#meta/foo.cm".to_string()),
174 execution_info: None,
175 ..Default::default()
176 }),
177 ..Default::default()
178 }],
179 HashMap::from([(
180 "./my_foo".to_string(),
181 ComponentDeclBuilder::new()
182 .child(
183 ChildBuilder::new()
184 .name("my_bar")
185 .url("fuchsia-pkg://fuchsia.com/bar#meta/bar.cm"),
186 )
187 .protocol_default("fuchsia.foo.bar")
188 .use_(UseBuilder::protocol().name("fuchsia.foo.bar"))
189 .expose(
190 ExposeBuilder::protocol()
191 .name("fuchsia.foo.bar")
192 .source(ExposeSource::Self_),
193 )
194 .offer(
195 OfferBuilder::protocol()
196 .name("fuchsia.foo.bar")
197 .source(OfferSource::Self_)
198 .target_static_child("my_bar"),
199 )
200 .build()
201 .native_into_fidl(),
202 )]),
203 HashMap::new(),
204 HashMap::new(),
205 )
206 }
207
208 #[fuchsia::test]
209 async fn segments() {
210 let realm_query = create_realm_query();
211
212 let segments =
213 get_all_route_segments("fuchsia.foo.bar".to_string(), &realm_query).await.unwrap();
214
215 assert_eq!(segments.len(), 4);
216
217 let mut found_use = false;
218 let mut found_offer = false;
219 let mut found_expose = false;
220 let mut found_declaration = false;
221
222 for segment in segments {
223 match segment {
224 RouteSegment::UseBy { moniker, capability } => {
225 found_use = true;
226 assert_eq!(moniker, "/my_foo".try_into().unwrap());
227 assert_eq!(
228 capability,
229 UseDecl::Protocol(UseProtocolDecl {
230 source: UseSource::Parent,
231 source_name: "fuchsia.foo.bar".parse().unwrap(),
232 source_dictionary: Default::default(),
233 target_path: Some("/svc/fuchsia.foo.bar".parse().unwrap()),
234 dependency_type: DependencyType::Strong,
235 numbered_handle: None,
236 availability: Availability::Required
237 })
238 );
239 }
240 RouteSegment::OfferBy { moniker, capability } => {
241 found_offer = true;
242 assert_eq!(moniker, "/my_foo".try_into().unwrap());
243 assert_eq!(
244 capability,
245 OfferDecl::Protocol(OfferProtocolDecl {
246 source: OfferSource::Self_,
247 source_name: "fuchsia.foo.bar".parse().unwrap(),
248 source_dictionary: Default::default(),
249 target: OfferTarget::Child(ChildRef {
250 name: "my_bar".parse().unwrap(),
251 collection: None,
252 }),
253 target_name: "fuchsia.foo.bar".parse().unwrap(),
254 dependency_type: DependencyType::Strong,
255 availability: Availability::Required
256 })
257 );
258 }
259 RouteSegment::ExposeBy { moniker, capability } => {
260 found_expose = true;
261 assert_eq!(moniker, "/my_foo".try_into().unwrap());
262 assert_eq!(
263 capability,
264 ExposeDecl::Protocol(ExposeProtocolDecl {
265 source: ExposeSource::Self_,
266 source_name: "fuchsia.foo.bar".parse().unwrap(),
267 source_dictionary: Default::default(),
268 target: ExposeTarget::Parent,
269 target_name: "fuchsia.foo.bar".parse().unwrap(),
270 availability: Availability::Required
271 })
272 );
273 }
274 RouteSegment::DeclareBy { moniker, capability } => {
275 found_declaration = true;
276 assert_eq!(moniker, "/my_foo".try_into().unwrap());
277 assert_eq!(
278 capability,
279 CapabilityDecl::Protocol(ProtocolDecl {
280 name: "fuchsia.foo.bar".parse().unwrap(),
281 source_path: Some("/svc/fuchsia.foo.bar".parse().unwrap()),
282 delivery: Default::default(),
283 })
284 );
285 }
286 }
287 }
288
289 assert!(found_use);
290 assert!(found_expose);
291 assert!(found_offer);
292 assert!(found_declaration);
293 }
294}