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