1use crate::{
2 error::{Convert, Error, Result},
3 types::{self, AttrStr, Object, ObjectRef},
4};
5use derive_new::new;
6use indexmap::IndexMap;
7use std::{collections::HashMap, str::FromStr};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum StrCase {
11 Lower,
12 Upper,
13 Pascal,
14 Camel,
15 Snake,
16 ScreamingSnake,
17 Kebab,
18 ScreamingKebab,
19}
20
21impl FromStr for StrCase {
22 type Err = Error;
23
24 fn from_str(s: &str) -> Result<Self> {
25 match s {
26 "lowercase" => Ok(StrCase::Lower),
27 "UPPERCASE" => Ok(StrCase::Upper),
28 "PascalCase" => Ok(StrCase::Pascal),
29 "camelCase" => Ok(StrCase::Camel),
30 "snake_case" => Ok(StrCase::Snake),
31 "SCREAMING_SNAKE_CASE" => Ok(StrCase::ScreamingSnake),
32 "kebab-case" => Ok(StrCase::Kebab),
33 "SCREAMING-KEBAB-CASE" => Ok(StrCase::ScreamingKebab),
34 c => bail!("Unsupported string case: {}", c),
35 }
36 }
37}
38
39#[derive(Clone, Debug, Default, PartialEq, Eq, new)]
40pub struct FieldAttr {
41 pub flatten: bool,
42 pub rename: Option<String>,
43 pub default: Option<Object>,
44 pub default_factory: Option<Object>,
45 pub skip: bool,
46 pub skip_deserializing: bool,
47}
48
49macro_rules! extract_parse {
50 ($dict:expr, $field:expr) => {
51 $dict
52 .as_ref()
53 .and_then(|map| {
54 map.get($field).map(|v| {
55 let s = v.as_str()?;
56 s.parse().with_context(|| {
57 format!("invalid string `{}` in attribute `{}`", s, $field)
58 })
59 })
60 })
61 .transpose()?
62 };
63}
64
65macro_rules! extract_bool {
66 ($dict:expr, $field:expr) => {
67 $dict
68 .as_ref()
69 .and_then(|map| map.get($field).map(|v| v.as_bool()))
70 .transpose()
71 .with_context(|| format!("expected `bool` in attribute `{}`", $field))?
72 .unwrap_or(false)
73 };
74}
75
76macro_rules! extract_str {
77 ($dict:expr, $field:expr) => {
78 $dict
79 .as_ref()
80 .and_then(|map| map.get($field).map(|v| v.as_str().map(|v| v.to_string())))
81 .transpose()
82 .with_context(|| format!("expected `str` in attribute `{}`", $field))?
83 };
84}
85
86macro_rules! extract {
87 ($dict:expr, $field:expr) => {
88 $dict
89 .as_ref()
90 .and_then(|map| map.get($field).map(|v| (*v).owned()))
91 };
92}
93
94impl FieldAttr {
95 pub fn parse(attr: &Option<&ObjectRef>) -> Result<Self> {
96 Ok(Self::new(
97 extract_bool!(attr, "perde_flatten"),
98 extract_str!(attr, "perde_rename"),
99 extract!(attr, "default"),
100 extract!(attr, "default_factory"),
101 extract_bool!(attr, "perde_skip"),
102 extract_bool!(attr, "perde_skip_deserializing"),
103 ))
104 }
105}
106
107#[derive(Clone, Debug, Default, new, PartialEq, Eq)]
108pub struct VariantAttr {
109 pub rename: Option<String>,
110}
111
112#[derive(Clone, Debug, Default, new, PartialEq, Eq)]
113pub struct ClassAttr {
114 pub rename_all: Option<StrCase>,
115 pub rename: Option<String>,
116 pub deny_unknown_fields: bool,
117 pub default: bool,
118}
119
120impl ClassAttr {
121 pub fn parse(attr: &Option<HashMap<&str, &ObjectRef>>) -> Result<Self> {
122 Ok(Self::new(
123 extract_parse!(attr, "rename_all"),
124 extract_str!(attr, "rename"),
125 extract_bool!(attr, "deny_unknown_fields"),
126 extract_bool!(attr, "default"),
127 ))
128 }
129}
130
131#[derive(Clone, Debug, Default, new, PartialEq, Eq)]
132pub struct EnumAttr {
133 pub rename_all: Option<StrCase>,
134 pub rename: Option<String>,
135}
136
137impl EnumAttr {
138 pub fn parse(attr: &Option<HashMap<&str, &ObjectRef>>) -> Result<Self> {
139 Ok(Self::new(
140 extract_parse!(attr, "rename_all"),
141 extract_str!(attr, "rename"),
142 ))
143 }
144}
145
146#[derive(Debug, Clone, PartialEq, Eq)]
147pub enum Primitive {
148 Bool,
149 Int,
150 Float,
151 Str,
152 Bytes,
153 ByteArray,
154}
155
156impl Primitive {
157 pub fn name(&self) -> &str {
158 match self {
159 Self::Bool => "bool".into(),
160 Self::Int => "int".into(),
161 Self::Float => "float".into(),
162 Self::Str => "str".into(),
163 Self::Bytes => "bytes".into(),
164 Self::ByteArray => "bytearray".into(),
165 }
166 }
167}
168
169#[derive(Debug, Clone, new, PartialEq, Eq)]
170pub struct Dict {
171 pub key: Box<Schema>,
172 pub value: Box<Schema>,
173}
174
175impl Dict {
176 pub fn name(&self) -> &str {
177 "dict"
178 }
179}
180
181#[derive(Debug, Clone, new, PartialEq, Eq)]
182pub struct List {
183 pub value: Box<Schema>,
184}
185
186impl List {
187 pub fn name(&self) -> &str {
188 "list"
189 }
190}
191
192#[derive(Debug, Clone, new, PartialEq, Eq)]
193pub struct Set {
194 pub value: Box<Schema>,
195}
196
197impl Set {
198 pub fn name(&self) -> &str {
199 "set"
200 }
201}
202
203#[derive(Debug, Clone, new, PartialEq, Eq)]
204pub struct FrozenSet {
205 pub value: Box<Schema>,
206}
207
208impl FrozenSet {
209 pub fn name(&self) -> &str {
210 "fronzen_set"
211 }
212}
213
214#[derive(Debug, Clone, new, PartialEq, Eq)]
215pub struct Tuple {
216 pub args: Vec<Schema>,
217}
218
219impl Tuple {
220 pub fn name(&self) -> &str {
221 "tuple"
222 }
223}
224
225#[derive(Debug, Clone, new, PartialEq, Eq)]
226pub struct Enum {
227 pub attr: EnumAttr,
228 pub variants: IndexMap<String, VariantSchema>,
229}
230
231impl Enum {
232 pub fn name(&self) -> &str {
233 "enum"
234 }
235}
236
237#[derive(Debug, Clone, new, PartialEq, Eq)]
238pub struct VariantSchema {
239 pub name: String,
240 pub attr: VariantAttr,
241 pub value: Object,
242}
243
244#[derive(Debug, Clone, new, PartialEq, Eq)]
245pub struct Class {
246 pub ty: types::Class,
247 pub name: String,
248 pub attr: ClassAttr,
249 pub fields: IndexMap<String, FieldSchema>,
250 pub flatten_fields: IndexMap<String, FieldSchema>,
251}
252
253impl Class {
254 pub fn name(&self) -> &str {
255 self.ty.name()
256 }
257}
258
259#[derive(Debug, Clone, new, PartialEq, Eq)]
260pub struct FieldSchema {
261 pub name: AttrStr,
262 pub pos: usize,
263 pub attr: FieldAttr,
264 pub schema: Schema,
265}
266
267#[derive(Debug, Clone, new, PartialEq, Eq)]
268pub struct Optional {
269 pub value: Box<Schema>,
270}
271
272impl Optional {
273 pub fn name(&self) -> &str {
274 "optional"
275 }
276}
277
278#[derive(Debug, Clone, new, PartialEq, Eq)]
279pub struct Union {
280 pub variants: Vec<Schema>,
281}
282
283impl Union {
284 pub fn name(&self) -> &str {
285 "union"
286 }
287}
288
289#[derive(Debug, Clone, new, PartialEq, Eq)]
290pub struct Any;
291
292#[derive(Debug, Clone, new, PartialEq, Eq)]
293pub enum Schema {
294 Primitive(Primitive),
295 Dict(Dict),
296 List(List),
297 Set(Set),
298 FrozenSet(FrozenSet),
299 Tuple(Tuple),
300 Class(Class),
301 Enum(Enum),
302 Optional(Optional),
303 Union(Union),
304 Any(Any),
305}
306
307impl Schema {
308 pub fn name(&self) -> &str {
309 match self {
310 Self::Primitive(p) => p.name(),
311 Self::Dict(d) => d.name(),
312 Self::List(l) => l.name(),
313 Self::Set(s) => s.name(),
314 Self::FrozenSet(s) => s.name(),
315 Self::Tuple(t) => t.name(),
316 Self::Class(c) => c.name(),
317 Self::Enum(e) => e.name(),
318 Self::Optional(o) => o.name(),
319 Self::Union(u) => u.name(),
320 Self::Any(_) => "any",
321 }
322 }
323}
324
325#[derive(new, Clone, Debug)]
326pub struct WithSchema<'a> {
327 pub schema: &'a Schema,
328 pub object: &'a ObjectRef,
329}
330
331pub struct StaticSchema {
332 pub boolean: Schema,
333 pub int: Schema,
334 pub string: Schema,
335 pub float: Schema,
336 pub bytes: Schema,
337 pub bytearray: Schema,
338 pub dict: Schema,
339 pub list: Schema,
340 pub set: Schema,
341 pub frozenset: Schema,
342}
343
344pub fn static_schema() -> &'static StaticSchema {
345 &STATIC_SCHEMA
346}
347
348lazy_static::lazy_static! {
349 static ref STATIC_SCHEMA: StaticSchema = {
350 StaticSchema {
351 boolean: Schema::Primitive(Primitive::Bool),
352 int: Schema::Primitive(Primitive::Int),
353 string: Schema::Primitive(Primitive::Str),
354 float: Schema::Primitive(Primitive::Float),
355 bytes: Schema::Primitive(Primitive::Bytes),
356 bytearray: Schema::Primitive(Primitive::ByteArray),
357 dict: Schema::Dict(Dict::new(Box::new(Schema::Any(Any::new())), Box::new(Schema::Any(Any::new())))),
358 list: Schema::List(List::new(Box::new(Schema::Any(Any::new())))),
359 set: Schema::Set(Set::new(Box::new(Schema::Any(Any::new())))),
360 frozenset: Schema::FrozenSet(FrozenSet::new(Box::new(Schema::Any(Any::new())))),
361 }
362 };
363}
364
365unsafe impl Sync for StaticSchema {}