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