use super::predicate::Constraint;
use super::transform::Transform;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum TypeSchema {
String(StringSchema),
Number(NumberSchema),
Integer(IntegerSchema),
Int32(Int32Schema),
Int64(Int64Schema),
Uint32(Uint32Schema),
Uint64(Uint64Schema),
Boolean(BooleanSchema),
Money(MoneySchema),
Currency(CurrencySchema),
Decimal(DecimalSchema),
Percentage(PercentageSchema),
Date(DateSchema),
Object(ObjectSchema),
Array(ArraySchema),
Tuple(TupleSchema),
Enum(EnumSchema),
Literal(LiteralSchema),
Never(NeverSchema),
Union(UnionSchema),
Intersection(IntersectionSchema),
Record(RecordSchema),
Preprocess(PreprocessSchema),
Catch(CatchSchema),
Ref {
#[serde(rename = "$ref")]
name: String,
},
Any(AnySchema),
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct StringSchema {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transforms: Vec<Transform>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub example: Option<String>,
}
impl StringSchema {
pub fn new() -> Self {
Self::default()
}
pub fn transform(mut self, t: Transform) -> Self {
self.transforms.push(t);
self
}
pub fn constraint(mut self, c: Constraint) -> Self {
self.constraints.push(c);
self
}
pub fn description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct NumberSchema {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transforms: Vec<Transform>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl NumberSchema {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct IntegerSchema {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transforms: Vec<Transform>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl IntegerSchema {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Int32Schema {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transforms: Vec<Transform>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl Int32Schema {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Int64Schema {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transforms: Vec<Transform>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl Int64Schema {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Uint32Schema {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transforms: Vec<Transform>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl Uint32Schema {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct Uint64Schema {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transforms: Vec<Transform>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl Uint64Schema {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct BooleanSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl BooleanSchema {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MoneySchema {
#[serde(default = "default_scale")]
pub scale: u8,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transforms: Vec<Transform>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
fn default_scale() -> u8 {
2
}
impl Default for MoneySchema {
fn default() -> Self {
Self {
scale: 2,
transforms: Vec::new(),
constraints: Vec::new(),
description: None,
}
}
}
impl MoneySchema {
pub fn new() -> Self {
Self::default()
}
pub fn with_scale(mut self, scale: u8) -> Self {
self.scale = scale;
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CurrencySchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(default = "default_scale")]
pub scale: u8,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transforms: Vec<Transform>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl Default for CurrencySchema {
fn default() -> Self {
Self {
code: None,
scale: 2,
transforms: Vec::new(),
constraints: Vec::new(),
description: None,
}
}
}
impl CurrencySchema {
pub fn new() -> Self {
Self::default()
}
pub fn with_code(mut self, code: impl Into<String>) -> Self {
self.code = Some(code.into());
self
}
pub fn with_scale(mut self, scale: u8) -> Self {
self.scale = scale;
self
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct DecimalSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub precision: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scale: Option<u8>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transforms: Vec<Transform>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl DecimalSchema {
pub fn new() -> Self {
Self::default()
}
pub fn with_precision(mut self, precision: u8) -> Self {
self.precision = Some(precision);
self
}
pub fn with_scale(mut self, scale: u8) -> Self {
self.scale = Some(scale);
self
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PercentageFormat {
Whole,
#[default]
Decimal,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct PercentageSchema {
#[serde(default)]
pub format: PercentageFormat,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub allow_over_100: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scale: Option<u8>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transforms: Vec<Transform>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl PercentageSchema {
pub fn new() -> Self {
Self::default()
}
pub fn whole() -> Self {
Self {
format: PercentageFormat::Whole,
..Self::default()
}
}
pub fn decimal() -> Self {
Self {
format: PercentageFormat::Decimal,
..Self::default()
}
}
pub fn allow_over_100(mut self) -> Self {
self.allow_over_100 = true;
self
}
pub fn with_scale(mut self, scale: u8) -> Self {
self.scale = Some(scale);
self
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct DateSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transforms: Vec<Transform>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl DateSchema {
pub fn new() -> Self {
Self::default()
}
pub fn with_format(mut self, format: impl Into<String>) -> Self {
self.format = Some(format.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct ObjectSchema {
#[serde(
rename = "properties",
default,
alias = "fields",
skip_serializing_if = "IndexMap::is_empty"
)]
pub fields: IndexMap<String, PropertySchema>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub pages: IndexMap<String, PageSchema>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub acroform_mappings: Vec<AcroFormFieldComposition>,
#[serde(default = "default_false", skip_serializing_if = "is_false")]
pub additional_properties: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub unknown_keys: Option<UnknownKeysBehavior>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub catchall: Option<Box<TypeSchema>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rules: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct PageSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub fields: IndexMap<String, RenderMetadata>,
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct AcroFormFieldComposition {
pub field_id: String,
pub page: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub copy: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub compose: Vec<String>,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub separator: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
}
fn default_false() -> bool {
false
}
fn is_false(b: &bool) -> bool {
!*b
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UnknownKeysBehavior {
Strict,
Passthrough,
Strip,
}
impl ObjectSchema {
pub fn new() -> Self {
Self::default()
}
pub fn field(mut self, name: impl Into<String>, schema: TypeSchema) -> Self {
self.fields.insert(
name.into(),
PropertySchema {
schema,
required: true,
description: None,
label: None,
render: None,
acroform: None,
section: None,
},
);
self
}
pub fn property(self, name: impl Into<String>, schema: TypeSchema) -> Self {
self.field(name, schema)
}
pub fn optional_field(mut self, name: impl Into<String>, schema: TypeSchema) -> Self {
self.fields.insert(
name.into(),
PropertySchema {
schema,
required: false,
description: None,
label: None,
render: None,
acroform: None,
section: None,
},
);
self
}
pub fn optional_property(self, name: impl Into<String>, schema: TypeSchema) -> Self {
self.optional_field(name, schema)
}
pub fn page(mut self, page_num: impl Into<String>, page_schema: PageSchema) -> Self {
self.pages.insert(page_num.into(), page_schema);
self
}
pub fn rule(mut self, constraint: Constraint) -> Self {
self.rules.push(constraint);
self
}
pub fn allow_additional(mut self) -> Self {
self.additional_properties = true;
self.unknown_keys = Some(UnknownKeysBehavior::Passthrough);
self
}
pub fn strict(mut self) -> Self {
self.additional_properties = false;
self.unknown_keys = Some(UnknownKeysBehavior::Strict);
self
}
pub fn passthrough(mut self) -> Self {
self.additional_properties = true;
self.unknown_keys = Some(UnknownKeysBehavior::Passthrough);
self
}
pub fn strip(mut self) -> Self {
self.additional_properties = false;
self.unknown_keys = Some(UnknownKeysBehavior::Strip);
self
}
pub fn catchall(mut self, schema: TypeSchema) -> Self {
self.catchall = Some(Box::new(schema));
self.additional_properties = true;
self.unknown_keys = Some(UnknownKeysBehavior::Passthrough);
self
}
}
impl PageSchema {
pub fn new() -> Self {
Self::default()
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn field(mut self, name: impl Into<String>, render: RenderMetadata) -> Self {
self.fields.insert(name.into(), render);
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PropertySchema {
#[serde(flatten)]
pub schema: TypeSchema,
#[serde(default = "default_true", skip_serializing_if = "is_true")]
pub required: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub render: Option<RenderMetadata>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub acroform: Option<AcroFormMetadata>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub section: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RenderMetadata {
#[serde(rename = "type")]
pub render_type: String,
#[serde(default)]
pub page: u32,
pub x: f32,
pub y: f32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub font_size: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub font: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub align: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub v_align: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub h_scale: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub width: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub height: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_width: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub multiline: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub line_height: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub box_number: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AcroFormMetadata {
pub field_id: String,
pub field_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub copy_suffix: Option<String>,
}
fn default_true() -> bool {
true
}
fn is_true(b: &bool) -> bool {
*b
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ArraySchema {
pub items: Box<TypeSchema>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_items: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_items: Option<usize>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<Constraint>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl ArraySchema {
pub fn new(items: TypeSchema) -> Self {
Self {
items: Box::new(items),
min_items: None,
max_items: None,
constraints: Vec::new(),
description: None,
}
}
pub fn min_items(mut self, n: usize) -> Self {
self.min_items = Some(n);
self
}
pub fn max_items(mut self, n: usize) -> Self {
self.max_items = Some(n);
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TupleSchema {
pub items: Vec<TypeSchema>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl TupleSchema {
pub fn new(items: Vec<TypeSchema>) -> Self {
Self {
items,
description: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EnumSchema {
pub values: Vec<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl EnumSchema {
pub fn new(values: Vec<Value>) -> Self {
Self {
values,
description: None,
}
}
pub fn from_strings(values: &[&str]) -> Self {
Self {
values: values
.iter()
.map(|s| Value::String(s.to_string()))
.collect(),
description: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LiteralSchema {
pub value: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl LiteralSchema {
pub fn new(value: Value) -> Self {
Self {
value,
description: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct NeverSchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl NeverSchema {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UnionSchema {
#[serde(rename = "oneOf")]
pub variants: Vec<TypeSchema>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub discriminator: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl UnionSchema {
pub fn new(variants: Vec<TypeSchema>) -> Self {
Self {
variants,
discriminator: None,
description: None,
}
}
pub fn discriminated(variants: Vec<TypeSchema>, discriminator: impl Into<String>) -> Self {
Self {
variants,
discriminator: Some(discriminator.into()),
description: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IntersectionSchema {
#[serde(rename = "allOf")]
pub variants: Vec<TypeSchema>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl IntersectionSchema {
pub fn new(variants: Vec<TypeSchema>) -> Self {
Self {
variants,
description: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RecordSchema {
pub value: Box<TypeSchema>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key: Option<Box<TypeSchema>>,
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
pub partial: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl RecordSchema {
pub fn new(value: TypeSchema) -> Self {
Self {
value: Box::new(value),
key: None,
partial: false,
description: None,
}
}
pub fn with_key(mut self, key: TypeSchema) -> Self {
self.key = Some(Box::new(key));
self
}
pub fn partial(mut self) -> Self {
self.partial = true;
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PreprocessSchema {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub transforms: Vec<Transform>,
pub schema: Box<TypeSchema>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl PreprocessSchema {
pub fn new(schema: TypeSchema) -> Self {
Self {
transforms: Vec::new(),
schema: Box::new(schema),
description: None,
}
}
pub fn transform(mut self, transform: Transform) -> Self {
self.transforms.push(transform);
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CatchSchema {
pub schema: Box<TypeSchema>,
pub value: Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl CatchSchema {
pub fn new(schema: TypeSchema, value: Value) -> Self {
Self {
schema: Box::new(schema),
value,
description: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct AnySchema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl AnySchema {
pub fn new() -> Self {
Self::default()
}
}
impl TypeSchema {
pub fn string() -> Self {
Self::String(StringSchema::new())
}
pub fn number() -> Self {
Self::Number(NumberSchema::new())
}
pub fn integer() -> Self {
Self::Integer(IntegerSchema::new())
}
pub fn int32() -> Self {
Self::Int32(Int32Schema::new())
}
pub fn int64() -> Self {
Self::Int64(Int64Schema::new())
}
pub fn uint32() -> Self {
Self::Uint32(Uint32Schema::new())
}
pub fn uint64() -> Self {
Self::Uint64(Uint64Schema::new())
}
pub fn boolean() -> Self {
Self::Boolean(BooleanSchema::new())
}
pub fn money() -> Self {
Self::Money(MoneySchema::new())
}
pub fn currency() -> Self {
Self::Currency(CurrencySchema::new())
}
pub fn currency_with_code(code: impl Into<String>) -> Self {
Self::Currency(CurrencySchema::new().with_code(code))
}
pub fn decimal() -> Self {
Self::Decimal(DecimalSchema::new())
}
pub fn decimal_with(precision: u8, scale: u8) -> Self {
Self::Decimal(
DecimalSchema::new()
.with_precision(precision)
.with_scale(scale),
)
}
pub fn percentage() -> Self {
Self::Percentage(PercentageSchema::new())
}
pub fn percentage_whole() -> Self {
Self::Percentage(PercentageSchema::whole())
}
pub fn date() -> Self {
Self::Date(DateSchema::new())
}
pub fn object() -> Self {
Self::Object(ObjectSchema::new())
}
pub fn array(items: TypeSchema) -> Self {
Self::Array(ArraySchema::new(items))
}
pub fn tuple(items: Vec<TypeSchema>) -> Self {
Self::Tuple(TupleSchema::new(items))
}
pub fn enum_values(values: &[&str]) -> Self {
Self::Enum(EnumSchema::from_strings(values))
}
pub fn intersection(variants: Vec<TypeSchema>) -> Self {
Self::Intersection(IntersectionSchema::new(variants))
}
pub fn record(value: TypeSchema) -> Self {
Self::Record(RecordSchema::new(value))
}
pub fn preprocess(schema: TypeSchema, transforms: Vec<Transform>) -> Self {
Self::Preprocess(PreprocessSchema {
transforms,
schema: Box::new(schema),
description: None,
})
}
pub fn catch(schema: TypeSchema, value: Value) -> Self {
Self::Catch(CatchSchema::new(schema, value))
}
pub fn literal(value: Value) -> Self {
Self::Literal(LiteralSchema::new(value))
}
pub fn nullable(schema: TypeSchema) -> Self {
Self::Union(UnionSchema::new(vec![
schema,
TypeSchema::literal(Value::Null),
]))
}
pub fn never() -> Self {
Self::Never(NeverSchema::new())
}
pub fn ref_to(name: impl Into<String>) -> Self {
Self::Ref { name: name.into() }
}
pub fn any() -> Self {
Self::Any(AnySchema::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::error::ErrorMeta;
use crate::ir::predicate::Predicate;
#[test]
fn test_string_schema_serde() {
let schema =
TypeSchema::String(StringSchema::new().transform(Transform::trim()).constraint(
Constraint::new(
Predicate::regex(r"^\d{9}$"),
ErrorMeta::new("INVALID", "Invalid format"),
),
));
let json = serde_json::to_string_pretty(&schema).unwrap();
assert!(json.contains("string"));
assert!(json.contains("trim"));
let parsed: TypeSchema = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, schema);
}
#[test]
fn test_object_schema_serde() {
let schema = TypeSchema::Object(
ObjectSchema::new()
.property("name", TypeSchema::string())
.optional_property("age", TypeSchema::integer())
.strip()
.catchall(TypeSchema::integer()),
);
let json = serde_json::to_string_pretty(&schema).unwrap();
let parsed: TypeSchema = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, schema);
}
#[test]
fn test_object_schema_serializes_properties_key() {
let schema = TypeSchema::Object(ObjectSchema::new().property("name", TypeSchema::string()));
let json = serde_json::to_string(&schema).unwrap();
assert!(json.contains("\"properties\""));
assert!(!json.contains("\"fields\""));
let legacy = r#"{"type":"object","fields":{"name":{"type":"string"}}}"#;
let parsed: TypeSchema = serde_json::from_str(legacy).unwrap();
assert_eq!(parsed, schema);
}
#[test]
fn test_array_schema_serde() {
let schema = TypeSchema::array(TypeSchema::string());
let json = serde_json::to_string(&schema).unwrap();
let parsed: TypeSchema = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, schema);
}
#[test]
fn test_ref_schema_serde() {
let schema = TypeSchema::ref_to("Payee");
let json = serde_json::to_string(&schema).unwrap();
assert!(json.contains("$ref"));
assert!(json.contains("Payee"));
}
#[test]
fn test_enum_schema() {
let schema = TypeSchema::enum_values(&["SSN", "EIN", "ITIN"]);
let json = serde_json::to_string(&schema).unwrap();
assert!(json.contains("SSN"));
assert!(json.contains("EIN"));
}
#[test]
fn test_literal_schema_serde() {
let schema = TypeSchema::literal(serde_json::json!("active"));
let json = serde_json::to_string(&schema).unwrap();
let parsed: TypeSchema = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, schema);
}
#[test]
fn test_never_schema_serde() {
let schema = TypeSchema::never();
let json = serde_json::to_string(&schema).unwrap();
let parsed: TypeSchema = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, schema);
}
#[test]
fn test_tuple_schema_serde() {
let schema = TypeSchema::tuple(vec![TypeSchema::string(), TypeSchema::integer()]);
let json = serde_json::to_string(&schema).unwrap();
let parsed: TypeSchema = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, schema);
}
#[test]
fn test_nullable_schema() {
let schema = TypeSchema::nullable(TypeSchema::string());
let json = serde_json::to_string(&schema).unwrap();
let parsed: TypeSchema = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, schema);
}
#[test]
fn test_intersection_schema_serde() {
let schema =
TypeSchema::intersection(vec![TypeSchema::string(), TypeSchema::literal(Value::Null)]);
let json = serde_json::to_string(&schema).unwrap();
let parsed: TypeSchema = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, schema);
}
#[test]
fn test_record_schema_serde() {
let schema = TypeSchema::Record(
RecordSchema::new(TypeSchema::integer()).with_key(TypeSchema::string()),
);
let json = serde_json::to_string(&schema).unwrap();
let parsed: TypeSchema = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, schema);
}
#[test]
fn test_preprocess_catch_schema_serde() {
let schema = TypeSchema::catch(
TypeSchema::preprocess(TypeSchema::string(), vec![Transform::trim()]),
serde_json::json!("fallback"),
);
let json = serde_json::to_string(&schema).unwrap();
let parsed: TypeSchema = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, schema);
}
}