Skip to main content

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::types::common::*;
6use crate::{
7    AnyRef, AsClause, AsClauseContext, Canonicalize, CanonicalizeContext, CapabilityClause,
8    CapabilityId, DictionaryRef, Error, EventScope, FromClause, FromClauseContext, PathClause,
9    SourceAvailability,
10};
11
12use crate::one_or_many::{
13    OneOrMany, one_or_many_from_context, one_or_many_from_impl, option_one_or_many_as_ref,
14};
15use crate::types::right::{Rights, RightsClause};
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 itertools::Either;
22use reference_doc::ReferenceDoc;
23use serde::{Deserialize, Serialize};
24
25use std::fmt;
26use std::fmt::Write;
27use std::path::PathBuf;
28#[allow(unused)] // A test-only macro is defined outside of a test builds.
29use std::str::FromStr;
30use std::sync::Arc;
31
32/// Example:
33///
34/// ```json5
35/// offer: [
36///     {
37///         protocol: "fuchsia.logger.LogSink",
38///         from: "#logger",
39///         to: [ "#fshost", "#pkg_cache" ],
40///         dependency: "weak",
41///     },
42///     {
43///         protocol: [
44///             "fuchsia.ui.app.ViewProvider",
45///             "fuchsia.fonts.Provider",
46///         ],
47///         from: "#session",
48///         to: [ "#ui_shell" ],
49///         dependency: "strong",
50///     },
51///     {
52///         directory: "blobfs",
53///         from: "self",
54///         to: [ "#pkg_cache" ],
55///     },
56///     {
57///         directory: "fshost-config",
58///         from: "parent",
59///         to: [ "#fshost" ],
60///         as: "config",
61///     },
62///     {
63///         storage: "cache",
64///         from: "parent",
65///         to: [ "#logger" ],
66///     },
67///     {
68///         runner: "web",
69///         from: "parent",
70///         to: [ "#user-shell" ],
71///     },
72///     {
73///         resolver: "full-resolver",
74///         from: "parent",
75///         to: [ "#user-shell" ],
76///     },
77///     {
78///         event_stream: "stopped",
79///         from: "framework",
80///         to: [ "#logger" ],
81///     },
82/// ],
83/// ```
84#[derive(Deserialize, Debug, PartialEq, Clone, ReferenceDoc, Serialize)]
85#[serde(deny_unknown_fields)]
86#[reference_doc(fields_as = "list", top_level_doc_after_fields)]
87pub struct Offer {
88    /// When routing a service, the [name](#name) of a [service capability][doc-service].
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub service: Option<OneOrMany<Name>>,
91
92    /// When routing a protocol, the [name](#name) of a [protocol capability][doc-protocol].
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub protocol: Option<OneOrMany<Name>>,
95
96    /// When routing a directory, the [name](#name) of a [directory capability][doc-directory].
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub directory: Option<OneOrMany<Name>>,
99
100    /// When routing a runner, the [name](#name) of a [runner capability][doc-runners].
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub runner: Option<OneOrMany<Name>>,
103
104    /// When routing a resolver, the [name](#name) of a [resolver capability][doc-resolvers].
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub resolver: Option<OneOrMany<Name>>,
107
108    /// When routing a storage capability, the [name](#name) of a [storage capability][doc-storage].
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub storage: Option<OneOrMany<Name>>,
111
112    /// When routing a dictionary, the [name](#name) of a [dictionary capability][doc-dictionaries].
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub dictionary: Option<OneOrMany<Name>>,
115
116    /// When routing a config, the [name](#name) of a configuration capability.
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub config: Option<OneOrMany<Name>>,
119
120    /// `from`: The source of the capability, one of:
121    /// - `parent`: The component's parent. This source can be used for all
122    ///     capability types.
123    /// - `self`: This component. Requires a corresponding
124    ///     [`capability`](#capabilities) declaration.
125    /// - `framework`: The Component Framework runtime.
126    /// - `#<child-name>`: A [reference](#references) to a child component
127    ///     instance. This source can only be used when offering protocol,
128    ///     directory, or runner capabilities.
129    /// - `void`: The source is intentionally omitted. Only valid when `availability` is
130    ///     `optional` or `transitional`.
131    pub from: OneOrMany<OfferFromRef>,
132
133    /// Capability target(s). One of:
134    /// - `#<target-name>` or \[`#name1`, ...\]: A [reference](#references) to a child or collection,
135    ///   or an array of references.
136    /// - `all`: Short-hand for an `offer` clause containing all child [references](#references).
137    pub to: OneOrMany<OfferToRef>,
138
139    /// An explicit [name](#name) for the capability as it will be known by the target. If omitted,
140    /// defaults to the original name. `as` cannot be used when an array of multiple names is
141    /// provided.
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub r#as: Option<Name>,
144
145    /// The type of dependency between the source and
146    /// targets, one of:
147    /// - `strong`: a strong dependency, which is used to determine shutdown
148    ///     ordering. Component manager is guaranteed to stop the target before the
149    ///     source. This is the default.
150    /// - `weak`: a weak dependency, which is ignored during
151    ///     shutdown. When component manager stops the parent realm, the source may
152    ///     stop before the clients. Clients of weak dependencies must be able to
153    ///     handle these dependencies becoming unavailable.
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub dependency: Option<DependencyType>,
156
157    /// (`directory` only) the maximum [directory rights][doc-directory-rights] to apply to
158    /// the offered directory capability.
159    #[serde(skip_serializing_if = "Option::is_none")]
160    #[reference_doc(json_type = "array of string")]
161    pub rights: Option<Rights>,
162
163    /// (`directory` only) the relative path of a subdirectory within the source directory
164    /// capability to route.
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub subdir: Option<RelativePath>,
167
168    /// (`event_stream` only) the name(s) of the event streams being offered.
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub event_stream: Option<OneOrMany<Name>>,
171
172    /// (`event_stream` only) When defined the event stream will contain events about only the
173    /// components defined in the scope.
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub scope: Option<OneOrMany<EventScope>>,
176
177    /// `availability` _(optional)_: The expectations around this capability's availability. Affects
178    /// build-time and runtime route validation. One of:
179    /// - `required` (default): a required dependency, the source must exist and provide it. Use
180    ///     this when the target of this offer requires this capability to function properly.
181    /// - `optional`: an optional dependency. Use this when the target of the offer can function
182    ///     with or without this capability. The target must not have a `required` dependency on the
183    ///     capability. The ultimate source of this offer must be `void` or an actual component.
184    /// - `same_as_target`: the availability expectations of this capability will match the
185    ///     target's. If the target requires the capability, then this field is set to `required`.
186    ///     If the target has an optional dependency on the capability, then the field is set to
187    ///     `optional`.
188    /// - `transitional`: like `optional`, but will tolerate a missing source. Use this
189    ///     only to avoid validation errors during transitional periods of multi-step code changes.
190    ///
191    /// For more information, see the
192    /// [availability](/docs/concepts/components/v2/capabilities/availability.md) documentation.
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub availability: Option<Availability>,
195
196    /// Whether or not the source of this offer must exist. One of:
197    /// - `required` (default): the source (`from`) must be defined in this manifest.
198    /// - `unknown`: the source of this offer will be rewritten to `void` if its source (`from`)
199    ///     is not defined in this manifest after includes are processed.
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub source_availability: Option<SourceAvailability>,
202
203    /// Whether or not the target of this offer must exist. One of:
204    /// - `required` (default): the target (`to`) must be defined in this
205    ///   manifest.
206    /// - `unknown`: this offer is omitted if its target (`to`) is not defined
207    ///     in this manifest after includes are processed.
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub target_availability: Option<TargetAvailability>,
210}
211
212impl Offer {
213    /// Creates a new empty offer. This offer just has the `from` and `to` fields set, so to make
214    /// it useful it needs at least the capability name set in the necessary attribute.
215    pub fn empty(from: OneOrMany<OfferFromRef>, to: OneOrMany<OfferToRef>) -> Offer {
216        Self {
217            protocol: None,
218            from,
219            to,
220            r#as: None,
221            service: None,
222            directory: None,
223            config: None,
224            runner: None,
225            resolver: None,
226            storage: None,
227            dictionary: None,
228            dependency: None,
229            rights: None,
230            subdir: None,
231            event_stream: None,
232            scope: None,
233            availability: None,
234            source_availability: None,
235            target_availability: None,
236        }
237    }
238}
239
240impl Default for Offer {
241    fn default() -> Self {
242        Self {
243            from: OneOrMany::One(OfferFromRef::Self_),
244            to: OneOrMany::Many(vec![]),
245            service: None,
246            protocol: None,
247            directory: None,
248            storage: None,
249            runner: None,
250            resolver: None,
251            dictionary: None,
252            config: None,
253            r#as: None,
254            rights: None,
255            subdir: None,
256            dependency: None,
257            event_stream: None,
258            scope: None,
259            availability: None,
260            source_availability: None,
261            target_availability: None,
262        }
263    }
264}
265
266impl FromClause for Offer {
267    fn from_(&self) -> OneOrMany<AnyRef<'_>> {
268        one_or_many_from_impl(&self.from)
269    }
270}
271
272impl Canonicalize for Offer {
273    fn canonicalize(&mut self) {
274        // Sort the names of the capabilities. Only capabilities with OneOrMany values are included here.
275        if let Some(service) = &mut self.service {
276            service.canonicalize();
277        } else if let Some(protocol) = &mut self.protocol {
278            protocol.canonicalize();
279        } else if let Some(directory) = &mut self.directory {
280            directory.canonicalize();
281        } else if let Some(runner) = &mut self.runner {
282            runner.canonicalize();
283        } else if let Some(resolver) = &mut self.resolver {
284            resolver.canonicalize();
285        } else if let Some(storage) = &mut self.storage {
286            storage.canonicalize();
287        } else if let Some(event_stream) = &mut self.event_stream {
288            event_stream.canonicalize();
289            if let Some(scope) = &mut self.scope {
290                scope.canonicalize();
291            }
292        }
293    }
294}
295
296impl CapabilityClause for Offer {
297    fn service(&self) -> Option<OneOrMany<&BorrowedName>> {
298        option_one_or_many_as_ref(&self.service)
299    }
300    fn protocol(&self) -> Option<OneOrMany<&BorrowedName>> {
301        option_one_or_many_as_ref(&self.protocol)
302    }
303    fn directory(&self) -> Option<OneOrMany<&BorrowedName>> {
304        option_one_or_many_as_ref(&self.directory)
305    }
306    fn storage(&self) -> Option<OneOrMany<&BorrowedName>> {
307        option_one_or_many_as_ref(&self.storage)
308    }
309    fn runner(&self) -> Option<OneOrMany<&BorrowedName>> {
310        option_one_or_many_as_ref(&self.runner)
311    }
312    fn resolver(&self) -> Option<OneOrMany<&BorrowedName>> {
313        option_one_or_many_as_ref(&self.resolver)
314    }
315    fn event_stream(&self) -> Option<OneOrMany<&BorrowedName>> {
316        option_one_or_many_as_ref(&self.event_stream)
317    }
318    fn dictionary(&self) -> Option<OneOrMany<&BorrowedName>> {
319        option_one_or_many_as_ref(&self.dictionary)
320    }
321    fn config(&self) -> Option<OneOrMany<&BorrowedName>> {
322        option_one_or_many_as_ref(&self.config)
323    }
324
325    fn set_service(&mut self, o: Option<OneOrMany<Name>>) {
326        self.service = o;
327    }
328    fn set_protocol(&mut self, o: Option<OneOrMany<Name>>) {
329        self.protocol = o;
330    }
331    fn set_directory(&mut self, o: Option<OneOrMany<Name>>) {
332        self.directory = o;
333    }
334    fn set_storage(&mut self, o: Option<OneOrMany<Name>>) {
335        self.storage = o;
336    }
337    fn set_runner(&mut self, o: Option<OneOrMany<Name>>) {
338        self.runner = o;
339    }
340    fn set_resolver(&mut self, o: Option<OneOrMany<Name>>) {
341        self.resolver = o;
342    }
343    fn set_event_stream(&mut self, o: Option<OneOrMany<Name>>) {
344        self.event_stream = o;
345    }
346    fn set_dictionary(&mut self, o: Option<OneOrMany<Name>>) {
347        self.dictionary = o;
348    }
349    fn set_config(&mut self, o: Option<OneOrMany<Name>>) {
350        self.config = o
351    }
352
353    fn availability(&self) -> Option<Availability> {
354        self.availability
355    }
356    fn set_availability(&mut self, a: Option<Availability>) {
357        self.availability = a;
358    }
359
360    fn decl_type(&self) -> &'static str {
361        "offer"
362    }
363    fn supported(&self) -> &[&'static str] {
364        &[
365            "service",
366            "protocol",
367            "directory",
368            "storage",
369            "runner",
370            "resolver",
371            "event_stream",
372            "config",
373        ]
374    }
375    fn are_many_names_allowed(&self) -> bool {
376        [
377            "service",
378            "protocol",
379            "directory",
380            "storage",
381            "runner",
382            "resolver",
383            "event_stream",
384            "config",
385        ]
386        .contains(&self.capability_type().unwrap())
387    }
388}
389
390impl PathClause for Offer {
391    fn path(&self) -> Option<&Path> {
392        None
393    }
394}
395
396impl RightsClause for Offer {
397    fn rights(&self) -> Option<&Rights> {
398        self.rights.as_ref()
399    }
400}
401
402impl AsClause for Offer {
403    fn r#as(&self) -> Option<&BorrowedName> {
404        self.r#as.as_ref().map(Name::as_ref)
405    }
406}
407
408/// A reference in an `offer to`.
409#[derive(Debug, Deserialize, PartialEq, Eq, Hash, Clone, Serialize)]
410#[serde(rename_all = "snake_case")]
411pub enum TargetAvailability {
412    Required,
413    Unknown,
414}
415
416#[derive(PartialEq, Clone)]
417pub enum OfferToAllCapability<'a> {
418    Dictionary(&'a str),
419    Protocol(&'a str),
420}
421
422impl<'a> OfferToAllCapability<'a> {
423    pub fn name(&self) -> &'a str {
424        match self {
425            OfferToAllCapability::Dictionary(name) => name,
426            OfferToAllCapability::Protocol(name) => name,
427        }
428    }
429
430    pub fn offer_type(&self) -> &'static str {
431        match self {
432            OfferToAllCapability::Dictionary(_) => "Dictionary",
433            OfferToAllCapability::Protocol(_) => "Protocol",
434        }
435    }
436
437    pub fn offer_type_plural(&self) -> &'static str {
438        match self {
439            OfferToAllCapability::Dictionary(_) => "dictionaries",
440            OfferToAllCapability::Protocol(_) => "protocols",
441        }
442    }
443}
444
445pub fn offer_to_all_from_offer(value: &Offer) -> impl Iterator<Item = OfferToAllCapability<'_>> {
446    if let Some(protocol) = &value.protocol {
447        Either::Left(
448            protocol.iter().map(|protocol| OfferToAllCapability::Protocol(protocol.as_str())),
449        )
450    } else if let Some(dictionary) = &value.dictionary {
451        Either::Right(
452            dictionary
453                .iter()
454                .map(|dictionary| OfferToAllCapability::Dictionary(dictionary.as_str())),
455        )
456    } else {
457        panic!("Expected a dictionary or a protocol");
458    }
459}
460pub fn offer_to_all_and_component_diff_sources_message<'a>(
461    capability: impl Iterator<Item = OfferToAllCapability<'a>>,
462    component: &str,
463) -> String {
464    let mut output = String::new();
465    let mut capability = capability.peekable();
466    write!(&mut output, "{} ", capability.peek().unwrap().offer_type()).unwrap();
467    for (i, capability) in capability.enumerate() {
468        if i > 0 {
469            write!(&mut output, ", ").unwrap();
470        }
471        write!(&mut output, "{}", capability.name()).unwrap();
472    }
473    write!(
474        &mut output,
475        r#" is offered to both "all" and child component "{}" with different sources"#,
476        component
477    )
478    .unwrap();
479    output
480}
481
482pub fn offer_to_all_and_component_diff_capabilities_message<'a>(
483    capability: impl Iterator<Item = OfferToAllCapability<'a>>,
484    component: &str,
485) -> String {
486    let mut output = String::new();
487    let mut capability_peek = capability.peekable();
488
489    // Clone is needed so the iterator can be moved forward.
490    // This doesn't actually allocate memory or copy a string, as only the reference
491    // held by the OfferToAllCapability<'a> is copied.
492    let first_offer_to_all = capability_peek.peek().unwrap().clone();
493    write!(&mut output, "{} ", first_offer_to_all.offer_type()).unwrap();
494    for (i, capability) in capability_peek.enumerate() {
495        if i > 0 {
496            write!(&mut output, ", ").unwrap();
497        }
498        write!(&mut output, "{}", capability.name()).unwrap();
499    }
500    write!(&mut output, r#" is aliased to "{}" with the same name as an offer to "all", but from different source {}"#, component, first_offer_to_all.offer_type_plural()).unwrap();
501    output
502}
503
504/// Returns `Ok(true)` if desugaring the `offer_to_all` using `name` duplicates
505/// `specific_offer`. Returns `Ok(false)` if not a duplicate.
506///
507/// Returns Err if there is a validation error.
508pub fn offer_to_all_would_duplicate(
509    offer_to_all: &Offer,
510    specific_offer: &Offer,
511    target: &cm_types::BorrowedName,
512) -> Result<bool, Error> {
513    // Only protocols and dictionaries may be offered to all
514    assert!(offer_to_all.protocol.is_some() || offer_to_all.dictionary.is_some());
515
516    // If none of the pairs of the cross products of the two offer's protocols
517    // match, then the offer is certainly not a duplicate
518    if CapabilityId::from_offer_expose(specific_offer).iter().flatten().all(
519        |specific_offer_cap_id| {
520            CapabilityId::from_offer_expose(offer_to_all)
521                .iter()
522                .flatten()
523                .all(|offer_to_all_cap_id| offer_to_all_cap_id != specific_offer_cap_id)
524        },
525    ) {
526        return Ok(false);
527    }
528
529    let to_field_matches = specific_offer.to.iter().any(
530        |specific_offer_to| matches!(specific_offer_to, OfferToRef::Named(c) if **c == *target),
531    );
532
533    if !to_field_matches {
534        return Ok(false);
535    }
536
537    if offer_to_all.from != specific_offer.from {
538        return Err(Error::validate(offer_to_all_and_component_diff_sources_message(
539            offer_to_all_from_offer(offer_to_all),
540            target.as_str(),
541        )));
542    }
543
544    // Since the capability ID's match, the underlying protocol must also match
545    if offer_to_all_from_offer(offer_to_all).all(|to_all_protocol| {
546        offer_to_all_from_offer(specific_offer)
547            .all(|to_specific_protocol| to_all_protocol != to_specific_protocol)
548    }) {
549        return Err(Error::validate(offer_to_all_and_component_diff_capabilities_message(
550            offer_to_all_from_offer(offer_to_all),
551            target.as_str(),
552        )));
553    }
554
555    Ok(true)
556}
557
558/// A reference in an `offer from`.
559#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
560#[reference(
561    expected = "\"parent\", \"framework\", \"self\", \"void\", \"#<child-name>\", or a dictionary path"
562)]
563pub enum OfferFromRef {
564    /// A reference to a child or collection.
565    Named(Name),
566    /// A reference to the parent.
567    Parent,
568    /// A reference to the framework.
569    Framework,
570    /// A reference to this component.
571    Self_,
572    /// An intentionally omitted source.
573    Void,
574    /// A reference to a dictionary.
575    Dictionary(DictionaryRef),
576}
577
578impl OfferFromRef {
579    pub fn is_named(&self) -> bool {
580        match self {
581            OfferFromRef::Named(_) => true,
582            _ => false,
583        }
584    }
585}
586
587/// A reference in an `offer to`.
588#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
589#[reference(expected = "\"#<child-name>\", \"#<collection-name>\", or \"self/<dictionary>\"")]
590pub enum OfferToRef {
591    /// A reference to a child or collection.
592    Named(Name),
593
594    /// Syntax sugar that results in the offer decl applying to all children and collections
595    All,
596
597    /// A reference to a dictionary defined by this component, the form "self/<dictionary>".
598    OwnDictionary(Name),
599}
600
601/// Generates deserializer for `OneOrMany<OfferToRef>`.
602#[derive(OneOrMany, Debug, Clone)]
603#[one_or_many(
604    expected = "one or an array of \"#<child-name>\", \"#<collection-name>\", or \"self/<dictionary>\", with unique elements",
605    inner_type = "OfferToRef",
606    min_length = 1,
607    unique_items = true
608)]
609pub struct OneOrManyOfferToRefs;
610
611/// Generates deserializer for `OneOrMany<OfferFromRef>`.
612#[derive(OneOrMany, Debug, Clone)]
613#[one_or_many(
614    expected = "one or an array of \"parent\", \"framework\", \"self\", \"#<child-name>\", \"#<collection-name>\", or a dictionary path",
615    inner_type = "OfferFromRef",
616    min_length = 1,
617    unique_items = true
618)]
619pub struct OneOrManyOfferFromRefs;
620
621#[derive(Debug, Clone, Serialize)]
622pub struct ContextOffer {
623    #[serde(skip)]
624    pub origin: Arc<PathBuf>,
625    #[serde(skip_serializing_if = "Option::is_none")]
626    pub service: Option<ContextSpanned<OneOrMany<Name>>>,
627    #[serde(skip_serializing_if = "Option::is_none")]
628    pub protocol: Option<ContextSpanned<OneOrMany<Name>>>,
629    #[serde(skip_serializing_if = "Option::is_none")]
630    pub directory: Option<ContextSpanned<OneOrMany<Name>>>,
631    #[serde(skip_serializing_if = "Option::is_none")]
632    pub runner: Option<ContextSpanned<OneOrMany<Name>>>,
633    #[serde(skip_serializing_if = "Option::is_none")]
634    pub resolver: Option<ContextSpanned<OneOrMany<Name>>>,
635    #[serde(skip_serializing_if = "Option::is_none")]
636    pub storage: Option<ContextSpanned<OneOrMany<Name>>>,
637    #[serde(skip_serializing_if = "Option::is_none")]
638    pub dictionary: Option<ContextSpanned<OneOrMany<Name>>>,
639    #[serde(skip_serializing_if = "Option::is_none")]
640    pub config: Option<ContextSpanned<OneOrMany<Name>>>,
641    pub from: ContextSpanned<OneOrMany<OfferFromRef>>,
642    pub to: ContextSpanned<OneOrMany<OfferToRef>>,
643    #[serde(skip_serializing_if = "Option::is_none")]
644    pub r#as: Option<ContextSpanned<Name>>,
645    #[serde(skip_serializing_if = "Option::is_none")]
646    pub dependency: Option<ContextSpanned<DependencyType>>,
647    #[serde(skip_serializing_if = "Option::is_none")]
648    pub rights: Option<ContextSpanned<Rights>>,
649    #[serde(skip_serializing_if = "Option::is_none")]
650    pub subdir: Option<ContextSpanned<RelativePath>>,
651    #[serde(skip_serializing_if = "Option::is_none")]
652    pub event_stream: Option<ContextSpanned<OneOrMany<Name>>>,
653    #[serde(skip_serializing_if = "Option::is_none")]
654    pub scope: Option<ContextSpanned<OneOrMany<EventScope>>>,
655    #[serde(skip_serializing_if = "Option::is_none")]
656    pub availability: Option<ContextSpanned<Availability>>,
657    #[serde(skip_serializing_if = "Option::is_none")]
658    pub source_availability: Option<ContextSpanned<SourceAvailability>>,
659    #[serde(skip_serializing_if = "Option::is_none")]
660    pub target_availability: Option<ContextSpanned<TargetAvailability>>,
661}
662
663impl ContextCapabilityClause for ContextOffer {
664    fn service(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
665        option_one_or_many_as_ref_context(&self.service)
666    }
667    fn protocol(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
668        option_one_or_many_as_ref_context(&self.protocol)
669    }
670    fn directory(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
671        option_one_or_many_as_ref_context(&self.directory)
672    }
673    fn storage(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
674        option_one_or_many_as_ref_context(&self.storage)
675    }
676    fn runner(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
677        option_one_or_many_as_ref_context(&self.runner)
678    }
679    fn resolver(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
680        option_one_or_many_as_ref_context(&self.resolver)
681    }
682    fn event_stream(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
683        option_one_or_many_as_ref_context(&self.event_stream)
684    }
685    fn dictionary(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
686        option_one_or_many_as_ref_context(&self.dictionary)
687    }
688    fn config(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
689        option_one_or_many_as_ref_context(&self.config)
690    }
691
692    fn decl_type(&self) -> &'static str {
693        "offer"
694    }
695    fn supported(&self) -> &[&'static str] {
696        &[
697            "service",
698            "protocol",
699            "directory",
700            "storage",
701            "event_stream",
702            "runner",
703            "resolver",
704            "config",
705        ]
706    }
707    fn are_many_names_allowed(&self) -> bool {
708        [
709            "service",
710            "protocol",
711            "directory",
712            "storage",
713            "runner",
714            "resolver",
715            "event_stream",
716            "config",
717        ]
718        .contains(&self.capability_type(None).unwrap())
719    }
720
721    fn set_service(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
722        self.service = o;
723    }
724
725    fn set_protocol(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
726        self.protocol = o;
727    }
728
729    fn set_directory(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
730        self.directory = o;
731    }
732
733    fn set_storage(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
734        self.storage = o;
735    }
736
737    fn set_runner(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
738        self.runner = o;
739    }
740    fn set_resolver(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
741        self.resolver = o;
742    }
743    fn set_event_stream(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
744        self.event_stream = o;
745    }
746    fn set_dictionary(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
747        self.dictionary = o;
748    }
749    fn set_config(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
750        self.config = o;
751    }
752
753    fn origin(&self) -> &Arc<PathBuf> {
754        &self.origin
755    }
756
757    fn availability(&self) -> Option<ContextSpanned<Availability>> {
758        self.availability.clone()
759    }
760    fn set_availability(&mut self, a: Option<ContextSpanned<Availability>>) {
761        self.availability = a;
762    }
763}
764
765impl CanonicalizeContext for ContextOffer {
766    fn canonicalize_context(&mut self) {
767        // Sort the names of the capabilities. Only capabilities with OneOrMany values are included here.
768        if let Some(service) = &mut self.service {
769            service.value.canonicalize_context();
770        } else if let Some(protocol) = &mut self.protocol {
771            protocol.value.canonicalize_context();
772        } else if let Some(directory) = &mut self.directory {
773            directory.value.canonicalize_context();
774        } else if let Some(runner) = &mut self.runner {
775            runner.value.canonicalize_context();
776        } else if let Some(resolver) = &mut self.resolver {
777            resolver.value.canonicalize_context();
778        } else if let Some(storage) = &mut self.storage {
779            storage.value.canonicalize_context();
780        } else if let Some(event_stream) = &mut self.event_stream {
781            event_stream.value.canonicalize_context();
782            if let Some(scope) = &mut self.scope {
783                scope.value.canonicalize_context();
784            }
785        }
786    }
787}
788
789impl PartialEq for ContextOffer {
790    fn eq(&self, other: &Self) -> bool {
791        macro_rules! cmp {
792            ($field:ident) => {
793                match (&self.$field, &other.$field) {
794                    (Some(a), Some(b)) => a.value == b.value,
795                    (None, None) => true,
796                    _ => false,
797                }
798            };
799        }
800
801        cmp!(service)
802            && cmp!(protocol)
803            && cmp!(directory)
804            && cmp!(runner)
805            && cmp!(resolver)
806            && cmp!(storage)
807            && cmp!(dictionary)
808            && cmp!(config)
809            && self.from.value == other.from.value
810            && self.to.value == other.to.value
811            && cmp!(r#as)
812            && cmp!(dependency)
813            && cmp!(rights)
814            && cmp!(subdir)
815            && cmp!(event_stream)
816            && cmp!(scope)
817            && cmp!(availability)
818            && cmp!(source_availability)
819            && cmp!(target_availability)
820    }
821}
822
823impl Eq for ContextOffer {}
824
825impl Default for ContextOffer {
826    fn default() -> Self {
827        let synthetic_origin = Arc::new(PathBuf::from("synthetic"));
828
829        Self {
830            from: ContextSpanned {
831                value: OneOrMany::One(OfferFromRef::Self_),
832                origin: synthetic_origin.clone(),
833            },
834            to: ContextSpanned { value: OneOrMany::Many(vec![]), origin: synthetic_origin.clone() },
835            origin: synthetic_origin,
836            service: None,
837            protocol: None,
838            directory: None,
839            storage: None,
840            runner: None,
841            resolver: None,
842            dictionary: None,
843            config: None,
844            r#as: None,
845            rights: None,
846            subdir: None,
847            dependency: None,
848            event_stream: None,
849            scope: None,
850            availability: None,
851            source_availability: None,
852            target_availability: None,
853        }
854    }
855}
856
857impl ContextPathClause for ContextOffer {
858    fn path(&self) -> Option<&ContextSpanned<Path>> {
859        None
860    }
861}
862
863impl AsClauseContext for ContextOffer {
864    fn r#as(&self) -> Option<ContextSpanned<&BorrowedName>> {
865        self.r#as.as_ref().map(|spanned_name| ContextSpanned {
866            value: spanned_name.value.as_ref(),
867            origin: spanned_name.origin.clone(),
868        })
869    }
870}
871
872impl FromClauseContext for ContextOffer {
873    fn from_(&self) -> ContextSpanned<OneOrMany<AnyRef<'_>>> {
874        one_or_many_from_context(&self.from)
875    }
876}
877
878impl Hydrate for Offer {
879    type Output = ContextOffer;
880
881    fn hydrate(self, file: &Arc<PathBuf>) -> Result<Self::Output, Error> {
882        Ok(ContextOffer {
883            origin: file.clone(),
884            service: hydrate_opt_simple(self.service, file),
885            protocol: hydrate_opt_simple(self.protocol, file),
886            directory: hydrate_opt_simple(self.directory, file),
887            runner: hydrate_opt_simple(self.runner, file),
888            resolver: hydrate_opt_simple(self.resolver, file),
889            storage: hydrate_opt_simple(self.storage, file),
890            dictionary: hydrate_opt_simple(self.dictionary, file),
891            config: hydrate_opt_simple(self.config, file),
892            from: hydrate_simple(self.from, file),
893            to: hydrate_simple(self.to, file),
894            r#as: hydrate_opt_simple(self.r#as, file),
895            dependency: hydrate_opt_simple(self.dependency, file),
896            rights: hydrate_opt_simple(self.rights, file),
897            subdir: hydrate_opt_simple(self.subdir, file),
898            event_stream: hydrate_opt_simple(self.event_stream, file),
899            scope: hydrate_opt_simple(self.scope, file),
900            availability: hydrate_opt_simple(self.availability, file),
901            source_availability: hydrate_opt_simple(self.source_availability, file),
902            target_availability: hydrate_opt_simple(self.target_availability, file),
903        })
904    }
905}
906
907pub fn offer_to_all_from_context_offer(
908    value: &ContextOffer,
909) -> impl Iterator<Item = OfferToAllCapability<'_>> {
910    if let Some(protocol) = &value.protocol {
911        Either::Left(
912            protocol.value.iter().map(|protocol| OfferToAllCapability::Protocol(protocol.as_str())),
913        )
914    } else if let Some(dictionary) = &value.dictionary {
915        Either::Right(
916            dictionary
917                .value
918                .iter()
919                .map(|dictionary| OfferToAllCapability::Dictionary(dictionary.as_str())),
920        )
921    } else {
922        panic!("Expected a dictionary or a protocol");
923    }
924}
925
926/// Returns `Ok(true)` if desugaring the `offer_to_all` using `name` duplicates
927/// `specific_offer`. Returns `Ok(false)` if not a duplicate.
928///
929/// Returns Err if there is a validation error.
930pub fn offer_to_all_would_duplicate_context(
931    offer_to_all: &ContextSpanned<ContextOffer>,
932    specific_offer: &ContextSpanned<ContextOffer>,
933    target: &cm_types::BorrowedName,
934) -> Result<bool, Error> {
935    // Only protocols and dictionaries may be offered to all
936    assert!(offer_to_all.value.protocol.is_some() || offer_to_all.value.dictionary.is_some());
937
938    // If none of the pairs of the cross products of the two offer's protocols
939    // match, then the offer is certainly not a duplicate
940    if CapabilityId::from_context_offer(specific_offer).iter().flatten().all(
941        |specific_offer_cap_id| {
942            CapabilityId::from_context_offer(offer_to_all)
943                .iter()
944                .flatten()
945                .all(|offer_to_all_cap_id| offer_to_all_cap_id.0 != specific_offer_cap_id.0)
946        },
947    ) {
948        return Ok(false);
949    }
950
951    let to_field_matches = specific_offer.value.to.value.iter().any(
952        |specific_offer_to| matches!(specific_offer_to, OfferToRef::Named(c) if *c == *target),
953    );
954
955    if !to_field_matches {
956        return Ok(false);
957    }
958
959    if offer_to_all.value.from != specific_offer.value.from {
960        return Err(Error::validate_contexts(
961            offer_to_all_and_component_diff_sources_message(
962                offer_to_all_from_context_offer(&offer_to_all.value),
963                target.as_str(),
964            ),
965            vec![offer_to_all.origin.clone(), specific_offer.origin.clone()],
966        ));
967    }
968
969    // Since the capability ID's match, the underlying protocol must also match
970    if offer_to_all_from_context_offer(&offer_to_all.value).all(|to_all_protocol| {
971        offer_to_all_from_context_offer(&specific_offer.value)
972            .all(|to_specific_protocol| to_all_protocol != to_specific_protocol)
973    }) {
974        return Err(Error::validate_contexts(
975            offer_to_all_and_component_diff_capabilities_message(
976                offer_to_all_from_context_offer(&offer_to_all.value),
977                target.as_str(),
978            ),
979            vec![offer_to_all.origin.clone(), specific_offer.origin.clone()],
980        ));
981    }
982
983    Ok(true)
984}
985
986#[cfg(test)]
987pub fn create_offer(
988    protocol_name: &str,
989    from: OneOrMany<OfferFromRef>,
990    to: OneOrMany<OfferToRef>,
991) -> Offer {
992    Offer {
993        protocol: Some(OneOrMany::One(Name::from_str(protocol_name).unwrap())),
994        ..Offer::empty(from, to)
995    }
996}
997
998#[cfg(test)]
999mod tests {
1000    use super::*;
1001
1002    #[test]
1003    fn test_offer_would_duplicate() {
1004        let offer = create_offer(
1005            "fuchsia.logger.LegacyLog",
1006            OneOrMany::One(OfferFromRef::Parent {}),
1007            OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
1008        );
1009
1010        let offer_to_all = create_offer(
1011            "fuchsia.logger.LogSink",
1012            OneOrMany::One(OfferFromRef::Parent {}),
1013            OneOrMany::One(OfferToRef::All),
1014        );
1015
1016        // different protocols
1017        assert!(
1018            !offer_to_all_would_duplicate(
1019                &offer_to_all,
1020                &offer,
1021                &Name::from_str("something").unwrap()
1022            )
1023            .unwrap()
1024        );
1025
1026        let offer = create_offer(
1027            "fuchsia.logger.LogSink",
1028            OneOrMany::One(OfferFromRef::Parent {}),
1029            OneOrMany::One(OfferToRef::Named(Name::from_str("not-something").unwrap())),
1030        );
1031
1032        // different targets
1033        assert!(
1034            !offer_to_all_would_duplicate(
1035                &offer_to_all,
1036                &offer,
1037                &Name::from_str("something").unwrap()
1038            )
1039            .unwrap()
1040        );
1041
1042        let mut offer = create_offer(
1043            "fuchsia.logger.LogSink",
1044            OneOrMany::One(OfferFromRef::Parent {}),
1045            OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
1046        );
1047
1048        offer.r#as = Some(Name::from_str("FakeLog").unwrap());
1049
1050        // target has alias
1051        assert!(
1052            !offer_to_all_would_duplicate(
1053                &offer_to_all,
1054                &offer,
1055                &Name::from_str("something").unwrap()
1056            )
1057            .unwrap()
1058        );
1059
1060        let offer = create_offer(
1061            "fuchsia.logger.LogSink",
1062            OneOrMany::One(OfferFromRef::Parent {}),
1063            OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
1064        );
1065
1066        assert!(
1067            offer_to_all_would_duplicate(
1068                &offer_to_all,
1069                &offer,
1070                &Name::from_str("something").unwrap()
1071            )
1072            .unwrap()
1073        );
1074
1075        let offer = create_offer(
1076            "fuchsia.logger.LogSink",
1077            OneOrMany::One(OfferFromRef::Named(Name::from_str("other").unwrap())),
1078            OneOrMany::One(OfferToRef::Named(Name::from_str("something").unwrap())),
1079        );
1080
1081        assert!(
1082            offer_to_all_would_duplicate(
1083                &offer_to_all,
1084                &offer,
1085                &Name::from_str("something").unwrap()
1086            )
1087            .is_err()
1088        );
1089    }
1090}