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