use std::collections::HashMap;
use std::hash::Hash;
use serde_json::{Map, Value};
enum ResourceValue {
Present(Value),
Missing,
}
pub struct ResourceMap {
fields: Vec<(String, ResourceValue)>,
}
impl ResourceMap {
pub fn new() -> Self {
Self { fields: Vec::new() }
}
pub fn field(mut self, key: &str, value: impl Into<Value>) -> Self {
self.fields
.push((key.to_string(), ResourceValue::Present(value.into())));
self
}
pub fn when(mut self, key: &str, condition: bool, value: impl FnOnce() -> Value) -> Self {
if condition {
self.fields
.push((key.to_string(), ResourceValue::Present(value())));
} else {
self.fields.push((key.to_string(), ResourceValue::Missing));
}
self
}
pub fn unless(self, key: &str, condition: bool, value: impl FnOnce() -> Value) -> Self {
self.when(key, !condition, value)
}
pub fn merge_when(
mut self,
condition: bool,
fields: impl FnOnce() -> Vec<(&'static str, Value)>,
) -> Self {
if condition {
for (key, value) in fields() {
self.fields
.push((key.to_string(), ResourceValue::Present(value)));
}
}
self
}
pub fn when_some<T: serde::Serialize>(mut self, key: &str, value: &Option<T>) -> Self {
if let Some(v) = value {
self.fields.push((
key.to_string(),
ResourceValue::Present(serde_json::to_value(v).unwrap_or(Value::Null)),
));
} else {
self.fields.push((key.to_string(), ResourceValue::Missing));
}
self
}
pub fn when_loaded<K, M>(
mut self,
key: &str,
lookup_key: &K,
map: &HashMap<K, M>,
transform: impl FnOnce(&M) -> Value,
) -> Self
where
K: Eq + Hash,
{
if let Some(item) = map.get(lookup_key) {
self.fields
.push((key.to_string(), ResourceValue::Present(transform(item))));
} else {
self.fields.push((key.to_string(), ResourceValue::Missing));
}
self
}
pub fn when_loaded_many<K, M>(
mut self,
key: &str,
lookup_key: &K,
map: &HashMap<K, Vec<M>>,
transform: impl FnOnce(&[M]) -> Value,
) -> Self
where
K: Eq + Hash,
{
if let Some(items) = map.get(lookup_key) {
self.fields
.push((key.to_string(), ResourceValue::Present(transform(items))));
} else {
self.fields.push((key.to_string(), ResourceValue::Missing));
}
self
}
pub fn build(self) -> Value {
let mut map = Map::new();
for (key, value) in self.fields {
if let ResourceValue::Present(v) = value {
map.insert(key, v);
}
}
Value::Object(map)
}
}
impl Default for ResourceMap {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_field_basic() {
let value = ResourceMap::new()
.field("id", json!(1))
.field("name", json!("Alice"))
.build();
assert_eq!(value, json!({"id": 1, "name": "Alice"}));
}
#[test]
fn test_when_true() {
let value = ResourceMap::new()
.field("id", json!(1))
.when("email", true, || json!("a@b.com"))
.build();
assert_eq!(value, json!({"id": 1, "email": "a@b.com"}));
}
#[test]
fn test_when_false() {
let value = ResourceMap::new()
.field("id", json!(1))
.when("email", false, || json!("a@b.com"))
.build();
assert_eq!(value, json!({"id": 1}));
}
#[test]
fn test_unless_true() {
let value = ResourceMap::new()
.field("id", json!(1))
.unless("debug", true, || json!(true))
.build();
assert_eq!(value, json!({"id": 1}));
}
#[test]
fn test_unless_false() {
let value = ResourceMap::new()
.field("id", json!(1))
.unless("debug", false, || json!(true))
.build();
assert_eq!(value, json!({"id": 1, "debug": true}));
}
#[test]
fn test_merge_when_true() {
let value = ResourceMap::new()
.field("id", json!(1))
.merge_when(true, || vec![("a", json!(1)), ("b", json!(2))])
.build();
assert_eq!(value, json!({"id": 1, "a": 1, "b": 2}));
}
#[test]
fn test_merge_when_false() {
let value = ResourceMap::new()
.field("id", json!(1))
.merge_when(false, || vec![("a", json!(1)), ("b", json!(2))])
.build();
assert_eq!(value, json!({"id": 1}));
}
#[test]
fn test_when_some_present() {
let bio: Option<&str> = Some("hello");
let value = ResourceMap::new()
.field("id", json!(1))
.when_some("bio", &bio)
.build();
assert_eq!(value, json!({"id": 1, "bio": "hello"}));
}
#[test]
fn test_when_some_none() {
let bio: Option<String> = None;
let value = ResourceMap::new()
.field("id", json!(1))
.when_some("bio", &bio)
.build();
assert_eq!(value, json!({"id": 1}));
}
#[test]
fn test_when_loaded_present() {
let mut authors = HashMap::new();
authors.insert(10, "Alice".to_string());
let value = ResourceMap::new()
.field("id", json!(1))
.when_loaded("author", &10, &authors, |name| json!(name))
.build();
assert_eq!(value, json!({"id": 1, "author": "Alice"}));
}
#[test]
fn test_when_loaded_missing() {
let authors: HashMap<i32, String> = HashMap::new();
let value = ResourceMap::new()
.field("id", json!(1))
.when_loaded("author", &10, &authors, |name| json!(name))
.build();
assert_eq!(value, json!({"id": 1}));
}
#[test]
fn test_when_loaded_many_present() {
let mut tags = HashMap::new();
tags.insert(1, vec!["rust", "web"]);
let value = ResourceMap::new()
.field("id", json!(1))
.when_loaded_many("tags", &1, &tags, |t| json!(t))
.build();
assert_eq!(value, json!({"id": 1, "tags": ["rust", "web"]}));
}
#[test]
fn test_when_loaded_many_missing() {
let tags: HashMap<i32, Vec<&str>> = HashMap::new();
let value = ResourceMap::new()
.field("id", json!(1))
.when_loaded_many("tags", &1, &tags, |t| json!(t))
.build();
assert_eq!(value, json!({"id": 1}));
}
#[test]
fn test_when_loaded_many_empty_vec() {
let mut tags: HashMap<i32, Vec<&str>> = HashMap::new();
tags.insert(1, vec![]);
let value = ResourceMap::new()
.field("id", json!(1))
.when_loaded_many("tags", &1, &tags, |t| json!(t))
.build();
assert_eq!(value, json!({"id": 1, "tags": []}));
}
#[test]
fn test_when_loaded_combined() {
let mut authors = HashMap::new();
authors.insert(10, "Alice".to_string());
let mut tags: HashMap<i32, Vec<&str>> = HashMap::new();
tags.insert(1, vec!["rust", "web"]);
let value = ResourceMap::new()
.field("id", json!(1))
.when_loaded("author", &10, &authors, |name| json!(name))
.when_loaded_many("tags", &1, &tags, |t| json!(t))
.build();
assert_eq!(
value,
json!({"id": 1, "author": "Alice", "tags": ["rust", "web"]})
);
let value = ResourceMap::new()
.field("id", json!(2))
.when_loaded("author", &99, &authors, |name| json!(name))
.when_loaded_many("tags", &1, &tags, |t| json!(t))
.build();
assert_eq!(value, json!({"id": 2, "tags": ["rust", "web"]}));
let value = ResourceMap::new()
.field("id", json!(3))
.when_loaded("author", &10, &authors, |name| json!(name))
.when_loaded_many("tags", &99, &tags, |t| json!(t))
.build();
assert_eq!(value, json!({"id": 3, "author": "Alice"}));
}
#[test]
fn test_field_order_preserved() {
let value = ResourceMap::new()
.field("c", json!(3))
.field("a", json!(1))
.field("b", json!(2))
.build();
let keys: Vec<&String> = value.as_object().unwrap().keys().collect();
assert_eq!(keys, vec!["c", "a", "b"]);
}
}