diff --git a/Cargo.toml b/Cargo.toml index 97a9fee..332a50f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bomboni" -version = "0.1.39" +version = "0.1.40" authors = ["Tin Rabzelj "] description = "Utility Library for Rust" repository = "https://github.com/tinrab/bomboni" @@ -38,9 +38,9 @@ tokio = ["bomboni_common/tokio"] tonic = ["bomboni_proto/tonic", "bomboni_request/tonic"] [dependencies] -bomboni_common = { path = "bomboni_common", version = "0.1.39" } +bomboni_common = { path = "bomboni_common", version = "0.1.40" } -bomboni_prost = { path = "bomboni_prost", version = "0.1.39", optional = true } -bomboni_proto = { path = "bomboni_proto", version = "0.1.39", optional = true } -bomboni_request = { path = "bomboni_request", version = "0.1.39", optional = true } -bomboni_template = { path = "bomboni_template", version = "0.1.39", optional = true } +bomboni_prost = { path = "bomboni_prost", version = "0.1.40", optional = true } +bomboni_proto = { path = "bomboni_proto", version = "0.1.40", optional = true } +bomboni_request = { path = "bomboni_request", version = "0.1.40", optional = true } +bomboni_template = { path = "bomboni_template", version = "0.1.40", optional = true } diff --git a/bomboni_common/Cargo.toml b/bomboni_common/Cargo.toml index 0b593e3..b1875b2 100644 --- a/bomboni_common/Cargo.toml +++ b/bomboni_common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bomboni_common" -version = "0.1.39" +version = "0.1.40" authors = ["Tin Rabzelj "] description = "Common things for Bomboni library." repository = "https://github.com/tinrab/bomboni" diff --git a/bomboni_prost/Cargo.toml b/bomboni_prost/Cargo.toml index c4d74c4..72707fd 100644 --- a/bomboni_prost/Cargo.toml +++ b/bomboni_prost/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bomboni_prost" -version = "0.1.39" +version = "0.1.40" authors = ["Tin Rabzelj "] description = "Utilities for working with prost. Part of Bomboni library." repository = "https://github.com/tinrab/bomboni" diff --git a/bomboni_proto/Cargo.toml b/bomboni_proto/Cargo.toml index 90c02e0..bd58ca5 100644 --- a/bomboni_proto/Cargo.toml +++ b/bomboni_proto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bomboni_proto" -version = "0.1.39" +version = "0.1.40" authors = ["Tin Rabzelj "] description = "Utilities for working with Protobuf/gRPC. Part of Bomboni library." repository = "https://github.com/tinrab/bomboni" @@ -36,5 +36,5 @@ serde_json = { version = "1.0.108", optional = true } serde_json = "1.0.108" [build-dependencies] -bomboni_prost = { path = "../bomboni_prost", version = "0.1.39" } +bomboni_prost = { path = "../bomboni_prost", version = "0.1.40" } prost-build = "0.12.3" diff --git a/bomboni_request/Cargo.toml b/bomboni_request/Cargo.toml index 72160d9..cf3f246 100644 --- a/bomboni_request/Cargo.toml +++ b/bomboni_request/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bomboni_request" -version = "0.1.39" +version = "0.1.40" authors = ["Tin Rabzelj "] description = "Utilities for working with API requests. Part of Bomboni library." repository = "https://github.com/tinrab/bomboni" @@ -19,8 +19,8 @@ testing = [] tonic = ["bomboni_proto/tonic", "dep:tonic"] [dependencies] -bomboni_common = { path = "../bomboni_common", version = "0.1.39" } -bomboni_proto = { path = "../bomboni_proto", version = "0.1.39" } +bomboni_common = { path = "../bomboni_common", version = "0.1.40" } +bomboni_proto = { path = "../bomboni_proto", version = "0.1.40" } thiserror = "1.0.50" itertools = "0.12.0" time = { version = "0.3.30", features = ["formatting", "parsing"] } @@ -35,7 +35,7 @@ rand = "0.8.5" regex = "1.10.2" tonic = { version = "0.10.2", optional = true } -bomboni_request_derive = { path = "../bomboni_request_derive", version = "0.1.39", optional = true } +bomboni_request_derive = { path = "../bomboni_request_derive", version = "0.1.40", optional = true } [dev-dependencies] serde = { version = "1.0.193", features = ["derive"] } diff --git a/bomboni_request/src/parse/mod.rs b/bomboni_request/src/parse/mod.rs index 34d2b88..d90c26f 100644 --- a/bomboni_request/src/parse/mod.rs +++ b/bomboni_request/src/parse/mod.rs @@ -1803,4 +1803,58 @@ mod tests { ) && field == "item.nested_value" )); } + + #[test] + fn wrap_request_message() { + #[derive(Debug, Clone, PartialEq, Default)] + struct Request { + value: Option, + } + + impl Request { + pub const NAME: &'static str = "Request"; + } + + #[derive(Debug, Clone, PartialEq, Default, Parse)] + #[parse(source = Request, request, write)] + struct ParsedRequest { + #[parse(source_option)] + value: i32, + } + + #[derive(Debug, Clone, PartialEq, Default, Parse)] + #[parse(source = Request, request { name = "Test" }, write)] + struct ParsedCustomNameRequest { + #[parse(source_option)] + value: i32, + } + + assert!(matches!( + ParsedRequest::parse(Request { value: None }).unwrap_err(), + RequestError::BadRequest { name, violations } + if name == Request::NAME && matches!( + violations.get(0).unwrap(), + FieldError { + error, field, .. + } if matches!( + error.as_any().downcast_ref::().unwrap(), + CommonError::RequiredFieldMissing { .. } + ) && field == "value" + ) + )); + + assert!(matches!( + ParsedCustomNameRequest::parse(Request { value: None }).unwrap_err(), + RequestError::BadRequest { name, violations } + if name == "Test" && matches!( + violations.get(0).unwrap(), + FieldError { + error, field, .. + } if matches!( + error.as_any().downcast_ref::().unwrap(), + CommonError::RequiredFieldMissing { .. } + ) && field == "value" + ) + )); + } } diff --git a/bomboni_request_derive/Cargo.toml b/bomboni_request_derive/Cargo.toml index d77bc9b..08f1eec 100644 --- a/bomboni_request_derive/Cargo.toml +++ b/bomboni_request_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bomboni_request_derive" -version = "0.1.39" +version = "0.1.40" authors = ["Tin Rabzelj "] description = "Provides derive implementations for Bomboni library." repository = "https://github.com/tinrab/bomboni" diff --git a/bomboni_request_derive/src/parse/message/parse.rs b/bomboni_request_derive/src/parse/message/parse.rs index 06ebbc7..f3949a2 100644 --- a/bomboni_request_derive/src/parse/message/parse.rs +++ b/bomboni_request_derive/src/parse/message/parse.rs @@ -88,7 +88,8 @@ pub fn expand(options: &ParseOptions, fields: &[ParseField]) -> syn::Result syn::Result } @@ -111,55 +111,67 @@ pub fn expand(options: &ParseOptions, fields: &[ParseField]) -> syn::Result( - source: #source, - query_builder: &SearchQueryBuilder

