1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
mod arrays;
mod enums;
mod literals;
mod numbers;
mod objects;
mod strings;
mod structs;
mod tuples;
mod unions;

pub use arrays::*;
pub use enums::*;
pub use literals::*;
pub use numbers::*;
pub use objects::*;
pub use strings::*;
pub use structs::*;
pub use tuples::*;
pub use unions::*;

/// All possible types within a schema.
#[derive(Clone, Debug, Default)]
pub enum SchemaType {
    Boolean,
    Null,
    #[default]
    Unknown,
    Array(ArrayType),
    Enum(EnumType),
    Float(FloatType),
    Integer(IntegerType),
    Literal(LiteralType),
    Object(ObjectType),
    Struct(StructType),
    String(StringType),
    Tuple(TupleType),
    Union(UnionType),
}

impl SchemaType {
    /// Infer a schema from a type that implements [`Schematic`].
    pub fn infer<T: Schematic>() -> SchemaType {
        T::generate_schema()
    }

    /// Create an array schema with the provided item types.
    pub fn array(items_type: SchemaType) -> SchemaType {
        SchemaType::Array(ArrayType {
            items_type: Box::new(items_type),
            ..ArrayType::default()
        })
    }

    /// Create a float schema with the provided kind.
    pub fn float(kind: FloatKind) -> SchemaType {
        SchemaType::Float(FloatType {
            kind,
            ..FloatType::default()
        })
    }

    /// Create an integer schema with the provided kind.
    pub fn integer(kind: IntegerKind) -> SchemaType {
        SchemaType::Integer(IntegerType {
            kind,
            ..IntegerType::default()
        })
    }

    /// Create a literal schema with the provided value.
    pub fn literal(value: LiteralValue) -> SchemaType {
        SchemaType::Literal(LiteralType {
            value: Some(value),
            ..LiteralType::default()
        })
    }

    /// Create an indexed/mapable object schema with the provided key and value types.
    pub fn object(key_type: SchemaType, value_type: SchemaType) -> SchemaType {
        SchemaType::Object(ObjectType {
            key_type: Box::new(key_type),
            value_type: Box::new(value_type),
            ..ObjectType::default()
        })
    }

    /// Create a string schema.
    pub fn string() -> SchemaType {
        SchemaType::String(StringType::default())
    }

    /// Create a struct/shape schema with the provided fields.
    pub fn structure<I>(fields: I) -> SchemaType
    where
        I: IntoIterator<Item = SchemaField>,
    {
        SchemaType::Struct(StructType {
            fields: fields.into_iter().collect(),
            ..StructType::default()
        })
    }

    /// Create a tuple schema with the provided item types.
    pub fn tuple<I>(items_types: I) -> SchemaType
    where
        I: IntoIterator<Item = SchemaType>,
    {
        SchemaType::Tuple(TupleType {
            items_types: items_types.into_iter().map(Box::new).collect(),
            ..TupleType::default()
        })
    }

    /// Create an "any of" union.
    pub fn union<I>(variants_types: I) -> SchemaType
    where
        I: IntoIterator<Item = SchemaType>,
    {
        SchemaType::Union(UnionType {
            variants_types: variants_types.into_iter().map(Box::new).collect(),
            ..UnionType::default()
        })
    }

    /// Create a "one of" union.
    pub fn union_one<I>(variants_types: I) -> SchemaType
    where
        I: IntoIterator<Item = SchemaType>,
    {
        SchemaType::Union(UnionType {
            operator: UnionOperator::OneOf,
            variants_types: variants_types.into_iter().map(Box::new).collect(),
            ..UnionType::default()
        })
    }

    /// Return a `name` from the inner schema type.
    pub fn get_name(&self) -> Option<&String> {
        match self {
            SchemaType::Boolean => None,
            SchemaType::Null => None,
            SchemaType::Unknown => None,
            SchemaType::Array(ArrayType { name, .. }) => name.as_ref(),
            SchemaType::Enum(EnumType { name, .. }) => name.as_ref(),
            SchemaType::Float(FloatType { name, .. }) => name.as_ref(),
            SchemaType::Integer(IntegerType { name, .. }) => name.as_ref(),
            SchemaType::Literal(LiteralType { name, .. }) => name.as_ref(),
            SchemaType::Object(ObjectType { name, .. }) => name.as_ref(),
            SchemaType::Struct(StructType { name, .. }) => name.as_ref(),
            SchemaType::String(StringType { name, .. }) => name.as_ref(),
            SchemaType::Tuple(TupleType { name, .. }) => name.as_ref(),
            SchemaType::Union(UnionType { name, .. }) => name.as_ref(),
        }
    }
}

#[derive(Clone, Debug, Default)]
pub struct SchemaField {
    pub name: Option<String>,
    pub description: Option<String>,
    pub type_of: SchemaType,
    pub deprecated: bool,
    pub hidden: bool,
    pub nullable: bool,
    pub optional: bool,
    pub read_only: bool,
    pub write_only: bool,
}

pub trait Schematic {
    /// Create and return a schema that models the structure of the implementing type.
    /// The schema can be used to generate code, documentation, or other artifacts.
    fn generate_schema() -> SchemaType {
        SchemaType::Unknown
    }
}

// CORE

impl<T: Schematic> Schematic for &T {
    fn generate_schema() -> SchemaType {
        T::generate_schema()
    }
}

impl<T: Schematic> Schematic for &mut T {
    fn generate_schema() -> SchemaType {
        T::generate_schema()
    }
}

impl<T: Schematic> Schematic for Box<T> {
    fn generate_schema() -> SchemaType {
        T::generate_schema()
    }
}

impl<T: Schematic> Schematic for Option<T> {
    fn generate_schema() -> SchemaType {
        SchemaType::union_one([T::generate_schema(), SchemaType::Null])
    }
}