Skip to content

Commit

Permalink
Wrap errors for request messages
Browse files Browse the repository at this point in the history
  • Loading branch information
tinrab committed Dec 20, 2023
1 parent 17579ab commit aead671
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 66 deletions.
12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bomboni"
version = "0.1.39"
version = "0.1.40"
authors = ["Tin Rabzelj <tin@flinect.com>"]
description = "Utility Library for Rust"
repository = "https://github.com/tinrab/bomboni"
Expand Down Expand Up @@ -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 }
2 changes: 1 addition & 1 deletion bomboni_common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bomboni_common"
version = "0.1.39"
version = "0.1.40"
authors = ["Tin Rabzelj <tin@flinect.com>"]
description = "Common things for Bomboni library."
repository = "https://github.com/tinrab/bomboni"
Expand Down
2 changes: 1 addition & 1 deletion bomboni_prost/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bomboni_prost"
version = "0.1.39"
version = "0.1.40"
authors = ["Tin Rabzelj <tin@flinect.com>"]
description = "Utilities for working with prost. Part of Bomboni library."
repository = "https://github.com/tinrab/bomboni"
Expand Down
4 changes: 2 additions & 2 deletions bomboni_proto/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bomboni_proto"
version = "0.1.39"
version = "0.1.40"
authors = ["Tin Rabzelj <tin@flinect.com>"]
description = "Utilities for working with Protobuf/gRPC. Part of Bomboni library."
repository = "https://github.com/tinrab/bomboni"
Expand Down Expand Up @@ -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"
8 changes: 4 additions & 4 deletions bomboni_request/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bomboni_request"
version = "0.1.39"
version = "0.1.40"
authors = ["Tin Rabzelj <tin@flinect.com>"]
description = "Utilities for working with API requests. Part of Bomboni library."
repository = "https://github.com/tinrab/bomboni"
Expand All @@ -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"] }
Expand All @@ -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"] }
Expand Down
54 changes: 54 additions & 0 deletions bomboni_request/src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1803,4 +1803,58 @@ mod tests {
) && field == "item.nested_value"
));
}

#[test]
fn wrap_request_message() {
#[derive(Debug, Clone, PartialEq, Default)]
struct Request {
value: Option<i32>,
}

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::<CommonError>().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::<CommonError>().unwrap(),
CommonError::RequiredFieldMissing { .. }
) && field == "value"
)
));
}
}
2 changes: 1 addition & 1 deletion bomboni_request_derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bomboni_request_derive"
version = "0.1.39"
version = "0.1.40"
authors = ["Tin Rabzelj <tin@flinect.com>"]
description = "Provides derive implementations for Bomboni library."
repository = "https://github.com/tinrab/bomboni"
Expand Down
108 changes: 60 additions & 48 deletions bomboni_request_derive/src/parse/message/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ pub fn expand(options: &ParseOptions, fields: &[ParseField]) -> syn::Result<Toke
quote!()
};

if let Some(query_options) = options
let mut query_token_type = quote!();
let mut parse = if let Some(query_options) = options
.list_query
.as_ref()
.or(options.search_query.as_ref())
Expand All @@ -100,8 +101,7 @@ pub fn expand(options: &ParseOptions, fields: &[ParseField]) -> syn::Result<Toke
.iter()
.find(|field| field.ident.as_ref().unwrap() == query_field_ident)
.unwrap();
let query_token_type = if let Some(token_type) = get_query_field_token_type(&query_field.ty)
{
query_token_type = if let Some(token_type) = get_query_field_token_type(&query_field.ty) {
quote! {
<PageToken = #token_type>
}
Expand All @@ -111,55 +111,67 @@ pub fn expand(options: &ParseOptions, fields: &[ParseField]) -> syn::Result<Toke
}
};

return Ok(if options.search_query.is_some() {
quote! {
impl #ident #type_params #where_clause {
#[allow(clippy::ignored_unit_patterns)]
fn parse_search_query<P: PageTokenBuilder #query_token_type >(
source: #source,
query_builder: &SearchQueryBuilder<P>
) -> Result<Self, RequestError> {
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<P: PageTokenBuilder #query_token_type >(
source: #source,
query_builder: &SearchQueryBuilder<P>
) -> Result<Self, RequestError> {
#parse
}
}
} else {
quote! {
impl #ident #type_params #where_clause {
#[allow(clippy::ignored_unit_patterns)]
fn parse_list_query<P: PageTokenBuilder #query_token_type >(
source: #source,
query_builder: &ListQueryBuilder<P>
) -> Result<Self, RequestError> {
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<P: PageTokenBuilder #query_token_type >(
source: #source,
query_builder: &ListQueryBuilder<P>
) -> Result<Self, RequestError> {
#parse
}
}
});
}

Ok(quote! {
impl #type_params RequestParse<#source> for #ident #type_params #where_clause {
#[allow(clippy::ignored_unit_patterns)]
fn parse(source: #source) -> RequestResult<Self> {
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<Self> {
#parse
}
}
}
})
Expand Down
46 changes: 46 additions & 0 deletions bomboni_request_derive/src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ pub struct ParseOptions {
/// Parse search query fields.
#[darling(default)]
pub search_query: Option<QueryOptions>,
/// Marks this message as a request message.
/// Errors will be wrapped with request's name.
#[darling(default)]
pub request: Option<RequestOptions>,
}

#[derive(FromMeta, Debug)]
Expand All @@ -41,6 +45,11 @@ pub struct ParseTaggedUnion {
pub field: Ident,
}

#[derive(Debug)]
pub struct RequestOptions {
pub name: Option<Expr>,
}

#[derive(Debug, FromField)]
#[darling(attributes(parse))]
pub struct ParseField {
Expand Down Expand Up @@ -227,6 +236,43 @@ pub fn parse_default_expr(meta: &Meta) -> darling::Result<Expr> {
}
}

impl FromMeta for RequestOptions {
fn from_list(items: &[ast::NestedMeta]) -> darling::Result<Self> {
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<Self> {
Ok(Self { name: None })
}
}

impl FromMeta for DeriveOptions {
fn from_expr(expr: &Expr) -> darling::Result<Self> {
Ok(match expr {
Expand Down
6 changes: 3 additions & 3 deletions bomboni_template/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "bomboni_template"
version = "0.1.39"
version = "0.1.40"
authors = ["Tin Rabzelj <tin@flinect.com>"]
description = "Utilities for working Handlebars templates. Part of Bomboni library."
repository = "https://github.com/tinrab/bomboni"
Expand All @@ -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"
Expand Down

0 comments on commit aead671

Please sign in to comment.