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