sapphire_framework_bridge_api/
config.rs1use std::fmt;
7
8use crate::settings::{Entry, Scope, SettingDef, Value};
9use serde::{Deserialize, Serialize};
10
11#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
13pub struct ConfigListParams {
14 #[serde(default)]
16 pub scope: Option<Scope>,
17}
18
19#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
21pub struct ConfigGetParams {
22 pub key: String,
24}
25
26#[derive(Clone, PartialEq, Serialize, Deserialize)]
28pub struct ConfigEntry {
29 pub key: String,
31 pub value: Value,
33}
34
35#[derive(Clone, PartialEq, Serialize, Deserialize)]
37pub struct ConfigSetParams {
38 #[serde(default)]
40 pub scope: Option<Scope>,
41 pub entries: Vec<ConfigEntry>,
43 #[serde(default)]
45 pub dry_run: bool,
46}
47
48impl 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#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
72pub struct ConfigFieldError {
73 pub key: String,
75 pub message: String,
77}
78
79#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
82pub struct ConfigFieldErrors {
83 pub fields: Vec<ConfigFieldError>,
85}
86
87impl ConfigFieldErrors {
88 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 pub fn to_data(&self) -> serde_json::Value {
97 serde_json::to_value(self).expect("field errors are plain strings")
98 }
99
100 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#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
110pub struct ConfigUnsetParams {
111 #[serde(default)]
113 pub scope: Option<Scope>,
114 pub keys: Vec<String>,
116 #[serde(default)]
118 pub dry_run: bool,
119}
120
121#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
123pub struct ConfigSchemaResult {
124 pub settings: Vec<SettingDef>,
126}
127
128#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
130pub struct ConfigListResult {
131 pub entries: Vec<Entry>,
133}
134
135#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
137pub struct ConfigWriteResult {
138 pub changed: Vec<String>,
140 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(¶ms).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}