You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
programming-rust-examples/json-macro/src/macros.rs

146 lines
4.4 KiB
Rust

// macros.rs
pub use std::collections::HashMap;
pub use std::boxed::Box;
pub use std::string::ToString;
#[macro_export]
macro_rules! json {
(null) => {
$crate::Json::Null
};
([ $( $element:tt ),* ]) => {
$crate::Json::Array(vec![ $( json!($element) ),* ])
};
({ $( $key:tt : $value:tt ),* }) => {
{
let mut fields = $crate::macros::Box::new(
$crate::macros::HashMap::new());
$(
fields.insert($crate::macros::ToString::to_string($key),
json!($value));
)*
$crate::Json::Object(fields)
}
};
($other:tt) => {
$crate::Json::from($other)
};
}
#[cfg(test)]
mod tests {
use crate::Json;
#[test]
fn json_with_rust_expressions() {
const HELLO: &'static str = "hello";
let macro_generated_value =
json!({
"math_works": (4 - 2 == 2),
"en": HELLO,
HELLO: "bonjour!"
})
;
let hand_coded_value = Json::Object(Box::new(vec![
("math_works".to_string(), Json::Boolean(true)),
("en".to_string(), Json::String("hello".to_string())),
("hello".to_string(), Json::String("bonjour!".to_string())),
].into_iter().collect()));
assert_eq!(macro_generated_value, hand_coded_value);
}
// Tests from earlier in the chapter should actually pass with this macro.
#[test]
fn original_example() {
let hand_coded_value = {
let students = Json::Array(vec![
Json::Object(Box::new(vec![
("name".to_string(), Json::String("Jim Blandy".to_string())),
("class_of".to_string(), Json::Number(1926.0)),
("major".to_string(), Json::String("Tibetan throat singing".to_string()))
].into_iter().collect())),
Json::Object(Box::new(vec![
("name".to_string(), Json::String("Jason Orendorff".to_string())),
("class_of".to_string(), Json::Number(1702.0)),
("major".to_string(), Json::String("Knots".to_string()))
].into_iter().collect()))
]);
students
};
let macro_generated_value = {
let students = json!([
{
"name": "Jim Blandy",
"class_of": 1926,
"major": "Tibetan throat singing"
},
{
"name": "Jason Orendorff",
"class_of": 1702,
"major": "Knots"
}
]);
students
};
assert_eq!(macro_generated_value, hand_coded_value);
}
#[test]
fn json_array_with_json_element() {
let macro_generated_value = json!(
[
// valid JSON that doesn't match `$element:expr`
{
"pitch": 440.0
}
]
);
let hand_coded_value =
Json::Array(vec![
Json::Object(Box::new(vec![
("pitch".to_string(), Json::Number(440.0))
].into_iter().collect()))
]);
assert_eq!(macro_generated_value, hand_coded_value);
}
#[test]
fn json_monolith() {
let width = 4.0;
let desc =
json!({
"width": width,
"height": (width * 9.0 / 4.0)
});
let hand_coded_value =
Json::Object(Box::new(vec![
("width".to_string(), Json::Number(width)),
("height".to_string(), Json::Number(width * 9.0 / 4.0))
].into_iter().collect()));
assert_eq!(desc, hand_coded_value);
}
#[test]
fn hygiene() {
// The surprise is that *the macro works as-is*.
// Rust renames the variable for you!
let fields = "Fields, W.C.";
let role = json!({
"name": "Larson E. Whipsnade",
"actor": fields
});
let hand_coded_value =
Json::Object(Box::new(vec![
("name".to_string(), Json::String("Larson E. Whipsnade".to_string())),
("actor".to_string(), Json::String("Fields, W.C.".to_string()))
].into_iter().collect()));
assert_eq!(role, hand_coded_value);
}
}