Skip to main content

component_debug/cli/
capability.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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    /// The capability was used by a component instance in its manifest.
25    UseBy {
26        /// The moniker of the component using the capability.
27        moniker: Moniker,
28        /// The source name of the capability being used.
29        capability: String,
30        /// The source location the capability is used from (e.g. "parent", "self", "framework").
31        source: String,
32    },
33
34    /// The capability was offered by a component instance in its manifest.
35    OfferBy {
36        /// The moniker of the component offering the capability.
37        moniker: Moniker,
38        /// The source name of the capability being offered.
39        capability: String,
40        /// The source location offering the capability (e.g. "self", "parent", "#child").
41        source: String,
42        /// The target child or collection receiving the offer (e.g. "#child").
43        target: String,
44    },
45
46    /// The capability was exposed by a component instance in its manifest.
47    ExposeBy {
48        /// The moniker of the component exposing the capability.
49        moniker: Moniker,
50        /// The source name of the capability being exposed.
51        capability: String,
52        /// The source location of the exposed capability (e.g. "self", "#child").
53        source: String,
54        /// The target destination of the exposed capability (e.g. "parent", "framework").
55        target: String,
56    },
57
58    /// The capability was declared by a component instance in its manifest.
59    DeclareBy {
60        /// The moniker of the component declaring the capability.
61        moniker: Moniker,
62        /// The name of the declared capability.
63        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}