diff options
| author | hozan23 <hozan23@karyontech.net> | 2024-06-30 20:03:02 +0200 | 
|---|---|---|
| committer | hozan23 <hozan23@karyontech.net> | 2024-06-30 20:03:02 +0200 | 
| commit | 3c55168b72c022b618822c7993b7692f583506db (patch) | |
| tree | 146c03f6cc19956ec0acfb7ba26a6202cb5a9647 /jsonrpc/impl | |
| parent | 2ec4d4a3c3779dc016c8437891f825a54a805808 (diff) | |
jsonrpc: remove redundant macro codes in the main crate and clean up
internal proc macros
Diffstat (limited to 'jsonrpc/impl')
| -rw-r--r-- | jsonrpc/impl/Cargo.toml | 26 | ||||
| -rw-r--r-- | jsonrpc/impl/src/lib.rs | 174 | 
2 files changed, 200 insertions, 0 deletions
diff --git a/jsonrpc/impl/Cargo.toml b/jsonrpc/impl/Cargo.toml new file mode 100644 index 0000000..cf001e1 --- /dev/null +++ b/jsonrpc/impl/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "karyon_jsonrpc_macro" +description = "Internal crate for Karyon library." +version.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +license.workspace = true +authors.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[features] +default = ["smol"] +smol = [] +tokio = [] + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full"] } + +serde_json = "1.0.117" diff --git a/jsonrpc/impl/src/lib.rs b/jsonrpc/impl/src/lib.rs new file mode 100644 index 0000000..8814e61 --- /dev/null +++ b/jsonrpc/impl/src/lib.rs @@ -0,0 +1,174 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{ +    parse_macro_input, spanned::Spanned, FnArg, ImplItem, ItemImpl, ReturnType, Signature, Type, +    TypePath, +}; + +macro_rules! err { +    ($($tt:tt)*) => { +        return syn::Error::new($($tt)*).to_compile_error().into() +    }; +} + +#[proc_macro_attribute] +pub fn rpc_impl(_attr: TokenStream, item: TokenStream) -> TokenStream { +    let item2 = item.clone(); +    let parsed_input = parse_macro_input!(item2 as ItemImpl); + +    let self_ty = match *parsed_input.self_ty { +        Type::Path(p) => p, +        _ => err!( +            parsed_input.span(), +            "Implementing the trait `RPCService` on this type is unsupported" +        ), +    }; + +    let methods = match parse_struct_methods(&self_ty, parsed_input.items) { +        Ok(res) => res, +        Err(err) => return err.to_compile_error().into(), +    }; + +    let mut method_idents = vec![]; +    for method in methods.iter() { +        method_idents.push(method.ident.clone()); +        if method.inputs.len() != 2 { +            err!( +                method.span(), +                "requires `&self` and a parameter of type `serde_json::Value`" +            ); +        } + +        if let Err(err) = validate_method(method) { +            return err.to_compile_error().into(); +        } +    } + +    let impl_methods: Vec<TokenStream2> = method_idents.iter().map( +        |m| quote! { +            stringify!(#m) => Some(Box::new(move |params: serde_json::Value| Box::pin(self.#m(params)))), +        }, +    ).collect(); + +    let item: TokenStream2 = item.into(); +    quote! { +        impl karyon_jsonrpc::RPCService for #self_ty { +            fn get_method<'a>( +                &'a self, +                name: &'a str +            ) -> Option<karyon_jsonrpc::RPCMethod> { +                match name { +                #(#impl_methods)* +                    _ => None, +                } +            } +            fn name(&self) -> String{ +                stringify!(#self_ty).to_string() +            } +        } +        #item +    } +    .into() +} + +#[proc_macro_attribute] +pub fn rpc_pubsub_impl(_attr: TokenStream, item: TokenStream) -> TokenStream { +    let item2 = item.clone(); +    let parsed_input = parse_macro_input!(item2 as ItemImpl); + +    let self_ty = match *parsed_input.self_ty { +        Type::Path(p) => p, +        _ => err!( +            parsed_input.span(), +            "Implementing the trait `PubSubRPCService` on this type is unsupported" +        ), +    }; + +    let methods = match parse_struct_methods(&self_ty, parsed_input.items) { +        Ok(res) => res, +        Err(err) => return err.to_compile_error().into(), +    }; + +    let mut method_idents = vec![]; +    for method in methods.iter() { +        method_idents.push(method.ident.clone()); +        if method.inputs.len() != 4 { +            err!(method.span(), "requires `&self` and three parameters: `Arc<Channel>`, method: `String`, and `serde_json::Value`"); +        } +        if let Err(err) = validate_method(method) { +            return err.to_compile_error().into(); +        } +    } + +    let impl_methods: Vec<TokenStream2> = method_idents.iter().map( +        |m| quote! { +            stringify!(#m) => { +                Some(Box::new( +                    move |chan: std::sync::Arc<karyon_jsonrpc::Channel>, method: String, params: serde_json::Value| { +                    Box::pin(self.#m(chan, method, params)) +                })) +            }, +        }, +    ).collect(); + +    let item: TokenStream2 = item.into(); +    quote! { +        impl karyon_jsonrpc::PubSubRPCService for #self_ty { +            fn get_pubsub_method<'a>( +                &'a self, +                name: &'a str +            ) -> Option<karyon_jsonrpc::PubSubRPCMethod> { +                match name { +                #(#impl_methods)* +                    _ => None, +                } +            } + +            fn name(&self) -> String{ +                stringify!(#self_ty).to_string() +            } +        } +        #item +    } +    .into() +} + +fn parse_struct_methods( +    self_ty: &TypePath, +    items: Vec<ImplItem>, +) -> Result<Vec<Signature>, syn::Error> { +    let mut methods: Vec<Signature> = vec![]; + +    if items.is_empty() { +        return Err(syn::Error::new( +            self_ty.span(), +            "At least one method should be implemented", +        )); +    } + +    for item in items { +        match item { +            ImplItem::Fn(method) => { +                methods.push(method.sig); +            } +            _ => return Err(syn::Error::new(item.span(), "Unexpected item!")), +        } +    } + +    Ok(methods) +} + +fn validate_method(method: &Signature) -> Result<(), syn::Error> { +    if let FnArg::Typed(_) = method.inputs[0] { +        return Err(syn::Error::new(method.span(), "requires `&self` parameter")); +    } + +    if let ReturnType::Default = method.output { +        return Err(syn::Error::new( +            method.span(), +            "requires `Result<serde_json::Value, RPCError>` as return type", +        )); +    } +    Ok(()) +}  | 
