flatbuffers-tools 0.1.0

Flatbuffers utilities to simplify work with it
Documentation
use crate::RpcService;

use core::fmt;

const TAB: &str = "    ";

#[derive(Copy, Clone)]
#[repr(transparent)]
///Generates file with constants defining rpc method names.
pub struct RpcMethodDefines<'a> {
    ///Service definition
    pub service: &'a RpcService,
}

impl<'a> fmt::Display for RpcMethodDefines<'a> {
    #[inline]
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt.write_str("#[rustfmt::skip]\n")?;

        for method in self.service.methods.iter() {
            let method = method.name.as_str();
            let name = method.to_uppercase();
            fmt.write_fmt(format_args!("\npub const {name}: &str = \"{method}\";"))?;
        }

        Ok(())
    }
}

#[derive(Copy, Clone)]
///Generates module file interface to parse and forward flatbuffer requests to user's created modules' functions
///
///Generated code has following requirements:
///
///## Dependencies
///
///- `flatbuffers` - To parse and generate flatbuffer
///- `xxhash-rust` - To generate efficient method dispatcher using 128bit hashes. Needs features `xxh3` and `const_xxh3`
///
///## User modules
///
///Assuming schema has two methods `daily` and `transform_new_image`, file will import following
///modules and functions:
///
///```rust,ignore
///mod daily;
///pub use daily::daily;
///mod transform_new_image;
///pub use transform_new_image::transform_new_image;
///```
///
///## Module function signature:
///
///```rust,ignore
///pub async fn daily<'a, 'b>(
///    rpc_argument: &Request,
///    mandatory_flatbuffer_builder: &'b mut FlatBufferBuilder<'a>,
///    extra_arg: &ExtraArgument,
///) -> Result<ResponseBuilder<'a, 'b>, CommonError> {
///```
///
///## Result type
///
///In above example `ResponseBuilder` MUST be builder struct generated by `flatc` which uses `mandatory_flatbuffer_builder`
///
///## Error type
///
///In above example `CommonError` must implement following method:
///
///```rust,ignore
///impl Error {
///    #[inline]
///    ///Creates flatbuffer error object
///    pub fn to_interface<'a>(
///        &self,
///        builder: &mut flatbuffers::FlatBufferBuilder<'a>,
///    ) -> flatbuffers::WIPOffset<interface::Error<'a>> {
///     todo!()
///    }
///}
///```
///
///Where `interface::Error` MUST be flatbuffer struct generated by `flatc`
///
///## Dispatch signature
///
///As result following signature will be generated:
///```rust,ignore
///pub async fn dispatch(extra_arg: &ExtraArgument, method: &str, input: &[u8], builder: &mut flatbuffers::FlatBufferBuilder<'_>) -> Option<Result<(), ()>>;
///```
///
///Where return result indicates following:
///
///`None` - Unknown method
///`Some(Ok(()))` - RPC call dispatched successfully, result is written to `builder`
///`Some(Err(()))` - RPC call failed, error is written to `builder`
pub struct RpcServiceImplDefines<'a> {
    ///Service definition
    pub service: &'a RpcService,
    ///Extra arguments to define in `dispatch` and pass to every RPC method
    ///
    ///Following names are used by code generator:
    ///- `method`
    ///- `input`
    ///- `builder`
    pub extra_args: &'a [(&'a str, &'a str)],
    ///Callback to be called when RPC input cannot be parsed
    ///
    ///Must have following signature
    ///
    ///```rust,ignore
    ///fn on_invalid_request(builder: &mut flatbuffers::FlatBufferBuilder, error: flatbuffers::InvalidFlatbuffer) -> Option<Result<(), ()>>;
    ///```
    pub on_invalid_request_cb: &'a str,
    ///Specifies whether flatbuffers input and output MUST be prefixed with `size`.
    ///
    ///Namely it increases size of message by adding header with size when set to `true`.
    pub is_size_prefixed: bool,
    ///Defines constant to limit flatbuffer message size.
    pub default_message_limit: &'a str,
}

impl<'a> fmt::Display for RpcServiceImplDefines<'a> {
    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
        let (parse_method, finish_method) = if self.is_size_prefixed {
            ("size_prefixed_root_with_opts", "finish_size_prefixed")
        } else {
            ("root_with_opts", "finish")
        };
        let default_message_limit = self.default_message_limit;
        let on_invalid_request_cb = self.on_invalid_request_cb;

