component_debug/
config.rs
1use 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 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<_> = 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#[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}