- ) -> Result { - Ok(Self { - #query_field_ident: { - #parse_query - query - }, - #parse_fields - #skipped_fields - }) - } + quote! { + Ok(Self { + #query_field_ident: { + #parse_query + query + }, + #parse_fields + #skipped_fields + }) + } + } else { + quote! { + Ok(Self { + #parse_fields + #skipped_fields + }) + } + }; + + if let Some(request_options) = options.request.as_ref() { + let request_name = if let Some(name) = request_options.name.as_ref() { + quote! { #name } + } else { + quote! { #source::NAME } + }; + parse = quote! { + (|| { #parse })().map_err(|err: RequestError| err.wrap_request(#request_name)) + }; + } + + Ok(if options.search_query.is_some() { + quote! { + impl #ident #type_params #where_clause { + #[allow(clippy::ignored_unit_patterns)] + fn parse_search_query( + source: #source, + query_builder: &SearchQueryBuilder

+ ) -> Result { + #parse } } - } else { - quote! { - impl #ident #type_params #where_clause { - #[allow(clippy::ignored_unit_patterns)] - fn parse_list_query( - source: #source, - query_builder: &ListQueryBuilder

- ) -> Result { - Ok(Self { - #query_field_ident: { - #parse_query - query - }, - #parse_fields - #skipped_fields - }) - } + } + } else if options.list_query.is_some() { + quote! { + impl #ident #type_params #where_clause { + #[allow(clippy::ignored_unit_patterns)] + fn parse_list_query( + source: #source, + query_builder: &ListQueryBuilder

+ ) -> Result { + #parse } } - }); - } - - Ok(quote! { - impl #type_params RequestParse<#source> for #ident #type_params #where_clause { - #[allow(clippy::ignored_unit_patterns)] - fn parse(source: #source) -> RequestResult { - Ok(Self { - #parse_fields - #skipped_fields - }) + } + } else { + quote! { + impl #type_params RequestParse<#source> for #ident #type_params #where_clause { + #[allow(clippy::ignored_unit_patterns)] + fn parse(source: #source) -> RequestResult { + #parse + } } } }) diff --git a/bomboni_request_derive/src/parse/mod.rs b/bomboni_request_derive/src/parse/mod.rs index e577a3f..e594e4d 100644 --- a/bomboni_request_derive/src/parse/mod.rs +++ b/bomboni_request_derive/src/parse/mod.rs @@ -33,6 +33,10 @@ pub struct ParseOptions { /// Parse search query fields. #[darling(default)] pub search_query: Option, + /// Marks this message as a request message. + /// Errors will be wrapped with request's name. + #[darling(default)] + pub request: Option, } #[derive(FromMeta, Debug)] @@ -41,6 +45,11 @@ pub struct ParseTaggedUnion { pub field: Ident, } +#[derive(Debug)] +pub struct RequestOptions { + pub name: Option, +} + #[derive(Debug, FromField)] #[darling(attributes(parse))] pub struct ParseField { @@ -227,6 +236,43 @@ pub fn parse_default_expr(meta: &Meta) -> darling::Result { } } +impl FromMeta for RequestOptions { + fn from_list(items: &[ast::NestedMeta]) -> darling::Result { + let mut options = Self { name: None }; + for item in items { + match item { + ast::NestedMeta::Meta(meta) => { + let ident = meta.path().get_ident().unwrap(); + match ident.to_string().as_str() { + "name" => { + if let Meta::NameValue(MetaNameValue { value, .. }) = meta { + options.name = Some(value.clone()); + } else { + return Err( + darling::Error::custom("expected name value").with_span(meta) + ); + } + } + _ => { + return Err( + darling::Error::custom("unknown request option").with_span(ident) + ); + } + } + } + ast::NestedMeta::Lit(lit) => { + return Err(darling::Error::custom("unexpected literal").with_span(lit)); + } + } + } + Ok(options) + } + + fn from_word() -> darling::Result { + Ok(Self { name: None }) + } +} + impl FromMeta for DeriveOptions { fn from_expr(expr: &Expr) -> darling::Result { Ok(match expr { diff --git a/bomboni_template/Cargo.toml b/bomboni_template/Cargo.toml index e204f4b..3405f8e 100644 --- a/bomboni_template/Cargo.toml +++ b/bomboni_template/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bomboni_template" -version = "0.1.39" +version = "0.1.40" authors = ["Tin Rabzelj "] description = "Utilities for working Handlebars templates. Part of Bomboni library." repository = "https://github.com/tinrab/bomboni" @@ -17,8 +17,8 @@ path = "src/lib.rs" testing = [] [dependencies] -bomboni_common = { path = "../bomboni_common", version = "0.1.39" } -bomboni_proto = { version = "0.1.39", path = "../bomboni_proto", features = [ +bomboni_common = { path = "../bomboni_common", version = "0.1.40" } +bomboni_proto = { version = "0.1.40", path = "../bomboni_proto", features = [ "json", ] } thiserror = "1.0.50"