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::{
6    AnyRef, AsClause, CapabilityClause, Error, FromClause, OfferFromRef, PathClause,
7    option_one_or_many_as_ref,
8};
9
10use crate::one_or_many::OneOrMany;
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;
20
21/// Example:
22///
23/// ```json5
24/// environments: [
25///     {
26///         name: "test-env",
27///         extends: "realm",
28///         runners: [
29///             {
30///                 runner: "gtest-runner",
31///                 from: "#gtest",
32///             },
33///         ],
34///         resolvers: [
35///             {
36///                 resolver: "full-resolver",
37///                 from: "parent",
38///                 scheme: "fuchsia-pkg",
39///             },
40///         ],
41///     },
42/// ],
43/// ```
44#[derive(Deserialize, Debug, PartialEq, ReferenceDoc, Serialize)]
45#[serde(deny_unknown_fields)]
46#[reference_doc(fields_as = "list", top_level_doc_after_fields)]
47pub struct Environment {
48    /// The name of the environment, which is a string of one or more of the
49    /// following characters: `a-z`, `0-9`, `_`, `.`, `-`. The name identifies this
50    /// environment when used in a [reference](#references).
51    pub name: Name,
52
53    /// How the environment should extend this realm's environment.
54    /// - `realm`: Inherit all properties from this component's environment.
55    /// - `none`: Start with an empty environment, do not inherit anything.
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub extends: Option<EnvironmentExtends>,
58
59    /// The runners registered in the environment. An array of objects
60    /// with the following properties:
61    #[reference_doc(recurse)]
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub runners: Option<Vec<RunnerRegistration>>,
64
65    /// The resolvers registered in the environment. An array of
66    /// objects with the following properties:
67    #[reference_doc(recurse)]
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub resolvers: Option<Vec<ResolverRegistration>>,
70
71    /// Debug protocols available to any component in this environment acquired
72    /// through `use from debug`.
73    #[reference_doc(recurse)]
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub debug: Option<Vec<DebugRegistration>>,
76
77    /// The number of milliseconds to wait, after notifying a component in this environment that it
78    /// should terminate, before forcibly killing it. This field is required if the environment
79    /// extends from `none`.
80    #[serde(rename = "__stop_timeout_ms")]
81    #[reference_doc(json_type = "number", rename = "__stop_timeout_ms")]
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub stop_timeout_ms: Option<StopTimeoutMs>,
84}
85
86impl Environment {
87    pub fn merge_from(&mut self, other: &mut Self) -> Result<(), Error> {
88        if self.extends.is_none() {
89            self.extends = other.extends.take();
90        } else if other.extends.is_some() && other.extends != self.extends {
91            return Err(Error::validate(
92                "cannot merge `environments` that declare conflicting `extends`",
93            ));
94        }
95
96        if self.stop_timeout_ms.is_none() {
97            self.stop_timeout_ms = other.stop_timeout_ms;
98        } else if other.stop_timeout_ms.is_some() && other.stop_timeout_ms != self.stop_timeout_ms {
99            return Err(Error::validate(
100                "cannot merge `environments` that declare conflicting `stop_timeout_ms`",
101            ));
102        }
103
104        // Perform naive vector concatenation and rely on later validation to ensure
105        // no conflicting entries.
106        match &mut self.runners {
107            Some(r) => {
108                if let Some(o) = &mut other.runners {
109                    r.append(o);
110                }
111            }
112            None => self.runners = other.runners.take(),
113        }
114
115        match &mut self.resolvers {
116            Some(r) => {
117                if let Some(o) = &mut other.resolvers {
118                    r.append(o);
119                }
120            }
121            None => self.resolvers = other.resolvers.take(),
122        }
123
124        match &mut self.debug {
125            Some(r) => {
126                if let Some(o) = &mut other.debug {
127                    r.append(o);
128                }
129            }
130            None => self.debug = other.debug.take(),
131        }
132        Ok(())
133    }
134}
135
136/// A reference in an environment.
137#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
138#[reference(expected = "\"#<environment-name>\"")]
139pub enum EnvironmentRef {
140    /// A reference to an environment defined in this component.
141    Named(Name),
142}
143
144#[derive(Deserialize, Debug, PartialEq, Serialize)]
145#[serde(rename_all = "lowercase")]
146pub enum EnvironmentExtends {
147    Realm,
148    None,
149}
150
151/// The stop timeout configured in an environment.
152#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
153pub struct StopTimeoutMs(pub u32);
154
155impl<'de> de::Deserialize<'de> for StopTimeoutMs {
156    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
157    where
158        D: de::Deserializer<'de>,
159    {
160        struct Visitor;
161
162        impl<'de> de::Visitor<'de> for Visitor {
163            type Value = StopTimeoutMs;
164
165            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166                f.write_str("an unsigned 32-bit integer")
167            }
168
169            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
170            where
171                E: de::Error,
172            {
173                if v < 0 || v > i64::from(u32::max_value()) {
174                    return Err(E::invalid_value(
175                        de::Unexpected::Signed(v),
176                        &"an unsigned 32-bit integer",
177                    ));
178                }
179                Ok(StopTimeoutMs(v as u32))
180            }
181
182            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
183            where
184                E: de::Error,
185            {
186                self.visit_i64(value as i64)
187            }
188        }
189
190        deserializer.deserialize_i64(Visitor)
191    }
192}
193
194/// A reference in an environment registration.
195#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
196#[reference(expected = "\"parent\", \"self\", or \"#<child-name>\"")]
197pub enum RegistrationRef {
198    /// A reference to a child.
199    Named(Name),
200    /// A reference to the parent.
201    Parent,
202    /// A reference to this component.
203    Self_,
204}
205
206#[derive(Deserialize, Debug, PartialEq, ReferenceDoc, Serialize)]
207#[serde(deny_unknown_fields)]
208#[reference_doc(fields_as = "list")]
209pub struct RunnerRegistration {
210    /// The [name](#name) of a runner capability, whose source is specified in `from`.
211    pub runner: Name,
212
213    /// The source of the runner capability, one of:
214    /// - `parent`: The component's parent.
215    /// - `self`: This component.
216    /// - `#<child-name>`: A [reference](#references) to a child component
217    ///     instance.
218    pub from: RegistrationRef,
219
220    /// An explicit name for the runner as it will be known in
221    /// this environment. If omitted, defaults to `runner`.
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub r#as: Option<Name>,
224}
225
226#[derive(Deserialize, Debug, PartialEq, ReferenceDoc, Serialize)]
227#[serde(deny_unknown_fields)]
228#[reference_doc(fields_as = "list")]
229pub struct ResolverRegistration {
230    /// The [name](#name) of a resolver capability,
231    /// whose source is specified in `from`.
232    pub resolver: Name,
233
234    /// The source of the resolver capability, one of:
235    /// - `parent`: The component's parent.
236    /// - `self`: This component.
237    /// - `#<child-name>`: A [reference](#references) to a child component
238    ///     instance.
239    pub from: RegistrationRef,
240
241    /// The URL scheme for which the resolver should handle
242    /// resolution.
243    pub scheme: cm_types::UrlScheme,
244}
245
246impl FromClause for RunnerRegistration {
247    fn from_(&self) -> OneOrMany<AnyRef<'_>> {
248        OneOrMany::One(AnyRef::from(&self.from))
249    }
250}
251
252impl FromClause for ResolverRegistration {
253    fn from_(&self) -> OneOrMany<AnyRef<'_>> {
254        OneOrMany::One(AnyRef::from(&self.from))
255    }
256}
257
258#[derive(Deserialize, Debug, Clone, PartialEq, ReferenceDoc, Serialize)]
259#[serde(deny_unknown_fields)]
260#[reference_doc(fields_as = "list")]
261pub struct DebugRegistration {
262    /// The name(s) of the protocol(s) to make available.
263    pub protocol: Option<OneOrMany<Name>>,
264
265    /// The source of the capability(s), one of:
266    /// - `parent`: The component's parent.
267    /// - `self`: This component.
268    /// - `#<child-name>`: A [reference](#references) to a child component
269    ///     instance.
270    pub from: OfferFromRef,
271
272    /// If specified, the name that the capability in `protocol` should be made
273    /// available as to clients. Disallowed if `protocol` is an array.
274    #[serde(skip_serializing_if = "Option::is_none")]
275    pub r#as: Option<Name>,
276}
277
278impl AsClause for DebugRegistration {
279    fn r#as(&self) -> Option<&BorrowedName> {
280        self.r#as.as_ref().map(Name::as_ref)
281    }
282}
283
284impl PathClause for DebugRegistration {
285    fn path(&self) -> Option<&Path> {
286        None
287    }
288}
289
290impl FromClause for DebugRegistration {
291    fn from_(&self) -> OneOrMany<AnyRef<'_>> {
292        OneOrMany::One(AnyRef::from(&self.from))
293    }
294}
295
296impl CapabilityClause for DebugRegistration {
297    fn service(&self) -> Option<OneOrMany<&BorrowedName>> {
298        None
299    }
300    fn protocol(&self) -> Option<OneOrMany<&BorrowedName>> {
301        option_one_or_many_as_ref(&self.protocol)
302    }
303    fn directory(&self) -> Option<OneOrMany<&BorrowedName>> {
304        None
305    }
306    fn storage(&self) -> Option<OneOrMany<&BorrowedName>> {
307        None
308    }
309    fn runner(&self) -> Option<OneOrMany<&BorrowedName>> {
310        None
311    }
312    fn resolver(&self) -> Option<OneOrMany<&BorrowedName>> {
313        None
314    }
315    fn event_stream(&self) -> Option<OneOrMany<&BorrowedName>> {
316        None
317    }
318    fn dictionary(&self) -> Option<OneOrMany<&BorrowedName>> {
319        None
320    }
321    fn config(&self) -> Option<OneOrMany<&BorrowedName>> {
322        None
323    }
324
325    fn set_service(&mut self, _o: Option<OneOrMany<Name>>) {}
326    fn set_protocol(&mut self, o: Option<OneOrMany<Name>>) {
327        self.protocol = o;
328    }
329    fn set_directory(&mut self, _o: Option<OneOrMany<Name>>) {}
330    fn set_storage(&mut self, _o: Option<OneOrMany<Name>>) {}
331    fn set_runner(&mut self, _o: Option<OneOrMany<Name>>) {}
332    fn set_resolver(&mut self, _o: Option<OneOrMany<Name>>) {}
333    fn set_event_stream(&mut self, _o: Option<OneOrMany<Name>>) {}
334    fn set_dictionary(&mut self, _o: Option<OneOrMany<Name>>) {}
335    fn set_config(&mut self, _o: Option<OneOrMany<Name>>) {}
336
337    fn availability(&self) -> Option<Availability> {
338        None
339    }
340    fn set_availability(&mut self, _a: Option<Availability>) {}
341
342    fn decl_type(&self) -> &'static str {
343        "debug"
344    }
345    fn supported(&self) -> &[&'static str] {
346        &["service", "protocol"]
347    }
348    fn are_many_names_allowed(&self) -> bool {
349        ["protocol"].contains(&self.capability_type().unwrap())
350    }
351}