Skip to main content

cml/types/
capability.rs

1// Copyright 2025 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::{
6    AnyRef, AsClauseContext, CanonicalizeContext, ConfigNestedValueType, ConfigType, Error,
7};
8
9use crate::one_or_many::{OneOrMany, always_one_context};
10use crate::types::common::*;
11use crate::types::right::{Rights, RightsClause};
12pub use cm_types::{
13    Availability, BorrowedName, BoundedName, DeliveryType, DependencyType, HandleType, Name,
14    OnTerminate, ParseError, Path, RelativePath, StartupMode, StorageId, Url,
15};
16use cml_macro::Reference;
17use reference_doc::ReferenceDoc;
18use serde::{Deserialize, Serialize};
19use std::num::NonZeroU32;
20
21use std::fmt;
22use std::path::PathBuf;
23use std::sync::Arc;
24
25#[derive(Deserialize, Debug, PartialEq, Clone, ReferenceDoc, Serialize, Default)]
26#[serde(deny_unknown_fields)]
27#[reference_doc(fields_as = "list")]
28pub struct Capability {
29    /// The [name](#name) for this service capability. Specifying `path` is valid
30    /// only when this value is a string.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    #[reference_doc(skip = true)]
33    pub service: Option<OneOrMany<Name>>,
34
35    /// The [name](#name) for this protocol capability. Specifying `path` is valid
36    /// only when this value is a string.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    #[reference_doc(skip = true)]
39    pub protocol: Option<OneOrMany<Name>>,
40
41    /// The [name](#name) for this directory capability.
42    #[serde(skip_serializing_if = "Option::is_none")]
43    #[reference_doc(skip = true)]
44    pub directory: Option<Name>,
45
46    /// The [name](#name) for this storage capability.
47    #[serde(skip_serializing_if = "Option::is_none")]
48    #[reference_doc(skip = true)]
49    pub storage: Option<Name>,
50
51    /// The [name](#name) for this runner capability.
52    #[serde(skip_serializing_if = "Option::is_none")]
53    #[reference_doc(skip = true)]
54    pub runner: Option<Name>,
55
56    /// The [name](#name) for this resolver capability.
57    #[serde(skip_serializing_if = "Option::is_none")]
58    #[reference_doc(skip = true)]
59    pub resolver: Option<Name>,
60
61    /// The [name](#name) for this event_stream capability.
62    #[serde(skip_serializing_if = "Option::is_none")]
63    #[reference_doc(skip = true)]
64    pub event_stream: Option<OneOrMany<Name>>,
65
66    /// The [name](#name) for this dictionary capability.
67    #[serde(skip_serializing_if = "Option::is_none")]
68    #[reference_doc(skip = true)]
69    pub dictionary: Option<Name>,
70
71    /// The [name](#name) for this configuration capability.
72    #[serde(skip_serializing_if = "Option::is_none")]
73    #[reference_doc(skip = true)]
74    pub config: Option<Name>,
75
76    /// The path within the [outgoing directory][glossary.outgoing directory] of the component's
77    /// program to source the capability.
78    ///
79    /// For `protocol` and `service`, defaults to `/svc/${protocol}`, otherwise required.
80    ///
81    /// For `protocol`, the target of the path MUST be a channel, which tends to speak
82    /// the protocol matching the name of this capability.
83    ///
84    /// For `service`, `directory`, the target of the path MUST be a directory.
85    ///
86    /// For `runner`, the target of the path MUST be a channel and MUST speak
87    /// the protocol `fuchsia.component.runner.ComponentRunner`.
88    ///
89    /// For `resolver`, the target of the path MUST be a channel and MUST speak
90    /// the protocol `fuchsia.component.resolution.Resolver`.
91    ///
92    /// For `dictionary`, this is optional. If provided, it is a path to a
93    /// `fuchsia.component.sandbox/DictionaryRouter` served by the program which should return a
94    /// `fuchsia.component.sandbox/DictionaryRef`, by which the program may dynamically provide
95    /// a dictionary from itself. If this is set for `dictionary`, `offer` to this dictionary
96    /// is not allowed.
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub path: Option<Path>,
99
100    /// (`directory` only) The maximum [directory rights][doc-directory-rights] that may be set
101    /// when using this directory.
102    #[serde(skip_serializing_if = "Option::is_none")]
103    #[reference_doc(json_type = "array of string")]
104    pub rights: Option<Rights>,
105
106    /// (`storage` only) The source component of an existing directory capability backing this
107    /// storage capability, one of:
108    /// - `parent`: The component's parent.
109    /// - `self`: This component.
110    /// - `#<child-name>`: A [reference](#references) to a child component
111    ///     instance.
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub from: Option<CapabilityFromRef>,
114
115    /// (`storage` only) The [name](#name) of the directory capability backing the storage. The
116    /// capability must be available from the component referenced in `from`.
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub backing_dir: Option<Name>,
119
120    /// (`storage` only) A subdirectory within `backing_dir` where per-component isolated storage
121    /// directories are created
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub subdir: Option<RelativePath>,
124
125    /// (`storage` only) The identifier used to isolated storage for a component, one of:
126    /// - `static_instance_id`: The instance ID in the component ID index is used
127    ///     as the key for a component's storage. Components which are not listed in
128    ///     the component ID index will not be able to use this storage capability.
129    /// - `static_instance_id_or_moniker`: If the component is listed in the
130    ///     component ID index, the instance ID is used as the key for a component's
131    ///     storage. Otherwise, the component's moniker from the storage
132    ///     capability is used.
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub storage_id: Option<StorageId>,
135
136    /// (`configuration` only) The type of configuration, one of:
137    /// - `bool`: Boolean type.
138    /// - `uint8`: Unsigned 8 bit type.
139    /// - `uint16`: Unsigned 16 bit type.
140    /// - `uint32`: Unsigned 32 bit type.
141    /// - `uint64`: Unsigned 64 bit type.
142    /// - `int8`: Signed 8 bit type.
143    /// - `int16`: Signed 16 bit type.
144    /// - `int32`: Signed 32 bit type.
145    /// - `int64`: Signed 64 bit type.
146    /// - `string`: ASCII string type.
147    /// - `vector`: Vector type. See `element` for the type of the element within the vector.
148    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
149    #[reference_doc(rename = "type")]
150    pub config_type: Option<ConfigType>,
151
152    /// (`configuration` only) Only supported if this configuration `type` is 'string'.
153    /// This is the max size of the string.
154    #[serde(rename = "max_size", skip_serializing_if = "Option::is_none")]
155    #[reference_doc(rename = "max_size")]
156    pub config_max_size: Option<NonZeroU32>,
157
158    /// (`configuration` only) Only supported if this configuration `type` is 'vector'.
159    /// This is the max number of elements in the vector.
160    #[serde(rename = "max_count", skip_serializing_if = "Option::is_none")]
161    #[reference_doc(rename = "max_count")]
162    pub config_max_count: Option<NonZeroU32>,
163
164    /// (`configuration` only) Only supported if this configuration `type` is 'vector'.
165    /// This is the type of the elements in the configuration vector.
166    ///
167    /// Example (simple type):
168    ///
169    /// ```json5
170    /// { type: "uint8" }
171    /// ```
172    ///
173    /// Example (string type):
174    ///
175    /// ```json5
176    /// {
177    ///   type: "string",
178    ///   max_size: 100,
179    /// }
180    /// ```
181    #[serde(rename = "element", skip_serializing_if = "Option::is_none")]
182    #[reference_doc(rename = "element", json_type = "object")]
183    pub config_element_type: Option<ConfigNestedValueType>,
184
185    /// (`configuration` only) The value of the configuration.
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub value: Option<serde_json::Value>,
188
189    /// (`protocol` only) Specifies when the framework will open the protocol
190    /// from this component's outgoing directory when someone requests the
191    /// capability. Allowed values are:
192    ///
193    /// - `eager`: (default) the framework will open the capability as soon as
194    ///   some consumer component requests it.
195    /// - `on_readable`: the framework will open the capability when the server
196    ///   endpoint pipelined in a connection request becomes readable.
197    ///
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub delivery: Option<DeliveryType>,
200}
201
202/// A reference in a `storage from`.
203#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
204#[reference(expected = "\"parent\", \"self\", or \"#<child-name>\"")]
205pub enum CapabilityFromRef {
206    /// A reference to a child.
207    Named(Name),
208    /// A reference to the parent.
209    Parent,
210    /// A reference to this component.
211    Self_,
212}
213
214#[derive(Debug, Clone, Serialize)]
215pub struct ContextCapability {
216    #[serde(skip)]
217    pub origin: Arc<PathBuf>,
218
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub service: Option<ContextSpanned<OneOrMany<Name>>>,
221
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub protocol: Option<ContextSpanned<OneOrMany<Name>>>,
224
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub directory: Option<ContextSpanned<Name>>,
227
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub storage: Option<ContextSpanned<Name>>,
230
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub runner: Option<ContextSpanned<Name>>,
233
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub resolver: Option<ContextSpanned<Name>>,
236
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub event_stream: Option<ContextSpanned<OneOrMany<Name>>>,
239
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub dictionary: Option<ContextSpanned<Name>>,
242
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub config: Option<ContextSpanned<Name>>,
245
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub path: Option<ContextSpanned<Path>>,
248
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub rights: Option<ContextSpanned<Rights>>,
251
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub from: Option<ContextSpanned<CapabilityFromRef>>,
254
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub backing_dir: Option<ContextSpanned<Name>>,
257
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub subdir: Option<ContextSpanned<RelativePath>>,
260
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub storage_id: Option<ContextSpanned<StorageId>>,
263
264    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
265    pub config_type: Option<ContextSpanned<ConfigType>>,
266
267    #[serde(rename = "max_size", skip_serializing_if = "Option::is_none")]
268    pub config_max_size: Option<ContextSpanned<NonZeroU32>>,
269
270    #[serde(rename = "max_count", skip_serializing_if = "Option::is_none")]
271    pub config_max_count: Option<ContextSpanned<NonZeroU32>>,
272
273    #[serde(rename = "element", skip_serializing_if = "Option::is_none")]
274    pub config_element_type: Option<ContextSpanned<ConfigNestedValueType>>,
275
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub value: Option<ContextSpanned<serde_json::Value>>,
278
279    #[serde(skip_serializing_if = "Option::is_none")]
280    pub delivery: Option<ContextSpanned<DeliveryType>>,
281}
282
283impl Default for ContextCapability {
284    fn default() -> Self {
285        Self {
286            origin: Arc::new(PathBuf::new()),
287            service: None,
288            protocol: None,
289            directory: None,
290            storage: None,
291            runner: None,
292            resolver: None,
293            event_stream: None,
294            dictionary: None,
295            config: None,
296            path: None,
297            rights: None,
298            from: None,
299            backing_dir: None,
300            subdir: None,
301            storage_id: None,
302            config_type: None,
303            config_max_size: None,
304            config_max_count: None,
305            config_element_type: None,
306            value: None,
307            delivery: None,
308        }
309    }
310}
311
312impl CanonicalizeContext for ContextCapability {
313    fn canonicalize_context(&mut self) {
314        // Sort the names of the capabilities. Only capabilities with OneOrMany values are included here.
315        if let Some(service) = &mut self.service {
316            service.value.canonicalize_context()
317        } else if let Some(protocol) = &mut self.protocol {
318            protocol.value.canonicalize_context()
319        } else if let Some(event_stream) = &mut self.event_stream {
320            event_stream.value.canonicalize_context()
321        }
322    }
323}
324
325impl RightsClause for ContextCapability {
326    fn rights(&self) -> Option<&Rights> {
327        self.rights.as_ref().map(|r| &r.value)
328    }
329}
330
331impl ContextCapabilityClause for ContextCapability {
332    fn service(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
333        option_one_or_many_as_ref_context(&self.service)
334    }
335    fn protocol(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
336        option_one_or_many_as_ref_context(&self.protocol)
337    }
338    fn directory(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
339        self.directory.as_ref().map(|s| ContextSpanned {
340            value: OneOrMany::One((s.value).as_ref()),
341            origin: s.origin.clone(),
342        })
343    }
344    fn storage(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
345        self.storage.as_ref().map(|s| ContextSpanned {
346            value: OneOrMany::One((s.value).as_ref()),
347            origin: s.origin.clone(),
348        })
349    }
350    fn runner(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
351        self.runner.as_ref().map(|s| ContextSpanned {
352            value: OneOrMany::One((s.value).as_ref()),
353            origin: s.origin.clone(),
354        })
355    }
356    fn resolver(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
357        self.resolver.as_ref().map(|s| ContextSpanned {
358            value: OneOrMany::One((s.value).as_ref()),
359            origin: s.origin.clone(),
360        })
361    }
362    fn event_stream(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
363        option_one_or_many_as_ref_context(&self.event_stream)
364    }
365    fn dictionary(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
366        self.dictionary.as_ref().map(|s| ContextSpanned {
367            value: OneOrMany::One((s.value).as_ref()),
368            origin: s.origin.clone(),
369        })
370    }
371    fn config(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
372        self.config.as_ref().map(|s| ContextSpanned {
373            value: OneOrMany::One((s.value).as_ref()),
374            origin: s.origin.clone(),
375        })
376    }
377
378    fn decl_type(&self) -> &'static str {
379        "capability"
380    }
381    fn supported(&self) -> &[&'static str] {
382        &[
383            "service",
384            "protocol",
385            "directory",
386            "storage",
387            "event_stream",
388            "runner",
389            "resolver",
390            "config",
391            "dictionary",
392        ]
393    }
394    fn are_many_names_allowed(&self) -> bool {
395        ["service", "protocol", "event_stream"].contains(&self.capability_type(None).unwrap())
396    }
397
398    fn set_service(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
399        self.service = o;
400    }
401    fn set_protocol(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
402        self.protocol = o;
403    }
404    fn set_directory(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
405        self.directory = always_one_context(o);
406    }
407    fn set_storage(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
408        self.storage = always_one_context(o);
409    }
410    fn set_runner(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
411        self.runner = always_one_context(o);
412    }
413    fn set_resolver(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
414        self.resolver = always_one_context(o);
415    }
416    fn set_event_stream(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
417        self.event_stream = o;
418    }
419    fn set_dictionary(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
420        self.dictionary = always_one_context(o);
421    }
422    fn set_config(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
423        self.config = always_one_context(o);
424    }
425
426    /// Returns the origin of this capability.
427    fn origin(&self) -> &Arc<PathBuf> {
428        &self.origin
429    }
430
431    fn availability(&self) -> Option<ContextSpanned<Availability>> {
432        None
433    }
434    fn set_availability(&mut self, _a: Option<ContextSpanned<Availability>>) {}
435}
436
437impl PartialEq for ContextCapability {
438    fn eq(&self, other: &Self) -> bool {
439        macro_rules! cmp {
440            ($field:ident) => {
441                match (&self.$field, &other.$field) {
442                    (Some(a), Some(b)) => a.value == b.value,
443                    (None, None) => true,
444                    _ => false,
445                }
446            };
447        }
448
449        cmp!(service)
450            && cmp!(protocol)
451            && cmp!(directory)
452            && cmp!(storage)
453            && cmp!(runner)
454            && cmp!(resolver)
455            && cmp!(dictionary)
456            && cmp!(config)
457            && cmp!(path)
458            && cmp!(rights)
459            && cmp!(from)
460            && cmp!(event_stream)
461            && cmp!(backing_dir)
462            && cmp!(subdir)
463            && cmp!(storage_id)
464            && cmp!(config_type)
465            && cmp!(config_max_size)
466            && cmp!(config_max_count)
467            && cmp!(config_element_type)
468            && cmp!(value)
469            && cmp!(delivery)
470    }
471}
472
473impl Eq for ContextCapability {}
474
475impl ContextPathClause for ContextCapability {
476    fn path(&self) -> Option<&ContextSpanned<Path>> {
477        self.path.as_ref()
478    }
479}
480
481impl AsClauseContext for ContextCapability {
482    fn r#as(&self) -> Option<ContextSpanned<&BorrowedName>> {
483        None
484    }
485}
486
487impl Hydrate for Capability {
488    type Output = ContextCapability;
489
490    fn hydrate(self, file: &Arc<PathBuf>) -> Result<Self::Output, Error> {
491        Ok(ContextCapability {
492            origin: file.clone(),
493            service: hydrate_opt_simple(self.service, file),
494            protocol: hydrate_opt_simple(self.protocol, file),
495            directory: hydrate_opt_simple(self.directory, file),
496            storage: hydrate_opt_simple(self.storage, file),
497            runner: hydrate_opt_simple(self.runner, file),
498            resolver: hydrate_opt_simple(self.resolver, file),
499            dictionary: hydrate_opt_simple(self.dictionary, file),
500            config: hydrate_opt_simple(self.config, file),
501            path: hydrate_opt_simple(self.path, file),
502            rights: hydrate_opt_simple(self.rights, file),
503            from: hydrate_opt_simple(self.from, file),
504            event_stream: hydrate_opt_simple(self.event_stream, file),
505            backing_dir: hydrate_opt_simple(self.backing_dir, file),
506            subdir: hydrate_opt_simple(self.subdir, file),
507            storage_id: hydrate_opt_simple(self.storage_id, file),
508            config_type: hydrate_opt_simple(self.config_type, file),
509            config_max_size: hydrate_opt_simple(self.config_max_size, file),
510            config_max_count: hydrate_opt_simple(self.config_max_count, file),
511            config_element_type: hydrate_opt_simple(self.config_element_type, file),
512            value: hydrate_opt_simple(self.value, file),
513            delivery: hydrate_opt_simple(self.delivery, file),
514        })
515    }
516}
517
518/// Converts Capability -> CS ContextCapability
519pub fn span_capability(cap: Capability) -> ContextSpanned<ContextCapability> {
520    let context_cap = ContextCapability {
521        origin: Arc::new(PathBuf::from("programmatic_manifest.cml")),
522        service: cap.service.map(synthetic_span),
523        protocol: cap.protocol.map(synthetic_span),
524        directory: cap.directory.map(synthetic_span),
525        storage: cap.storage.map(synthetic_span),
526        runner: cap.runner.map(synthetic_span),
527        resolver: cap.resolver.map(synthetic_span),
528        event_stream: cap.event_stream.map(synthetic_span),
529        dictionary: cap.dictionary.map(synthetic_span),
530        config: cap.config.map(synthetic_span),
531        path: cap.path.map(synthetic_span),
532        rights: cap.rights.map(synthetic_span),
533        from: cap.from.map(synthetic_span),
534        backing_dir: cap.backing_dir.map(synthetic_span),
535        subdir: cap.subdir.map(synthetic_span),
536        storage_id: cap.storage_id.map(synthetic_span),
537        config_type: cap.config_type.map(synthetic_span),
538        config_max_size: cap.config_max_size.map(synthetic_span),
539        config_max_count: cap.config_max_count.map(synthetic_span),
540        config_element_type: cap.config_element_type.map(synthetic_span),
541        value: cap.value.map(synthetic_span),
542        delivery: cap.delivery.map(synthetic_span),
543    };
544
545    synthetic_span(context_cap)
546}