Skip to main content

agent_core/
jmap.rs

1// jmap -- typed JSON mapping for records and string enums over json::Value: the Field trait
2// for member values, the Json trait for records, and the record! / wire_enum! macros that
3// declare a type and its mapping in one place.  Missing or mistyped members read as their
4// default; unknown members are ignored, so old and new programs read each other's data
5// (protocols, project files, settings).
6//
7//   record! { pub struct P { pub a: String, pub n: u32, pub items: Vec<Item> } }
8//   wire_enum! { pub enum Kind { Pc = "pc", Router = "router" } }   // Default = first variant
9//   p.to_json() / P::from_json(&v) / Field::put / Field::take
10//   list(&[T]) / from_list(&v) / str_of / u64_of / obj([...]) / empty_object()
11//
12// Field is implemented for String, bool, the integer types, Value (kept as is), Vec<T>,
13// Option<T> (null = None) and BTreeMap<String, T> (an object).  Floating point has no exact
14// JSON token here: store fixed-point integers instead.
15
16use alloc::collections::BTreeMap;
17use alloc::string::String;
18use alloc::vec::Vec;
19
20pub use crate::json::Value;
21
22/// a value that maps to one JSON member
23pub trait Field: Sized + Default {
24    fn put(&self) -> Value;
25    fn take(v: Option<&Value>) -> Self;
26}
27
28/// a record (a JSON object)
29pub trait Json: Sized {
30    fn to_json(&self) -> Value;
31    fn from_json(v: &Value) -> Option<Self>;
32}
33
34impl Field for String {
35    fn put(&self) -> Value {
36        Value::string(self.as_str())
37    }
38    fn take(v: Option<&Value>) -> Self {
39        v.and_then(Value::as_str).map(String::from).unwrap_or_default()
40    }
41}
42
43impl Field for bool {
44    fn put(&self) -> Value {
45        Value::Bool(*self)
46    }
47    fn take(v: Option<&Value>) -> Self {
48        v.and_then(Value::as_bool).unwrap_or(false)
49    }
50}
51
52impl Field for u64 {
53    fn put(&self) -> Value {
54        Value::u64(*self)
55    }
56    fn take(v: Option<&Value>) -> Self {
57        v.and_then(Value::as_u64).unwrap_or(0)
58    }
59}
60
61impl Field for i64 {
62    fn put(&self) -> Value {
63        Value::i64(*self)
64    }
65    fn take(v: Option<&Value>) -> Self {
66        v.and_then(Value::as_i64).unwrap_or(0)
67    }
68}
69
70impl Field for u32 {
71    fn put(&self) -> Value {
72        Value::u64(*self as u64)
73    }
74    fn take(v: Option<&Value>) -> Self {
75        v.and_then(Value::as_u64).map_or(0, |x| x.min(u32::MAX as u64) as u32)
76    }
77}
78
79impl Field for i32 {
80    fn put(&self) -> Value {
81        Value::i64(*self as i64)
82    }
83    fn take(v: Option<&Value>) -> Self {
84        v.and_then(Value::as_i64).map_or(0, |x| x.clamp(i32::MIN as i64, i32::MAX as i64) as i32)
85    }
86}
87
88impl Field for u16 {
89    fn put(&self) -> Value {
90        Value::u64(*self as u64)
91    }
92    fn take(v: Option<&Value>) -> Self {
93        v.and_then(Value::as_u64).map_or(0, |x| x.min(u16::MAX as u64) as u16)
94    }
95}
96
97impl Field for u8 {
98    fn put(&self) -> Value {
99        Value::u64(*self as u64)
100    }
101    fn take(v: Option<&Value>) -> Self {
102        v.and_then(Value::as_u64).map_or(0, |x| x.min(255) as u8)
103    }
104}
105
106impl Default for Value {
107    fn default() -> Value {
108        Value::Null
109    }
110}
111
112/// a free-form member (configuration of a plugin, a service, ...)
113impl Field for Value {
114    fn put(&self) -> Value {
115        self.clone()
116    }
117    fn take(v: Option<&Value>) -> Self {
118        v.cloned().unwrap_or(Value::Null)
119    }
120}
121
122impl<T: Field> Field for Vec<T> {
123    fn put(&self) -> Value {
124        Value::Array(self.iter().map(Field::put).collect())
125    }
126    fn take(v: Option<&Value>) -> Self {
127        v.and_then(Value::as_array).map(|a| a.iter().map(|x| T::take(Some(x))).collect()).unwrap_or_default()
128    }
129}
130
131impl<T: Field> Field for Option<T> {
132    fn put(&self) -> Value {
133        match self {
134            Some(x) => x.put(),
135            None => Value::Null,
136        }
137    }
138    fn take(v: Option<&Value>) -> Self {
139        match v {
140            None | Some(Value::Null) => None,
141            Some(x) => Some(T::take(Some(x))),
142        }
143    }
144}
145
146/// a map with string keys as a JSON object (members in key order)
147impl<T: Field> Field for BTreeMap<String, T> {
148    fn put(&self) -> Value {
149        Value::Object(self.iter().map(|(k, v)| (k.clone(), v.put())).collect())
150    }
151    fn take(v: Option<&Value>) -> Self {
152        v.and_then(Value::as_object).map(|o| o.iter().map(|(k, x)| (k.clone(), T::take(Some(x)))).collect()).unwrap_or_default()
153    }
154}
155
156/// an empty JSON object (used by record!)
157pub fn empty_object() -> Value {
158    Value::Object(Vec::new())
159}
160
161/// declare a record: the struct (Clone, Debug, Default, PartialEq) and its Json / Field
162/// mapping; the JSON member names are the field names
163#[macro_export]
164macro_rules! record {
165    ($(#[$m:meta])* pub struct $name:ident { $( $(#[$fm:meta])* pub $f:ident : $t:ty ),* $(,)? }) => {
166        $(#[$m])*
167        #[derive(Clone, Debug, Default, PartialEq)]
168        pub struct $name { $( $(#[$fm])* pub $f: $t ),* }
169        impl $crate::jmap::Json for $name {
170            fn to_json(&self) -> $crate::jmap::Value {
171                #[allow(unused_mut)]
172                let mut o = $crate::jmap::empty_object();
173                $( o.insert(stringify!($f), $crate::jmap::Field::put(&self.$f)); )*
174                o
175            }
176            fn from_json(v: &$crate::jmap::Value) -> Option<Self> {
177                v.as_object()?;
178                Some($name { $( $f: $crate::jmap::Field::take(v.get(stringify!($f))) ),* })
179            }
180        }
181        impl $crate::jmap::Field for $name {
182            fn put(&self) -> $crate::jmap::Value {
183                $crate::jmap::Json::to_json(self)
184            }
185            fn take(v: Option<&$crate::jmap::Value>) -> Self {
186                v.and_then(<$name as $crate::jmap::Json>::from_json).unwrap_or_default()
187            }
188        }
189    };
190}
191
192/// declare an enum carried as a string: Default is the first variant, ALL, as_str / parse,
193/// Field
194#[macro_export]
195macro_rules! wire_enum {
196    ($(#[$m:meta])* pub enum $name:ident { $first:ident = $fs:literal $(, $v:ident = $s:literal)* $(,)? }) => {
197        $(#[$m])*
198        #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
199        pub enum $name { #[default] $first $(, $v)* }
200        impl $name {
201            /// every variant in declaration order
202            pub const ALL: &'static [$name] = &[$name::$first $(, $name::$v)*];
203            /// the wire name
204            pub fn as_str(&self) -> &'static str {
205                match self { $name::$first => $fs $(, $name::$v => $s)* }
206            }
207            /// the variant of a wire name
208            pub fn parse(s: &str) -> Option<$name> {
209                match s { $fs => Some($name::$first) $(, $s => Some($name::$v))*, _ => None }
210            }
211        }
212        impl $crate::jmap::Field for $name {
213            fn put(&self) -> $crate::jmap::Value {
214                $crate::jmap::Value::string(self.as_str())
215            }
216            fn take(v: Option<&$crate::jmap::Value>) -> Self {
217                v.and_then($crate::jmap::Value::as_str).and_then($name::parse).unwrap_or_default()
218            }
219        }
220    };
221}
222
223/// a list of records as a JSON array
224pub fn list<T: Json>(items: &[T]) -> Value {
225    Value::Array(items.iter().map(Json::to_json).collect())
226}
227
228/// the records of a JSON array (or of the array under `items` of an object)
229pub fn from_list<T: Json>(v: &Value) -> Vec<T> {
230    let arr = v.as_array().or_else(|| v.get("items").and_then(Value::as_array));
231    arr.map(|a| a.iter().filter_map(T::from_json).collect()).unwrap_or_default()
232}
233
234/// a string member of an object ("" when missing)
235pub fn str_of<'a>(v: &'a Value, k: &str) -> &'a str {
236    v.get(k).and_then(Value::as_str).unwrap_or("")
237}
238
239/// an unsigned member of an object (0 when missing)
240pub fn u64_of(v: &Value, k: &str) -> u64 {
241    v.get(k).and_then(Value::as_u64).unwrap_or(0)
242}
243
244/// an object built from (name, value) pairs
245pub fn obj<const N: usize>(fields: [(&str, Value); N]) -> Value {
246    Value::object(fields)
247}