use std::any::TypeId;
use std::collections::HashMap;
use eure_document::Text;
use eure_document::identifier::Identifier;
use indexmap::IndexMap;
use crate::{
CodegenDefaults, ExtTypeSchema, RootCodegen, SchemaDocument, SchemaMetadata, SchemaNode,
SchemaNodeContent, SchemaNodeId, TextSchema, TypeCodegen,
};
pub struct SchemaNodeSpec {
pub content: SchemaNodeContent,
pub metadata: SchemaMetadata,
pub ext_types: IndexMap<Identifier, ExtTypeSchema>,
pub type_codegen: TypeCodegen,
}
pub trait BuildSchema {
fn type_name() -> Option<&'static str> {
None
}
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent;
fn schema_metadata() -> SchemaMetadata {
SchemaMetadata::default()
}
fn build_schema_node(ctx: &mut SchemaBuilder) -> SchemaNodeSpec {
SchemaNodeSpec {
content: Self::build_schema(ctx),
metadata: Self::schema_metadata(),
ext_types: IndexMap::new(),
type_codegen: TypeCodegen::None,
}
}
}
pub struct SchemaBuilder {
doc: SchemaDocument,
cache: HashMap<TypeId, SchemaNodeId>,
}
impl SchemaBuilder {
pub fn new() -> Self {
Self {
doc: SchemaDocument {
nodes: Vec::new(),
root: SchemaNodeId(0), types: Default::default(),
root_codegen: RootCodegen::default(),
codegen_defaults: CodegenDefaults::default(),
},
cache: HashMap::new(),
}
}
pub fn build<T: BuildSchema + 'static>(&mut self) -> SchemaNodeId {
let type_id = TypeId::of::<T>();
if let Some(&id) = self.cache.get(&type_id) {
return id;
}
let type_name = T::type_name();
if let Some(name) = type_name {
let content_id = self.reserve_node();
let spec = T::build_schema_node(self);
self.set_node_spec(content_id, spec);
if let Ok(ident) = name.parse::<eure_document::identifier::Identifier>() {
self.doc.types.insert(ident, content_id);
}
let ref_id = self.create_node(SchemaNodeContent::Reference(crate::TypeReference {
namespace: None,
name: name.parse().expect("valid type name"),
}));
self.cache.insert(type_id, ref_id);
ref_id
} else {
let id = self.reserve_node();
self.cache.insert(type_id, id);
let spec = T::build_schema_node(self);
self.set_node_spec(id, spec);
id
}
}
pub fn create_node(&mut self, content: SchemaNodeContent) -> SchemaNodeId {
let id = SchemaNodeId(self.doc.nodes.len());
self.doc.nodes.push(SchemaNode {
content,
metadata: SchemaMetadata::default(),
ext_types: Default::default(),
type_codegen: TypeCodegen::None,
});
id
}
pub fn create_node_with_metadata(
&mut self,
content: SchemaNodeContent,
metadata: SchemaMetadata,
) -> SchemaNodeId {
let id = SchemaNodeId(self.doc.nodes.len());
self.doc.nodes.push(SchemaNode {
content,
metadata,
ext_types: Default::default(),
type_codegen: TypeCodegen::None,
});
id
}
fn reserve_node(&mut self) -> SchemaNodeId {
let id = SchemaNodeId(self.doc.nodes.len());
self.doc.nodes.push(SchemaNode {
content: SchemaNodeContent::Any, metadata: SchemaMetadata::default(),
ext_types: Default::default(),
type_codegen: TypeCodegen::None,
});
id
}
fn set_node_spec(&mut self, id: SchemaNodeId, spec: SchemaNodeSpec) {
let node = &mut self.doc.nodes[id.0];
node.content = spec.content;
node.metadata = spec.metadata;
node.ext_types = spec.ext_types;
node.type_codegen = spec.type_codegen;
}
pub fn node_mut(&mut self, id: SchemaNodeId) -> &mut SchemaNode {
&mut self.doc.nodes[id.0]
}
pub fn register_type(&mut self, name: &str, id: SchemaNodeId) {
if let Ok(ident) = name.parse() {
self.doc.types.insert(ident, id);
}
}
pub fn finish(mut self, root: SchemaNodeId) -> SchemaDocument {
self.doc.root = root;
self.doc
}
}
impl Default for SchemaBuilder {
fn default() -> Self {
Self::new()
}
}
impl SchemaDocument {
pub fn of<T: BuildSchema + 'static>() -> SchemaDocument {
let mut builder = SchemaBuilder::new();
let root = builder.build::<T>();
builder.finish(root)
}
}
impl BuildSchema for String {
fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
SchemaNodeContent::Text(crate::TextSchema::default())
}
}
impl BuildSchema for &str {
fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
SchemaNodeContent::Text(crate::TextSchema::default())
}
}
impl BuildSchema for bool {
fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
SchemaNodeContent::Boolean
}
}
macro_rules! impl_build_schema_int {
($($ty:ty),*) => {
$(
impl BuildSchema for $ty {
fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
SchemaNodeContent::Integer(crate::IntegerSchema::default())
}
}
)*
};
}
impl_build_schema_int!(
u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
);
impl BuildSchema for f32 {
fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
SchemaNodeContent::Float(crate::FloatSchema {
precision: crate::FloatPrecision::F32,
..Default::default()
})
}
}
impl BuildSchema for f64 {
fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
SchemaNodeContent::Float(crate::FloatSchema {
precision: crate::FloatPrecision::F64,
..Default::default()
})
}
}
impl BuildSchema for () {
fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
SchemaNodeContent::Null
}
}
impl BuildSchema for Text {
fn build_schema(_ctx: &mut SchemaBuilder) -> SchemaNodeContent {
SchemaNodeContent::Text(TextSchema {
language: None,
min_length: None,
max_length: None,
pattern: None,
unknown_fields: IndexMap::new(),
})
}
}
impl<T: BuildSchema + 'static> BuildSchema for Option<T> {
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
let some_schema = ctx.build::<T>();
let none_schema = ctx.create_node(SchemaNodeContent::Null);
SchemaNodeContent::Union(crate::UnionSchema {
variants: IndexMap::from([
("some".to_string(), some_schema),
("none".to_string(), none_schema),
]),
unambiguous: Default::default(),
interop: crate::interop::UnionInterop::default(),
deny_untagged: Default::default(),
})
}
}
impl<T: BuildSchema + 'static, E: BuildSchema + 'static> BuildSchema for Result<T, E> {
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
let ok_schema = ctx.build::<T>();
let err_schema = ctx.build::<E>();
SchemaNodeContent::Union(crate::UnionSchema {
variants: IndexMap::from([
("ok".to_string(), ok_schema),
("err".to_string(), err_schema),
]),
unambiguous: Default::default(),
interop: crate::interop::UnionInterop::default(),
deny_untagged: Default::default(),
})
}
}
impl<T: BuildSchema + 'static> BuildSchema for Vec<T> {
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
let item = ctx.build::<T>();
SchemaNodeContent::Array(crate::ArraySchema {
item,
min_length: None,
max_length: None,
unique: false,
contains: None,
binding_style: None,
})
}
}
impl<K: BuildSchema + 'static, V: BuildSchema + 'static> BuildSchema
for std::collections::HashMap<K, V>
{
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
let key = ctx.build::<K>();
let value = ctx.build::<V>();
SchemaNodeContent::Map(crate::MapSchema {
key,
value,
min_size: None,
max_size: None,
})
}
}
impl<K: BuildSchema + 'static, V: BuildSchema + 'static> BuildSchema
for std::collections::BTreeMap<K, V>
{
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
let key = ctx.build::<K>();
let value = ctx.build::<V>();
SchemaNodeContent::Map(crate::MapSchema {
key,
value,
min_size: None,
max_size: None,
})
}
}
impl<K: BuildSchema + 'static, V: BuildSchema + 'static> BuildSchema for IndexMap<K, V> {
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
let key = ctx.build::<K>();
let value = ctx.build::<V>();
SchemaNodeContent::Map(crate::MapSchema {
key,
value,
min_size: None,
max_size: None,
})
}
}
impl<T: BuildSchema + 'static> BuildSchema for Box<T> {
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
T::build_schema(ctx)
}
}
impl<T: BuildSchema + 'static> BuildSchema for std::rc::Rc<T> {
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
T::build_schema(ctx)
}
}
impl<T: BuildSchema + 'static> BuildSchema for std::sync::Arc<T> {
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
T::build_schema(ctx)
}
}
impl<A: BuildSchema + 'static> BuildSchema for (A,) {
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
let elements = vec![ctx.build::<A>()];
SchemaNodeContent::Tuple(crate::TupleSchema {
elements,
binding_style: None,
})
}
}
impl<A: BuildSchema + 'static, B: BuildSchema + 'static> BuildSchema for (A, B) {
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
let elements = vec![ctx.build::<A>(), ctx.build::<B>()];
SchemaNodeContent::Tuple(crate::TupleSchema {
elements,
binding_style: None,
})
}
}
impl<A: BuildSchema + 'static, B: BuildSchema + 'static, C: BuildSchema + 'static> BuildSchema
for (A, B, C)
{
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
let elements = vec![ctx.build::<A>(), ctx.build::<B>(), ctx.build::<C>()];
SchemaNodeContent::Tuple(crate::TupleSchema {
elements,
binding_style: None,
})
}
}
impl<
A: BuildSchema + 'static,
B: BuildSchema + 'static,
C: BuildSchema + 'static,
D: BuildSchema + 'static,
> BuildSchema for (A, B, C, D)
{
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
let elements = vec![
ctx.build::<A>(),
ctx.build::<B>(),
ctx.build::<C>(),
ctx.build::<D>(),
];
SchemaNodeContent::Tuple(crate::TupleSchema {
elements,
binding_style: None,
})
}
}
impl<
A: BuildSchema + 'static,
B: BuildSchema + 'static,
C: BuildSchema + 'static,
D: BuildSchema + 'static,
E: BuildSchema + 'static,
> BuildSchema for (A, B, C, D, E)
{
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
let elements = vec![
ctx.build::<A>(),
ctx.build::<B>(),
ctx.build::<C>(),
ctx.build::<D>(),
ctx.build::<E>(),
];
SchemaNodeContent::Tuple(crate::TupleSchema {
elements,
binding_style: None,
})
}
}
impl<
A: BuildSchema + 'static,
B: BuildSchema + 'static,
C: BuildSchema + 'static,
D: BuildSchema + 'static,
E: BuildSchema + 'static,
F: BuildSchema + 'static,
> BuildSchema for (A, B, C, D, E, F)
{
fn build_schema(ctx: &mut SchemaBuilder) -> SchemaNodeContent {
let elements = vec![
ctx.build::<A>(),
ctx.build::<B>(),
ctx.build::<C>(),
ctx.build::<D>(),
ctx.build::<E>(),
ctx.build::<F>(),
];
SchemaNodeContent::Tuple(crate::TupleSchema {
elements,
binding_style: None,
})
}
}