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