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