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 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 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 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#[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}