1use crate::capability::get_all_route_segments;
6use anyhow::Result;
7use cm_rust::{ExposeDeclCommon, OfferDeclCommon, SourceName, UseDeclCommon};
8use flex_fuchsia_sys2 as fsys;
9use moniker::Moniker;
10
11#[cfg(feature = "serde")]
12use {
13 schemars::JsonSchema,
14 serde::{Deserialize, Serialize},
15};
16
17#[cfg_attr(
18 feature = "serde",
19 derive(Serialize, Deserialize, JsonSchema),
20 serde(tag = "type", rename_all = "snake_case")
21)]
22#[derive(Debug, PartialEq, Eq, Clone)]
23pub enum RouteSegment {
24 UseBy {
26 moniker: Moniker,
28 capability: String,
30 source: String,
32 },
33
34 OfferBy {
36 moniker: Moniker,
38 capability: String,
40 source: String,
42 target: String,
44 },
45
46 ExposeBy {
48 moniker: Moniker,
50 capability: String,
52 source: String,
54 target: String,
56 },
57
58 DeclareBy {
60 moniker: Moniker,
62 capability: String,
64 },
65}
66
67impl From<crate::capability::RouteSegment> for RouteSegment {
68 fn from(segment: crate::capability::RouteSegment) -> Self {
69 match segment {
70 crate::capability::RouteSegment::UseBy { moniker, capability } => Self::UseBy {
71 moniker,
72 capability: capability.source_name().to_string(),
73 source: capability.source().to_string(),
74 },
75 crate::capability::RouteSegment::OfferBy { moniker, capability } => Self::OfferBy {
76 moniker,
77 capability: capability.source_name().to_string(),
78 source: capability.source().to_string(),
79 target: capability.target().to_string(),
80 },
81 crate::capability::RouteSegment::ExposeBy { moniker, capability } => Self::ExposeBy {
82 moniker,
83 capability: capability.source_name().to_string(),
84 source: capability.source().to_string(),
85 target: capability.target().to_string(),
86 },
87 crate::capability::RouteSegment::DeclareBy { moniker, capability } => {
88 Self::DeclareBy { moniker, capability: capability.name().to_string() }
89 }
90 }
91 }
92}
93
94impl std::fmt::Display for RouteSegment {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 match self {
97 Self::UseBy { moniker, capability, source } => {
98 write!(f, "`{moniker}` used `{capability}` from {source}")
99 }
100 Self::OfferBy { moniker, capability, source, target } => {
101 write!(f, "`{moniker}` offered `{capability}` from {source} to {target}")
102 }
103 Self::ExposeBy { moniker, capability, source, target } => {
104 write!(f, "`{moniker}` exposed `{capability}` from {source} to {target}")
105 }
106 Self::DeclareBy { moniker, capability } => {
107 write!(f, "`{moniker}` declared capability `{capability}`")
108 }
109 }
110 }
111}
112
113pub async fn capability_cmd_serialized(
114 query: String,
115 realm_query: fsys::RealmQueryProxy,
116) -> Result<Vec<RouteSegment>> {
117 let segments = get_all_route_segments(query, &realm_query).await?;
118 Ok(segments.into_iter().map(Into::into).collect())
119}
120
121pub async fn capability_cmd_print<W: std::io::Write>(
122 query: String,
123 realm_query: fsys::RealmQueryProxy,
124 mut writer: W,
125) -> Result<()> {
126 let segments = capability_cmd_serialized(query, realm_query).await?;
127
128 let mut decls = vec![];
129 let mut exposes = vec![];
130 let mut offers = vec![];
131 let mut uses = vec![];
132
133 for s in segments {
134 match &s {
135 RouteSegment::DeclareBy { .. } => decls.push(s),
136 RouteSegment::ExposeBy { .. } => exposes.push(s),
137 RouteSegment::OfferBy { .. } => offers.push(s),
138 RouteSegment::UseBy { .. } => uses.push(s),
139 }
140 }
141
142 if decls.is_empty() {
143 writeln!(writer, "Declarations: None")?;
144 } else {
145 writeln!(writer, "Declarations:")?;
146 for decl in decls {
147 writeln!(writer, " {}", decl)?;
148 }
149 }
150
151 writeln!(writer, "")?;
152
153 if exposes.is_empty() {
154 writeln!(writer, "Exposes: None")?;
155 } else {
156 writeln!(writer, "Exposes:")?;
157 for decl in exposes {
158 writeln!(writer, " {}", decl)?;
159 }
160 }
161
162 writeln!(writer, "")?;
163
164 if offers.is_empty() {
165 writeln!(writer, "Offers: None")?;
166 } else {
167 writeln!(writer, "Offers:")?;
168 for decl in offers {
169 writeln!(writer, " {}", decl)?;
170 }
171 }
172
173 writeln!(writer, "")?;
174
175 if uses.is_empty() {
176 writeln!(writer, "Uses: None")?;
177 } else {
178 writeln!(writer, "Uses:")?;
179 for decl in uses {
180 writeln!(writer, " {}", decl)?;
181 }
182 }
183
184 Ok(())
185}
186
187pub async fn capability_cmd<W: std::io::Write>(
188 query: String,
189 realm_query: fsys::RealmQueryProxy,
190 writer: W,
191) -> Result<()> {
192 capability_cmd_print(query, realm_query, writer).await
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198 use cm_rust::{
199 CapabilityDecl, ExposeDecl, ExposeSource, ExposeTarget, OfferDecl, OfferSource,
200 ProtocolDecl, UseDecl, UseSource,
201 };
202 use cm_rust_testing::{ExposeBuilder, OfferBuilder, UseBuilder};
203 use cm_types::{DeliveryType, Name};
204
205 #[test]
206 fn test_route_segment_conversion_and_display() {
207 let moniker = Moniker::parse_str("foo/bar").unwrap();
208
209 let use_decl: UseDecl =
210 UseBuilder::protocol().name("fuchsia.foo.Bar").source(UseSource::Parent).build();
211 let seg: RouteSegment = crate::capability::RouteSegment::UseBy {
212 moniker: moniker.clone(),
213 capability: use_decl,
214 }
215 .into();
216 assert_eq!(
217 seg,
218 RouteSegment::UseBy {
219 moniker: moniker.clone(),
220 capability: "fuchsia.foo.Bar".to_string(),
221 source: "parent".to_string(),
222 }
223 );
224 assert_eq!(format!("{seg}"), "`foo/bar` used `fuchsia.foo.Bar` from parent");
225
226 let offer_decl: OfferDecl = OfferBuilder::protocol()
227 .name("fuchsia.foo.Bar")
228 .source(OfferSource::Self_)
229 .target_static_child("child")
230 .build();
231 let seg: RouteSegment = crate::capability::RouteSegment::OfferBy {
232 moniker: moniker.clone(),
233 capability: offer_decl,
234 }
235 .into();
236 assert_eq!(
237 seg,
238 RouteSegment::OfferBy {
239 moniker: moniker.clone(),
240 capability: "fuchsia.foo.Bar".to_string(),
241 source: "self".to_string(),
242 target: "child `#child`".to_string(),
243 }
244 );
245 assert_eq!(
246 format!("{seg}"),
247 "`foo/bar` offered `fuchsia.foo.Bar` from self to child `#child`"
248 );
249
250 let expose_decl: ExposeDecl = ExposeBuilder::protocol()
251 .name("fuchsia.foo.Bar")
252 .source(ExposeSource::Self_)
253 .target(ExposeTarget::Parent)
254 .build();
255 let seg: RouteSegment = crate::capability::RouteSegment::ExposeBy {
256 moniker: moniker.clone(),
257 capability: expose_decl,
258 }
259 .into();
260 assert_eq!(
261 seg,
262 RouteSegment::ExposeBy {
263 moniker: moniker.clone(),
264 capability: "fuchsia.foo.Bar".to_string(),
265 source: "self".to_string(),
266 target: "parent".to_string(),
267 }
268 );
269 assert_eq!(format!("{seg}"), "`foo/bar` exposed `fuchsia.foo.Bar` from self to parent");
270
271 let cap_decl = CapabilityDecl::Protocol(ProtocolDecl {
272 name: Name::new("fuchsia.foo.Bar").unwrap(),
273 source_path: None,
274 delivery: DeliveryType::Immediate,
275 });
276 let seg: RouteSegment = crate::capability::RouteSegment::DeclareBy {
277 moniker: moniker.clone(),
278 capability: cap_decl,
279 }
280 .into();
281 assert_eq!(
282 seg,
283 RouteSegment::DeclareBy {
284 moniker: moniker.clone(),
285 capability: "fuchsia.foo.Bar".to_string(),
286 }
287 );
288 assert_eq!(format!("{seg}"), "`foo/bar` declared capability `fuchsia.foo.Bar`");
289 }
290
291 #[cfg(feature = "serde")]
292 #[test]
293 fn test_route_segment_serde() {
294 let moniker = Moniker::parse_str("foo/bar").unwrap();
295 let seg = RouteSegment::UseBy {
296 moniker,
297 capability: "fuchsia.foo.Bar".to_string(),
298 source: "parent".to_string(),
299 };
300 let serialized = serde_json::to_string(&seg).unwrap();
301 assert_eq!(
302 serialized,
303 r#"{"type":"use_by","moniker":"foo/bar","capability":"fuchsia.foo.Bar","source":"parent"}"#
304 );
305 let deserialized: RouteSegment = serde_json::from_str(&serialized).unwrap();
306 assert_eq!(seg, deserialized);
307 }
308}