Skip to main content

sapphire_framework_bridge_api/
config.rs

1//! The `config.*` methods: the registry's settings, read and written by key.
2//!
3//! The types themselves ([`Scope`], [`Value`], [`Entry`], [`SettingDef`]) are the settings
4//! API's; this module only wraps them in parameters and results.
5
6use std::fmt;
7
8use crate::settings::{Entry, Scope, SettingDef, Value};
9use serde::{Deserialize, Serialize};
10
11/// Parameters of `config.list`.
12#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
13pub struct ConfigListParams {
14    /// Only what this scope's file holds; `None` lists every key's effective value.
15    #[serde(default)]
16    pub scope: Option<Scope>,
17}
18
19/// Parameters of `config.get`.
20#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
21pub struct ConfigGetParams {
22    /// The key.
23    pub key: String,
24}
25
26/// One key and the value to give it.
27#[derive(Clone, PartialEq, Serialize, Deserialize)]
28pub struct ConfigEntry {
29    /// The key.
30    pub key: String,
31    /// The new value.
32    pub value: Value,
33}
34
35/// Parameters of `config.set`: a batch, applied whole or not at all.
36#[derive(Clone, PartialEq, Serialize, Deserialize)]
37pub struct ConfigSetParams {
38    /// The scope to write; `None` writes each key to its home scope.
39    #[serde(default)]
40    pub scope: Option<Scope>,
41    /// The changes.
42    pub entries: Vec<ConfigEntry>,
43    /// Check the batch, validators included, and write nothing.
44    #[serde(default)]
45    pub dry_run: bool,
46}
47
48// The values may be secrets, and this crate cannot tell which keys are: so no value is
49// ever printed.
50impl fmt::Debug for ConfigEntry {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        f.debug_struct("ConfigEntry")
53            .field("key", &self.key)
54            .field("value", &format_args!("<secret>"))
55            .finish()
56    }
57}
58
59impl fmt::Debug for ConfigSetParams {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        f.debug_struct("ConfigSetParams")
62            .field("scope", &self.scope)
63            .field("entries", &self.entries)
64            .field("dry_run", &self.dry_run)
65            .finish()
66    }
67}
68
69/// One key a rejected batch names, and what is wrong with it. The message never holds a
70/// value.
71#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
72pub struct ConfigFieldError {
73    /// The key.
74    pub key: String,
75    /// What is wrong.
76    pub message: String,
77}
78
79/// The keys a rejected `config.set` or `config.unset` (dry run or not) names: the `data` of
80/// its error, beside the message that stays what it always was.
81#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
82pub struct ConfigFieldErrors {
83    /// One entry per key at fault.
84    pub fields: Vec<ConfigFieldError>,
85}
86
87impl ConfigFieldErrors {
88    /// The field errors in an error's `data`; `None` when it holds none. Read an RPC
89    /// error's with `sapphire_bridge_client::ConfigFieldErrorsExt::from_rpc`.
90    pub fn from_data(data: Option<&serde_json::Value>) -> Option<Self> {
91        let errors: Self = serde_json::from_value(data?.clone()).ok()?;
92        (!errors.fields.is_empty()).then_some(errors)
93    }
94
95    /// The `data` that carries these.
96    pub fn to_data(&self) -> serde_json::Value {
97        serde_json::to_value(self).expect("field errors are plain strings")
98    }
99
100    /// One `{key}: {message}` line per field.
101    pub fn lines(&self) -> impl Iterator<Item = String> + '_ {
102        self.fields
103            .iter()
104            .map(|f| format!("{}: {}", f.key, f.message))
105    }
106}
107
108/// Parameters of `config.unset`.
109#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
110pub struct ConfigUnsetParams {
111    /// The scope to remove from; `None` removes each key from its home scope.
112    #[serde(default)]
113    pub scope: Option<Scope>,
114    /// The keys, or prefixes of several keys.
115    pub keys: Vec<String>,
116    /// Check the batch, validators included, and write nothing.
117    #[serde(default)]
118    pub dry_run: bool,
119}
120
121/// Result of `config.schema`.
122#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
123pub struct ConfigSchemaResult {
124    /// Every setting the bridge knows.
125    pub settings: Vec<SettingDef>,
126}
127
128/// Result of `config.list`.
129#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
130pub struct ConfigListResult {
131    /// The settings, in schema order.
132    pub entries: Vec<Entry>,
133}
134
135/// Result of `config.set` and `config.unset`.
136#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
137pub struct ConfigWriteResult {
138    /// The keys the batch touched.
139    pub changed: Vec<String>,
140    /// Every key written but not yet in effect, from this batch or an earlier one.
141    pub pending_restart: Vec<String>,
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn config_set_params_never_print_values() {
150        let params = ConfigSetParams {
151            scope: None,
152            entries: vec![ConfigEntry {
153                key: "embed.remote.key".into(),
154                value: Value::String("sk-hunter2".into()),
155            }],
156            dry_run: false,
157        };
158        let shown = format!("{params:?}");
159        assert!(!shown.contains("hunter2"), "{shown}");
160        assert!(shown.contains("<secret>") && shown.contains("embed.remote.key"));
161    }
162
163    #[test]
164    fn set_params_round_trip() {
165        let params = ConfigSetParams {
166            scope: Some(Scope::Device),
167            entries: vec![ConfigEntry {
168                key: "net.discovery".into(),
169                value: Value::Bool(false),
170            }],
171            dry_run: false,
172        };
173        let json = serde_json::to_string(&params).unwrap();
174        assert_eq!(
175            serde_json::from_str::<ConfigSetParams>(&json).unwrap(),
176            params
177        );
178    }
179
180    #[test]
181    fn dry_run_defaults_to_false() {
182        let set: ConfigSetParams = serde_json::from_str(r#"{"entries":[]}"#).unwrap();
183        assert!(!set.dry_run);
184        let unset: ConfigUnsetParams = serde_json::from_str(r#"{"keys":[]}"#).unwrap();
185        assert!(!unset.dry_run);
186    }
187}