        fmt.write_str("//Generated by flatbuffers-tools\n")?;

        for method in self.service.methods.iter() {
            let method = method.name.as_str();
            fmt.write_fmt(format_args!("mod {method};\n"))?;
            fmt.write_fmt(format_args!("pub use {method}::{method};\n"))?;
        }

        //dispatch signature
        fmt.write_str("\n#[rustfmt::skip]\n")?;
        fmt.write_str("pub async fn dispatch(")?;
        for arg in self.extra_args.iter() {
            let name = arg.0;
            let typ = arg.1;
            fmt.write_fmt(format_args!("{name}: {typ},"))?;
        }
        fmt.write_str("method: &str, input: &[u8], builder: &mut flatbuffers::FlatBufferBuilder<'_>) -> Option<Result<(), ()>> {\n")?;

        //HASH table
        for method in self.service.methods.iter() {
            //const hashes
            let define_name = method.name.to_uppercase();
            let method = method.name.as_str();
            fmt.write_fmt(format_args!(
                "{TAB}const {define_name}: u128 = xxhash_rust::const_xxh3::xxh3_128(b\"{method}\");\n"
            ))?;
        }

        //flatbuffer options
        fmt.write_fmt(format_args!(
            r#"
    const OPTIONS: flatbuffers::VerifierOptions = flatbuffers::VerifierOptions {{
        max_depth: 64,
        max_tables: 100,
        max_apparent_size: {default_message_limit},
        ignore_missing_null_terminator: false,
    }};
    "#
        ))?;
        //match method
        fmt.write_fmt(format_args!(
            "match xxhash_rust::xxh3::xxh3_128(method.as_bytes()) {{\n"
        ))?;

        for method in self.service.methods.iter() {
            assert_eq!(
                method.arguments.len(),
                1,
                "We require all RPC methods to have 1 argument"
            );
            let argument = &method.arguments[0];
            let method = method.name.as_str();
            let define_name = method.to_uppercase();
            //parse flatbuffer
            fmt.write_fmt(format_args!("{TAB}{TAB}{define_name} => match flatbuffers::{parse_method}::<crate::interface::{argument}>(&OPTIONS, input) {{\n"))?;
            //dispatch task
            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}Ok(req) => match {method}(req, builder"))?;
            //forward extra arguments
            for arg in self.extra_args.iter() {
                let name = arg.0;
                fmt.write_str(",")?;
                fmt.write_str(name)?;
            }
            //finish dispsatch
            fmt.write_str(").await {\n")?;

            //handle task Ok
            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}{TAB}Ok(response) => {{\n"))?;
            fmt.write_fmt(format_args!(
                "{TAB}{TAB}{TAB}{TAB}{TAB}let result = response.finish();\n"
            ))?;
            fmt.write_fmt(format_args!(
                "{TAB}{TAB}{TAB}{TAB}{TAB}builder.{finish_method}(result, None);\n"
            ))?;
            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}{TAB}{TAB}Some(Ok(()))\n"))?;
            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}{TAB}}}\n"))?;
            //handle task Err
            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}{TAB}Err(error) => {{\n"))?;
            fmt.write_fmt(format_args!(
                "{TAB}{TAB}{TAB}{TAB}{TAB}let result = error.to_interface(builder);\n"
            ))?;
            fmt.write_fmt(format_args!(
                "{TAB}{TAB}{TAB}{TAB}{TAB}builder.{finish_method}(result, None);\n"
            ))?;
            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}{TAB}{TAB}Some(Err(()))\n"))?;
            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}{TAB}}}\n"))?;

            fmt.write_fmt(format_args!("{TAB}{TAB}{TAB}}}\n"))?;
            //handle parse error
            fmt.write_fmt(format_args!(
                "{TAB}{TAB}{TAB}Err(error) => {on_invalid_request_cb}(builder, error),\n"
            ))?;
            fmt.write_fmt(format_args!("{TAB}{TAB}}}\n"))?;
        }
        fmt.write_fmt(format_args!("{TAB}{TAB}_ => None,\n"))?;
        fmt.write_fmt(format_args!("{TAB}}}\n"))?;

        //end
        fmt.write_str("}")
    }
}