cml/types/
expose.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::types::right::{Rights, RightsClause};
6use crate::{
7    AnyRef, AsClause, Canonicalize, CapabilityClause, DictionaryRef, EventScope, FilterClause,
8    FromClause, PathClause, SourceAvailability, SpannedCapabilityClause, one_or_many_from_impl,
9    option_one_or_many_as_ref, option_spanned_one_or_many_as_ref,
10};
11
12use crate::one_or_many::OneOrMany;
13pub use cm_types::{
14    Availability, BorrowedName, BoundedName, DependencyType, HandleType, Name, OnTerminate,
15    ParseError, Path, RelativePath, StartupMode, Url,
16};
17use cml_macro::{OneOrMany, Reference};
18use json_spanned_value::Spanned;
19use reference_doc::ReferenceDoc;
20use serde::{Deserialize, Serialize};
21use serde_json::{Map, Value};
22
23use std::fmt;
24
25/// Example:
26///
27/// ```json5
28/// expose: [
29///     {
30///         directory: "themes",
31///         from: "self",
32///     },
33///     {
34///         protocol: "pkg.Cache",
35///         from: "#pkg_cache",
36///         as: "fuchsia.pkg.PackageCache",
37///     },
38///     {
39///         protocol: [
40///             "fuchsia.ui.app.ViewProvider",
41///             "fuchsia.fonts.Provider",
42///         ],
43///         from: "self",
44///     },
45///     {
46///         runner: "web-chromium",
47///         from: "#web_runner",
48///         as: "web",
49///     },
50///     {
51///         resolver: "full-resolver",
52///         from: "#full-resolver",
53///     },
54/// ],
55/// ```
56#[derive(Deserialize, Debug, PartialEq, Clone, ReferenceDoc, Serialize)]
57#[serde(deny_unknown_fields)]
58#[reference_doc(fields_as = "list", top_level_doc_after_fields)]
59pub struct Expose {
60    /// When routing a service, the [name](#name) of a [service capability][doc-service].
61    #[serde(skip_serializing_if = "Option::is_none")]
62    #[reference_doc(skip = true)]
63    pub service: Option<OneOrMany<Name>>,
64
65    /// When routing a protocol, the [name](#name) of a [protocol capability][doc-protocol].
66    #[serde(skip_serializing_if = "Option::is_none")]
67    #[reference_doc(skip = true)]
68    pub protocol: Option<OneOrMany<Name>>,
69
70    /// When routing a directory, the [name](#name) of a [directory capability][doc-directory].
71    #[serde(skip_serializing_if = "Option::is_none")]
72    #[reference_doc(skip = true)]
73    pub directory: Option<OneOrMany<Name>>,
74
75    /// When routing a runner, the [name](#name) of a [runner capability][doc-runners].
76    #[serde(skip_serializing_if = "Option::is_none")]
77    #[reference_doc(skip = true)]
78    pub runner: Option<OneOrMany<Name>>,
79
80    /// When routing a resolver, the [name](#name) of a [resolver capability][doc-resolvers].
81    #[serde(skip_serializing_if = "Option::is_none")]
82    #[reference_doc(skip = true)]
83    pub resolver: Option<OneOrMany<Name>>,
84
85    /// When routing a dictionary, the [name](#name) of a [dictionary capability][doc-dictionaries].
86    #[serde(skip_serializing_if = "Option::is_none")]
87    #[reference_doc(skip = true)]
88    pub dictionary: Option<OneOrMany<Name>>,
89
90    /// When routing a config, the [name](#name) of a configuration capability.
91    #[serde(skip_serializing_if = "Option::is_none")]
92    #[reference_doc(skip = true)]
93    pub config: Option<OneOrMany<Name>>,
94
95    /// `from`: The source of the capability, one of:
96    /// - `self`: This component. Requires a corresponding
97    ///     [`capability`](#capabilities) declaration.
98    /// - `framework`: The Component Framework runtime.
99    /// - `#<child-name>`: A [reference](#references) to a child component
100    ///     instance.
101    pub from: OneOrMany<ExposeFromRef>,
102
103    /// The [name](#name) for the capability as it will be known by the target. If omitted,
104    /// defaults to the original name. `as` cannot be used when an array of multiple capability
105    /// names is provided.
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub r#as: Option<Name>,
108
109    /// The capability target. Either `parent` or `framework`. Defaults to `parent`.
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub to: Option<ExposeToRef>,
112
113    /// (`directory` only) the maximum [directory rights][doc-directory-rights] to apply to
114    /// the exposed directory capability.
115    #[serde(skip_serializing_if = "Option::is_none")]
116    #[reference_doc(json_type = "array of string")]
117    pub rights: Option<Rights>,
118
119    /// (`directory` only) the relative path of a subdirectory within the source directory
120    /// capability to route.
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub subdir: Option<RelativePath>,
123
124    /// (`event_stream` only) the name(s) of the event streams being exposed.
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub event_stream: Option<OneOrMany<Name>>,
127
128    /// (`event_stream` only) the scope(s) of the event streams being exposed. This is used to
129    /// downscope the range of components to which an event stream refers and make it refer only to
130    /// the components defined in the scope.
131    #[serde(skip_serializing_if = "Option::is_none")]
132    pub scope: Option<OneOrMany<EventScope>>,
133
134    /// `availability` _(optional)_: The expectations around this capability's availability. Affects
135    /// build-time and runtime route validation. One of:
136    /// - `required` (default): a required dependency, the source must exist and provide it. Use
137    ///     this when the target of this expose requires this capability to function properly.
138    /// - `optional`: an optional dependency. Use this when the target of the expose can function
139    ///     with or without this capability. The target must not have a `required` dependency on the
140    ///     capability. The ultimate source of this expose must be `void` or an actual component.
141    /// - `same_as_target`: the availability expectations of this capability will match the
142    ///     target's. If the target requires the capability, then this field is set to `required`.
143    ///     If the target has an optional dependency on the capability, then the field is set to
144    ///     `optional`.
145    /// - `transitional`: like `optional`, but will tolerate a missing source. Use this
146    ///     only to avoid validation errors during transitional periods of multi-step code changes.
147    ///
148    /// For more information, see the
149    /// [availability](/docs/concepts/components/v2/capabilities/availability.md) documentation.
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub availability: Option<Availability>,
152
153    /// Whether or not the source of this offer must exist. One of:
154    /// - `required` (default): the source (`from`) must be defined in this manifest.
155    /// - `unknown`: the source of this offer will be rewritten to `void` if its source (`from`)
156    ///     is not defined in this manifest after includes are processed.
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub source_availability: Option<SourceAvailability>,
159}
160
161impl Expose {
162    pub fn new_from(from: OneOrMany<ExposeFromRef>) -> Self {
163        Self {
164            from,
165            service: None,
166            protocol: None,
167            directory: None,
168            config: None,
169            runner: None,
170            resolver: None,
171            dictionary: None,
172            r#as: None,
173            to: None,
174            rights: None,
175            subdir: None,
176            event_stream: None,
177            scope: None,
178            availability: None,
179            source_availability: None,
180        }
181    }
182}
183
184impl FromClause for Expose {
185    fn from_(&self) -> OneOrMany<AnyRef<'_>> {
186        one_or_many_from_impl(&self.from)
187    }
188}
189
190impl AsClause for Expose {
191    fn r#as(&self) -> Option<&BorrowedName> {
192        self.r#as.as_ref().map(Name::as_ref)
193    }
194}
195
196impl PathClause for Expose {
197    fn path(&self) -> Option<&Path> {
198        None
199    }
200}
201
202impl FilterClause for Expose {
203    fn filter(&self) -> Option<&Map<String, Value>> {
204        None
205    }
206}
207
208impl RightsClause for Expose {
209    fn rights(&self) -> Option<&Rights> {
210        self.rights.as_ref()
211    }
212}
213
214impl Canonicalize for Expose {
215    fn canonicalize(&mut self) {
216        // Sort the names of the capabilities. Only capabilities with OneOrMany values are included here.
217        if let Some(service) = &mut self.service {
218            service.canonicalize();
219        } else if let Some(protocol) = &mut self.protocol {
220            protocol.canonicalize();
221        } else if let Some(directory) = &mut self.directory {
222            directory.canonicalize();
223        } else if let Some(runner) = &mut self.runner {
224            runner.canonicalize();
225        } else if let Some(resolver) = &mut self.resolver {
226            resolver.canonicalize();
227        } else if let Some(event_stream) = &mut self.event_stream {
228            event_stream.canonicalize();
229            if let Some(scope) = &mut self.scope {
230                scope.canonicalize();
231            }
232        }
233        // TODO(https://fxbug.dev/300500098): canonicalize dictionaries
234    }
235}
236
237impl CapabilityClause for Expose {
238    fn service(&self) -> Option<OneOrMany<&BorrowedName>> {
239        option_one_or_many_as_ref(&self.service)
240    }
241    fn protocol(&self) -> Option<OneOrMany<&BorrowedName>> {
242        option_one_or_many_as_ref(&self.protocol)
243    }
244    fn directory(&self) -> Option<OneOrMany<&BorrowedName>> {
245        option_one_or_many_as_ref(&self.directory)
246    }
247    fn storage(&self) -> Option<OneOrMany<&BorrowedName>> {
248        None
249    }
250    fn runner(&self) -> Option<OneOrMany<&BorrowedName>> {
251        option_one_or_many_as_ref(&self.runner)
252    }
253    fn resolver(&self) -> Option<OneOrMany<&BorrowedName>> {
254        option_one_or_many_as_ref(&self.resolver)
255    }
256    fn event_stream(&self) -> Option<OneOrMany<&BorrowedName>> {
257        option_one_or_many_as_ref(&self.event_stream)
258    }
259    fn dictionary(&self) -> Option<OneOrMany<&BorrowedName>> {
260        option_one_or_many_as_ref(&self.dictionary)
261    }
262    fn config(&self) -> Option<OneOrMany<&BorrowedName>> {
263        option_one_or_many_as_ref(&self.config)
264    }
265
266    fn set_service(&mut self, o: Option<OneOrMany<Name>>) {
267        self.service = o;
268    }
269    fn set_protocol(&mut self, o: Option<OneOrMany<Name>>) {
270        self.protocol = o;
271    }
272    fn set_directory(&mut self, o: Option<OneOrMany<Name>>) {
273        self.directory = o;
274    }
275    fn set_storage(&mut self, _o: Option<OneOrMany<Name>>) {}
276    fn set_runner(&mut self, o: Option<OneOrMany<Name>>) {
277        self.runner = o;
278    }
279    fn set_resolver(&mut self, o: Option<OneOrMany<Name>>) {
280        self.resolver = o;
281    }
282    fn set_event_stream(&mut self, o: Option<OneOrMany<Name>>) {
283        self.event_stream = o;
284    }
285    fn set_dictionary(&mut self, o: Option<OneOrMany<Name>>) {
286        self.dictionary = o;
287    }
288    fn set_config(&mut self, o: Option<OneOrMany<Name>>) {
289        self.config = o;
290    }
291
292    fn availability(&self) -> Option<Availability> {
293        None
294    }
295    fn set_availability(&mut self, _a: Option<Availability>) {}
296
297    fn decl_type(&self) -> &'static str {
298        "expose"
299    }
300    fn supported(&self) -> &[&'static str] {
301        &[
302            "service",
303            "protocol",
304            "directory",
305            "runner",
306            "resolver",
307            "event_stream",
308            "dictionary",
309            "config",
310        ]
311    }
312    fn are_many_names_allowed(&self) -> bool {
313        [
314            "service",
315            "protocol",
316            "directory",
317            "runner",
318            "resolver",
319            "event_stream",
320            "dictionary",
321            "config",
322        ]
323        .contains(&self.capability_type().unwrap())
324    }
325}
326
327#[derive(Deserialize, Debug, PartialEq, Clone)]
328#[serde(deny_unknown_fields)]
329pub struct SpannedExpose {
330    /// When routing a service, the [name](#name) of a [service capability][doc-service].
331    pub service: Option<OneOrMany<Name>>,
332
333    /// When routing a protocol, the [name](#name) of a [protocol capability][doc-protocol].
334    pub protocol: Option<OneOrMany<Name>>,
335
336    /// When routing a directory, the [name](#name) of a [directory capability][doc-directory].
337    pub directory: Option<OneOrMany<Name>>,
338
339    /// When routing a runner, the [name](#name) of a [runner capability][doc-runners].
340    pub runner: Option<OneOrMany<Name>>,
341
342    /// When routing a resolver, the [name](#name) of a [resolver capability][doc-resolvers].
343    pub resolver: Option<OneOrMany<Name>>,
344
345    /// When routing a dictionary, the [name](#name) of a [dictionary capability][doc-dictionaries].
346    pub dictionary: Option<OneOrMany<Name>>,
347
348    /// When routing a config, the [name](#name) of a configuration capability.
349    pub config: Option<OneOrMany<Name>>,
350
351    /// `from`: The source of the capability, one of:
352    /// - `self`: This component. Requires a corresponding
353    ///     [`capability`](#capabilities) declaration.
354    /// - `framework`: The Component Framework runtime.
355    /// - `#<child-name>`: A [reference](#references) to a child component
356    ///     instance.
357    pub from: Spanned<OneOrMany<ExposeFromRef>>,
358
359    /// The [name](#name) for the capability as it will be known by the target. If omitted,
360    /// defaults to the original name. `as` cannot be used when an array of multiple capability
361    /// names is provided.
362    pub r#as: Option<Spanned<Name>>,
363
364    /// The capability target. Either `parent` or `framework`. Defaults to `parent`.
365    pub to: Option<Spanned<ExposeToRef>>,
366
367    /// (`directory` only) the maximum [directory rights][doc-directory-rights] to apply to
368    /// the exposed directory capability.
369    pub rights: Option<Spanned<Rights>>,
370
371    /// (`directory` only) the relative path of a subdirectory within the source directory
372    /// capability to route.
373    pub subdir: Option<Spanned<RelativePath>>,
374
375    /// (`event_stream` only) the name(s) of the event streams being exposed.
376    pub event_stream: Option<Spanned<OneOrMany<Name>>>,
377
378    /// (`event_stream` only) the scope(s) of the event streams being exposed. This is used to
379    /// downscope the range of components to which an event stream refers and make it refer only to
380    /// the components defined in the scope.
381    pub scope: Option<OneOrMany<EventScope>>,
382
383    /// `availability` _(optional)_: The expectations around this capability's availability. Affects
384    /// build-time and runtime route validation. One of:
385    /// - `required` (default): a required dependency, the source must exist and provide it. Use
386    ///     this when the target of this expose requires this capability to function properly.
387    /// - `optional`: an optional dependency. Use this when the target of the expose can function
388    ///     with or without this capability. The target must not have a `required` dependency on the
389    ///     capability. The ultimate source of this expose must be `void` or an actual component.
390    /// - `same_as_target`: the availability expectations of this capability will match the
391    ///     target's. If the target requires the capability, then this field is set to `required`.
392    ///     If the target has an optional dependency on the capability, then the field is set to
393    ///     `optional`.
394    /// - `transitional`: like `optional`, but will tolerate a missing source. Use this
395    ///     only to avoid validation errors during transitional periods of multi-step code changes.
396    ///
397    /// For more information, see the
398    /// [availability](/docs/concepts/components/v2/capabilities/availability.md) documentation.
399    pub availability: Option<Availability>,
400
401    /// Whether or not the source of this offer must exist. One of:
402    /// - `required` (default): the source (`from`) must be defined in this manifest.
403    /// - `unknown`: the source of this offer will be rewritten to `void` if its source (`from`)
404    ///     is not defined in this manifest after includes are processed.
405    pub source_availability: Option<SourceAvailability>,
406}
407
408impl AsClause for SpannedExpose {
409    fn r#as(&self) -> Option<&BorrowedName> {
410        self.r#as.as_ref().map(|spanned_value| {
411            let bounded_name: &BoundedName<255> = spanned_value.as_ref();
412            let borrowed_name: &BorrowedName = bounded_name.as_ref();
413            borrowed_name
414        })
415    }
416}
417
418impl SpannedCapabilityClause for SpannedExpose {
419    fn service(&self) -> Option<OneOrMany<&BorrowedName>> {
420        option_one_or_many_as_ref(&self.service)
421    }
422    fn protocol(&self) -> Option<OneOrMany<&BorrowedName>> {
423        option_one_or_many_as_ref(&self.protocol)
424    }
425    fn directory(&self) -> Option<OneOrMany<&BorrowedName>> {
426        option_one_or_many_as_ref(&self.directory)
427    }
428    fn storage(&self) -> Option<OneOrMany<&BorrowedName>> {
429        None
430    }
431    fn runner(&self) -> Option<OneOrMany<&BorrowedName>> {
432        option_one_or_many_as_ref(&self.runner)
433    }
434    fn resolver(&self) -> Option<OneOrMany<&BorrowedName>> {
435        option_one_or_many_as_ref(&self.resolver)
436    }
437    fn event_stream(&self) -> Option<OneOrMany<&BorrowedName>> {
438        option_spanned_one_or_many_as_ref(&self.event_stream)
439    }
440    fn dictionary(&self) -> Option<OneOrMany<&BorrowedName>> {
441        option_one_or_many_as_ref(&self.dictionary)
442    }
443    fn config(&self) -> Option<OneOrMany<&BorrowedName>> {
444        option_one_or_many_as_ref(&self.config)
445    }
446
447    fn decl_type(&self) -> &'static str {
448        "expose"
449    }
450    fn supported(&self) -> &[&'static str] {
451        &[
452            "service",
453            "protocol",
454            "directory",
455            "runner",
456            "resolver",
457            "event_stream",
458            "dictionary",
459            "config",
460        ]
461    }
462}
463
464/// Generates deserializer for `OneOrMany<ExposeFromRef>`.
465#[derive(OneOrMany, Debug, Clone)]
466#[one_or_many(
467    expected = "one or an array of \"framework\", \"self\", \"#<child-name>\", or a dictionary path",
468    inner_type = "ExposeFromRef",
469    min_length = 1,
470    unique_items = true
471)]
472pub struct OneOrManyExposeFromRefs;
473
474/// A reference in an `expose from`.
475#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
476#[reference(expected = "\"framework\", \"self\", \"void\", or \"#<child-name>\"")]
477pub enum ExposeFromRef {
478    /// A reference to a child or collection.
479    Named(Name),
480    /// A reference to the framework.
481    Framework,
482    /// A reference to this component.
483    Self_,
484    /// An intentionally omitted source.
485    Void,
486    /// A reference to a dictionary.
487    Dictionary(DictionaryRef),
488}
489
490/// A reference in an `expose to`.
491#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
492#[reference(expected = "\"parent\", \"framework\", or none")]
493pub enum ExposeToRef {
494    /// A reference to the parent.
495    Parent,
496    /// A reference to the framework.
497    Framework,
498}