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