use super::CliResult;
use crate::args::{SchemaArgs, SchemaType};
use citum_schema::InputBibliography;
use citum_schema::Style;
use citum_schema::locale::RawLocale;
use citum_schema_data::AbbreviationMap;
use citum_server::rpc::{
FormatDocumentParams, RenderBibliographyParams, RenderCitationParams, ValidateStyleParams,
};
use schemars::schema_for;
use std::fs;
const TOLERANT_TYPE_NAMES: &[&str] = &[
"ArticleJournalBibliographyConfig",
"AudioVisualWork",
"BibliographyConfig",
"BibliographyOptions",
"BibliographySortPartitioning",
"BibliographySpec",
"Brief",
"CitationOptions",
"CitationSpec",
"Classic",
"Collection",
"CollectionComponent",
"CompoundNumericConfig",
"Config",
"ContributorConfig",
"Dataset",
"DateConfig",
"Event",
"Hearing",
"IntegralNameConfig",
"LegalCase",
"LocalizedTemplateSpec",
"LocatorConfig",
"LocatorKindConfig",
"LocatorPattern",
"Monograph",
"NoteConfig",
"Patent",
"Regulation",
"Serial",
"SerialComponent",
"Software",
"Standard",
"Statute",
"Style",
"Substitute",
"TitleRendering",
"TitlesConfig",
"Treaty",
];
fn stamp_unevaluated_properties(schema: &mut serde_json::Value) {
fn stamp_object(obj: &mut serde_json::Map<String, serde_json::Value>) {
if obj.get("type").and_then(|v| v.as_str()) != Some("object") {
return;
}
if obj.contains_key("unevaluatedProperties") {
return;
}
if obj.get("additionalProperties") == Some(&serde_json::Value::Bool(false)) {
return;
}
obj.insert(
"unevaluatedProperties".to_string(),
serde_json::Value::Bool(false),
);
}
if let Some(root_obj) = schema.as_object_mut() {
let root_is_tolerant = root_obj
.get("title")
.and_then(|v| v.as_str())
.is_some_and(|t| TOLERANT_TYPE_NAMES.contains(&t));
if root_is_tolerant {
stamp_object(root_obj);
}
if let Some(defs) = root_obj.get_mut("$defs").and_then(|v| v.as_object_mut()) {
for name in TOLERANT_TYPE_NAMES {
if let Some(def_obj) = defs.get_mut(*name).and_then(|v| v.as_object_mut()) {
stamp_object(def_obj);
}
}
if let Some(ir) = defs
.get_mut("InputReference")
.and_then(|v| v.as_object_mut())
&& let Some(one_of) = ir.get_mut("oneOf").and_then(|v| v.as_array_mut())
{
for branch in one_of {
if let Some(branch_obj) = branch.as_object_mut() {
branch_obj.remove("additionalProperties");
stamp_object(branch_obj);
}
}
}
}
}
}
fn build_server_schema() -> Result<serde_json::Value, serde_json::Error> {
use serde_json::{Map, Value};
let methods: &[(&str, Value)] = &[
(
"render_citation",
serde_json::to_value(schema_for!(RenderCitationParams))?,
),
(
"render_bibliography",
serde_json::to_value(schema_for!(RenderBibliographyParams))?,
),
(
"validate_style",
serde_json::to_value(schema_for!(ValidateStyleParams))?,
),
(
"format_document",
serde_json::to_value(schema_for!(FormatDocumentParams))?,
),
];
let mut merged_defs: Map<String, Value> = Map::new();
let mut method_schemas: Vec<(&str, Value)> = Vec::new();
for (name, schema) in methods {
let mut schema = schema.clone();
if let Some(obj) = schema.as_object_mut() {
obj.remove("$schema");
if let Some(Value::Object(defs_map)) = obj.remove("$defs") {
merged_defs.extend(defs_map);
}
}
method_schemas.push((name, schema));
}
let mut doc = serde_json::json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Citum Server RPC",
"description": "Parameter schemas for all Citum JSON-RPC methods.",
});
if let Some(obj) = doc.as_object_mut() {
if !merged_defs.is_empty() {
obj.insert("$defs".to_string(), Value::Object(merged_defs));
}
for (name, schema) in method_schemas {
obj.insert(name.to_string(), schema);
}
}
Ok(doc)
}
pub(super) fn run_schema(args: SchemaArgs) -> CliResult {
if let Some(dir) = args.out_dir {
fs::create_dir_all(&dir)?;
let server_schema = build_server_schema()?;
let mut schemas: Vec<(&str, serde_json::Value)> = vec![
("style", serde_json::to_value(schema_for!(Style))?),
("bib", serde_json::to_value(schema_for!(InputBibliography))?),
("locale", serde_json::to_value(schema_for!(RawLocale))?),
(
"citation",
serde_json::to_value(schema_for!(citum_schema::Citations))?,
),
(
"registry",
serde_json::to_value(schema_for!(citum_schema::StyleRegistry))?,
),
(
"abbrev-map",
serde_json::to_value(schema_for!(AbbreviationMap))?,
),
("server", server_schema),
];
for (_, schema) in &mut schemas {
stamp_unevaluated_properties(schema);
}
for (name, schema) in &schemas {
let filename = format!("{name}.json");
let path = dir.join(&filename);
fs::write(&path, serde_json::to_string_pretty(&schema)?)?;
}
println!("Schemas exported to {}", dir.display());
return Ok(());
}
if let Some(t) = args.r#type {
let mut schema: serde_json::Value = match t {
SchemaType::Style => serde_json::to_value(schema_for!(Style))?,
SchemaType::Bib => serde_json::to_value(schema_for!(InputBibliography))?,
SchemaType::Locale => serde_json::to_value(schema_for!(RawLocale))?,
SchemaType::Citation => serde_json::to_value(schema_for!(citum_schema::Citations))?,
SchemaType::Registry => serde_json::to_value(schema_for!(citum_schema::StyleRegistry))?,
SchemaType::AbbrevMap => serde_json::to_value(schema_for!(AbbreviationMap))?,
SchemaType::Server => build_server_schema()?,
};
stamp_unevaluated_properties(&mut schema);
println!("{}", serde_json::to_string_pretty(&schema)?);
return Ok(());
}
Err(
"Specify a schema type (style, bib, locale, citation, registry, abbrev-map, server) or --out-dir"
.into(),
)
}