use crate::{Error, Layout, SchemaVersion, primitives};
use serde_json::Value;
use specta::{Types, datatype::NamedDataType};
use std::collections::BTreeMap;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct JsonSchema {
pub schema_version: SchemaVersion,
pub layout: Layout,
pub title: Option<String>,
pub description: Option<String>,
}
impl Default for JsonSchema {
fn default() -> Self {
Self {
schema_version: SchemaVersion::default(),
layout: Layout::default(),
title: None,
description: None,
}
}
}
impl JsonSchema {
pub fn new() -> Self {
Self::default()
}
pub fn schema_version(mut self, version: SchemaVersion) -> Self {
self.schema_version = version;
self
}
pub fn layout(mut self, layout: Layout) -> Self {
self.layout = layout;
self
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn export(&self, types: &Types) -> Result<String, Error> {
let value = self.export_as_value(types)?;
Ok(serde_json::to_string_pretty(&value)?)
}
pub fn export_as_value(&self, types: &Types) -> Result<Value, Error> {
match self.layout {
Layout::SingleFile => self.export_single_file(types),
Layout::Files => Err(Error::ConversionError(
"Use export_to() for Files layout".to_string(),
)),
}
}
pub fn export_to(&self, path: impl AsRef<Path>, types: &Types) -> Result<(), Error> {
let path = path.as_ref();
match self.layout {
Layout::SingleFile => {
let json = self.export_single_file(types)?;
std::fs::write(path, serde_json::to_string_pretty(&json)?)?;
Ok(())
}
Layout::Files => self.export_files(path, types),
}
}
fn export_single_file(&self, types: &Types) -> Result<Value, Error> {
let mut definitions = BTreeMap::new();
for ndt in types.into_sorted_iter() {
let schema = primitives::export(self, types, &ndt)?;
let name = ndt.name().to_string();
definitions.insert(name, schema);
}
let defs_key = self.schema_version.definitions_key();
let mut root = serde_json::json!({
"$schema": self.schema_version.uri(),
defs_key: definitions,
});
if let Some(title) = &self.title {
root.as_object_mut()
.unwrap()
.insert("title".to_string(), Value::String(title.clone()));
}
if let Some(description) = &self.description {
root.as_object_mut().unwrap().insert(
"description".to_string(),
Value::String(description.clone()),
);
}
Ok(root)
}
fn export_files(&self, base_path: &Path, types: &Types) -> Result<(), Error> {
std::fs::create_dir_all(base_path)?;
let mut by_module: BTreeMap<String, Vec<NamedDataType>> = BTreeMap::new();
for ndt in types.into_sorted_iter() {
let module = ndt.module_path().to_string().replace("::", "/");
by_module.entry(module).or_default().push(ndt.clone());
}
for (module, ndts) in by_module {
let module_dir = if module.is_empty() {
base_path.to_path_buf()
} else {
base_path.join(&module)
};
std::fs::create_dir_all(&module_dir)?;
for ndt in &ndts {
let schema = primitives::export(self, types, ndt)?;
let filename = format!("{}.schema.json", ndt.name());
let file_path = module_dir.join(filename);
let mut root = serde_json::json!({
"$schema": self.schema_version.uri(),
});
if let Some(obj) = schema.as_object() {
for (k, v) in obj {
root.as_object_mut().unwrap().insert(k.clone(), v.clone());
}
}
std::fs::write(file_path, serde_json::to_string_pretty(&root)?)?;
}
}
Ok(())
}
}