use serde::Serialize;
use serde_json::{Map, Value};
use crate::spec::field::{FieldKind, FieldMeta};
#[derive(Debug, Clone, Default, Serialize)]
pub struct Schema {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>,
#[serde(rename = "description", skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "properties", skip_serializing_if = "Option::is_none")]
pub properties: Option<Map<String, Value>>,
#[serde(rename = "items", skip_serializing_if = "Option::is_none")]
pub items: Option<Box<Schema>>,
#[serde(rename = "required", skip_serializing_if = "Option::is_none")]
pub required: Option<Vec<String>>,
#[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
pub r#enum: Option<Vec<Value>>,
#[serde(rename = "default", skip_serializing_if = "Option::is_none")]
pub default: Option<Value>,
#[serde(rename = "format", skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
#[serde(rename = "oneOf", skip_serializing_if = "Option::is_none")]
pub one_of: Option<Vec<Value>>,
#[serde(rename = "const", skip_serializing_if = "Option::is_none")]
pub r#const: Option<Value>,
}
impl Schema {
pub fn object() -> Self {
Schema {
r#type: Some("object".to_string()),
properties: Some(Map::new()),
..Default::default()
}
}
}
pub fn schema_to_value(s: &Schema) -> Value {
serde_json::to_value(s).unwrap_or(Value::Object(Map::new()))
}
pub fn build_schema(fields: &[FieldMeta]) -> Schema {
let mut s = Schema::object();
let mut required = Vec::new();
let props = s.properties.as_mut().unwrap();
for f in fields {
if f.skip {
continue;
}
props.insert(f.json_name.clone(), schema_to_value(&field_schema(f)));
if f.required {
required.push(f.json_name.clone());
}
}
if !required.is_empty() {
s.required = Some(required);
}
s
}
pub fn field_schema(f: &FieldMeta) -> Schema {
match f.kind {
FieldKind::Bytes => decorated(
f,
Schema {
r#type: Some("string".to_string()),
description: opt(f.description.clone()),
..Default::default()
},
),
FieldKind::Duration => decorated(
f,
Schema {
r#type: Some("string".to_string()),
format: Some("duration".to_string()),
description: opt(f.description.clone()),
..Default::default()
},
),
FieldKind::Time => decorated(
f,
Schema {
r#type: Some("string".to_string()),
format: Some("date-time".to_string()),
description: opt(f.description.clone()),
..Default::default()
},
),
FieldKind::Slice => {
let mut s = Schema {
r#type: Some("array".to_string()),
description: opt(f.description.clone()),
..Default::default()
};
if let Some(e) = &f.elem {
s.items = Some(Box::new(field_schema(e)));
}
decorated(f, s)
}
FieldKind::Struct => {
let cs = build_schema(&f.children);
Schema {
r#type: Some("object".to_string()),
description: opt(f.description.clone()),
properties: cs.properties,
required: cs.required,
..Default::default()
}
}
FieldKind::Ptr => {
let mut s = match &f.elem {
Some(e) => field_schema(e),
None => Schema::default(),
};
if !f.description.is_empty() {
s.description = Some(f.description.clone());
}
if let Some(d) = effective_default(f) {
s.default = Some(d);
}
if !f.enum_values.is_empty() {
s.r#enum = Some(f.enum_values.clone());
}
s
}
FieldKind::Union => {
let mut s = Schema {
description: opt(f.description.clone()),
..Default::default()
};
if let Some(u) = &f.union {
let mut branches = Vec::with_capacity(u.variants.len());
for v in &u.variants {
let mut branch = Schema::object();
let props = branch.properties.as_mut().unwrap();
let disc = Schema {
r#type: Some("string".to_string()),
r#const: Some(Value::String(v.name.clone())),
..Default::default()
};
let mut required = vec![u.tag.clone()];
props.insert(
u.tag.clone(),
serde_json::to_value(&disc).unwrap_or(Value::Null),
);
for c in &v.meta {
if c.skip {
continue;
}
props.insert(c.json_name.clone(), schema_to_value(&field_schema(c)));
if c.required {
required.push(c.json_name.clone());
}
}
branch.required = Some(required);
branches.push(schema_to_value(&branch));
}
s.one_of = Some(branches);
}
s
}
_ => decorated(
f,
Schema {
r#type: Some(f.kind.schema_type().to_string()),
description: opt(f.description.clone()),
..Default::default()
},
),
}
}
fn opt(s: String) -> Option<String> {
if s.is_empty() { None } else { Some(s) }
}
fn decorated(f: &FieldMeta, mut s: Schema) -> Schema {
if let Some(d) = effective_default(f) {
s.default = Some(d);
}
if !f.enum_values.is_empty() {
s.r#enum = Some(f.enum_values.clone());
}
s
}
fn effective_default(f: &FieldMeta) -> Option<Value> {
f.mcp.default.clone().or_else(|| f.default.clone())
}
impl crate::spec::XyzSchema for Value {
fn xyz_schema() -> Option<Schema> {
None
}
}