Skip to content

Commit

Permalink
Add source_try_from
Browse files Browse the repository at this point in the history
  • Loading branch information
tinrab committed Dec 15, 2023
1 parent f0bb454 commit 29f7871
Show file tree
Hide file tree
Showing 14 changed files with 167 additions and 30 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.30"
version = "0.1.31"
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.30" }
bomboni_common = { path = "bomboni_common", version = "0.1.31" }

bomboni_prost = { path = "bomboni_prost", version = "0.1.30", optional = true }
bomboni_proto = { path = "bomboni_proto", version = "0.1.30", optional = true }
bomboni_request = { path = "bomboni_request", version = "0.1.30", optional = true }
bomboni_template = { path = "bomboni_template", version = "0.1.30", optional = true }
bomboni_prost = { path = "bomboni_prost", version = "0.1.31", optional = true }
bomboni_proto = { path = "bomboni_proto", version = "0.1.31", optional = true }
bomboni_request = { path = "bomboni_request", version = "0.1.31", optional = true }
bomboni_template = { path = "bomboni_template", version = "0.1.31", 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.30"
version = "0.1.31"
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.30"
version = "0.1.31"
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.30"
version = "0.1.31"
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.30" }
bomboni_prost = { path = "../bomboni_prost", version = "0.1.31" }
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.30"
version = "0.1.31"
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.30" }
bomboni_proto = { path = "../bomboni_proto", version = "0.1.30" }
bomboni_common = { path = "../bomboni_common", version = "0.1.31" }
bomboni_proto = { path = "../bomboni_proto", version = "0.1.31" }
thiserror = "1.0.50"
itertools = "0.12.0"
time = { version = "0.3.30", features = ["formatting", "parsing"] }
Expand All @@ -35,4 +35,4 @@ 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.30", optional = true }
bomboni_request_derive = { path = "../bomboni_request_derive", version = "0.1.31", optional = true }
2 changes: 2 additions & 0 deletions bomboni_request/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ pub enum CommonError {
UnknownOneofVariant,
#[error("invalid numeric value")]
InvalidNumericValue,
#[error("failed to convert value")]
FailedConvertValue,
#[error("out of range")]
NumericOutOfRange,
#[error("duplicate value")]
Expand Down
76 changes: 74 additions & 2 deletions bomboni_request/src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ mod tests {
struct ParsedItem {
#[parse(source_name = "string")]
s: String,
#[parse(source_option, source_name = "optional_string")]
#[parse(source_name = "optional_string")]
opt_s: Option<String>,
required_string: String,
#[parse(source_option)]
Expand Down Expand Up @@ -1059,7 +1059,7 @@ mod tests {
match self {
Self::String(_) => "string",
Self::Data(_) => "data",
Self::Null(_) => "null",
Self::Null(()) => "null",
Self::Empty => "empty",
Self::Dropped(_) => "dropped",
}
Expand Down Expand Up @@ -1109,4 +1109,76 @@ mod tests {
assert_eq!(ParsedItem::parse(Item::Empty).unwrap(), ParsedItem::Empty);
assert_eq!(Item::from(ParsedItem::Empty), Item::Empty);
}

#[test]
fn source_convert() {
#[derive(Debug, Clone, PartialEq, Default)]
struct Item {
casted_value: u32,
optional_casted: Option<u32>,
}

#[derive(Debug, Clone, PartialEq, Default, Parse)]
#[parse(source = Item, write)]
struct ParsedItem {
#[parse(source_try_from = u32)]
casted_value: usize,
#[parse(source_try_from = u32)]
optional_casted: Option<usize>,
}

#[derive(Debug, Clone, PartialEq)]
enum Union {
A(u32),
B(Option<u32>),
}

impl Union {
pub fn get_variant_name(&self) -> &'static str {
match self {
Self::A(_) => "a",
Self::B(_) => "b",
}
}
}

#[derive(Debug, Clone, PartialEq, Parse)]
#[parse(source = Union, write)]
enum ParsedUnion {
#[parse(source_try_from = u32)]
A(usize),
#[parse(source_try_from = u32)]
B(Option<usize>),
}

assert_eq!(
ParsedItem::parse(Item {
casted_value: 42,
optional_casted: Some(42),
})
.unwrap(),
ParsedItem {
casted_value: 42,
optional_casted: Some(42),
}
);
assert_eq!(
Item::from(ParsedItem {
casted_value: 42,
optional_casted: Some(42),
}),
Item {
casted_value: 42,
optional_casted: Some(42),
}
);

assert_eq!(
ParsedUnion::parse(Union::A(42)).unwrap(),
ParsedUnion::A(42)
);
assert_eq!(Union::from(ParsedUnion::A(42)), Union::A(42));
assert_eq!(Union::from(ParsedUnion::B(Some(42))), Union::B(Some(42)));
assert_eq!(Union::from(ParsedUnion::B(None)), Union::B(None));
}
}
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.30"
version = "0.1.31"
authors = ["Tin Rabzelj <tin@flinect.com>"]
description = "Provides derive implementations for Bomboni library."
repository = "https://github.com/tinrab/bomboni"
Expand Down
23 changes: 21 additions & 2 deletions bomboni_request_derive/src/parse/message/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,14 @@ fn expand_parse_field(field: &ParseField) -> syn::Result<TokenStream> {
"custom `parse_with` and `write_with` functions cannot be used alongside `with`",
));
}
if field.wrapper && field.source_try_from.is_some() {
return Err(syn::Error::new_spanned(
&field.ident,
"wrapper fields cannot be casted",
));
}

let parse_source = if field.with.is_some() || field.parse_with.is_some() {
let mut parse_source = if field.with.is_some() || field.parse_with.is_some() {
let parse_with = if let Some(with) = field.with.as_ref() {
quote! {
#with::parse
Expand Down Expand Up @@ -288,6 +294,13 @@ fn expand_parse_field(field: &ParseField) -> syn::Result<TokenStream> {
quote!()
};

if field.source_try_from.is_some() {
parse_source.extend(quote! {
let target = target.try_into()
.map_err(|_| RequestError::field(#field_name, CommonError::FailedConvertValue))?;
});
}

let mut parse = quote! {
let target = source.#source_ident;
};
Expand Down Expand Up @@ -320,6 +333,7 @@ fn expand_parse_field(field: &ParseField) -> syn::Result<TokenStream> {

// Source field for nested messages is always wrapped in `Option`
let source_option = field.source_option
|| is_option
|| (is_nested
&& (field.with.is_none() && field.parse_with.is_none())
&& !is_vec
Expand Down Expand Up @@ -423,7 +437,12 @@ fn expand_parse_field(field: &ParseField) -> syn::Result<TokenStream> {
}

fn expand_parse_resource_field(field: &ParseField) -> syn::Result<TokenStream> {
if field.source_option || field.enumeration || field.oneof || field.regex.is_some() {
if field.source_option
|| field.enumeration
|| field.oneof
|| field.regex.is_some()
|| field.source_try_from.is_some()
{
return Err(syn::Error::new_spanned(
&field.ident,
"some of these options cannot be used alongside `resource`",
Expand Down
11 changes: 10 additions & 1 deletion bomboni_request_derive/src/parse/message/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ fn expand_write_field(field: &ParseField) -> TokenStream {
..
} = get_proto_type_info(field_type);

let write_target = if field.with.is_some() || field.write_with.is_some() {
let mut write_target = if field.with.is_some() || field.write_with.is_some() {
let write_with = if let Some(with) = field.with.as_ref() {
quote! {
#with::write
Expand Down Expand Up @@ -127,6 +127,14 @@ fn expand_write_field(field: &ParseField) -> TokenStream {
quote!()
};

if let Some(source_try_from) = field.source_try_from.as_ref() {
let err_literal = format!("failed to convert `{source_ident}` to `{source_try_from}`");
write_target.extend(quote! {
let source: #source_try_from = source.try_into()
.expect(#err_literal);
});
}

let mut write = quote! {
let source = value.#target_ident;
};
Expand All @@ -138,6 +146,7 @@ fn expand_write_field(field: &ParseField) -> TokenStream {
};

let source_option = field.source_option
|| is_option
|| (is_nested
&& (field.with.is_none() && field.parse_with.is_none())
&& !is_vec
Expand Down
6 changes: 6 additions & 0 deletions bomboni_request_derive/src/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ pub struct ParseField {
/// True if the source field should be dereferenced from a `Box` type.
#[darling(default)]
pub source_box: bool,
/// Type used to convert from and into the target type.
#[darling(default)]
pub source_try_from: Option<Ident>,
/// Parses enum value from `i32`.
#[darling(default)]
pub enumeration: bool,
Expand Down Expand Up @@ -102,6 +105,9 @@ pub struct ParseVariant {
/// True if the source is an empty unit variant.
#[darling(default)]
pub source_empty: bool,
/// Type used to convert from and into the target type.
#[darling(default)]
pub source_try_from: Option<Ident>,
/// Parses enum value from `i32`.
#[darling(default)]
pub enumeration: bool,
Expand Down
21 changes: 18 additions & 3 deletions bomboni_request_derive/src/parse/oneof/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ fn expand_parse_variant(variant: &ParseVariant) -> syn::Result<TokenStream> {
"custom `parse_with` and `write_with` functions cannot be used alongside `with`",
));
}
if variant.wrapper && variant.source_try_from.is_some() {
return Err(syn::Error::new_spanned(
&variant.ident,
"wrapper variants cannot be casted",
));
}

if variant.fields.len() != 1 {
return Err(syn::Error::new_spanned(
Expand All @@ -180,7 +186,7 @@ fn expand_parse_variant(variant: &ParseVariant) -> syn::Result<TokenStream> {
));
}

let parse_source = if variant.with.is_some() || variant.parse_with.is_some() {
let mut parse_source = if variant.with.is_some() || variant.parse_with.is_some() {
let parse_with = if let Some(with) = variant.with.as_ref() {
quote! {
#with::parse
Expand Down Expand Up @@ -235,6 +241,13 @@ fn expand_parse_variant(variant: &ParseVariant) -> syn::Result<TokenStream> {
quote!()
};

if variant.source_try_from.is_some() {
parse_source.extend(quote! {
let target = target.try_into()
.map_err(|_| RequestError::field(variant_name, CommonError::FailedConvertValue))?;
});
}

let mut parse = quote! {
let target = source;
};
Expand Down Expand Up @@ -266,8 +279,10 @@ fn expand_parse_variant(variant: &ParseVariant) -> syn::Result<TokenStream> {
quote! { Default::default() }
};

let source_option = variant.source_option || is_option;

if is_option {
if variant.source_option {
if source_option {
if is_string {
parse.extend(quote! {
let target = if let Some(target) = target.filter(|target| !target.is_empty()) {
Expand Down Expand Up @@ -323,7 +338,7 @@ fn expand_parse_variant(variant: &ParseVariant) -> syn::Result<TokenStream> {
});
}
} else {
if variant.source_option {
if source_option {
if variant.default.is_some() {
parse.extend(quote! {
let target = target.unwrap_or_else(|| #default_expr);
Expand Down
Loading

0 comments on commit 29f7871

Please sign in to comment.