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