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