component_debug/
config.rs

1// Copyright 2023 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::realm::{get_resolved_declaration, resolve_declaration};
6use cm_rust::NativeIntoFidl;
7use config_value_file::field::config_value_from_json_value;
8use moniker::Moniker;
9use {fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_sys2 as fsys};
10
11pub async fn resolve_raw_config_overrides(
12    realm_query: &fsys::RealmQueryProxy,
13    moniker: &Moniker,
14    url: &str,
15    raw_overrides: &[RawConfigEntry],
16) -> Result<Vec<fdecl::ConfigOverride>, ConfigResolveError> {
17    // Don't worry about any of the below failure modes if there aren't actually any overrides.
18    if raw_overrides.is_empty() {
19        return Ok(vec![]);
20    }
21
22    let manifest = resolve_manifest(moniker, realm_query, url).await?;
23    let config = manifest.config.as_ref().ok_or(ConfigResolveError::MissingConfigSchema)?;
24
25    let mut resolved_overrides = vec![];
26    for raw_override in raw_overrides {
27        let config_field =
28            config.fields.iter().find(|f| f.key == raw_override.key).ok_or_else(|| {
29                ConfigResolveError::MissingConfigField { name: raw_override.key.clone() }
30            })?;
31
32        let resolved_value = config_value_from_json_value(&raw_override.value, &config_field.type_)
33            .map_err(ConfigResolveError::FieldTypeError)?;
34        resolved_overrides.push(fdecl::ConfigOverride {
35            key: Some(raw_override.key.clone()),
36            value: Some(resolved_value.native_into_fidl()),
37            ..Default::default()
38        });
39    }
40
41    Ok(resolved_overrides)
42}
43
44pub async fn resolve_raw_config_capabilities(
45    realm_query: &fsys::RealmQueryProxy,
46    moniker: &Moniker,
47    url: &str,
48    raw_capabilities: &[RawConfigEntry],
49) -> Result<Vec<fdecl::Configuration>, ConfigResolveError> {
50    // Don't worry about any of the below failure modes if there aren't actually any overrides.
51    if raw_capabilities.is_empty() {
52        return Ok(vec![]);
53    }
54
55    let manifest = resolve_manifest(moniker, realm_query, url).await?;
56    let config_uses: Vec<_> = manifest
57        .uses
58        .into_iter()
59        .filter_map(|u| if let cm_rust::UseDecl::Config(c) = u { Some(c) } else { None })
60        .collect();
61
62    let mut resolved_capabilities = vec![];
63    for raw_capability in raw_capabilities {
64        let config_field =
65            config_uses.iter().find(|f| f.source_name == raw_capability.key.as_str()).ok_or_else(
66                || ConfigResolveError::MissingConfigField { name: raw_capability.key.clone() },
67            )?;
68
69        let resolved_value =
70            config_value_from_json_value(&raw_capability.value, &config_field.type_)
71                .map_err(ConfigResolveError::FieldTypeError)?;
72        resolved_capabilities.push(fdecl::Configuration {
73            name: Some(raw_capability.key.clone()),
74            value: Some(resolved_value.native_into_fidl()),
75            ..Default::default()
76        });
77    }
78
79    Ok(resolved_capabilities)
80}
81
82pub(crate) enum UseConfigurationOrConfigField {
83    UseConfiguration(cm_rust::UseConfigurationDecl),
84    ConfigField(cm_rust::ConfigField),
85}
86
87pub(crate) async fn resolve_config_decls(
88    realm_query: &fsys::RealmQueryProxy,
89    moniker: &Moniker,
90) -> Result<Vec<UseConfigurationOrConfigField>, ConfigResolveError> {
91    let manifest = get_resolved_declaration(moniker, realm_query).await?;
92    let config_decls = manifest
93        .config
94        .into_iter()
95        .map(|c| c.fields)
96        .flatten()
97        .map(UseConfigurationOrConfigField::ConfigField);
98    Ok(manifest
99        .uses
100        .into_iter()
101        .filter_map(|u| if let cm_rust::UseDecl::Config(c) = u { Some(c) } else { None })
102        .map(UseConfigurationOrConfigField::UseConfiguration)
103        .chain(config_decls)
104        .collect())
105}
106
107async fn resolve_manifest(
108    moniker: &Moniker,
109    realm_query: &fsys::RealmQueryProxy,
110    url: &str,
111) -> Result<cm_rust::ComponentDecl, ConfigResolveError> {
112    let parent = moniker.parent().ok_or_else(|| ConfigResolveError::BadMoniker(moniker.clone()))?;
113    let leaf = moniker.leaf().ok_or_else(|| ConfigResolveError::BadMoniker(moniker.clone()))?;
114    let collection =
115        leaf.collection().ok_or_else(|| ConfigResolveError::BadMoniker(moniker.clone()))?;
116    let child_location = fsys::ChildLocation::Collection(collection.to_string());
117    let manifest = resolve_declaration(realm_query, &parent, &child_location, url).await?;
118    Ok(manifest)
119}
120
121/// [`RawConfigEntry`] may either represent a config override (where the key is
122/// usually `snake_case`) or a configuration capability declaration (where the
123/// key is usually `fully.qualified.Name`).
124#[derive(Debug, PartialEq)]
125pub struct RawConfigEntry {
126    key: String,
127    value: serde_json::Value,
128}
129
130impl std::str::FromStr for RawConfigEntry {
131    type Err = ConfigParseError;
132    fn from_str(s: &str) -> Result<Self, Self::Err> {
133        let (key, value) = s.split_once("=").ok_or(ConfigParseError::MissingEqualsSeparator)?;
134        Ok(Self {
135            key: key.to_owned(),
136            value: serde_json::from_str(&value).map_err(ConfigParseError::InvalidJsonValue)?,
137        })
138    }
139}
140
141#[derive(Debug, thiserror::Error)]
142pub enum ConfigResolveError {
143    #[error("`{_0}` does not reference a dynamic instance.")]
144    BadMoniker(Moniker),
145
146    #[error("Failed to get component manifest: {_0:?}")]
147    FailedToGetManifest(
148        #[source]
149        #[from]
150        crate::realm::GetDeclarationError,
151    ),
152
153    #[error("Provided component URL points to a manifest without a config schema.")]
154    MissingConfigSchema,
155
156    #[error("Component does not have a config field named `{name}`.")]
157    MissingConfigField { name: String },
158
159    #[error("Couldn't resolve provided config value to the declared type in component manifest.")]
160    FieldTypeError(#[source] config_value_file::field::FieldError),
161}
162
163#[derive(Debug, thiserror::Error)]
164pub enum ConfigParseError {
165    #[error("Config override did not have a `=` to delimit key and value strings.")]
166    MissingEqualsSeparator,
167
168    #[error("Unable to parse provided value as JSON.")]
169    InvalidJsonValue(#[source] serde_json::Error),
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use assert_matches::assert_matches;
176    use std::str::FromStr;
177
178    #[test]
179    fn parse_config() {
180        let config = RawConfigEntry::from_str("foo=\"bar\"").unwrap();
181        assert_eq!(config.key, "foo");
182        assert_matches!(config.value, serde_json::Value::String(s) if &s == "bar");
183
184        let config = RawConfigEntry::from_str("foo=true").unwrap();
185        assert_eq!(config.key, "foo");
186        assert_matches!(config.value, serde_json::Value::Bool(true));
187
188        RawConfigEntry::from_str("invalid").unwrap_err();
189    }
190
191    #[test]
192    fn parse_config_capabilities() {
193        let config = RawConfigEntry::from_str("fuchsia.my.Config=\"bar\"").unwrap();
194        assert_eq!(config.key, "fuchsia.my.Config");
195        assert_matches!(config.value, serde_json::Value::String(s) if &s == "bar");
196    }
197}