1use crate::format::Format;
2use std::collections::BTreeMap;
3
4#[derive(Debug, Clone, Default, PartialEq)]
5pub struct Schema {
6 pub types: BTreeMap<String, ObjectType>,
7 pub unions: BTreeMap<String, UnionType>,
11}
12
13impl Schema {
14 pub fn get(&self, name: &str) -> Option<&ObjectType> {
15 self.types.get(name)
16 }
17
18 pub fn union(&self, name: &str) -> Option<&UnionType> {
19 self.unions.get(name)
20 }
21
22 pub fn declares(&self, name: &str) -> bool {
24 self.types.contains_key(name) || self.unions.contains_key(name)
25 }
26}
27
28#[derive(Debug, Clone, Default, PartialEq)]
34pub struct UnionType {
35 pub name: String,
36 pub tag: String,
38 pub variants: Vec<Variant>,
41}
42
43impl UnionType {
44 pub fn variant(&self, tag: &str) -> Option<&Variant> {
45 self.variants.iter().find(|v| v.tag == tag)
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct Variant {
51 pub tag: String,
53 pub type_name: String,
57}
58
59#[derive(Debug, Clone, Default, PartialEq)]
60pub struct ObjectType {
61 pub name: String,
62 pub fields: Vec<Field>,
64 pub deny_unknown_fields: bool,
65}
66
67impl ObjectType {
68 pub fn field(&self, name: &str) -> Option<&Field> {
69 self.fields.iter().find(|f| f.name == name)
70 }
71}
72
73#[derive(Debug, Clone, PartialEq)]
74pub struct Field {
75 pub name: String,
76 pub ty: Type,
77 pub presence: Presence,
78 pub rules: Vec<Rule>,
79}
80
81#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
91pub struct Presence {
92 pub optional: bool,
93 pub nullable: bool,
94}
95
96impl Presence {
97 pub const fn required() -> Self {
98 Self { optional: false, nullable: false }
99 }
100
101 pub const fn nullable() -> Self {
102 Self { optional: false, nullable: true }
103 }
104
105 pub const fn optional() -> Self {
106 Self { optional: true, nullable: false }
107 }
108
109 pub const fn optional_nullable() -> Self {
110 Self { optional: true, nullable: true }
111 }
112}
113
114#[derive(Debug, Clone, PartialEq)]
115pub enum Type {
116 Bool,
117 Int(IntType),
118 Float,
119 String,
120 Date,
122 DateTime,
124 Enum(Vec<String>),
125 Array {
128 item: Box<Type>,
129 item_nullable: bool,
130 },
131 Object(Box<ObjectType>),
132 Ref(String),
133}
134
135impl Type {
136 pub fn kind(&self) -> &'static str {
137 match self {
138 Type::Bool => "bool",
139 Type::Int(_) => "integer",
140 Type::Float => "float",
141 Type::String => "string",
142 Type::Date => "date",
143 Type::DateTime => "datetime",
144 Type::Enum(_) => "enum",
145 Type::Array { .. } => "array",
146 Type::Object(_) | Type::Ref(_) => "object",
147 }
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq)]
154pub struct IntType {
155 pub width: IntWidth,
156 pub signed: bool,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum IntWidth {
161 W8,
162 W16,
163 W32,
164 W64,
165}
166
167impl IntType {
168 pub fn range(self) -> (i128, i128) {
169 let bits = match self.width {
170 IntWidth::W8 => 8,
171 IntWidth::W16 => 16,
172 IntWidth::W32 => 32,
173 IntWidth::W64 => 64,
174 };
175 if self.signed {
176 let half = 1_i128 << (bits - 1);
177 (-half, half - 1)
178 } else {
179 (0, (1_i128 << bits) - 1)
180 }
181 }
182
183 pub fn name(self) -> &'static str {
184 match (self.signed, self.width) {
185 (true, IntWidth::W8) => "i8",
186 (true, IntWidth::W16) => "i16",
187 (true, IntWidth::W32) => "i32",
188 (true, IntWidth::W64) => "i64",
189 (false, IntWidth::W8) => "u8",
190 (false, IntWidth::W16) => "u16",
191 (false, IntWidth::W32) => "u32",
192 (false, IntWidth::W64) => "u64",
193 }
194 }
195
196 pub fn fits_js_number(self) -> bool {
198 !matches!(self.width, IntWidth::W64)
199 }
200}
201
202#[derive(Debug, Clone, PartialEq)]
203pub enum Rule {
204 MinLen(usize),
205 MaxLen(usize),
206 Range {
207 min: i128,
208 max: i128,
209 },
210 MinItems(usize),
211 MaxItems(usize),
212 Format(Format),
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn integer_ranges_are_exact_at_the_edges() {
223 let u64t = IntType { width: IntWidth::W64, signed: false };
224 assert_eq!(u64t.range(), (0, i128::from(u64::MAX)));
225
226 let i32t = IntType { width: IntWidth::W32, signed: true };
227 assert_eq!(i32t.range(), (i128::from(i32::MIN), i128::from(i32::MAX)));
228
229 let u8t = IntType { width: IntWidth::W8, signed: false };
230 assert_eq!(u8t.range(), (0, 255));
231 }
232
233 #[test]
234 fn only_64_bit_integers_need_bigint_in_js() {
235 assert!(IntType { width: IntWidth::W32, signed: true }.fits_js_number());
236 assert!(!IntType { width: IntWidth::W64, signed: false }.fits_js_number());
237 }
238
239 #[test]
240 fn presence_axes_are_independent() {
241 assert_eq!(
242 Presence::optional_nullable(),
243 Presence { optional: true, nullable: true }
244 );
245 assert_ne!(Presence::optional(), Presence::nullable());
246 }
247}