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