pub mod build;
pub mod codegen;
pub mod convert;
pub mod identifiers;
pub mod interop;
pub mod parse;
pub mod synth;
pub mod type_path_trace;
pub mod validate;
pub mod write;
pub use build::{BuildSchema, SchemaBuilder, SchemaNodeSpec};
pub use codegen::{
CodegenDefaults, FieldCodegen, RecordCodegen, RootCodegen, TypeCodegen, UnionCodegen,
};
use eure_document::Text;
use eure_document::constructor::DocumentConstructor;
use eure_document::document::EureDocument;
use eure_document::identifier::Identifier;
use eure_document::layout::LayoutStyle;
use eure_document::write::{IntoEure, WriteError};
use eure_macros::{FromEure, IntoEure};
use indexmap::{IndexMap, IndexSet};
use num_bigint::BigInt;
use regex::Regex;
use crate::interop::UnionInterop;
#[derive(Debug, Clone, PartialEq)]
pub struct SchemaDocument {
pub nodes: Vec<SchemaNode>,
pub root: SchemaNodeId,
pub types: IndexMap<Identifier, SchemaNodeId>,
pub root_codegen: RootCodegen,
pub codegen_defaults: CodegenDefaults,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExtTypeSchema {
pub schema: SchemaNodeId,
pub optional: bool,
pub binding_style: Option<BindingStyle>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SchemaNodeId(pub usize);
#[derive(Debug, Clone, PartialEq)]
pub struct SchemaNode {
pub content: SchemaNodeContent,
pub metadata: SchemaMetadata,
pub ext_types: IndexMap<Identifier, ExtTypeSchema>,
pub type_codegen: TypeCodegen,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SchemaNodeContent {
Any,
Text(TextSchema),
Integer(IntegerSchema),
Float(FloatSchema),
Boolean,
Null,
Literal(EureDocument),
Array(ArraySchema),
Map(MapSchema),
Record(RecordSchema),
Tuple(TupleSchema),
Union(UnionSchema),
Reference(TypeReference),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SchemaKind {
Any,
Text,
Integer,
Float,
Boolean,
Null,
Literal,
Array,
Map,
Record,
Tuple,
Union,
Reference,
}
impl std::fmt::Display for SchemaKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
Self::Any => "any",
Self::Text => "text",
Self::Integer => "integer",
Self::Float => "float",
Self::Boolean => "boolean",
Self::Null => "null",
Self::Literal => "literal",
Self::Array => "array",
Self::Map => "map",
Self::Record => "record",
Self::Tuple => "tuple",
Self::Union => "union",
Self::Reference => "reference",
};
write!(f, "{}", name)
}
}
impl SchemaNodeContent {
pub fn kind(&self) -> SchemaKind {
match self {
Self::Any => SchemaKind::Any,
Self::Text(_) => SchemaKind::Text,
Self::Integer(_) => SchemaKind::Integer,
Self::Float(_) => SchemaKind::Float,
Self::Boolean => SchemaKind::Boolean,
Self::Null => SchemaKind::Null,
Self::Literal(_) => SchemaKind::Literal,
Self::Array(_) => SchemaKind::Array,
Self::Map(_) => SchemaKind::Map,
Self::Record(_) => SchemaKind::Record,
Self::Tuple(_) => SchemaKind::Tuple,
Self::Union(_) => SchemaKind::Union,
Self::Reference(_) => SchemaKind::Reference,
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub enum Bound<T> {
#[default]
Unbounded,
Inclusive(T),
Exclusive(T),
}
#[derive(Debug, Clone, Default, FromEure, IntoEure)]
#[eure(crate = eure_document, rename_all = "kebab-case", allow_unknown_fields, allow_unknown_extensions)]
pub struct TextSchema {
#[eure(default)]
pub language: Option<String>,
#[eure(default)]
pub min_length: Option<u32>,
#[eure(default)]
pub max_length: Option<u32>,
#[eure(default)]
pub pattern: Option<Regex>,
#[eure(flatten)]
pub unknown_fields: IndexMap<String, EureDocument>,
}
impl TextSchema {
pub fn is_shorthand_compatible(&self) -> bool {
matches!(
self,
Self {
language: _,
min_length: None,
max_length: None,
pattern: None,
unknown_fields: _
}
) && self.unknown_fields.is_empty()
}
pub fn shorthand(&self) -> Option<Text> {
self.is_shorthand_compatible().then(|| {
if let Some(language) = &self.language {
Text::inline_implicit(format!("text.{}", language))
} else {
Text::inline_implicit("text")
}
})
}
pub fn write(&self, c: &mut DocumentConstructor) -> Result<(), WriteError> {
if let Some(shorthand) = self.shorthand() {
c.write(shorthand)
} else {
c.record(|rec| {
rec.constructor().set_variant("text")?;
<Self as IntoEure>::write_flatten(self.clone(), rec)?;
Ok(())
})
}
}
}
impl PartialEq for TextSchema {
fn eq(&self, other: &Self) -> bool {
self.language == other.language
&& self.min_length == other.min_length
&& self.max_length == other.max_length
&& self.unknown_fields == other.unknown_fields
&& match (&self.pattern, &other.pattern) {
(None, None) => true,
(Some(a), Some(b)) => a.as_str() == b.as_str(),
_ => false,
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct IntegerSchema {
pub min: Bound<BigInt>,
pub max: Bound<BigInt>,
pub multiple_of: Option<BigInt>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FloatPrecision {
F32,
#[default]
F64,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct FloatSchema {
pub min: Bound<f64>,
pub max: Bound<f64>,
pub multiple_of: Option<f64>,
pub precision: FloatPrecision,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ArraySchema {
pub item: SchemaNodeId,
pub min_length: Option<u32>,
pub max_length: Option<u32>,
pub unique: bool,
pub contains: Option<SchemaNodeId>,
pub binding_style: Option<BindingStyle>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MapSchema {
pub key: SchemaNodeId,
pub value: SchemaNodeId,
pub min_size: Option<u32>,
pub max_size: Option<u32>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RecordFieldSchema {
pub schema: SchemaNodeId,
pub optional: bool,
pub binding_style: Option<BindingStyle>,
pub field_codegen: FieldCodegen,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct RecordSchema {
pub properties: IndexMap<String, RecordFieldSchema>,
pub flatten: Vec<SchemaNodeId>,
pub unknown_fields: UnknownFieldsPolicy,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub enum UnknownFieldsPolicy {
#[default]
Deny,
Allow,
Schema(SchemaNodeId),
}
#[derive(Debug, Clone, PartialEq)]
pub struct TupleSchema {
pub elements: Vec<SchemaNodeId>,
pub binding_style: Option<BindingStyle>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UnionSchema {
pub variants: IndexMap<String, SchemaNodeId>,
pub unambiguous: IndexSet<String>,
pub interop: UnionInterop,
pub deny_untagged: IndexSet<String>,
}
pub type BindingStyle = LayoutStyle;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeReference {
pub namespace: Option<String>,
pub name: Identifier,
}
#[derive(Debug, Clone, PartialEq, FromEure)]
#[eure(crate = eure_document, rename_all = "lowercase")]
pub enum Description {
String(String),
Markdown(String),
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct SchemaMetadata {
pub description: Option<Description>,
pub deprecated: bool,
pub default: Option<EureDocument>,
pub examples: Option<Vec<EureDocument>>,
}
impl SchemaDocument {
pub fn new() -> Self {
Self {
nodes: vec![SchemaNode {
content: SchemaNodeContent::Any,
metadata: SchemaMetadata::default(),
ext_types: IndexMap::new(),
type_codegen: TypeCodegen::None,
}],
root: SchemaNodeId(0),
types: IndexMap::new(),
root_codegen: RootCodegen::default(),
codegen_defaults: CodegenDefaults::default(),
}
}
pub fn node(&self, id: SchemaNodeId) -> &SchemaNode {
&self.nodes[id.0]
}
pub fn node_mut(&mut self, id: SchemaNodeId) -> &mut SchemaNode {
&mut self.nodes[id.0]
}
pub fn create_node(&mut self, content: SchemaNodeContent) -> SchemaNodeId {
let id = SchemaNodeId(self.nodes.len());
self.nodes.push(SchemaNode {
content,
metadata: SchemaMetadata::default(),
ext_types: IndexMap::new(),
type_codegen: TypeCodegen::None,
});
id
}
pub fn register_type(&mut self, name: Identifier, node_id: SchemaNodeId) {
self.types.insert(name, node_id);
}
pub fn get_type(&self, name: &Identifier) -> Option<SchemaNodeId> {
self.types.get(name).copied()
}
}
impl Default for SchemaDocument {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct SchemaRef {
pub path: String,
pub node_id: eure_document::document::NodeId,
}