Skip to main content

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::common::*;
6use crate::types::right::Rights;
7use crate::{
8    AnyRef, AsClauseContext, CanonicalizeContext, DictionaryRef, Error, EventScope,
9    FromClauseContext, SourceAvailability,
10};
11
12use crate::one_or_many::{OneOrMany, one_or_many_from_context};
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 reference_doc::ReferenceDoc;
19use serde::{Deserialize, Serialize};
20
21use std::fmt;
22use std::path::PathBuf;
23use std::sync::Arc;
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
184/// Generates deserializer for `OneOrMany<ExposeFromRef>`.
185#[derive(OneOrMany, Debug, Clone)]
186#[one_or_many(
187    expected = "one or an array of \"framework\", \"self\", \"#<child-name>\", or a dictionary path",
188    inner_type = "ExposeFromRef",
189    min_length = 1,
190    unique_items = true
191)]
192pub struct OneOrManyExposeFromRefs;
193
194/// A reference in an `expose from`.
195#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
196#[reference(expected = "\"framework\", \"self\", \"void\", or \"#<child-name>\"")]
197pub enum ExposeFromRef {
198    /// A reference to a child or collection.
199    Named(Name),
200    /// A reference to the framework.
201    Framework,
202    /// A reference to this component.
203    Self_,
204    /// An intentionally omitted source.
205    Void,
206    /// A reference to a dictionary.
207    Dictionary(DictionaryRef),
208}
209
210/// A reference in an `expose to`.
211#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
212#[reference(expected = "\"parent\", \"framework\", or none")]
213pub enum ExposeToRef {
214    /// A reference to the parent.
215    Parent,
216    /// A reference to the framework.
217    Framework,
218}
219
220#[derive(Debug, Clone, Serialize)]
221pub struct ContextExpose {
222    #[serde(skip)]
223    pub origin: Arc<PathBuf>,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub service: Option<ContextSpanned<OneOrMany<Name>>>,
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub protocol: Option<ContextSpanned<OneOrMany<Name>>>,
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub directory: Option<ContextSpanned<OneOrMany<Name>>>,
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub runner: Option<ContextSpanned<OneOrMany<Name>>>,
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub resolver: Option<ContextSpanned<OneOrMany<Name>>>,
234    #[serde(skip_serializing_if = "Option::is_none")]
235    pub dictionary: Option<ContextSpanned<OneOrMany<Name>>>,
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub config: Option<ContextSpanned<OneOrMany<Name>>>,
238    pub from: ContextSpanned<OneOrMany<ExposeFromRef>>,
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub to: Option<ContextSpanned<ExposeToRef>>,
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub r#as: Option<ContextSpanned<Name>>,
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub rights: Option<ContextSpanned<Rights>>,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub subdir: Option<ContextSpanned<RelativePath>>,
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub event_stream: Option<ContextSpanned<OneOrMany<Name>>>,
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub scope: Option<ContextSpanned<OneOrMany<EventScope>>>,
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub availability: Option<ContextSpanned<Availability>>,
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub source_availability: Option<ContextSpanned<SourceAvailability>>,
255}
256
257impl Default for ContextExpose {
258    fn default() -> Self {
259        Self {
260            origin: Arc::new(PathBuf::new()),
261
262            from: ContextSpanned {
263                value: OneOrMany::One(ExposeFromRef::Self_),
264                origin: Arc::new(PathBuf::new()),
265            },
266
267            service: None,
268            protocol: None,
269            directory: None,
270            runner: None,
271            resolver: None,
272            dictionary: None,
273            config: None,
274            to: None,
275            r#as: None,
276            rights: None,
277            subdir: None,
278            event_stream: None,
279            scope: None,
280            availability: None,
281            source_availability: None,
282        }
283    }
284}
285
286impl ContextCapabilityClause for ContextExpose {
287    fn service(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
288        option_one_or_many_as_ref_context(&self.service)
289    }
290    fn protocol(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
291        option_one_or_many_as_ref_context(&self.protocol)
292    }
293    fn directory(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
294        option_one_or_many_as_ref_context(&self.directory)
295    }
296    fn storage(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
297        None
298    }
299    fn runner(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
300        option_one_or_many_as_ref_context(&self.runner)
301    }
302    fn resolver(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
303        option_one_or_many_as_ref_context(&self.resolver)
304    }
305    fn event_stream(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
306        option_one_or_many_as_ref_context(&self.event_stream)
307    }
308    fn dictionary(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
309        option_one_or_many_as_ref_context(&self.dictionary)
310    }
311    fn config(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
312        option_one_or_many_as_ref_context(&self.config)
313    }
314
315    fn decl_type(&self) -> &'static str {
316        "expose"
317    }
318    fn supported(&self) -> &[&'static str] {
319        &[
320            "service",
321            "protocol",
322            "directory",
323            "event_stream",
324            "runner",
325            "resolver",
326            "config",
327            "dictionary",
328        ]
329    }
330    fn are_many_names_allowed(&self) -> bool {
331        [
332            "service",
333            "protocol",
334            "directory",
335            "runner",
336            "resolver",
337            "event_stream",
338            "config",
339            "dictionary",
340        ]
341        .contains(&self.capability_type(None).unwrap())
342    }
343
344    fn set_service(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
345        self.service = o;
346    }
347    fn set_protocol(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
348        self.protocol = o;
349    }
350    fn set_directory(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
351        self.directory = o;
352    }
353    fn set_storage(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
354    fn set_runner(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
355        self.runner = o;
356    }
357    fn set_resolver(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
358        self.resolver = o;
359    }
360    fn set_event_stream(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
361        self.event_stream = o;
362    }
363    fn set_dictionary(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
364        self.dictionary = o;
365    }
366    fn set_config(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
367        self.config = o;
368    }
369
370    fn origin(&self) -> &Arc<PathBuf> {
371        &self.origin
372    }
373
374    fn availability(&self) -> Option<ContextSpanned<Availability>> {
375        None
376    }
377    fn set_availability(&mut self, _a: Option<ContextSpanned<Availability>>) {}
378}
379
380impl CanonicalizeContext for ContextExpose {
381    fn canonicalize_context(&mut self) {
382        // Sort the names of the capabilities. Only capabilities with OneOrMany values are included here.
383        if let Some(service) = &mut self.service {
384            service.value.canonicalize_context();
385        } else if let Some(protocol) = &mut self.protocol {
386            protocol.value.canonicalize_context();
387        } else if let Some(directory) = &mut self.directory {
388            directory.value.canonicalize_context();
389        } else if let Some(runner) = &mut self.runner {
390            runner.value.canonicalize_context();
391        } else if let Some(resolver) = &mut self.resolver {
392            resolver.value.canonicalize_context();
393        } else if let Some(event_stream) = &mut self.event_stream {
394            event_stream.value.canonicalize_context();
395            if let Some(scope) = &mut self.scope {
396                scope.value.canonicalize_context();
397            }
398        }
399        // TODO(https://fxbug.dev/300500098): canonicalize dictionaries
400    }
401}
402
403impl PartialEq for ContextExpose {
404    fn eq(&self, other: &Self) -> bool {
405        macro_rules! cmp {
406            ($field:ident) => {
407                match (&self.$field, &other.$field) {
408                    (Some(a), Some(b)) => a.value == b.value,
409                    (None, None) => true,
410                    _ => false,
411                }
412            };
413        }
414
415        cmp!(service)
416            && cmp!(protocol)
417            && cmp!(directory)
418            && cmp!(runner)
419            && cmp!(resolver)
420            && cmp!(dictionary)
421            && cmp!(config)
422            && self.from.value == other.from.value
423            && cmp!(to)
424            && cmp!(r#as)
425            && cmp!(rights)
426            && cmp!(subdir)
427            && cmp!(event_stream)
428            && cmp!(scope)
429            && cmp!(availability)
430            && cmp!(source_availability)
431    }
432}
433
434impl Eq for ContextExpose {}
435
436impl ContextPathClause for ContextExpose {
437    fn path(&self) -> Option<&ContextSpanned<Path>> {
438        None
439    }
440}
441
442impl AsClauseContext for ContextExpose {
443    fn r#as(&self) -> Option<ContextSpanned<&BorrowedName>> {
444        self.r#as.as_ref().map(|spanned_name| ContextSpanned {
445            value: spanned_name.value.as_ref(),
446            origin: spanned_name.origin.clone(),
447        })
448    }
449}
450
451impl FromClauseContext for ContextExpose {
452    fn from_(&self) -> ContextSpanned<OneOrMany<AnyRef<'_>>> {
453        one_or_many_from_context(&self.from)
454    }
455}
456
457impl Hydrate for Expose {
458    type Output = ContextExpose;
459
460    fn hydrate(self, file: &Arc<PathBuf>) -> Result<Self::Output, Error> {
461        Ok(ContextExpose {
462            origin: file.clone(),
463            service: hydrate_opt_simple(self.service, file),
464            protocol: hydrate_opt_simple(self.protocol, file),
465            directory: hydrate_opt_simple(self.directory, file),
466            runner: hydrate_opt_simple(self.runner, file),
467            resolver: hydrate_opt_simple(self.resolver, file),
468            dictionary: hydrate_opt_simple(self.dictionary, file),
469            config: hydrate_opt_simple(self.config, file),
470            from: hydrate_simple(self.from, file),
471            to: hydrate_opt_simple(self.to, file),
472            r#as: hydrate_opt_simple(self.r#as, file),
473            rights: hydrate_opt_simple(self.rights, file),
474            subdir: hydrate_opt_simple(self.subdir, file),
475            event_stream: hydrate_opt_simple(self.event_stream, file),
476            scope: hydrate_opt_simple(self.scope, file),
477            availability: hydrate_opt_simple(self.availability, file),
478            source_availability: hydrate_opt_simple(self.source_availability, file),
479        })
480    }
481}