1use std::collections::HashMap;
2use std::hash::Hash;
3
4use serde_json::{Map, Value};
5
6enum ResourceValue {
8 Present(Value),
9 Missing,
10}
11
12pub struct ResourceMap {
31 fields: Vec<(String, ResourceValue)>,
32}
33
34impl ResourceMap {
35 pub fn new() -> Self {
37 Self { fields: Vec::new() }
38 }
39
40 pub fn field(mut self, key: &str, value: impl Into<Value>) -> Self {
42 self.fields
43 .push((key.to_string(), ResourceValue::Present(value.into())));
44 self
45 }
46
47 pub fn when(mut self, key: &str, condition: bool, value: impl FnOnce() -> Value) -> Self {
50 if condition {
51 self.fields
52 .push((key.to_string(), ResourceValue::Present(value())));
53 } else {
54 self.fields.push((key.to_string(), ResourceValue::Missing));
55 }
56 self
57 }
58
59 pub fn unless(self, key: &str, condition: bool, value: impl FnOnce() -> Value) -> Self {
61 self.when(key, !condition, value)
62 }
63
64 pub fn merge_when(
67 mut self,
68 condition: bool,
69 fields: impl FnOnce() -> Vec<(&'static str, Value)>,
70 ) -> Self {
71 if condition {
72 for (key, value) in fields() {
73 self.fields
74 .push((key.to_string(), ResourceValue::Present(value)));
75 }
76 }
77 self
78 }
79
80 pub fn when_some<T: serde::Serialize>(mut self, key: &str, value: &Option<T>) -> Self {
82 if let Some(v) = value {
83 self.fields.push((
84 key.to_string(),
85 ResourceValue::Present(serde_json::to_value(v).unwrap_or(Value::Null)),
86 ));
87 } else {
88 self.fields.push((key.to_string(), ResourceValue::Missing));
89 }
90 self
91 }
92
93 pub fn when_loaded<K, M>(
98 mut self,
99 key: &str,
100 lookup_key: &K,
101 map: &HashMap<K, M>,
102 transform: impl FnOnce(&M) -> Value,
103 ) -> Self
104 where
105 K: Eq + Hash,
106 {
107 if let Some(item) = map.get(lookup_key) {
108 self.fields
109 .push((key.to_string(), ResourceValue::Present(transform(item))));
110 } else {
111 self.fields.push((key.to_string(), ResourceValue::Missing));
112 }
113 self
114 }
115
116 pub fn when_loaded_many<K, M>(
122 mut self,
123 key: &str,
124 lookup_key: &K,
125 map: &HashMap<K, Vec<M>>,
126 transform: impl FnOnce(&[M]) -> Value,
127 ) -> Self
128 where
129 K: Eq + Hash,
130 {
131 if let Some(items) = map.get(lookup_key) {
132 self.fields
133 .push((key.to_string(), ResourceValue::Present(transform(items))));
134 } else {
135 self.fields.push((key.to_string(), ResourceValue::Missing));
136 }
137 self
138 }
139
140 pub fn build(self) -> Value {
143 let mut map = Map::new();
144 for (key, value) in self.fields {
145 if let ResourceValue::Present(v) = value {
146 map.insert(key, v);
147 }
148 }
149 Value::Object(map)
150 }
151}
152
153impl Default for ResourceMap {
154 fn default() -> Self {
155 Self::new()
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use serde_json::json;
163
164 #[test]
165 fn test_field_basic() {
166 let value = ResourceMap::new()
167 .field("id", json!(1))
168 .field("name", json!("Alice"))
169 .build();
170
171 assert_eq!(value, json!({"id": 1, "name": "Alice"}));
172 }
173
174 #[test]
175 fn test_when_true() {
176 let value = ResourceMap::new()
177 .field("id", json!(1))
178 .when("email", true, || json!("a@b.com"))
179 .build();
180
181 assert_eq!(value, json!({"id": 1, "email": "a@b.com"}));
182 }
183
184 #[test]
185 fn test_when_false() {
186 let value = ResourceMap::new()
187 .field("id", json!(1))
188 .when("email", false, || json!("a@b.com"))
189 .build();
190
191 assert_eq!(value, json!({"id": 1}));
192 }
193
194 #[test]
195 fn test_unless_true() {
196 let value = ResourceMap::new()
197 .field("id", json!(1))
198 .unless("debug", true, || json!(true))
199 .build();
200
201 assert_eq!(value, json!({"id": 1}));
202 }
203
204 #[test]
205 fn test_unless_false() {
206 let value = ResourceMap::new()
207 .field("id", json!(1))
208 .unless("debug", false, || json!(true))
209 .build();
210
211 assert_eq!(value, json!({"id": 1, "debug": true}));
212 }
213
214 #[test]
215 fn test_merge_when_true() {
216 let value = ResourceMap::new()
217 .field("id", json!(1))
218 .merge_when(true, || vec![("a", json!(1)), ("b", json!(2))])
219 .build();
220
221 assert_eq!(value, json!({"id": 1, "a": 1, "b": 2}));
222 }
223
224 #[test]
225 fn test_merge_when_false() {
226 let value = ResourceMap::new()
227 .field("id", json!(1))
228 .merge_when(false, || vec![("a", json!(1)), ("b", json!(2))])
229 .build();
230
231 assert_eq!(value, json!({"id": 1}));
232 }
233
234 #[test]
235 fn test_when_some_present() {
236 let bio: Option<&str> = Some("hello");
237 let value = ResourceMap::new()
238 .field("id", json!(1))
239 .when_some("bio", &bio)
240 .build();
241
242 assert_eq!(value, json!({"id": 1, "bio": "hello"}));
243 }
244
245 #[test]
246 fn test_when_some_none() {
247 let bio: Option<String> = None;
248 let value = ResourceMap::new()
249 .field("id", json!(1))
250 .when_some("bio", &bio)
251 .build();
252
253 assert_eq!(value, json!({"id": 1}));
254 }
255
256 #[test]
257 fn test_when_loaded_present() {
258 let mut authors = HashMap::new();
259 authors.insert(10, "Alice".to_string());
260
261 let value = ResourceMap::new()
262 .field("id", json!(1))
263 .when_loaded("author", &10, &authors, |name| json!(name))
264 .build();
265
266 assert_eq!(value, json!({"id": 1, "author": "Alice"}));
267 }
268
269 #[test]
270 fn test_when_loaded_missing() {
271 let authors: HashMap<i32, String> = HashMap::new();
272
273 let value = ResourceMap::new()
274 .field("id", json!(1))
275 .when_loaded("author", &10, &authors, |name| json!(name))
276 .build();
277
278 assert_eq!(value, json!({"id": 1}));
279 }
280
281 #[test]
282 fn test_when_loaded_many_present() {
283 let mut tags = HashMap::new();
284 tags.insert(1, vec!["rust", "web"]);
285
286 let value = ResourceMap::new()
287 .field("id", json!(1))
288 .when_loaded_many("tags", &1, &tags, |t| json!(t))
289 .build();
290
291 assert_eq!(value, json!({"id": 1, "tags": ["rust", "web"]}));
292 }
293
294 #[test]
295 fn test_when_loaded_many_missing() {
296 let tags: HashMap<i32, Vec<&str>> = HashMap::new();
297
298 let value = ResourceMap::new()
299 .field("id", json!(1))
300 .when_loaded_many("tags", &1, &tags, |t| json!(t))
301 .build();
302
303 assert_eq!(value, json!({"id": 1}));
304 }
305
306 #[test]
307 fn test_when_loaded_many_empty_vec() {
308 let mut tags: HashMap<i32, Vec<&str>> = HashMap::new();
309 tags.insert(1, vec![]);
310
311 let value = ResourceMap::new()
312 .field("id", json!(1))
313 .when_loaded_many("tags", &1, &tags, |t| json!(t))
314 .build();
315
316 assert_eq!(value, json!({"id": 1, "tags": []}));
317 }
318
319 #[test]
320 fn test_when_loaded_combined() {
321 let mut authors = HashMap::new();
322 authors.insert(10, "Alice".to_string());
323
324 let mut tags: HashMap<i32, Vec<&str>> = HashMap::new();
325 tags.insert(1, vec!["rust", "web"]);
326
327 let value = ResourceMap::new()
329 .field("id", json!(1))
330 .when_loaded("author", &10, &authors, |name| json!(name))
331 .when_loaded_many("tags", &1, &tags, |t| json!(t))
332 .build();
333
334 assert_eq!(
335 value,
336 json!({"id": 1, "author": "Alice", "tags": ["rust", "web"]})
337 );
338
339 let value = ResourceMap::new()
341 .field("id", json!(2))
342 .when_loaded("author", &99, &authors, |name| json!(name))
343 .when_loaded_many("tags", &1, &tags, |t| json!(t))
344 .build();
345
346 assert_eq!(value, json!({"id": 2, "tags": ["rust", "web"]}));
347
348 let value = ResourceMap::new()
350 .field("id", json!(3))
351 .when_loaded("author", &10, &authors, |name| json!(name))
352 .when_loaded_many("tags", &99, &tags, |t| json!(t))
353 .build();
354
355 assert_eq!(value, json!({"id": 3, "author": "Alice"}));
356 }
357
358 #[test]
359 fn test_field_order_preserved() {
360 let value = ResourceMap::new()
361 .field("c", json!(3))
362 .field("a", json!(1))
363 .field("b", json!(2))
364 .build();
365
366 let keys: Vec<&String> = value.as_object().unwrap().keys().collect();
367 assert_eq!(keys, vec!["c", "a", "b"]);
368 }
369}