Skip to main content

cml/types/
environment.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::one_or_many::{OneOrMany, option_one_or_many_as_ref};
6use crate::types::common::*;
7use crate::{
8    AnyRef, AsClause, AsClauseContext, CapabilityClause, ContextPathClause, Error, FromClause,
9    FromClauseContext, OfferFromRef, PathClause, merge_spanned_vec,
10};
11pub use cm_types::{
12    Availability, BorrowedName, BoundedName, DeliveryType, DependencyType, HandleType, Name,
13    OnTerminate, ParseError, Path, RelativePath, StartupMode, StorageId, Url,
14};
15use cml_macro::Reference;
16use reference_doc::ReferenceDoc;
17use serde::{Deserialize, Serialize, de};
18
19use std::fmt;
20use std::path::PathBuf;
21use std::sync::Arc;
22
23/// Example:
24///
25/// ```json5
26/// environments: [
27///     {
28///         name: "test-env",
29///         extends: "realm",
30///         runners: [
31///             {
32///                 runner: "gtest-runner",
33///                 from: "#gtest",
34///             },
35///         ],
36///         resolvers: [
37///             {
38///                 resolver: "full-resolver",
39///                 from: "parent",
40///                 scheme: "fuchsia-pkg",
41///             },
42///         ],
43///     },
44/// ],
45/// ```
46#[derive(Deserialize, Debug, PartialEq, ReferenceDoc, Serialize)]
47#[serde(deny_unknown_fields)]
48#[reference_doc(fields_as = "list", top_level_doc_after_fields)]
49pub struct Environment {
50    /// The name of the environment, which is a string of one or more of the
51    /// following characters: `a-z`, `0-9`, `_`, `.`, `-`. The name identifies this
52    /// environment when used in a [reference](#references).
53    pub name: Name,
54
55    /// How the environment should extend this realm's environment.
56    /// - `realm`: Inherit all properties from this component's environment.
57    /// - `none`: Start with an empty environment, do not inherit anything.
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub extends: Option<EnvironmentExtends>,
60
61    /// The runners registered in the environment. An array of objects
62    /// with the following properties:
63    #[reference_doc(recurse)]
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub runners: Option<Vec<RunnerRegistration>>,
66
67    /// The resolvers registered in the environment. An array of
68    /// objects with the following properties:
69    #[reference_doc(recurse)]
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub resolvers: Option<Vec<ResolverRegistration>>,
72
73    /// Debug protocols available to any component in this environment acquired
74    /// through `use from debug`.
75    #[reference_doc(recurse)]
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub debug: Option<Vec<DebugRegistration>>,
78
79    /// The number of milliseconds to wait, after notifying a component in this environment that it
80    /// should terminate, before forcibly killing it. This field is required if the environment
81    /// extends from `none`.
82    #[serde(rename = "__stop_timeout_ms")]
83    #[reference_doc(json_type = "number", rename = "__stop_timeout_ms")]
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub stop_timeout_ms: Option<StopTimeoutMs>,
86}
87
88impl Environment {
89    pub fn merge_from(&mut self, other: &mut Self) -> Result<(), Error> {
90        if self.extends.is_none() {
91            self.extends = other.extends.take();
92        } else if other.extends.is_some() && other.extends != self.extends {
93            return Err(Error::validate(
94                "cannot merge `environments` that declare conflicting `extends`",
95            ));
96        }
97
98        if self.stop_timeout_ms.is_none() {
99            self.stop_timeout_ms = other.stop_timeout_ms;
100        } else if other.stop_timeout_ms.is_some() && other.stop_timeout_ms != self.stop_timeout_ms {
101            return Err(Error::validate(
102                "cannot merge `environments` that declare conflicting `stop_timeout_ms`",
103            ));
104        }
105
106        // Perform naive vector concatenation and rely on later validation to ensure
107        // no conflicting entries.
108        match &mut self.runners {
109            Some(r) => {
110                if let Some(o) = &mut other.runners {
111                    r.append(o);
112                }
113            }
114            None => self.runners = other.runners.take(),
115        }
116
117        match &mut self.resolvers {
118            Some(r) => {
119                if let Some(o) = &mut other.resolvers {
120                    r.append(o);
121                }
122            }
123            None => self.resolvers = other.resolvers.take(),
124        }
125
126        match &mut self.debug {
127            Some(r) => {
128                if let Some(o) = &mut other.debug {
129                    r.append(o);
130                }
131            }
132            None => self.debug = other.debug.take(),
133        }
134        Ok(())
135    }
136}
137
138/// A reference in an environment.
139#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
140#[reference(expected = "\"#<environment-name>\"")]
141pub enum EnvironmentRef {
142    /// A reference to an environment defined in this component.
143    Named(Name),
144}
145
146#[derive(Deserialize, Debug, PartialEq, Serialize)]
147#[serde(rename_all = "lowercase")]
148pub enum EnvironmentExtends {
149    Realm,
150    None,
151}
152
153/// The stop timeout configured in an environment.
154#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
155pub struct StopTimeoutMs(pub u32);
156
157impl<'de> de::Deserialize<'de> for StopTimeoutMs {
158    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159    where
160        D: de::Deserializer<'de>,
161    {
162        struct Visitor;
163
164        impl<'de> de::Visitor<'de> for Visitor {
165            type Value = StopTimeoutMs;
166
167            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168                f.write_str("an unsigned 32-bit integer")
169            }
170
171            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
172            where
173                E: de::Error,
174            {
175                if v < 0 || v > i64::from(u32::max_value()) {
176                    return Err(E::invalid_value(
177                        de::Unexpected::Signed(v),
178                        &"an unsigned 32-bit integer",
179                    ));
180                }
181                Ok(StopTimeoutMs(v as u32))
182            }
183
184            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
185            where
186                E: de::Error,
187            {
188                self.visit_i64(value as i64)
189            }
190        }
191
192        deserializer.deserialize_i64(Visitor)
193    }
194}
195
196/// A reference in an environment registration.
197#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
198#[reference(expected = "\"parent\", \"self\", or \"#<child-name>\"")]
199pub enum RegistrationRef {
200    /// A reference to a child.
201    Named(Name),
202    /// A reference to the parent.
203    Parent,
204    /// A reference to this component.
205    Self_,
206}
207
208#[derive(Deserialize, Debug, PartialEq, ReferenceDoc, Serialize)]
209#[serde(deny_unknown_fields)]
210#[reference_doc(fields_as = "list")]
211pub struct RunnerRegistration {
212    /// The [name](#name) of a runner capability, whose source is specified in `from`.
213    pub runner: Name,
214
215    /// The source of the runner capability, one of:
216    /// - `parent`: The component's parent.
217    /// - `self`: This component.
218    /// - `#<child-name>`: A [reference](#references) to a child component
219    ///     instance.
220    pub from: RegistrationRef,
221
222    /// An explicit name for the runner as it will be known in
223    /// this environment. If omitted, defaults to `runner`.
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub r#as: Option<Name>,
226}
227
228impl Hydrate for RunnerRegistration {
229    type Output = ContextRunnerRegistration;
230
231    fn hydrate(self, file: &Arc<PathBuf>) -> Result<Self::Output, Error> {
232        let runner = hydrate_simple(self.runner, file);
233
234        let r#as = hydrate_opt_simple(self.r#as, file);
235
236        let from = hydrate_simple(self.from, file);
237
238        Ok(ContextRunnerRegistration { runner, r#as, from })
239    }
240}
241
242#[derive(Debug, PartialEq, Serialize)]
243pub struct ContextRunnerRegistration {
244    pub runner: ContextSpanned<Name>,
245    pub from: ContextSpanned<RegistrationRef>,
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub r#as: Option<ContextSpanned<Name>>,
248}
249
250impl FromClauseContext for ContextRunnerRegistration {
251    fn from_(&self) -> ContextSpanned<OneOrMany<AnyRef<'_>>> {
252        let origin = self.from.origin.clone();
253        let value = OneOrMany::One(AnyRef::from(&self.from.value));
254
255        ContextSpanned { value, origin }
256    }
257}
258
259impl FromClause for RunnerRegistration {
260    fn from_(&self) -> OneOrMany<AnyRef<'_>> {
261        OneOrMany::One(AnyRef::from(&self.from))
262    }
263}
264
265#[derive(Deserialize, Debug, PartialEq, ReferenceDoc, Serialize)]
266#[serde(deny_unknown_fields)]
267#[reference_doc(fields_as = "list")]
268pub struct ResolverRegistration {
269    /// The [name](#name) of a resolver capability,
270    /// whose source is specified in `from`.
271    pub resolver: Name,
272
273    /// The source of the resolver capability, one of:
274    /// - `parent`: The component's parent.
275    /// - `self`: This component.
276    /// - `#<child-name>`: A [reference](#references) to a child component
277    ///     instance.
278    pub from: RegistrationRef,
279
280    /// The URL scheme for which the resolver should handle
281    /// resolution.
282    pub scheme: cm_types::UrlScheme,
283}
284
285impl Hydrate for ResolverRegistration {
286    type Output = ContextResolverRegistration;
287
288    fn hydrate(self, file: &Arc<PathBuf>) -> Result<Self::Output, Error> {
289        let resolver = hydrate_simple(self.resolver, file);
290
291        let from = hydrate_simple(self.from, file);
292        let scheme = hydrate_simple(self.scheme, file);
293
294        Ok(ContextResolverRegistration { resolver, from, scheme })
295    }
296}
297
298#[derive(Debug, PartialEq, Serialize)]
299pub struct ContextResolverRegistration {
300    pub resolver: ContextSpanned<Name>,
301    pub from: ContextSpanned<RegistrationRef>,
302    pub scheme: ContextSpanned<cm_types::UrlScheme>,
303}
304
305impl FromClause for ResolverRegistration {
306    fn from_(&self) -> OneOrMany<AnyRef<'_>> {
307        OneOrMany::One(AnyRef::from(&self.from))
308    }
309}
310
311impl FromClauseContext for ContextResolverRegistration {
312    fn from_(&self) -> ContextSpanned<OneOrMany<AnyRef<'_>>> {
313        let origin = self.from.origin.clone();
314        let value = OneOrMany::One(AnyRef::from(&self.from.value));
315
316        ContextSpanned { value, origin }
317    }
318}
319
320#[derive(Deserialize, Debug, Clone, PartialEq, ReferenceDoc, Serialize)]
321#[serde(deny_unknown_fields)]
322#[reference_doc(fields_as = "list")]
323pub struct DebugRegistration {
324    /// The name(s) of the protocol(s) to make available.
325    pub protocol: Option<OneOrMany<Name>>,
326
327    /// The source of the capability(s), one of:
328    /// - `parent`: The component's parent.
329    /// - `self`: This component.
330    /// - `#<child-name>`: A [reference](#references) to a child component
331    ///     instance.
332    pub from: OfferFromRef,
333
334    /// If specified, the name that the capability in `protocol` should be made
335    /// available as to clients. Disallowed if `protocol` is an array.
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub r#as: Option<Name>,
338}
339
340impl Hydrate for DebugRegistration {
341    type Output = ContextDebugRegistration;
342
343    fn hydrate(self, file: &Arc<PathBuf>) -> Result<Self::Output, Error> {
344        let origin = file.clone();
345        let protocol = hydrate_opt_simple(self.protocol, file);
346        let from = hydrate_simple(self.from, file);
347        let r#as = hydrate_opt_simple(self.r#as, file);
348
349        Ok(ContextDebugRegistration { origin, protocol, from, r#as })
350    }
351}
352
353#[derive(Debug, Clone, PartialEq, Serialize)]
354pub struct ContextDebugRegistration {
355    #[serde(skip)]
356    pub origin: Arc<PathBuf>,
357    #[serde(skip_serializing_if = "Option::is_none")]
358    pub protocol: Option<ContextSpanned<OneOrMany<Name>>>,
359    pub from: ContextSpanned<OfferFromRef>,
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub r#as: Option<ContextSpanned<Name>>,
362}
363
364impl FromClauseContext for ContextDebugRegistration {
365    fn from_(&self) -> ContextSpanned<OneOrMany<AnyRef<'_>>> {
366        let origin = self.from.origin.clone();
367        let value = OneOrMany::One(AnyRef::from(&self.from.value));
368
369        ContextSpanned { value, origin }
370    }
371}
372
373impl AsClauseContext for ContextDebugRegistration {
374    fn r#as(&self) -> Option<ContextSpanned<&BorrowedName>> {
375        self.r#as.as_ref().map(|spanned_name| ContextSpanned {
376            value: spanned_name.value.as_ref(),
377            origin: spanned_name.origin.clone(),
378        })
379    }
380}
381
382impl ContextPathClause for ContextDebugRegistration {
383    fn path(&self) -> Option<&ContextSpanned<Path>> {
384        None
385    }
386}
387
388impl ContextCapabilityClause for ContextDebugRegistration {
389    fn service(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
390        None
391    }
392    fn protocol(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
393        option_one_or_many_as_ref_context(&self.protocol)
394    }
395    fn directory(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
396        None
397    }
398    fn storage(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
399        None
400    }
401    fn runner(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
402        None
403    }
404    fn resolver(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
405        None
406    }
407    fn event_stream(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
408        None
409    }
410    fn dictionary(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
411        None
412    }
413    fn config(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
414        None
415    }
416
417    fn decl_type(&self) -> &'static str {
418        "debug"
419    }
420    fn supported(&self) -> &[&'static str] {
421        &["service", "protocol"]
422    }
423    fn are_many_names_allowed(&self) -> bool {
424        ["protocol"].contains(&self.capability_type(None).unwrap())
425    }
426
427    fn set_service(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
428    fn set_protocol(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
429        self.protocol = o;
430    }
431    fn set_directory(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
432    fn set_storage(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
433    fn set_runner(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
434    fn set_resolver(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
435    fn set_event_stream(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
436    fn set_dictionary(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
437    fn set_config(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
438
439    fn origin(&self) -> &Arc<PathBuf> {
440        &self.origin
441    }
442
443    fn availability(&self) -> Option<ContextSpanned<Availability>> {
444        None
445    }
446    fn set_availability(&mut self, _a: Option<ContextSpanned<Availability>>) {}
447}
448
449impl AsClause for DebugRegistration {
450    fn r#as(&self) -> Option<&BorrowedName> {
451        self.r#as.as_ref().map(Name::as_ref)
452    }
453}
454
455impl PathClause for DebugRegistration {
456    fn path(&self) -> Option<&Path> {
457        None
458    }
459}
460
461impl FromClause for DebugRegistration {
462    fn from_(&self) -> OneOrMany<AnyRef<'_>> {
463        OneOrMany::One(AnyRef::from(&self.from))
464    }
465}
466
467impl CapabilityClause for DebugRegistration {
468    fn service(&self) -> Option<OneOrMany<&BorrowedName>> {
469        None
470    }
471    fn protocol(&self) -> Option<OneOrMany<&BorrowedName>> {
472        option_one_or_many_as_ref(&self.protocol)
473    }
474    fn directory(&self) -> Option<OneOrMany<&BorrowedName>> {
475        None
476    }
477    fn storage(&self) -> Option<OneOrMany<&BorrowedName>> {
478        None
479    }
480    fn runner(&self) -> Option<OneOrMany<&BorrowedName>> {
481        None
482    }
483    fn resolver(&self) -> Option<OneOrMany<&BorrowedName>> {
484        None
485    }
486    fn event_stream(&self) -> Option<OneOrMany<&BorrowedName>> {
487        None
488    }
489    fn dictionary(&self) -> Option<OneOrMany<&BorrowedName>> {
490        None
491    }
492    fn config(&self) -> Option<OneOrMany<&BorrowedName>> {
493        None
494    }
495
496    fn set_service(&mut self, _o: Option<OneOrMany<Name>>) {}
497    fn set_protocol(&mut self, o: Option<OneOrMany<Name>>) {
498        self.protocol = o;
499    }
500    fn set_directory(&mut self, _o: Option<OneOrMany<Name>>) {}
501    fn set_storage(&mut self, _o: Option<OneOrMany<Name>>) {}
502    fn set_runner(&mut self, _o: Option<OneOrMany<Name>>) {}
503    fn set_resolver(&mut self, _o: Option<OneOrMany<Name>>) {}
504    fn set_event_stream(&mut self, _o: Option<OneOrMany<Name>>) {}
505    fn set_dictionary(&mut self, _o: Option<OneOrMany<Name>>) {}
506    fn set_config(&mut self, _o: Option<OneOrMany<Name>>) {}
507
508    fn availability(&self) -> Option<Availability> {
509        None
510    }
511    fn set_availability(&mut self, _a: Option<Availability>) {}
512
513    fn decl_type(&self) -> &'static str {
514        "debug"
515    }
516    fn supported(&self) -> &[&'static str] {
517        &["service", "protocol"]
518    }
519    fn are_many_names_allowed(&self) -> bool {
520        ["protocol"].contains(&self.capability_type().unwrap())
521    }
522}
523
524impl Hydrate for Environment {
525    type Output = ContextEnvironment;
526
527    fn hydrate(self, file: &Arc<PathBuf>) -> Result<Self::Output, Error> {
528        let name = hydrate_simple(self.name, file);
529
530        let extends = hydrate_opt_simple(self.extends, file);
531        let stop_timeout_ms = hydrate_opt_simple(self.stop_timeout_ms, file);
532
533        let runners = hydrate_list(self.runners, file)?;
534        let resolvers = hydrate_list(self.resolvers, file)?;
535        let debug = hydrate_list(self.debug, file)?;
536
537        Ok(ContextEnvironment { name, extends, runners, resolvers, debug, stop_timeout_ms })
538    }
539}
540
541#[derive(Debug, PartialEq, Serialize)]
542pub struct ContextEnvironment {
543    pub name: ContextSpanned<Name>,
544    #[serde(skip_serializing_if = "Option::is_none")]
545    pub extends: Option<ContextSpanned<EnvironmentExtends>>,
546    #[serde(skip_serializing_if = "Option::is_none")]
547    pub runners: Option<Vec<ContextSpanned<ContextRunnerRegistration>>>,
548    #[serde(skip_serializing_if = "Option::is_none")]
549    pub resolvers: Option<Vec<ContextSpanned<ContextResolverRegistration>>>,
550    #[serde(skip_serializing_if = "Option::is_none")]
551    pub debug: Option<Vec<ContextSpanned<ContextDebugRegistration>>>,
552    #[serde(skip_serializing_if = "Option::is_none")]
553    #[serde(rename = "__stop_timeout_ms")]
554    pub stop_timeout_ms: Option<ContextSpanned<StopTimeoutMs>>,
555}
556
557impl ContextEnvironment {
558    pub fn merge_from(&mut self, mut other: Self) -> Result<(), Error> {
559        if let Some(other_extends) = other.extends.take() {
560            if let Some(my_extends) = &self.extends {
561                if my_extends.value != other_extends.value {
562                    return Err(Error::merge(
563                        format!(
564                            "Conflicting 'extends' field in environment '{}': found '{:?}' and '{:?}'",
565                            self.name.value, my_extends.value, other_extends.value
566                        ),
567                        Some(other_extends.origin),
568                    ));
569                }
570            } else {
571                self.extends = Some(other_extends);
572            }
573        }
574
575        if let Some(other_timeout) = other.stop_timeout_ms.take() {
576            if let Some(my_timeout) = &self.stop_timeout_ms {
577                if my_timeout.value != other_timeout.value {
578                    return Err(Error::merge(
579                        format!(
580                            "Conflicting 'stop_timeout_ms' in environment '{}'",
581                            self.name.value
582                        ),
583                        Some(other_timeout.origin),
584                    ));
585                }
586            } else {
587                self.stop_timeout_ms = Some(other_timeout);
588            }
589        }
590
591        merge_spanned_vec!(self, other, runners);
592        merge_spanned_vec!(self, other, resolvers);
593        merge_spanned_vec!(self, other, debug);
594
595        Ok(())
596    }
597}