component_debug/
config.rs1use 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 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 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#[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}