Skip to main content

vbare_gen/
lib.rs

1/*!
2`bare_gen` provides a simple function that generates Rust types from BARE schema files.
3Generated types implicitly implement `serde::Serialize` and `serde::Deserialize`, as `serde_bare`
4is used to handle encoding and decoding. Please see
5[serde_bare's documentation](https://docs.rs/serde_bare/latest/serde_bare/) for information on how
6the Rust data model maps to the BARE data model.
7
8To use this macro, define a BARE schema file and populate it with type declarations.
9
10For example:
11
12
13```bare
14// schema.bare
15type PublicKey data[128]
16type Time str # ISO 8601
17
18type Department enum {
19  ACCOUNTING
20  ADMINISTRATION
21  CUSTOMER_SERVICE
22  DEVELOPMENT
23
24  # Reserved for the CEO
25  JSMITH = 99
26}
27
28type Address list<str>[4] # street, city, state, country
29
30type Customer struct {
31  name: str
32  email: str
33  address: Address
34  orders: list<struct {
35    orderId: i64
36    quantity: i32
37  }>
38  metadata: map<str><data>
39}
40
41type Employee struct {
42  name: str
43  email: str
44  address: Address
45  department: Department
46  hireDate: Time
47  publicKey: optional<PublicKey>
48  metadata: map<str><data>
49}
50
51type TerminatedEmployee void
52
53type Person union {Customer | Employee | TerminatedEmployee}
54```
55
56Then, within a Rust source file:
57
58```ignore
59
60bare_gen::bare_schema("schema.bare", bare_gen::Config::default()); // TokenStream
61
62```
63
64# BARE => Rust Data Mapping
65
66In most areas, the BARE data model maps cleanly to a Rust representation. Unless otherwise
67specified, the most obvious Rust data type is generated from a given BARE type. For example,
68a BARE `option<type>` is mapped to Rust's `Option<type>`, BARE unions and enums are mapped to
69Rust `enum`s. See below for opinions that this crate has around data types that do not map
70as cleanly or require additional explanation.
71
72## Maps
73
74BARE maps are interpreted as `std::collections::HashMap<K, V>` in Rust by default.
75## Variable Length Integers
76
77The variable `uint` and `int` types are mapped to [`serde_bare::UInt`] and [`serde_bare::Int`]
78respectively. These types wrap `u64` and `i64` (the largest possible sized values stored in BARE
79variable length integers).
80
81Arrays that have 32 or less elements are mapped directly as Rust arrays, while BARE arrays with
82more than 32 elements are converted into `Vec<T>`.
83
84## Byte Arrays
85
86BARE `data` maps to `Vec<u8>`, except for fixed-size `data[N]` with 32 or fewer bytes, which maps to
87`[u8; N]`. Serde encodes a plain `Vec<u8>` one element at a time, so `Vec<u8>` fields are generated
88with `#[serde(with = "serde_bytes")]`, which routes them through serde_bare's bulk byte handling
89instead. This is roughly 9 times faster for large payloads and produces identical encoded bytes.
90
91Crates that include generated code therefore need a `serde_bytes` dependency. Fields holding nested
92byte arrays, such as `list<data>`, are not annotated, because `serde_bytes` does not support them.
93
94*/
95
96use std::{collections::BTreeMap, fs::read_to_string, path::Path};
97
98use heck::{ToSnakeCase, ToUpperCamelCase};
99use parser::{parse_string, AnyType, PrimitiveType, StructField};
100use proc_macro2::{Ident, Span, TokenStream};
101use quote::quote;
102
103mod parser;
104
105/// Configuration for `bare_schema` code generation.
106#[derive(Clone, Copy, Debug, Eq, PartialEq)]
107pub struct Config {}
108
109impl Default for Config {
110    fn default() -> Self {
111        Self {}
112    }
113}
114
115fn ident_from_string(s: &String) -> Ident {
116    Ident::new(s, Span::call_site())
117}
118
119#[derive(Clone, Copy)]
120struct Caps {
121    eq: bool,
122    hash: bool,
123}
124
125impl Caps {
126    const ALL: Caps = Caps {
127        eq: true,
128        hash: true,
129    };
130    const NONE: Caps = Caps {
131        eq: false,
132        hash: false,
133    };
134    fn and(self, other: Caps) -> Caps {
135        Caps {
136            eq: self.eq && other.eq,
137            hash: self.hash && other.hash,
138        }
139    }
140    fn derive_tokens(self) -> TokenStream {
141        match (self.eq, self.hash) {
142            (true, true) => quote! { , Eq, Hash },
143            (true, false) => quote! { , Eq },
144            (false, true) => quote! { , Hash },
145            (false, false) => quote! {},
146        }
147    }
148}
149
150/// `bare_schema` parses a BARE schema file and generates equivalent Rust code that is capable of
151/// being serialized to and deserialized from bytes using the BARE encoding format. The macro takes
152/// exactly one argument, a string that will be parsed as path pointing to a BARE schema file. The
153/// path is treated as relative to the file location of the macro's use.
154/// For details on how the BARE data model maps to the Rust data model, see the [`Serialize`
155/// derive macro's documentation.](https://docs.rs/serde_bare/latest/serde_bare/)
156pub fn bare_schema(schema_path: &Path, _config: Config) -> proc_macro2::TokenStream {
157    let file = read_to_string(schema_path).unwrap();
158    let mut schema_generator = SchemaGenerator {
159        global_output: Default::default(),
160        user_type_registry: parse_string(&file),
161    };
162
163    for (name, user_type) in &schema_generator.user_type_registry.clone() {
164        schema_generator.gen_user_type(&name, &user_type);
165    }
166
167    schema_generator.complete()
168}
169
170struct SchemaGenerator {
171    global_output: Vec<TokenStream>,
172    user_type_registry: BTreeMap<String, AnyType>,
173}
174
175impl SchemaGenerator {
176    /// Completes a generation cycle by consuming the `SchemaGenerator` and yielding a
177    /// `TokenStream`.
178    fn complete(self) -> TokenStream {
179        let SchemaGenerator { global_output, .. } = self;
180        quote! {
181            #[allow(unused_imports)]
182            use serde::{Serialize, Deserialize};
183            #[allow(unused_imports)]
184            use serde_bare::{Uint, Int};
185
186            #(#global_output)*
187        }
188    }
189
190    /// `gen_user_type` is responsible for generating the token streams of a single user type at a top
191    /// level. Rust does not support anonymous structs/enums/etc., so we must recursively parse any
192    /// anonymous definitions and generate top-level definitions. As such, this function may generate
193    /// multiple types.
194    fn gen_user_type(&mut self, name: &String, t: &AnyType) {
195        #[allow(unused_assignments)]
196        use AnyType::*;
197        let def = match t {
198            Primitive(p) => {
199                let def = gen_primative_type_def(p);
200                let ident = ident_from_string(name);
201                quote! {
202                    pub type #ident = #def;
203                }
204            }
205            List { inner, length } => {
206                let def = self.gen_list(name, inner.as_ref(), length);
207                let ident = ident_from_string(name);
208                quote! {
209                    pub type #ident = #def;
210                }
211            }
212            Struct(fields) => {
213                self.gen_struct(name, fields);
214                // `gen_struct` only has side-effects on the registry, so we return nothing
215                TokenStream::new()
216            }
217            Map { key, value } => {
218                let map_def = self.gen_map(name, key.as_ref(), value.as_ref());
219                let ident = ident_from_string(name);
220                quote! {
221                    pub type #ident = #map_def;
222                }
223            }
224            Optional(inner) => {
225                let inner_def = self.dispatch_type(name, inner);
226                let ident = ident_from_string(name);
227                quote! {
228                    pub type #ident = #inner_def;
229                }
230            }
231            TypeReference(reference) => {
232                panic!("Type reference is not valid as a top level definition: {reference}")
233            }
234            Enum(members) => {
235                self.gen_enum(name, members);
236                // `gen_enum` only has side-effects on the registry, so we return nothing
237                TokenStream::new()
238            }
239            Union(members) => {
240                self.gen_union(name, members);
241                // `gen_union` only has side-effects on the registry, so we return nothing
242                TokenStream::new()
243            }
244        };
245        self.global_output.push(def);
246    }
247
248    fn caps_of(&self, t: &AnyType) -> Caps {
249        match t {
250            AnyType::Primitive(p) => match p {
251                PrimitiveType::F32 | PrimitiveType::F64 => Caps::NONE,
252                PrimitiveType::UInt | PrimitiveType::Int => Caps {
253                    eq: true,
254                    hash: false,
255                },
256                _ => Caps::ALL,
257            },
258            AnyType::List { inner, .. } => self.caps_of(inner),
259            AnyType::Optional(inner) => self.caps_of(inner),
260            AnyType::Map { key, value } => Caps {
261                eq: self.caps_of(key).eq && self.caps_of(value).eq,
262                hash: false,
263            },
264            AnyType::Struct(fields) => fields
265                .iter()
266                .map(|f| self.caps_of(&f.type_r))
267                .fold(Caps::ALL, Caps::and),
268            AnyType::Union(members) => members
269                .iter()
270                .map(|m| self.caps_of(m))
271                .fold(Caps::ALL, Caps::and),
272            AnyType::Enum(_) => Caps::ALL,
273            AnyType::TypeReference(name) => match self.user_type_registry.get(name) {
274                Some(t) => self.caps_of(t),
275                None => Caps::ALL,
276            },
277        }
278    }
279
280    /// Reports whether a type is a BARE `data` field that maps to `Vec<u8>`, following type aliases
281    /// and looking through `optional<...>`. Serde encodes a plain `Vec<u8>` one element at a time,
282    /// so these fields are annotated with `serde_bytes` to reach serde_bare's bulk `serialize_bytes`
283    /// and `deserialize_byte_buf` paths. Both spellings produce identical bytes.
284    fn is_bytes_type(&self, t: &AnyType) -> bool {
285        match t {
286            AnyType::Primitive(PrimitiveType::Data(size)) => match size {
287                // Small fixed-size data maps to `[u8; N]`, which serde_bytes does not support.
288                Some(size) => *size > MAX_INLINE_DATA_LEN,
289                None => true,
290            },
291            AnyType::Optional(inner) => self.is_bytes_type(inner),
292            AnyType::TypeReference(name) => match self.user_type_registry.get(name) {
293                Some(t) => self.is_bytes_type(t),
294                None => false,
295            },
296            AnyType::Primitive(_)
297            | AnyType::List { .. }
298            | AnyType::Struct(_)
299            | AnyType::Enum(_)
300            | AnyType::Map { .. }
301            | AnyType::Union(_) => false,
302        }
303    }
304
305    fn dispatch_type(&mut self, name: &String, any_type: &AnyType) -> TokenStream {
306        match any_type {
307            AnyType::Primitive(p) => gen_primative_type_def(p),
308            AnyType::List { inner, length } => self.gen_list(name, inner.as_ref(), length),
309            AnyType::Struct(fields) => self.gen_struct(name, fields),
310            AnyType::Enum(members) => self.gen_enum(name, members),
311            AnyType::Map { key, value } => self.gen_map(name, key.as_ref(), value.as_ref()),
312            AnyType::Union(members) => self.gen_union(name, members),
313            AnyType::Optional(inner) => self.gen_option(name, inner),
314            AnyType::TypeReference(i) => {
315                let ident = ident_from_string(i);
316                quote! { #ident }
317            }
318        }
319    }
320
321    fn gen_map(&mut self, name: &String, key: &AnyType, value: &AnyType) -> TokenStream {
322        let key_def = self.dispatch_type(name, key);
323        let val_def = self.dispatch_type(name, value);
324        quote! {
325            std::collections::HashMap<#key_def, #val_def>
326        }
327    }
328
329    fn gen_list(
330        &mut self,
331        name: &String,
332        inner_type: &AnyType,
333        size: &Option<usize>,
334    ) -> TokenStream {
335        let inner_def = self.dispatch_type(name, inner_type);
336        match *size {
337            Some(size) if size <= 32 => quote! {
338                [#inner_def; #size]
339            },
340            _ => quote! {
341                Vec<#inner_def>
342            },
343        }
344    }
345
346    fn gen_struct(&mut self, name: &String, fields: &Vec<StructField>) -> TokenStream {
347        let extra = fields
348            .iter()
349            .map(|f| self.caps_of(&f.type_r))
350            .fold(Caps::ALL, Caps::and)
351            .derive_tokens();
352        // clone so we can safely drain this
353        let fields_clone = fields.clone();
354        let fields_gen = self.gen_struct_field(name, fields_clone);
355        self.gen_anonymous(name, |ident| {
356            quote! {
357                #[derive(Serialize, Deserialize, PartialEq, Debug, Clone #extra)]
358                pub struct #ident {
359                    #(#fields_gen),*
360                }
361            }
362        })
363    }
364
365    fn gen_union(&mut self, name: &String, members: &Vec<AnyType>) -> TokenStream {
366        let mut members_def: Vec<TokenStream> = Vec::with_capacity(members.len());
367        for (i, member) in members.iter().enumerate() {
368            // If this member is a user type alias for void, we'll not generate an inner type later
369            let is_void_type = match member {
370                AnyType::TypeReference(i) if self.user_type_registry.get(i).is_some() => {
371                    let reference = self.user_type_registry.get(i).unwrap();
372                    matches!(reference, AnyType::Primitive(PrimitiveType::Void))
373                }
374                _ => false,
375            };
376
377            // This is to allow the `registry` binding to not shadow the function arg, but instead
378            // rebind it as it's used in the subsequent `gen_anonymous` call. We'll get move errors if
379            // we don't do it this way.
380            #[allow(unused_assignments)]
381            let mut member_def = TokenStream::new();
382            member_def = match member {
383                AnyType::Struct(fields) => {
384                    let fields_defs = self.gen_struct_field(name, fields.clone());
385                    quote! {
386                        {
387                            #(#fields_defs),*
388                        }
389                    }
390                }
391                AnyType::TypeReference(i) if is_void_type => {
392                    let inner_def = ident_from_string(i);
393                    // The `inner_def` is always a top-level type here
394                    quote! {
395                        #inner_def
396                    }
397                }
398                _ => {
399                    let bytes_attr = if self.is_bytes_type(member) {
400                        quote! { #[serde(with = "serde_bytes")] }
401                    } else {
402                        quote! {}
403                    };
404                    let inner_def = self.dispatch_type(&format!("{name}Member{i}"), member);
405                    // The `inner_def` is always a top-level type here
406                    quote! {
407                        #bytes_attr
408                        #inner_def(#inner_def)
409                    }
410                }
411            };
412            members_def.push(member_def);
413        }
414        let extra = members
415            .iter()
416            .map(|m| self.caps_of(m))
417            .fold(Caps::ALL, Caps::and)
418            .derive_tokens();
419        self.gen_anonymous(name, |ident| {
420            quote! {
421                #[derive(Serialize, Deserialize, PartialEq, Debug, Clone #extra)]
422                pub enum #ident {
423                    #(#members_def),*
424                }
425            }
426        })
427    }
428
429    fn gen_option(&mut self, name: &String, inner: &AnyType) -> TokenStream {
430        let inner_def = self.dispatch_type(name, inner);
431        quote! {
432           Option<#inner_def>
433        }
434    }
435
436    fn gen_struct_field(
437        &mut self,
438        struct_name: &String,
439        fields: Vec<StructField>,
440    ) -> Vec<TokenStream> {
441        let mut fields_gen: Vec<TokenStream> = Vec::with_capacity(fields.len());
442        for StructField { name, type_r } in fields {
443            let name = name.to_snake_case();
444            let bytes_attr = if self.is_bytes_type(&type_r) {
445                quote! { #[serde(with = "serde_bytes")] }
446            } else {
447                quote! {}
448            };
449            #[allow(unused_assignments)]
450            let field_gen = self.dispatch_type(&format!("{struct_name}{name}"), &type_r);
451            let ident = ident_from_string(&name);
452            fields_gen.push(quote! {
453                #bytes_attr
454                pub #ident: #field_gen
455            })
456        }
457        fields_gen
458    }
459
460    fn gen_enum(&mut self, name: &String, members: &Vec<(String, Option<usize>)>) -> TokenStream {
461        let member_defs = members.iter().map(|(name, val)| {
462            let ident = ident_from_string(&name.to_upper_camel_case());
463            if let Some(val) = val {
464                quote! {
465                    #ident = #val
466                }
467            } else {
468                quote! {
469                    #ident
470                }
471            }
472        });
473        self.gen_anonymous(name, |ident| {
474            quote! {
475                #[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug, PartialOrd, Clone)]
476                #[repr(usize)]
477                pub enum #ident {
478                    #(#member_defs),*
479                }
480            }
481        })
482    }
483
484    /// `gen_anonymous` generates an identifier from the provided `name`, passed it to `inner`, pushes
485    /// the result of `inner` to the `registry`, and yields a quoted version of the generated
486    /// identifier. This is a common operation when generating types that are anonymous in a BARE
487    /// schema but not allowed by be defined anonymously in Rust.
488    fn gen_anonymous(
489        &mut self,
490        name: &String,
491        inner: impl FnOnce(Ident) -> TokenStream,
492    ) -> TokenStream {
493        let ident = ident_from_string(name);
494        self.global_output.push(inner(ident.clone()));
495        quote! {
496            #ident
497        }
498    }
499}
500
501/// Fixed-size `data` up to this length maps to a Rust array, anything longer maps to `Vec<u8>`.
502const MAX_INLINE_DATA_LEN: usize = 32;
503
504fn gen_primative_type_def(p: &PrimitiveType) -> TokenStream {
505    use PrimitiveType::*;
506    match p {
507        UInt => quote! { Uint },
508        U64 => quote! { u64 },
509        U32 => quote! { u32 },
510        U16 => quote! { u16 },
511        U8 => quote! { u8 },
512        Int => quote! { Int },
513        I64 => quote! { i64 },
514        I32 => quote! { i32 },
515        I16 => quote! { i16 },
516        I8 => quote! { i8 },
517        F64 => quote! { f64 },
518        F32 => quote! { f32 },
519        Str => quote! { String },
520        Data(s) => match s {
521            Some(size) if *size <= MAX_INLINE_DATA_LEN => quote! { [u8; #size] },
522            _ => quote! { Vec<u8> },
523        },
524        Void => quote! { () },
525        Bool => quote! { bool },
526    }
527}