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. Set
75`Config::use_hashable_map` to `true` to emit `rivet_util::serde::HashableMap<K, V>` instead.
76
77## Variable Length Integers
78
79The variable `uint` and `int` types are mapped to [`serde_bare::UInt`] and [`serde_bare::Int`]
80respectively. These types wrap `u64` and `i64` (the largest possible sized values stored in BARE
81variable length integers).
82
83Arrays that have 32 or less elements are mapped directly as Rust arrays, while BARE arrays with
84more than 32 elements are converted into `Vec<T>`.
85
86*/
87
88use std::{collections::BTreeMap, fs::read_to_string, path::Path};
89
90use heck::{ToSnakeCase, ToUpperCamelCase};
91use parser::{parse_string, AnyType, PrimativeType, StructField};
92use proc_macro2::{Ident, Span, TokenStream};
93use quote::quote;
94
95mod parser;
96
97/// Configuration for `bare_schema` code generation.
98#[derive(Clone, Copy, Debug, Eq, PartialEq)]
99pub struct Config {
100    /// When true, generated maps use `rivet_util::serde::HashableMap`.
101    pub use_hashable_map: bool,
102}
103
104impl Default for Config {
105    fn default() -> Self {
106        Self {
107            use_hashable_map: false,
108        }
109    }
110}
111
112impl Config {
113    /// Convenience constructor to emit `HashMap` rather than `HashableMap`.
114    pub fn with_hash_map() -> Self {
115        Self::default()
116    }
117
118    /// Convenience constructor to emit `HashableMap`.
119    pub fn with_hashable_map() -> Self {
120        Self {
121            use_hashable_map: true,
122        }
123    }
124}
125
126fn ident_from_string(s: &String) -> Ident {
127    Ident::new(s, Span::call_site())
128}
129
130/// `bare_schema` parses a BARE schema file and generates equivalent Rust code that is capable of
131/// being serialized to and deserialized from bytes using the BARE encoding format. The macro takes
132/// exactly one argument, a string that will be parsed as path pointing to a BARE schema file. The
133/// path is treated as relative to the file location of the macro's use.
134/// For details on how the BARE data model maps to the Rust data model, see the [`Serialize`
135/// derive macro's documentation.](https://docs.rs/serde_bare/latest/serde_bare/)
136pub fn bare_schema(schema_path: &Path, config: Config) -> proc_macro2::TokenStream {
137    let file = read_to_string(schema_path).unwrap();
138    let mut schema_generator = SchemaGenerator {
139        global_output: Default::default(),
140        user_type_registry: parse_string(&file),
141        config,
142    };
143
144    for (name, user_type) in &schema_generator.user_type_registry.clone() {
145        schema_generator.gen_user_type(&name, &user_type);
146    }
147
148    schema_generator.complete()
149}
150
151struct SchemaGenerator {
152    global_output: Vec<TokenStream>,
153    user_type_registry: BTreeMap<String, AnyType>,
154    config: Config,
155}
156
157impl SchemaGenerator {
158    /// Completes a generation cycle by consuming the `SchemaGenerator` and yielding a
159    /// `TokenStream`.
160    fn complete(self) -> TokenStream {
161        let SchemaGenerator { global_output, .. } = self;
162        quote! {
163            #[allow(unused_imports)]
164            use serde::{Serialize, Deserialize};
165            #[allow(unused_imports)]
166            use serde_bare::{Uint, Int};
167
168            #(#global_output)*
169        }
170    }
171
172    /// `gen_user_type` is responsible for generating the token streams of a single user type at a top
173    /// level. Rust does not support anonymous structs/enums/etc., so we must recursively parse any
174    /// anonymous definitions and generate top-level definitions. As such, this function may generate
175    /// multiple types.
176    fn gen_user_type(&mut self, name: &String, t: &AnyType) {
177        #[allow(unused_assignments)]
178        use AnyType::*;
179        let def = match t {
180            Primative(p) => {
181                let def = gen_primative_type_def(p);
182                let ident = ident_from_string(name);
183                quote! {
184                    pub type #ident = #def;
185                }
186            }
187            List { inner, length } => {
188                let def = self.gen_list(name, inner.as_ref(), length);
189                let ident = ident_from_string(name);
190                quote! {
191                    pub type #ident = #def;
192                }
193            }
194            Struct(fields) => {
195                self.gen_struct(name, fields);
196                // `gen_struct` only has side-effects on the registry, so we return nothing
197                TokenStream::new()
198            }
199            Map { key, value } => {
200                let map_def = self.gen_map(name, key.as_ref(), value.as_ref());
201                let ident = ident_from_string(name);
202                quote! {
203                    pub type #ident = #map_def;
204                }
205            }
206            Optional(inner) => {
207                let inner_def = self.dispatch_type(name, inner);
208                let ident = ident_from_string(name);
209                quote! {
210                    pub type #ident = #inner_def;
211                }
212            }
213            TypeReference(reference) => {
214                panic!("Type reference is not valid as a top level definition: {reference}")
215            }
216            Enum(members) => {
217                self.gen_enum(name, members);
218                // `gen_enum` only has side-effects on the registry, so we return nothing
219                TokenStream::new()
220            }
221            Union(members) => {
222                self.gen_union(name, members);
223                // `gen_union` only has side-effects on the registry, so we return nothing
224                TokenStream::new()
225            }
226        };
227        self.global_output.push(def);
228    }
229
230    fn dispatch_type(&mut self, name: &String, any_type: &AnyType) -> TokenStream {
231        match any_type {
232            AnyType::Primative(p) => gen_primative_type_def(p),
233            AnyType::List { inner, length } => self.gen_list(name, inner.as_ref(), length),
234            AnyType::Struct(fields) => self.gen_struct(name, fields),
235            AnyType::Enum(members) => self.gen_enum(name, members),
236            AnyType::Map { key, value } => self.gen_map(name, key.as_ref(), value.as_ref()),
237            AnyType::Union(members) => self.gen_union(name, members),
238            AnyType::Optional(inner) => self.gen_option(name, inner),
239            AnyType::TypeReference(i) => {
240                let ident = ident_from_string(i);
241                quote! { #ident }
242            }
243        }
244    }
245
246    fn gen_map(&mut self, name: &String, key: &AnyType, value: &AnyType) -> TokenStream {
247        let key_def = self.dispatch_type(name, key);
248        let val_def = self.dispatch_type(name, value);
249        if self.config.use_hashable_map {
250            quote! {
251                rivet_util::serde::HashableMap<#key_def, #val_def>
252            }
253        } else {
254            quote! {
255                std::collections::HashMap<#key_def, #val_def>
256            }
257        }
258    }
259
260    fn gen_list(
261        &mut self,
262        name: &String,
263        inner_type: &AnyType,
264        size: &Option<usize>,
265    ) -> TokenStream {
266        let inner_def = self.dispatch_type(name, inner_type);
267        match *size {
268            Some(size) if size <= 32 => quote! {
269                [#inner_def; #size]
270            },
271            _ => quote! {
272                Vec<#inner_def>
273            },
274        }
275    }
276
277    fn gen_struct(&mut self, name: &String, fields: &Vec<StructField>) -> TokenStream {
278        // clone so we can safely drain this
279        let fields_clone = fields.clone();
280        let fields_gen = self.gen_struct_field(name, fields_clone);
281        let hash_derive = if self.config.use_hashable_map {
282            quote! { , Hash }
283        } else {
284            TokenStream::new()
285        };
286        self.gen_anonymous(name, |ident| {
287            quote! {
288                #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone #hash_derive)]
289                pub struct #ident {
290                    #(#fields_gen),*
291                }
292            }
293        })
294    }
295
296    fn gen_union(&mut self, name: &String, members: &Vec<AnyType>) -> TokenStream {
297        let mut members_def: Vec<TokenStream> = Vec::with_capacity(members.len());
298        for (i, member) in members.iter().enumerate() {
299            // If this member is a user type alias for void, we'll not generate an inner type later
300            let is_void_type = match member {
301                AnyType::TypeReference(i) if self.user_type_registry.get(i).is_some() => {
302                    let reference = self.user_type_registry.get(i).unwrap();
303                    matches!(reference, AnyType::Primative(PrimativeType::Void))
304                }
305                _ => false,
306            };
307
308            // This is to allow the `registry` binding to not shadow the function arg, but instead
309            // rebind it as it's used in the subsequent `gen_anonymous` call. We'll get move errors if
310            // we don't do it this way.
311            #[allow(unused_assignments)]
312            let mut member_def = TokenStream::new();
313            member_def = match member {
314                AnyType::Struct(fields) => {
315                    let fields_defs = self.gen_struct_field(name, fields.clone());
316                    quote! {
317                        {
318                            #(#fields_defs),*
319                        }
320                    }
321                }
322                AnyType::TypeReference(i) if is_void_type => {
323                    let inner_def = ident_from_string(i);
324                    // The `inner_def` is always a top-level type here
325                    quote! {
326                        #inner_def
327                    }
328                }
329                _ => {
330                    let inner_def = self.dispatch_type(&format!("{name}Member{i}"), member);
331                    // The `inner_def` is always a top-level type here
332                    quote! {
333                        #inner_def(#inner_def)
334                    }
335                }
336            };
337            members_def.push(member_def);
338        }
339        let hash_derive = if self.config.use_hashable_map {
340            quote! { , Hash }
341        } else {
342            TokenStream::new()
343        };
344        self.gen_anonymous(name, |ident| {
345            quote! {
346                #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone #hash_derive)]
347                pub enum #ident {
348                    #(#members_def),*
349                }
350            }
351        })
352    }
353
354    fn gen_option(&mut self, name: &String, inner: &AnyType) -> TokenStream {
355        let inner_def = self.dispatch_type(name, inner);
356        quote! {
357           Option<#inner_def>
358        }
359    }
360
361    fn gen_struct_field(
362        &mut self,
363        struct_name: &String,
364        fields: Vec<StructField>,
365    ) -> Vec<TokenStream> {
366        let mut fields_gen: Vec<TokenStream> = Vec::with_capacity(fields.len());
367        for StructField { name, type_r } in fields {
368            let name = name.to_snake_case();
369            #[allow(unused_assignments)]
370            let field_gen = self.dispatch_type(&format!("{struct_name}{name}"), &type_r);
371            let ident = ident_from_string(&name);
372            fields_gen.push(quote! {
373                pub #ident: #field_gen
374            })
375        }
376        fields_gen
377    }
378
379    fn gen_enum(&mut self, name: &String, members: &Vec<(String, Option<usize>)>) -> TokenStream {
380        let member_defs = members.iter().map(|(name, val)| {
381            let ident = ident_from_string(&name.to_upper_camel_case());
382            if let Some(val) = val {
383                quote! {
384                    #ident = #val
385                }
386            } else {
387                quote! {
388                    #ident
389                }
390            }
391        });
392        let hash_derive = if self.config.use_hashable_map {
393            quote! { Hash, }
394        } else {
395            TokenStream::new()
396        };
397        self.gen_anonymous(name, |ident| {
398			quote! {
399				#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, PartialOrd, Ord, #hash_derive Clone)]
400				#[repr(usize)]
401				pub enum #ident {
402					#(#member_defs),*
403				}
404			}
405		})
406    }
407
408    /// `gen_anonymous` generates an identifier from the provided `name`, passed it to `inner`, pushes
409    /// the result of `inner` to the `registry`, and yields a quoted version of the generated
410    /// identifier. This is a common operation when generating types that are anonymous in a BARE
411    /// schema but not allowed by be defined anonymously in Rust.
412    fn gen_anonymous(
413        &mut self,
414        name: &String,
415        inner: impl FnOnce(Ident) -> TokenStream,
416    ) -> TokenStream {
417        let ident = ident_from_string(name);
418        self.global_output.push(inner(ident.clone()));
419        quote! {
420            #ident
421        }
422    }
423}
424
425fn gen_primative_type_def(p: &PrimativeType) -> TokenStream {
426    use PrimativeType::*;
427    match p {
428        UInt => quote! { Uint },
429        U64 => quote! { u64 },
430        U32 => quote! { u32 },
431        U16 => quote! { u16 },
432        U8 => quote! { u8 },
433        Int => quote! { Int },
434        I64 => quote! { i64 },
435        I32 => quote! { i32 },
436        I16 => quote! { i16 },
437        I8 => quote! { i8 },
438        F64 => quote! { f64 },
439        F32 => quote! { f32 },
440        Str => quote! { String },
441        Data(s) => match s {
442            Some(size) if *size <= 32 => quote! { [u8; #size] },
443            _ => quote! { Vec<u8> },
444        },
445        Void => quote! { () },
446        Bool => quote! { bool },
447    }
448}