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