164 lines
4.4 KiB
Rust
164 lines
4.4 KiB
Rust
use std::collections::BTreeMap;
|
|
|
|
use serde_json::{Map, Value};
|
|
|
|
use crate::{Schema, SchemaKind};
|
|
|
|
impl Schema {
|
|
pub fn from_json_sample(value: &Value) -> Self {
|
|
match value {
|
|
Value::Null => Self {
|
|
kind: SchemaKind::Null,
|
|
description: None,
|
|
required: true,
|
|
nullable: true,
|
|
default_value: None,
|
|
fields: BTreeMap::new(),
|
|
items: None,
|
|
enum_values: Vec::new(),
|
|
variants: Vec::new(),
|
|
},
|
|
Value::Bool(_) => scalar_schema(SchemaKind::Boolean),
|
|
Value::String(_) => scalar_schema(SchemaKind::String),
|
|
Value::Number(number) if number.is_i64() || number.is_u64() => {
|
|
scalar_schema(SchemaKind::Integer)
|
|
}
|
|
Value::Number(_) => scalar_schema(SchemaKind::Number),
|
|
Value::Array(items) => array_schema(items),
|
|
Value::Object(fields) => object_schema(fields),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn scalar_schema(kind: SchemaKind) -> Schema {
|
|
Schema {
|
|
kind,
|
|
description: None,
|
|
required: true,
|
|
nullable: false,
|
|
default_value: None,
|
|
fields: BTreeMap::new(),
|
|
items: None,
|
|
enum_values: Vec::new(),
|
|
variants: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn array_schema(items: &[Value]) -> Schema {
|
|
Schema {
|
|
kind: SchemaKind::Array,
|
|
description: None,
|
|
required: true,
|
|
nullable: false,
|
|
default_value: None,
|
|
fields: BTreeMap::new(),
|
|
items: items
|
|
.first()
|
|
.map(|item| Box::new(Schema::from_json_sample(item))),
|
|
enum_values: Vec::new(),
|
|
variants: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn object_schema(fields: &Map<String, Value>) -> Schema {
|
|
Schema {
|
|
kind: SchemaKind::Object,
|
|
description: None,
|
|
required: true,
|
|
nullable: false,
|
|
default_value: None,
|
|
fields: fields
|
|
.iter()
|
|
.map(|(name, value)| (name.clone(), Schema::from_json_sample(value)))
|
|
.collect(),
|
|
items: None,
|
|
enum_values: Vec::new(),
|
|
variants: Vec::new(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::collections::BTreeMap;
|
|
|
|
use serde_json::json;
|
|
|
|
use crate::{Schema, SchemaKind};
|
|
|
|
#[test]
|
|
fn infers_object_schema_from_json_sample() {
|
|
let schema = Schema::from_json_sample(&json!({
|
|
"lead": {
|
|
"email": "user@example.com",
|
|
"active": true
|
|
},
|
|
"score": 42
|
|
}));
|
|
|
|
assert_eq!(schema.kind, SchemaKind::Object);
|
|
assert_eq!(
|
|
schema.field("lead").map(|field| field.kind.clone()),
|
|
Some(SchemaKind::Object)
|
|
);
|
|
assert_eq!(
|
|
schema
|
|
.field("lead")
|
|
.and_then(|field| field.field("email"))
|
|
.map(|field| field.kind.clone()),
|
|
Some(SchemaKind::String)
|
|
);
|
|
assert_eq!(
|
|
schema.field("score").map(|field| field.kind.clone()),
|
|
Some(SchemaKind::Integer)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn infers_array_item_schema_from_first_sample_item() {
|
|
let schema = Schema::from_json_sample(&json!([
|
|
{
|
|
"id": "lead_01",
|
|
"tags": ["warm", "priority"]
|
|
}
|
|
]));
|
|
|
|
assert_eq!(schema.kind, SchemaKind::Array);
|
|
assert_eq!(
|
|
schema
|
|
.items
|
|
.as_deref()
|
|
.and_then(|item| item.field("id"))
|
|
.map(|field| field.kind.clone()),
|
|
Some(SchemaKind::String)
|
|
);
|
|
assert_eq!(
|
|
schema
|
|
.items
|
|
.as_deref()
|
|
.and_then(|item| item.field("tags"))
|
|
.map(|field| field.kind.clone()),
|
|
Some(SchemaKind::Array)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn infers_null_schema_for_null_sample() {
|
|
let schema = Schema::from_json_sample(&serde_json::Value::Null);
|
|
|
|
assert_eq!(
|
|
schema,
|
|
Schema {
|
|
kind: SchemaKind::Null,
|
|
description: None,
|
|
required: true,
|
|
nullable: true,
|
|
default_value: None,
|
|
fields: BTreeMap::new(),
|
|
items: None,
|
|
enum_values: Vec::new(),
|
|
variants: Vec::new(),
|
|
}
|
|
);
|
|
}
|
|
}
|