Skip to main content

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