flatbuffers_tools/
gen.rs

1use crate::RpcService;
2
3use core::fmt;
4
5const TAB: &str = "    ";
6
7#[derive(Copy, Clone)]
8#[repr(transparent)]
9///Generates file with constants defining rpc method names.
10pub struct RpcMethodDefines<'a> {
11    ///Service definition
12    pub service: &'a RpcService,
13}
14
15impl<'a> fmt::Display for RpcMethodDefines<'a> {
16    #[inline]
17    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
18        fmt.write_str("#[rustfmt::skip]\n")?;
19
20        for method in self.service.methods.iter() {
21            let method = method.name.as_str();
22            let name = method.to_uppercase();
23            fmt.write_fmt(format_args!("\npub const {name}: &str = \"{method}\";"))?;
24        }
25
26        Ok(())
27    }
28}
29
30#[derive(Copy, Clone)]
31///Generates module file interface to parse and forward flatbuffer requests to user's created modules' functions
32///
33///Generated code has following requirements:
34///
35///## Dependencies
36///
37///- `flatbuffers` - To parse and generate flatbuffer
38///- `xxhash-rust` - To generate efficient method dispatcher using 128bit hashes. Needs features `xxh3` and `const_xxh3`
39///
40///## User modules
41///
42///Assuming schema has two methods `daily` and `transform_new_image`, file will import following
43///modules and functions:
44///
45///```rust,ignore
46///mod daily;
47///pub use daily::daily;
48///mod transform_new_image;
49///pub use transform_new_image::transform_new_image;
50///```
51///
52///## Module function signature:
53///
54///```rust,ignore
55///pub async fn daily<'a, 'b>(
56///    rpc_argument: &Request,
57///    mandatory_flatbuffer_builder: &'b mut FlatBufferBuilder<'a>,
58///    extra_arg: &ExtraArgument,
59///) -> Result<ResponseBuilder<'a, 'b>, CommonError> {
60///```
61///
62///## Result type
63///
64///In above example `ResponseBuilder` MUST be builder struct generated by `flatc` which uses `mandatory_flatbuffer_builder`
65///
66///## Error type
67///
68///In above example `CommonError` must implement following method:
69///
70///```rust,ignore
71///impl Error {
72///    #[inline]
73///    ///Creates flatbuffer error object
74///    pub fn to_interface<'a>(
75///        &self,
76///        builder: &mut flatbuffers::FlatBufferBuilder<'a>,
77///    ) -> flatbuffers::WIPOffset<interface::Error<'a>> {
78///     todo!()
79///    }
80///}
81///```
82///
83///Where `interface::Error` MUST be flatbuffer struct generated by `flatc`
84///
85///## Dispatch signature
86///
87///As result following signature will be generated:
88///```rust,ignore
89///pub async fn dispatch(extra_arg: &ExtraArgument, method: &str, input: &[u8], builder: &mut flatbuffers::FlatBufferBuilder<'_>) -> Option<Result<(), ()>>;
90///```
91///
92///Where return result indicates following:
93///
94///`None` - Unknown method
95///`Some(Ok(()))` - RPC call dispatched successfully, result is written to `builder`
96///`Some(Err(()))` - RPC call failed, error is written to `builder`
97pub struct RpcServiceImplDefines<'a> {
98    ///Service definition
99    pub service: &'a RpcService,
100    ///Extra arguments to define in `dispatch` and pass to every RPC method
101    ///
102    ///Following names are used by code generator:
103    ///- `method`
104    ///- `input`
105    ///- `builder`
106    pub extra_args: &'a [(&'a str, &'a str)],
107    ///Callback to be called when RPC input cannot be parsed
108    ///
109    ///Must have following signature
110    ///
111    ///```rust,ignore
112    ///fn on_invalid_request(builder: &mut flatbuffers::FlatBufferBuilder, error: flatbuffers::InvalidFlatbuffer) -> Option<Result<(), ()>>;
113    ///```
114    pub on_invalid_request_cb: &'a str,
115    ///Specifies whether flatbuffers input and output MUST be prefixed with `size`.
116    ///
117    ///Namely it increases size of message by adding header with size when set to `true`.
118    pub is_size_prefixed: bool,
119    ///Defines constant to limit flatbuffer message size.
120    pub default_message_limit: &'a str,
121}
122
123impl<'a> fmt::Display for RpcServiceImplDefines<'a> {
124    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
125        let (parse_method, finish_method) = if self.is_size_prefixed {
126            ("size_prefixed_root_with_opts", "finish_size_prefixed")
127        } else {
128            ("root_with_opts", "finish")
129        };
130        let default_message_limit = self.default_message_limit;
131        let on_invalid_request_cb = self.on_invalid_request_cb;
132
133        fmt.write_str("//Generated by flatbuffers-tools\n")?;
134
135        for method in self.service.methods.iter() {
136            let method = method.name.as_str();
137            fmt.write_fmt(format_args!("mod {method};\n"))?;
138            fmt.write_fmt(format_args!("pub use {method}::{method};\n"))?;
139        }
140
141        //dispatch signature
142        fmt.write_str("\n#[rustfmt::skip]\n")?;
143        fmt.write_str("pub async fn dispatch(")?;
144        for arg in self.extra_args.iter() {
145            let name = arg.0;
146            let typ = arg.1;
147            fmt.write_fmt(format_args!("{name}: {typ},"))?;
148        }
149        fmt.write_str("method: &str, input: &[u8], builder: &mut flatbuffers::FlatBufferBuilder<'_>) -> Option<Result<(), ()>> {\n")?;
150
151        //HASH table
152        for method in self.service.methods.iter() {
153            //const hashes
154            let define_name = method.name.to_uppercase();
155            let method = method.name.as_str();
156            fmt.write_fmt(format_args!(
157                "{TAB}const {define_name}: u128 = xxhash_rust::const_xxh3::xxh3_128(b\"{method}\");\n"
158            ))?;
159        }
160
161        //flatbuffer options
162        fmt.write_fmt(format_args!(
163            r#"
164    const OPTIONS: flatbuffers::VerifierOptions = flatbuffers::VerifierOptions {{
165        max_depth: 64,
166        max_tables: 100,
167        max_apparent_size: {default_message_limit},
168        ignore_missing_null_terminator: false,
169    }};
170    "#
171        ))?;
172        //match method
173        fmt.write_fmt(format_args!(
174            "match xxhash_rust::xxh3::xxh3_128(method.as_bytes()) {{\n"
175        ))?;
176
177        for method in self.service.methods.iter() {
178            assert_eq!(
179                method.arguments.len(),
180                1,
181                "We require all RPC methods to have 1 argument"
182            );
183            let argument = &method.arguments[0];
184            let method = method.name.as_str();
185            let define_name = method.to_uppercase();
186            //parse flatbuffer
187            fmt.write_fmt(format_args!("{TAB}{TAB}{define_name} => match flatbuffers::{parse_method}::<crate::interface::{argument}>(&OPTIONS, input) {{\n"))?;
188            //dispatch task
189            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}Ok(req) => match {method}(req, builder"))?;
190            //forward extra arguments
191            for arg in self.extra_args.iter() {
192                let name = arg.0;
193                fmt.write_str(",")?;
194                fmt.write_str(name)?;
195            }
196            //finish dispsatch
197            fmt.write_str(").await {\n")?;
198
199            //handle task Ok
200            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}{TAB}Ok(response) => {{\n"))?;
201            fmt.write_fmt(format_args!(
202                "{TAB}{TAB}{TAB}{TAB}{TAB}let result = response.finish();\n"
203            ))?;
204            fmt.write_fmt(format_args!(
205                "{TAB}{TAB}{TAB}{TAB}{TAB}builder.{finish_method}(result, None);\n"
206            ))?;
207            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}{TAB}{TAB}Some(Ok(()))\n"))?;
208            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}{TAB}}}\n"))?;
209            //handle task Err
210            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}{TAB}Err(error) => {{\n"))?;
211            fmt.write_fmt(format_args!(
212                "{TAB}{TAB}{TAB}{TAB}{TAB}let result = error.to_interface(builder);\n"
213            ))?;
214            fmt.write_fmt(format_args!(
215                "{TAB}{TAB}{TAB}{TAB}{TAB}builder.{finish_method}(result, None);\n"
216            ))?;
217            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}{TAB}{TAB}Some(Err(()))\n"))?;
218            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}{TAB}}}\n"))?;
219
220            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}}}\n"))?;
221            //handle parse error
222            fmt.write_fmt(format_args!(
223                "{TAB}{TAB}{TAB}Err(error) => {on_invalid_request_cb}(builder, error),\n"
224            ))?;
225            fmt.write_fmt(format_args!("{TAB}{TAB}}}\n"))?;
226        }
227        fmt.write_fmt(format_args!("{TAB}{TAB}_ => None,\n"))?;
228        fmt.write_fmt(format_args!("{TAB}}}\n"))?;
229
230        //end
231        fmt.write_str("}")
232    }
233}