Skip to main content

perde_core/
schema.rs

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 {}