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::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
88/// A reference in an environment.
89#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
90#[reference(expected = "\"#<environment-name>\"")]
91pub enum EnvironmentRef {
92    /// A reference to an environment defined in this component.
93    Named(Name),
94}
95
96#[derive(Deserialize, Debug, PartialEq, Serialize)]
97#[serde(rename_all = "lowercase")]
98pub enum EnvironmentExtends {
99    Realm,
100    None,
101}
102
103/// The stop timeout configured in an environment.
104#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
105pub struct StopTimeoutMs(pub u32);
106
107impl<'de> de::Deserialize<'de> for StopTimeoutMs {
108    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
109    where
110        D: de::Deserializer<'de>,
111    {
112        struct Visitor;
113
114        impl<'de> de::Visitor<'de> for Visitor {
115            type Value = StopTimeoutMs;
116
117            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118                f.write_str("an unsigned 32-bit integer")
119            }
120
121            fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
122            where
123                E: de::Error,
124            {
125                if v < 0 || v > i64::from(u32::max_value()) {
126                    return Err(E::invalid_value(
127                        de::Unexpected::Signed(v),
128                        &"an unsigned 32-bit integer",
129                    ));
130                }
131                Ok(StopTimeoutMs(v as u32))
132            }
133
134            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
135            where
136                E: de::Error,
137            {
138                self.visit_i64(value as i64)
139            }
140        }
141
142        deserializer.deserialize_i64(Visitor)
143    }
144}
145
146/// A reference in an environment registration.
147#[derive(Debug, PartialEq, Eq, Hash, Clone, Reference)]
148#[reference(expected = "\"parent\", \"self\", or \"#<child-name>\"")]
149pub enum RegistrationRef {
150    /// A reference to a child.
151    Named(Name),
152    /// A reference to the parent.
153    Parent,
154    /// A reference to this component.
155    Self_,
156}
157
158#[derive(Deserialize, Debug, PartialEq, ReferenceDoc, Serialize)]
159#[serde(deny_unknown_fields)]
160#[reference_doc(fields_as = "list")]
161pub struct RunnerRegistration {
162    /// The [name](#name) of a runner capability, whose source is specified in `from`.
163    pub runner: Name,
164
165    /// The source of the runner capability, one of:
166    /// - `parent`: The component's parent.
167    /// - `self`: This component.
168    /// - `#<child-name>`: A [reference](#references) to a child component
169    ///     instance.
170    pub from: RegistrationRef,
171
172    /// An explicit name for the runner as it will be known in
173    /// this environment. If omitted, defaults to `runner`.
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub r#as: Option<Name>,
176}
177
178impl Hydrate for RunnerRegistration {
179    type Output = ContextRunnerRegistration;
180
181    fn hydrate(self, file: &Arc<PathBuf>) -> Result<Self::Output, Error> {
182        let runner = hydrate_simple(self.runner, file);
183
184        let r#as = hydrate_opt_simple(self.r#as, file);
185
186        let from = hydrate_simple(self.from, file);
187
188        Ok(ContextRunnerRegistration { runner, r#as, from })
189    }
190}
191
192#[derive(Debug, PartialEq, Serialize)]
193pub struct ContextRunnerRegistration {
194    pub runner: ContextSpanned<Name>,
195    pub from: ContextSpanned<RegistrationRef>,
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub r#as: Option<ContextSpanned<Name>>,
198}
199
200impl FromClauseContext for ContextRunnerRegistration {
201    fn from_(&self) -> ContextSpanned<OneOrMany<AnyRef<'_>>> {
202        let origin = self.from.origin.clone();
203        let value = OneOrMany::One(AnyRef::from(&self.from.value));
204
205        ContextSpanned { value, origin }
206    }
207}
208
209#[derive(Deserialize, Debug, PartialEq, ReferenceDoc, Serialize)]
210#[serde(deny_unknown_fields)]
211#[reference_doc(fields_as = "list")]
212pub struct ResolverRegistration {
213    /// The [name](#name) of a resolver capability,
214    /// whose source is specified in `from`.
215    pub resolver: Name,
216
217    /// The source of the resolver capability, one of:
218    /// - `parent`: The component's parent.
219    /// - `self`: This component.
220    /// - `#<child-name>`: A [reference](#references) to a child component
221    ///     instance.
222    pub from: RegistrationRef,
223
224    /// The URL scheme for which the resolver should handle
225    /// resolution.
226    pub scheme: cm_types::UrlScheme,
227}
228
229impl Hydrate for ResolverRegistration {
230    type Output = ContextResolverRegistration;
231
232    fn hydrate(self, file: &Arc<PathBuf>) -> Result<Self::Output, Error> {
233        let resolver = hydrate_simple(self.resolver, file);
234
235        let from = hydrate_simple(self.from, file);
236        let scheme = hydrate_simple(self.scheme, file);
237
238        Ok(ContextResolverRegistration { resolver, from, scheme })
239    }
240}
241
242#[derive(Debug, PartialEq, Serialize)]
243pub struct ContextResolverRegistration {
244    pub resolver: ContextSpanned<Name>,
245    pub from: ContextSpanned<RegistrationRef>,
246    pub scheme: ContextSpanned<cm_types::UrlScheme>,
247}
248
249impl FromClauseContext for ContextResolverRegistration {
250    fn from_(&self) -> ContextSpanned<OneOrMany<AnyRef<'_>>> {
251        let origin = self.from.origin.clone();
252        let value = OneOrMany::One(AnyRef::from(&self.from.value));
253
254        ContextSpanned { value, origin }
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 Hydrate for DebugRegistration {
279    type Output = ContextDebugRegistration;
280
281    fn hydrate(self, file: &Arc<PathBuf>) -> Result<Self::Output, Error> {
282        let origin = file.clone();
283        let protocol = hydrate_opt_simple(self.protocol, file);
284        let from = hydrate_simple(self.from, file);
285        let r#as = hydrate_opt_simple(self.r#as, file);
286
287        Ok(ContextDebugRegistration { origin, protocol, from, r#as })
288    }
289}
290
291#[derive(Debug, Clone, PartialEq, Serialize)]
292pub struct ContextDebugRegistration {
293    #[serde(skip)]
294    pub origin: Arc<PathBuf>,
295    #[serde(skip_serializing_if = "Option::is_none")]
296    pub protocol: Option<ContextSpanned<OneOrMany<Name>>>,
297    pub from: ContextSpanned<OfferFromRef>,
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub r#as: Option<ContextSpanned<Name>>,
300}
301
302impl FromClauseContext for ContextDebugRegistration {
303    fn from_(&self) -> ContextSpanned<OneOrMany<AnyRef<'_>>> {
304        let origin = self.from.origin.clone();
305        let value = OneOrMany::One(AnyRef::from(&self.from.value));
306
307        ContextSpanned { value, origin }
308    }
309}
310
311impl AsClauseContext for ContextDebugRegistration {
312    fn r#as(&self) -> Option<ContextSpanned<&BorrowedName>> {
313        self.r#as.as_ref().map(|spanned_name| ContextSpanned {
314            value: spanned_name.value.as_ref(),
315            origin: spanned_name.origin.clone(),
316        })
317    }
318}
319
320impl ContextPathClause for ContextDebugRegistration {
321    fn path(&self) -> Option<&ContextSpanned<Path>> {
322        None
323    }
324}
325
326impl ContextCapabilityClause for ContextDebugRegistration {
327    fn service(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
328        None
329    }
330    fn protocol(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
331        option_one_or_many_as_ref_context(&self.protocol)
332    }
333    fn directory(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
334        None
335    }
336    fn storage(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
337        None
338    }
339    fn runner(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
340        None
341    }
342    fn resolver(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
343        None
344    }
345    fn event_stream(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
346        None
347    }
348    fn dictionary(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
349        None
350    }
351    fn config(&self) -> Option<ContextSpanned<OneOrMany<&BorrowedName>>> {
352        None
353    }
354
355    fn decl_type(&self) -> &'static str {
356        "debug"
357    }
358    fn supported(&self) -> &[&'static str] {
359        &["service", "protocol"]
360    }
361    fn are_many_names_allowed(&self) -> bool {
362        ["protocol"].contains(&self.capability_type(None).unwrap())
363    }
364
365    fn set_service(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
366    fn set_protocol(&mut self, o: Option<ContextSpanned<OneOrMany<Name>>>) {
367        self.protocol = o;
368    }
369    fn set_directory(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
370    fn set_storage(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
371    fn set_runner(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
372    fn set_resolver(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
373    fn set_event_stream(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
374    fn set_dictionary(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
375    fn set_config(&mut self, _o: Option<ContextSpanned<OneOrMany<Name>>>) {}
376
377    fn origin(&self) -> &Arc<PathBuf> {
378        &self.origin
379    }
380
381    fn availability(&self) -> Option<ContextSpanned<Availability>> {
382        None
383    }
384    fn set_availability(&mut self, _a: Option<ContextSpanned<Availability>>) {}
385}
386
387impl Hydrate for Environment {
388    type Output = ContextEnvironment;
389
390    fn hydrate(self, file: &Arc<PathBuf>) -> Result<Self::Output, Error> {
391        let name = hydrate_simple(self.name, file);
392
393        let extends = hydrate_opt_simple(self.extends, file);
394        let stop_timeout_ms = hydrate_opt_simple(self.stop_timeout_ms, file);
395
396        let runners = hydrate_list(self.runners, file)?;
397        let resolvers = hydrate_list(self.resolvers, file)?;
398        let debug = hydrate_list(self.debug, file)?;
399
400        Ok(ContextEnvironment { name, extends, runners, resolvers, debug, stop_timeout_ms })
401    }
402}
403
404#[derive(Debug, PartialEq, Serialize)]
405pub struct ContextEnvironment {
406    pub name: ContextSpanned<Name>,
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub extends: Option<ContextSpanned<EnvironmentExtends>>,
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub runners: Option<Vec<ContextSpanned<ContextRunnerRegistration>>>,
411    #[serde(skip_serializing_if = "Option::is_none")]
412    pub resolvers: Option<Vec<ContextSpanned<ContextResolverRegistration>>>,
413    #[serde(skip_serializing_if = "Option::is_none")]
414    pub debug: Option<Vec<ContextSpanned<ContextDebugRegistration>>>,
415    #[serde(skip_serializing_if = "Option::is_none")]
416    #[serde(rename = "__stop_timeout_ms")]
417    pub stop_timeout_ms: Option<ContextSpanned<StopTimeoutMs>>,
418}
419
420impl ContextEnvironment {
421    pub fn merge_from(&mut self, mut other: Self) -> Result<(), Error> {
422        if let Some(other_extends) = other.extends.take() {
423            if let Some(my_extends) = &self.extends {
424                if my_extends.value != other_extends.value {
425                    return Err(Error::merge(
426                        format!(
427                            "Conflicting 'extends' field in environment '{}': found '{:?}' and '{:?}'",
428                            self.name.value, my_extends.value, other_extends.value
429                        ),
430                        Some(other_extends.origin),
431                    ));
432                }
433            } else {
434                self.extends = Some(other_extends);
435            }
436        }
437
438        if let Some(other_timeout) = other.stop_timeout_ms.take() {
439            if let Some(my_timeout) = &self.stop_timeout_ms {
440                if my_timeout.value != other_timeout.value {
441                    return Err(Error::merge(
442                        format!(
443                            "Conflicting 'stop_timeout_ms' in environment '{}'",
444                            self.name.value
445                        ),
446                        Some(other_timeout.origin),
447                    ));
448                }
449            } else {
450                self.stop_timeout_ms = Some(other_timeout);
451            }
452        }
453
454        merge_spanned_vec!(self, other, runners);
455        merge_spanned_vec!(self, other, resolvers);
456        merge_spanned_vec!(self, other, debug);
457
458        Ok(())
459    }
460}