use serde::{ser::SerializeStruct, Serialize};
use serde_json::{Map, Value};
use crate::{error::Error, resolver::SchemaResolver, scope::SchemaScope, scope::Space};
use super::{title, JsonSchemaExtractOptions, ModelContainer};
#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
pub struct Model {
#[serde(flatten)]
inner: ModelType,
pub attributes: Attributes,
#[serde(flatten)]
pub spaces: SpacesContainer,
}
impl Model {
pub fn new(inner: ModelType) -> Self {
Self {
inner,
attributes: Attributes::default(),
spaces: SpacesContainer::default(),
}
}
pub fn is_like(&self, other: &Model) -> bool {
self.inner == other.inner && self.spaces == other.spaces
}
pub fn with_attributes(mut self, attributes: &Attributes) -> Self {
self.attributes = attributes.clone();
self
}
pub fn inner(&self) -> &ModelType {
&self.inner
}
pub fn mut_inner(&mut self) -> &mut ModelType {
&mut self.inner
}
}
#[derive(Debug, Serialize, Clone, Eq, PartialEq)]
pub enum ModelType {
#[serde(rename = "primitive")]
PrimitiveType(PrimitiveType),
#[serde(rename = "object")]
ObjectType(ObjectType),
#[serde(rename = "array")]
ArrayType(ArrayType),
#[serde(rename = "enum")]
EnumType(EnumType),
#[serde(rename = "const")]
ConstType(ConstType),
#[serde(rename = "any")]
AnyType(AnyType),
#[serde(rename = "wrapper")]
WrapperType(WrapperType),
#[serde(rename = "optional")]
NullableOptionalWrapperType(NullableOptionalWrapperType),
#[serde(rename = "map")]
MapType(MapType),
#[serde(skip_serializing)]
FlatModel(FlatModel),
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct FlatModel {
pub name: Option<String>,
pub type_: String,
pub model: Option<Box<FlatModel>>,
pub attributes: Attributes,
pub spaces: SpacesContainer,
pub original: Option<u32>,
}
impl From<&FlatModel> for String {
fn from(m: &FlatModel) -> Self {
format!("{}:{:?}", m.type_, m.model)
}
}
#[derive(Debug, Eq, Serialize, Clone, Default)]
pub struct SpacesContainer {
#[serde(rename = "spaces")]
pub list: Vec<Space>,
}
impl PartialEq for SpacesContainer {
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl SpacesContainer {
pub fn add(&mut self, spaces: Vec<Space>) {
for space in spaces {
if !self.list.contains(&space) {
self.list.push(space);
}
}
}
}
#[derive(Debug, Serialize, Clone, Eq, PartialEq, Default)]
pub struct PrimitiveType {
#[serde(rename = "name")]
pub name: Option<String>,
#[serde(rename = "type")]
pub type_: String,
}
#[derive(Debug, Serialize, Clone, Eq, PartialEq, Default)]
pub struct ObjectType {
pub name: String,
pub properties: Vec<FlatModel>,
pub additional: bool,
}
#[derive(Debug, Serialize, Clone, Eq, PartialEq, Default)]
pub struct ArrayType {
#[serde(rename = "name")]
pub name: Option<String>,
#[serde(rename = "models")]
pub model: Box<FlatModel>,
}
#[derive(Debug, Serialize, Clone, Eq, PartialEq, Default)]
pub struct EnumType {
#[serde(rename = "name")]
pub name: String,
#[serde(rename = "type")]
pub type_: String,
#[serde(rename = "options")]
pub variants: Vec<String>,
}
#[derive(Debug, Serialize, Clone, Eq, PartialEq, Default)]
pub struct ConstType {
#[serde(rename = "name")]
pub name: String,
#[serde(rename = "type")]
pub type_: String,
#[serde(rename = "value")]
pub value: String,
}
#[derive(Debug, Clone, Eq, PartialEq, Default, Serialize)]
pub struct MapType {
pub name: Option<String>,
pub model: Box<FlatModel>,
}
#[derive(Debug, Serialize, Clone, Eq, PartialEq)]
pub struct AnyType {}
#[derive(Debug, Serialize, Clone)]
pub struct RegexpType {
#[serde(rename = "name")]
pub name: String,
#[serde(rename = "pattern")]
pub pattern: String,
}
#[derive(Debug, Serialize, Clone, Eq, PartialEq, Default)]
pub struct WrapperType {
#[serde(rename = "name")]
pub name: String,
#[serde(rename = "models")]
pub models: Vec<FlatModel>,
#[serde(rename = "kind")]
pub kind: WrapperTypeKind,
#[serde(rename = "strategy")]
pub strategy: WrapperStrategy,
}
#[derive(Debug, Default, Clone, Serialize, Eq, PartialEq)]
pub enum WrapperTypeKind {
AllOf,
#[default]
OneOf,
}
#[derive(Debug, Default, Clone, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum WrapperStrategy {
#[default]
BruteForce,
Internally(String),
Externally,
}
#[derive(Debug, Serialize, Clone, Eq, PartialEq, Default)]
pub struct NullableOptionalWrapperType {
#[serde(rename = "name")]
pub name: String,
#[serde(rename = "model")]
pub model: FlatModel,
}
#[derive(Debug, Serialize, Clone, Eq, PartialEq)]
pub struct Attributes {
#[serde(rename = "description")]
pub description: Option<String>,
#[serde(rename = "default")]
pub default: Option<Value>,
#[serde(rename = "nullable")]
pub nullable: bool,
#[serde(rename = "required")]
pub required: bool,
#[serde(rename = "reference")]
pub reference: bool,
#[serde(rename = "validation")]
pub validation: Option<std::collections::HashMap<String, Value>>,
#[serde(rename = "schema")]
pub schema: Option<Value>,
#[serde(rename = "x")]
pub x: std::collections::HashMap<String, Value>,
}
impl Model {
pub fn children(&self, container: &ModelContainer) -> Vec<u32> {
let children = match self.inner() {
ModelType::ArrayType(a) => {
vec![a.model.original]
}
ModelType::MapType(s) => {
vec![s.model.original]
}
ModelType::ObjectType(o) => o.properties.iter().map(|p| p.original).collect(),
ModelType::WrapperType(w) => w.models.iter().map(|p| p.original).collect(),
ModelType::NullableOptionalWrapperType(s) => {
vec![s.model.original]
}
_ => vec![],
};
let mut ids = children.iter().cloned().flatten().collect::<Vec<_>>();
let mut additional: Vec<u32> = vec![];
for id in ids.iter() {
additional.append(
&mut container
.models
.get(*id as usize)
.unwrap()
.children(container),
);
}
ids.append(&mut additional);
ids
}
pub fn flatten(
&self,
container: &mut ModelContainer,
scope: &mut SchemaScope,
) -> Result<FlatModel, Error> {
match self.inner() {
ModelType::ArrayType(a) => a.flatten(self),
ModelType::PrimitiveType(p) => p.flatten(self),
ModelType::AnyType(a) => a.flatten(self),
ModelType::MapType(s) => s.flatten(self),
ModelType::ObjectType(o) => o.flatten(container.add(scope, self.clone())),
ModelType::EnumType(e) => e.flatten(container.add(scope, self.clone())),
ModelType::ConstType(c) => c.flatten(container.add(scope, self.clone())),
ModelType::WrapperType(w) => w.flatten(container.add(scope, self.clone())),
ModelType::NullableOptionalWrapperType(s) => {
s.flatten(container.add(scope, self.clone()))
}
ModelType::FlatModel(f) => Ok(f.clone()),
}
.map(|mut s| {
s.spaces = self.spaces.clone();
s.customize_attributes(&self.attributes)
})
}
pub fn add_spaces(&mut self, scope: &mut SchemaScope) {
let spaces = scope.get_spaces();
if !spaces.is_empty() {
self.spaces.add(spaces);
}
}
pub fn name(&self) -> Result<&str, Error> {
match self.inner() {
ModelType::ObjectType(o) => Ok(&o.name),
ModelType::EnumType(e) => Ok(&e.name),
ModelType::ConstType(c) => Ok(&c.name),
ModelType::WrapperType(w) => Ok(&w.name),
ModelType::NullableOptionalWrapperType(s) => Ok(&s.name),
ModelType::PrimitiveType(p) => {
if let Some(s) = &p.name {
Ok(s)
} else {
Err(Error::CodegenCannotNameModelError(format!(
"primitive: {self:?}"
)))
}
}
ModelType::ArrayType(p) => {
if let Some(s) = &p.name {
Ok(s)
} else {
Err(Error::CodegenCannotNameModelError(format!(
"array: {self:?}"
)))
}
}
ModelType::MapType(p) => {
if let Some(s) = &p.name {
Ok(s)
} else {
Err(Error::CodegenCannotNameModelError(format!("map: {self:?}")))
}
}
_ => Err(Error::CodegenCannotNameModelError(format!(
"unknown: {self:?}"
))),
}
}
pub fn rename(self, name: String) -> Model {
Model::new(match self.inner {
ModelType::ObjectType(mut o) => {
o.name = name;
ModelType::ObjectType(o)
}
ModelType::EnumType(mut e) => {
e.name = name;
ModelType::EnumType(e)
}
ModelType::ConstType(mut c) => {
c.name = name;
ModelType::ConstType(c)
}
ModelType::WrapperType(mut w) => {
w.name = name;
ModelType::WrapperType(w)
}
ModelType::NullableOptionalWrapperType(mut s) => {
s.name = name;
ModelType::NullableOptionalWrapperType(s)
}
ModelType::PrimitiveType(mut p) => {
p.name = Some(name);
ModelType::PrimitiveType(p)
}
ModelType::ArrayType(mut p) => {
p.name = Some(name);
ModelType::ArrayType(p)
}
_ => panic!("Unsupported rename: {name}"),
})
}
}
impl Serialize for FlatModel {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut state = serializer.serialize_struct("FlattenedType", 9)?;
state.serialize_field("name", &self.name)?;
state.serialize_field("type", &self.type_)?;
state.serialize_field("model", &self.model)?;
state.serialize_field("required", &self.attributes.required)?;
state.serialize_field("nullable", &self.attributes.nullable)?;
state.serialize_field("validation", &self.attributes.validation)?;
state.serialize_field("x", &self.attributes.x)?;
state.serialize_field("description", &self.attributes.description)?;
state.serialize_field("default", &self.attributes.default)?;
state.end()
}
}
impl Default for FlatModel {
fn default() -> Self {
Self {
model: None,
name: None,
original: None,
type_: "".to_string(),
attributes: Attributes::default(),
spaces: SpacesContainer::default(),
}
}
}
impl FlatModel {
pub fn customize_attributes(mut self, attributes: &Attributes) -> Self {
self.attributes.required = attributes.required;
self.attributes.nullable = attributes.nullable;
self
}
}
impl PrimitiveType {
pub fn flatten(&self, added: &Model) -> Result<FlatModel, Error> {
Ok(FlatModel {
name: self.name.clone(),
type_: self.type_.clone(),
attributes: added.attributes.clone(),
..FlatModel::default()
})
}
}
impl ObjectType {
pub fn flatten(&self, added: (Option<u32>, &Model)) -> Result<FlatModel, Error> {
if let ModelType::ObjectType(linked) = added.1.inner() {
Ok(FlatModel {
name: None,
type_: "object".to_string(),
model: Some(Box::new(FlatModel {
type_: linked.name.clone(),
name: Some(linked.name.clone()),
..FlatModel::default()
})),
attributes: Attributes {
reference: true,
..added.1.attributes.clone()
},
original: added.0,
..FlatModel::default()
})
} else {
Err(Error::FlatteningTypeError)
}
}
}
impl ArrayType {
pub fn flatten(&self, added: &Model) -> Result<FlatModel, Error> {
let m = self.model.as_ref().clone();
Ok(FlatModel {
type_: "array".to_string(),
attributes: Attributes {
required: true,
reference: m.attributes.reference,
..added.attributes.clone()
},
original: m.original,
model: Some(Box::new(m)),
..FlatModel::default()
})
}
}
impl EnumType {
pub fn flatten(&self, added: (Option<u32>, &Model)) -> Result<FlatModel, Error> {
if let ModelType::EnumType(linked) = added.1.inner() {
Ok(FlatModel {
name: None,
type_: "enum".to_string(),
model: Some(Box::new(FlatModel {
type_: linked.type_.clone(),
name: Some(linked.name.clone()),
model: None,
attributes: Attributes {
required: true,
nullable: false,
..added.1.attributes.clone()
},
original: added.0,
..FlatModel::default()
})),
original: added.0,
attributes: added.1.attributes.clone(),
..FlatModel::default()
})
} else {
Err(Error::FlatteningTypeError)
}
}
}
impl ConstType {
pub fn flatten(&self, added: (Option<u32>, &Model)) -> Result<FlatModel, Error> {
if let ModelType::ConstType(linked) = added.1.inner() {
Ok(FlatModel {
name: Some(linked.name.clone()),
type_: "const".to_string(),
model: Some(Box::new(FlatModel {
type_: linked.type_.clone(),
name: Some(linked.value.clone()),
model: None,
attributes: Attributes {
required: true,
nullable: false,
..added.1.attributes.clone()
},
..FlatModel::default()
})),
original: added.0,
attributes: added.1.attributes.clone(),
..FlatModel::default()
})
} else {
Err(Error::FlatteningTypeError)
}
}
}
impl MapType {
pub fn flatten(&self, added: &Model) -> Result<FlatModel, Error> {
let m = self.model.as_ref().clone();
Ok(FlatModel {
type_: "map".to_string(),
original: m.original,
model: Some(Box::new(m)),
attributes: Attributes {
required: true,
..added.attributes.clone()
},
..FlatModel::default()
})
}
}
impl Default for Attributes {
fn default() -> Self {
Self {
description: None,
default: None,
nullable: false,
required: true,
validation: None,
reference: false,
schema: None,
x: std::collections::HashMap::new(),
}
}
}
impl AnyType {
pub fn model(schema: &Map<String, Value>, scope: &SchemaScope) -> Model {
log::debug!("{}: {:?} may be invalid json schema", scope, schema);
Model::new(ModelType::AnyType(Self {}))
}
pub fn flatten(&self, added: &Model) -> Result<FlatModel, Error> {
Ok(FlatModel {
name: None,
type_: "any".to_string(),
model: None,
attributes: added.attributes.clone(),
..FlatModel::default()
})
}
}
impl PartialEq for RegexpType {
fn eq(&self, other: &Self) -> bool {
self.pattern == other.pattern
}
}
impl WrapperType {
pub fn flatten(&self, added: (Option<u32>, &Model)) -> Result<FlatModel, Error> {
if let ModelType::WrapperType(linked) = added.1.inner() {
Ok(FlatModel {
name: None,
type_: "wrapper".to_string(),
model: Some(Box::new(FlatModel {
name: Some(linked.name.to_string()),
type_: "wrapper".to_string(),
model: None,
..FlatModel::default()
})),
attributes: added.1.attributes.clone(),
original: added.0,
..FlatModel::default()
})
} else {
Err(Error::FlatteningTypeError)
}
}
}
impl NullableOptionalWrapperType {
pub fn flatten(&self, added: (Option<u32>, &Model)) -> Result<FlatModel, Error> {
if let ModelType::NullableOptionalWrapperType(linked) = added.1.inner() {
let mut flat = linked.model.clone();
flat.name = Some(linked.name.clone());
Ok(FlatModel {
name: linked.model.name.clone(),
type_: "wrapper".to_string(),
model: Some(Box::new(flat)),
original: added.0,
..FlatModel::default()
})
} else {
Err(Error::FlatteningTypeError)
}
}
}
impl PrimitiveType {
pub fn from(
schema: &Map<String, Value>,
scope: &mut SchemaScope,
_resolver: &SchemaResolver,
options: &JsonSchemaExtractOptions,
) -> Self {
let type_ = schema.get("type").unwrap().as_str().unwrap().to_string();
let name = title::extract_title(schema, scope, options)
.map(Some)
.unwrap();
PrimitiveType { name, type_ }
}
}
pub fn as_regexp_type(container: &mut ModelContainer, pattern: &str) -> RegexpType {
container.upsert_regexp(RegexpType {
name: "Regexp".to_string(),
pattern: pattern.to_string(),
})
}