use serde_json::{Map, Value, json};
use crate::schema::{FieldDef, FieldKind, TableDef, TextFormat};
pub const JSON_SCHEMA_DIALECT: &str = "https://json-schema.org/draft/2020-12/schema";
#[must_use]
pub fn json_schema(table: &TableDef) -> Value {
let mut properties = Map::new();
let mut required = Vec::new();
for field in &table.fields {
properties.insert(field.name.clone(), field_schema(table, field));
if field.must_be_present() {
required.push(Value::String(field.name.clone()));
}
}
json!({
"$schema": JSON_SCHEMA_DIALECT,
"title": table.name,
"type": "object",
"properties": Value::Object(properties),
"required": Value::Array(required),
"additionalProperties": false,
})
}
fn field_schema(table: &TableDef, field: &FieldDef) -> Value {
let nullable = !field.must_be_present();
let mut schema = Map::new();
let mut set_type = |name: &str| {
schema.insert(
"type".to_owned(),
if nullable {
json!([name, "null"])
} else {
json!(name)
},
);
};
match &field.kind {
FieldKind::Text {
min_len,
max_len,
format,
} => {
set_type("string");
if let Some(min) = min_len {
schema.insert("minLength".to_owned(), json!(min));
}
if let Some(max) = max_len {
schema.insert("maxLength".to_owned(), json!(max));
}
if let Some(format) = format {
let name = match format {
TextFormat::Email => "email",
TextFormat::Url => "uri",
};
schema.insert("format".to_owned(), json!(name));
}
schema.insert("not".to_owned(), json!({ "pattern": "\u{0}" }));
}
FieldKind::Integer { min, max } => {
set_type("integer");
insert_bounds(&mut schema, min.map(|v| json!(v)), max.map(|v| json!(v)));
}
FieldKind::Real { min, max } => {
set_type("number");
insert_bounds(&mut schema, min.map(|v| json!(v)), max.map(|v| json!(v)));
}
FieldKind::Boolean => set_type("boolean"),
FieldKind::Timestamp => {
set_type("string");
schema.insert("format".to_owned(), json!("date-time"));
}
FieldKind::Uuid => {
set_type("string");
schema.insert("format".to_owned(), json!("uuid"));
}
FieldKind::Json => {
}
FieldKind::Enum { values } => {
set_type("string");
let mut members: Vec<Value> = values.iter().map(|value| json!(value)).collect();
if nullable {
members.push(Value::Null);
}
schema.insert("enum".to_owned(), Value::Array(members));
}
}
if table.is_primary_key(&field.name) {
schema.insert(
"description".to_owned(),
json!(format!("Primary key of `{}`.", table.name)),
);
}
Value::Object(schema)
}
fn insert_bounds(schema: &mut Map<String, Value>, min: Option<Value>, max: Option<Value>) {
if let Some(min) = min {
schema.insert("minimum".to_owned(), min);
}
if let Some(max) = max {
schema.insert("maximum".to_owned(), max);
}
}