use serde_json::{Map, Value as JsonValue};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShapeSchema {
Any,
Object(Vec<(String, ShapeSchema)>, Vec<String>),
Array(Box<ShapeSchema>),
String,
Number,
Integer,
Boolean,
Null,
}
pub fn shape_to_json_schema(schema: &ShapeSchema) -> JsonValue {
match schema {
ShapeSchema::Any => JsonValue::Object(Map::new()),
ShapeSchema::Object(properties, required) => {
let mut props = Map::new();
for (name, property) in properties {
props.insert(name.clone(), shape_to_json_schema(property));
}
let mut object = Map::new();
object.insert("type".to_owned(), JsonValue::String("object".to_owned()));
object.insert("properties".to_owned(), JsonValue::Object(props));
object.insert(
"required".to_owned(),
JsonValue::Array(
required
.iter()
.map(|name| JsonValue::String(name.clone()))
.collect(),
),
);
JsonValue::Object(object)
}
ShapeSchema::Array(items) => {
let mut object = Map::new();
object.insert("type".to_owned(), JsonValue::String("array".to_owned()));
object.insert("items".to_owned(), shape_to_json_schema(items));
JsonValue::Object(object)
}
ShapeSchema::String => typed("string"),
ShapeSchema::Number => typed("number"),
ShapeSchema::Integer => typed("integer"),
ShapeSchema::Boolean => typed("boolean"),
ShapeSchema::Null => typed("null"),
}
}
fn typed(kind: &str) -> JsonValue {
let mut object = Map::new();
object.insert("type".to_owned(), JsonValue::String(kind.to_owned()));
JsonValue::Object(object)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn any_is_empty_schema() {
assert_eq!(shape_to_json_schema(&ShapeSchema::Any), json!({}));
}
#[test]
fn scalar_types() {
assert_eq!(
shape_to_json_schema(&ShapeSchema::String),
json!({"type": "string"})
);
assert_eq!(
shape_to_json_schema(&ShapeSchema::Number),
json!({"type": "number"})
);
assert_eq!(
shape_to_json_schema(&ShapeSchema::Integer),
json!({"type": "integer"})
);
assert_eq!(
shape_to_json_schema(&ShapeSchema::Boolean),
json!({"type": "boolean"})
);
assert_eq!(
shape_to_json_schema(&ShapeSchema::Null),
json!({"type": "null"})
);
}
#[test]
fn array_wraps_items() {
let schema = ShapeSchema::Array(Box::new(ShapeSchema::String));
assert_eq!(
shape_to_json_schema(&schema),
json!({"type": "array", "items": {"type": "string"}})
);
}
#[test]
fn object_with_properties_and_required() {
let schema = ShapeSchema::Object(
vec![
("name".to_owned(), ShapeSchema::String),
("age".to_owned(), ShapeSchema::Integer),
],
vec!["name".to_owned()],
);
assert_eq!(
shape_to_json_schema(&schema),
json!({
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
},
"required": ["name"],
})
);
}
#[test]
fn nested_object_and_array() {
let schema = ShapeSchema::Object(
vec![(
"tags".to_owned(),
ShapeSchema::Array(Box::new(ShapeSchema::String)),
)],
vec![],
);
assert_eq!(
shape_to_json_schema(&schema),
json!({
"type": "object",
"properties": {
"tags": {"type": "array", "items": {"type": "string"}},
},
"required": [],
})
);
}
#[test]
fn any_nested_in_object() {
let schema = ShapeSchema::Object(vec![("data".to_owned(), ShapeSchema::Any)], vec![]);
assert_eq!(
shape_to_json_schema(&schema),
json!({
"type": "object",
"properties": {"data": {}},
"required": [],
})
);
}
}