use crate::{error::Error, scope::SchemaNamingStrategy};
use serde_json::Map;
use serde_json::Value;
use crate::schema::Schema;
use crate::scope::SchemaScope;
pub struct JsonSchemaNamer;
pub struct JsonSchemaNamerOptions {
overwrite: bool,
overwrite_ambiguous: bool,
base_name: Option<String>,
naming_strategy: SchemaNamingStrategy,
}
impl JsonSchemaNamer {
pub fn options() -> JsonSchemaNamerOptions {
JsonSchemaNamerOptions {
overwrite: false,
overwrite_ambiguous: false,
base_name: None,
naming_strategy: SchemaNamingStrategy::Default,
}
}
}
impl JsonSchemaNamerOptions {
pub fn with_overwrite(&mut self, value: bool) -> &mut Self {
self.overwrite = value;
self
}
pub fn with_overwrite_ambiguous(&mut self, value: bool) -> &mut Self {
self.overwrite_ambiguous = value;
self
}
pub fn with_base_name(&mut self, value: Option<String>) -> &mut Self {
self.base_name = value;
self
}
pub fn with_naming_strategy(&mut self, value: SchemaNamingStrategy) -> &mut Self {
self.naming_strategy = value;
self
}
pub fn process(&self, schema: &mut Schema) -> Result<(), Error> {
let root = schema.get_body_mut();
name_schema(
root,
&mut SchemaScope::new(self.naming_strategy.clone()),
&NamerOptions {
overwrite: self.overwrite,
overwrite_ambiguous: self.overwrite_ambiguous,
base_name: self.base_name.clone(), },
)
}
}
#[derive(Debug)]
pub struct NamerOptions {
pub overwrite: bool,
pub overwrite_ambiguous: bool,
pub base_name: Option<String>,
}
pub fn name_schema(
root: &mut Value,
scope: &mut SchemaScope,
options: &NamerOptions,
) -> Result<(), Error> {
match root {
Value::Object(ref mut map) => {
let title = get_title(map, scope, options)?;
if let Some(t) = &title {
scope.entity(t);
map.insert("title".to_string(), Value::String(t.clone()));
}
log::debug!("{}", scope);
if let Some(v) = map.get_mut("properties") {
scope.form("properties");
for (x, y) in v.as_object_mut().unwrap() {
scope.property(x);
name_schema(y, scope, options)?;
scope.pop();
}
scope.pop();
}
lazy_static! {
static ref NESTED_DEFINITIONS: [&'static str; 2] = ["definitions", "$defs"];
}
for key in NESTED_DEFINITIONS.iter() {
if let Some(v) = map.get_mut(*key) {
scope.any(key);
for (x, y) in v.as_object_mut().unwrap() {
scope.definition(x);
name_schema(y, scope, options)?;
scope.pop();
}
scope.pop();
}
}
lazy_static! {
static ref NESTED_NAMES: [&'static str; 5] =
["items", "oneOf", "allOf", "anyOf", "not"];
}
for key in NESTED_NAMES.iter() {
if let Some(value) = map.get_mut(*key) {
scope.form(key);
name_schema(value, scope, options)?;
scope.pop();
}
}
if title.is_some() {
scope.pop();
}
Ok(())
}
Value::Array(a) => {
for (index, x) in a.iter_mut().enumerate() {
scope.index(index);
name_schema(x, scope, options)?;
scope.pop();
}
Ok(())
}
_ => Ok(()),
}
}
fn get_title(
map: &Map<String, Value>,
scope: &mut SchemaScope,
options: &NamerOptions,
) -> Result<Option<String>, Error> {
let mut title = map.get("title").map(|t| t.as_str().unwrap().to_string());
if scope.is_empty() {
if title.is_none() || options.overwrite {
title = options.base_name.clone();
}
return Ok(Some(title.ok_or(Error::NamingBaseNameNotFound)?));
} else if title.is_none() || options.overwrite {
if !options.overwrite_ambiguous && scope.is_ambiguous() {
return Ok(None);
}
if map
.get("type")
.map(|s| match s {
Value::String(s) => s != "object", _ => false,
})
.unwrap_or(false)
{
return Ok(None);
}
let proposal = scope.namer().simple().map(|s| {
log::debug!("{} -> {}", scope, &s);
Some(s)
})?;
return Ok(proposal);
} else if title.is_some() {
log::debug!("{} -> leaving original", scope);
}
Ok(title)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_one_of_ambiguous() {
let expected = json!({
"type": "object",
"title": "JustTesting",
"oneOf": [
{
"title": "JustTestingOption1",
"type": "object",
"required": ["option1"],
"properties": {
"option1": { "type": "string" }
}
},
{
"title": "JustTestingOption2",
"type": "object",
"required": ["option2"],
"properties": {
"option2": { "type": "string" },
}
},
],
});
let value = json!({
"type": "object",
"title": "BasicName",
"oneOf": [
{
"title": "A",
"type": "object",
"required": ["option1"],
"properties": {
"option1": { "type": "string" }
}
},
{
"title": "B",
"type": "object",
"required": ["option2"],
"properties": {
"option2": { "type": "string" },
}
},
],
});
let mut schema = Schema::from_json(value);
let _result = JsonSchemaNamer::options()
.with_overwrite(true)
.with_overwrite_ambiguous(true)
.with_base_name(Some("JustTesting".to_string()))
.process(&mut schema);
assert_eq!(schema.get_body().to_string(), expected.to_string());
}
#[test]
fn test_one_of_ambiguous_false() {
let expected = json!({
"type": "object",
"title": "JustTesting",
"oneOf": [
{
"title": "A",
"type": "object",
"required": ["option1"],
"properties": {
"option1": { "type": "string" }
}
},
{
"title": "B",
"type": "object",
"required": ["option2"],
"properties": {
"option2": { "type": "string" },
}
},
],
});
let value = json!({
"type": "object",
"title": "BasicName",
"oneOf": [
{
"title": "A",
"type": "object",
"required": ["option1"],
"properties": {
"option1": { "type": "string" }
}
},
{
"title": "B",
"type": "object",
"required": ["option2"],
"properties": {
"option2": { "type": "string" },
}
},
],
});
let mut schema = Schema::from_json(value);
let _result = JsonSchemaNamer::options()
.with_overwrite(true)
.with_overwrite_ambiguous(false)
.with_base_name(Some("JustTesting".to_string()))
.process(&mut schema);
assert_eq!(schema.get_body().to_string(), expected.to_string());
}
#[test]
fn test_any_of_ambiguous() {
let expected = json!({
"type": "object",
"title": "JustTesting",
"anyOf": [
{
"title": "JustTestingOption1",
"type": "object",
"required": ["option1"],
"properties": {
"option1": { "type": "string" }
}
},
{
"title": "JustTestingOption2",
"type": "object",
"required": ["option2"],
"properties": {
"option2": { "type": "string" },
}
},
],
});
let value = json!({
"type": "object",
"title": "BasicName",
"anyOf": [
{
"title": "A",
"type": "object",
"required": ["option1"],
"properties": {
"option1": { "type": "string" }
}
},
{
"title": "B",
"type": "object",
"required": ["option2"],
"properties": {
"option2": { "type": "string" },
}
},
],
});
let mut schema = Schema::from_json(value);
let _result = JsonSchemaNamer::options()
.with_overwrite(true)
.with_overwrite_ambiguous(true)
.with_base_name(Some("JustTesting".to_string()))
.process(&mut schema);
assert_eq!(schema.get_body().to_string(), expected.to_string());
}
#[test]
fn test_any_of_ambiguous_false() {
let expected = json!({
"type": "object",
"title": "JustTesting",
"anyOf": [
{
"title": "A",
"type": "object",
"required": ["option1"],
"properties": {
"option1": { "type": "string" }
}
},
{
"title": "B",
"type": "object",
"required": ["option2"],
"properties": {
"option2": { "type": "string" },
}
},
],
});
let value = json!({
"type": "object",
"title": "BasicName",
"anyOf": [
{
"title": "A",
"type": "object",
"required": ["option1"],
"properties": {
"option1": { "type": "string" }
}
},
{
"title": "B",
"type": "object",
"required": ["option2"],
"properties": {
"option2": { "type": "string" },
}
},
],
});
let mut schema = Schema::from_json(value);
let _result = JsonSchemaNamer::options()
.with_overwrite(true)
.with_overwrite_ambiguous(false)
.with_base_name(Some("JustTesting".to_string()))
.process(&mut schema);
assert_eq!(schema.get_body().to_string(), expected.to_string());
}
}