diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 84f9d8b..6231847 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -21,7 +21,7 @@ jobs: run: cargo fmt -- --check - name: Features subsets run: | - ruby -e "fs=['bigdecimal', 'bigdecimal03', 'chrono', 'rust_decimal', 'time', 'time03', 'frunk']; \ + ruby -e "fs=['bigdecimal', 'bigdecimal03', 'chrono', 'rust_decimal', 'time', 'time03', 'frunk', 'derive']; \ (1..fs.length).each do |n| puts fs.combination(n).to_a.map {|x| x.join(\" \")}.join(\"\n\"); end" \ | while read -r line; do \ echo "$line" && cargo check --quiet --tests --no-default-features --features "flate2/zlib test $line"; \ @@ -30,3 +30,5 @@ jobs: run: cargo build - name: Run tests run: cargo test --features test + - name: Run derive tests + run: (cd derive && cargo test) diff --git a/Cargo.toml b/Cargo.toml index 935f0ba..d46ff13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,6 @@ +[workspace] +members = ["derive"] + [package] authors = ["blackbeam "] name = "mysql_common" @@ -12,9 +15,14 @@ categories = ["database"] # * Invoke `cargo readme > README.md` if relevant! version = "0.29.2" - edition = "2018" -exclude = ["/lib", "/proptest-regressions", "/test-data", "/wrapper.cc", "/wrapper.hh"] +exclude = [ + "/lib", + "/proptest-regressions", + "/test-data", + "/wrapper.cc", + "/wrapper.hh", +] [badges] travis-ci = { repository = "blackbeam/rust_mysql_common" } @@ -42,18 +50,26 @@ sha1 = "0.10.0" sha2 = "0.10.0" smallvec = { version = "1.6.1", features = ["union", "write"] } thiserror = "1.0.24" -time = { version = "0.2", default-features = false, features = ["std"], optional = true } -time03 = { package = "time", version = "0.3", default-features = false, features = ["parsing"], optional = true } -uuid = "1" +time = { version = "0.2", default-features = false, features = [ + "std", +], optional = true } +time03 = { package = "time", version = "0.3", default-features = false, features = [ + "parsing", +], optional = true } +uuid = { version = "1" } saturating = "0.1" -serde = "1" +serde = { version = "1", features = ["derive"] } serde_json = "1" +mysql-common-derive = { path = "derive", optional = true } + [dev-dependencies] proptest = "1.0" [build-dependencies] -bindgen = { version = "0.59.2", default-features = false, features = ["runtime"] } +bindgen = { version = "0.59.2", default-features = false, features = [ + "runtime", +] } cc = "1.0.54" cmake = "0.1.44" subprocess = "0.2.4" @@ -62,12 +78,20 @@ subprocess = "0.2.4" debug = true [features] -default = [ - "flate2/zlib", - "bigdecimal03", - "rust_decimal", +default = ["flate2/zlib", "bigdecimal03", "rust_decimal", "time03", "frunk"] +test = ["derive"] +derive = ["mysql-common-derive"] +nightly = ["test"] + +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +no-default-features = true +features = [ + "time", "time03", - "frunk", + "rust_decimal", + "chrono", + "bigdecimal03", + "bigdecimal", + "derive", ] -test = [] -nightly = ["test"] diff --git a/README.md b/README.md index cc2d04a..faefe4c 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,10 @@ Also crate provides from-row convertion for the following list of types (see `Fr | `time03` | Enables `time` v0.3.x types support | 🟢 | | `uuid` | Enables `Uuid` type support | 🟢 | | `frunk` | Enables `FromRow` for `frunk::Hlist!` types | 🟢 | +| `derive` | Enables [`FromValue` derive macro][2] | 🔴 | [1]: https://dev.mysql.com/doc/internals/en/binary-protocol-value.html +[2]: https://docs.rs/mysql-common-derive ## License diff --git a/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000..22a23ff --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "mysql-common-derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true +bench = false + +[dependencies] +darling = "0.14.1" +heck = "0.4.0" +num-bigint = "0.4.3" +proc-macro-crate = "1.2.1" +proc-macro-error = "1" +proc-macro2 = "1.0.42" +quote = "1.0.9" +syn = { version = "1.0.74", features = ["full"] } +termcolor = "1.1.3" +thiserror = "1" + +[dev-dependencies] +mysql_common = { path = "..", features = ["derive"] } +serde = { version = "1.0.93", features = ["derive"] } +serde_json = "1.0.93" diff --git a/derive/README.md b/derive/README.md new file mode 100644 index 0000000..e69de29 diff --git a/derive/src/error.rs b/derive/src/error.rs new file mode 100644 index 0000000..9d25db8 --- /dev/null +++ b/derive/src/error.rs @@ -0,0 +1,72 @@ +use proc_macro2::Span; +use proc_macro_error::{Diagnostic, Level}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("expected a struct with a single unnamed field")] + NotANewTypeStruct(Span), + #[error("structs with named fields are not supported")] + NamedFieldsNotSupported(Span), + #[error("unit structs are not supported")] + UnitStructsNotSupported(Span), + #[error("structs with unnamed fields are not supported")] + StructsWithUnnamedFieldsNotSupported(Span), + #[error("unions are not supported")] + UnionsNotSupported(Span), + #[error("enums are not supported")] + EnumsNotSupported(Span), + #[error("non-unit variants are not supported")] + NonUnitVariant(Span), + #[error("unsupported discriminant")] + UnsupportedDiscriminant(Span), + #[error("add #[mysql(explicit_invalid)] attribute to allow")] + ExplicitInvalid(Span), + #[error("no suitable crate found, use #[mysql(crate = \"..\")] to specify the crate name")] + NoCrateNameFound, + #[error("multiple crates found, use #[mysql(crate = \"..\")] to specify the particular name")] + MultipleCratesFound, + #[error(transparent)] + Syn(#[from] syn::Error), + #[error(transparent)] + Darling(#[from] darling::error::Error), + #[error("conflicting attributes")] + ConflictingsAttributes(Span, Span), + #[error("representation won't fit into MySql integer")] + UnsupportedRepresentation(Span), + #[error("this attribute requires `{}` attribute", 0)] + AttributeRequired(Span, &'static str), +} + +impl From for Diagnostic { + fn from(x: Error) -> Diagnostic { + match x { + Error::UnionsNotSupported(span) + | Error::EnumsNotSupported(span) + | Error::NonUnitVariant(span) + | Error::UnsupportedDiscriminant(span) + | Error::ExplicitInvalid(span) + | Error::NotANewTypeStruct(span) + | Error::NamedFieldsNotSupported(span) + | Error::UnitStructsNotSupported(span) + | Error::UnsupportedRepresentation(span) + | Error::StructsWithUnnamedFieldsNotSupported(span) => { + Diagnostic::spanned(span, Level::Error, format!("FromValue: {x}")) + } + Error::Syn(ref e) => { + Diagnostic::spanned(e.span(), Level::Error, format!("FromValue: {x}")) + } + Error::Darling(ref e) => { + Diagnostic::spanned(e.span(), Level::Error, format!("FromValue: {x}")) + } + Error::NoCrateNameFound => Diagnostic::new(Level::Error, format!("FromValue: {x}")), + Error::MultipleCratesFound => Diagnostic::new(Level::Error, format!("FromValue: {x}")), + Error::ConflictingsAttributes(s1, s2) => { + Diagnostic::spanned(s1, Level::Error, format!("FromValue: {x}")) + .span_error(s2, "conflicting attribute".into()) + } + Error::AttributeRequired(s, _) => { + Diagnostic::spanned(s, Level::Error, format!("FromValue: {x}")) + } + } + } +} diff --git a/derive/src/from_row/mod.rs b/derive/src/from_row/mod.rs new file mode 100644 index 0000000..3428a1e --- /dev/null +++ b/derive/src/from_row/mod.rs @@ -0,0 +1,16 @@ +use proc_macro2::TokenStream; + +mod structs; + +pub fn impl_from_row(input: &syn::DeriveInput) -> crate::Result { + match input.data { + syn::Data::Struct(ref data_struct) => structs::impl_from_row_for_struct( + &input.attrs, + &input.ident, + &input.generics, + data_struct, + ), + syn::Data::Enum(_) => Err(crate::Error::EnumsNotSupported(input.ident.span())), + syn::Data::Union(_) => Err(crate::Error::UnionsNotSupported(input.ident.span())), + } +} diff --git a/derive/src/from_row/structs/attrs/container.rs b/derive/src/from_row/structs/attrs/container.rs new file mode 100644 index 0000000..05781f2 --- /dev/null +++ b/derive/src/from_row/structs/attrs/container.rs @@ -0,0 +1,18 @@ +use darling::{util::SpannedValue, FromMeta}; + +use crate::from_value::{ + enums::attrs::container::{Crate, RenameAll}, + structs::attrs::container::Bound, +}; + +#[derive(Default, FromMeta)] +pub struct Mysql { + #[darling(default)] + pub crate_name: Crate, + #[darling(default)] + pub rename_all: Option, + #[darling(default)] + pub table_name: Option>, + #[darling(default)] + pub bound: Option, +} diff --git a/derive/src/from_row/structs/attrs/field.rs b/derive/src/from_row/structs/attrs/field.rs new file mode 100644 index 0000000..90f53f2 --- /dev/null +++ b/derive/src/from_row/structs/attrs/field.rs @@ -0,0 +1,9 @@ +use darling::FromMeta; + +#[derive(Debug, Default, FromMeta)] +pub struct Mysql { + #[darling(default)] + pub json: bool, + #[darling(default)] + pub rename: Option, +} diff --git a/derive/src/from_row/structs/attrs/mod.rs b/derive/src/from_row/structs/attrs/mod.rs new file mode 100644 index 0000000..a258593 --- /dev/null +++ b/derive/src/from_row/structs/attrs/mod.rs @@ -0,0 +1,2 @@ +pub mod container; +pub mod field; diff --git a/derive/src/from_row/structs/mod.rs b/derive/src/from_row/structs/mod.rs new file mode 100644 index 0000000..c588dff --- /dev/null +++ b/derive/src/from_row/structs/mod.rs @@ -0,0 +1,303 @@ +use darling::FromMeta; +use proc_macro2::{Span, TokenStream}; +use proc_macro_error::abort; +use quote::{ToTokens, TokenStreamExt}; +use syn::spanned::Spanned; + +use crate::from_value::enums::attrs::container::Crate; + +mod attrs; + +pub fn impl_from_row_for_struct( + attrs: &[syn::Attribute], + ident: &proc_macro2::Ident, + generics: &syn::Generics, + data_struct: &syn::DataStruct, +) -> crate::Result { + let fields = match &data_struct.fields { + syn::Fields::Named(fields) => fields, + syn::Fields::Unnamed(_) => { + return Err(crate::Error::StructsWithUnnamedFieldsNotSupported( + data_struct.struct_token.span, + )) + } + syn::Fields::Unit => { + return Err(crate::Error::UnitStructsNotSupported( + data_struct.struct_token.span, + )) + } + }; + + let meta = attrs + .into_iter() + .filter_map(|attr| attr.parse_meta().ok()) + .collect::>(); + + let item_attrs = meta + .iter() + .find_map(|x| match x { + syn::Meta::List(y) if y.path.is_ident("mysql") => Some(x), + _ => None, + }) + .map(|x| ::from_meta(x)) + .transpose()? + .unwrap_or_default(); + + let derived = GenericStruct { + ident, + fields, + item_attrs, + generics, + }; + Ok(quote::quote! { #derived }) +} + +struct GenericStruct<'a> { + ident: &'a proc_macro2::Ident, + item_attrs: attrs::container::Mysql, + fields: &'a syn::FieldsNamed, + generics: &'a syn::Generics, +} + +impl ToTokens for GenericStruct<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + ident, + item_attrs, + fields, + generics, + } = self; + + let crat = match self.item_attrs.crate_name { + Crate::NotFound => abort!(crate::Error::NoCrateNameFound), + Crate::Multiple => abort!(crate::Error::MultipleCratesFound), + Crate::Itself => syn::Ident::new("crate", Span::call_site()), + Crate::Found(ref name) => syn::Ident::new(name, Span::call_site()), + }; + + let impl_generics = (generics.params.len() > 0).then(|| { + let generics = self.generics.params.iter(); + quote::quote!(< #(#generics,)* >) + }); + + let ident_generics = (generics.params.len() > 0).then(|| { + let generics = self.generics.params.iter().map(|g| match g { + syn::GenericParam::Type(x) => { + let ident = &x.ident; + quote::quote!(#ident) + } + syn::GenericParam::Lifetime(x) => { + let lifetime = &x.lifetime; + quote::quote!(#lifetime) + } + syn::GenericParam::Const(x) => { + let ident = &x.ident; + quote::quote!(#ident) + } + }); + quote::quote!(< #(#generics,)* >) + }); + + let bounds = item_attrs.bound.as_ref().map(|bound| { + let bound = bound.0.iter(); + quote::quote!(where #(#bound,)*) + }); + + let table_name_constant = item_attrs.table_name.as_ref().map(|name| { + let lit = syn::LitStr::new(&*name, name.span()); + quote::quote!(const TABLE_NAME: &'static str = #lit;) + }); + + let fields_attrs = fields + .named + .iter() + .map(|f| { + let meta = f + .attrs + .iter() + .filter_map(|attr| attr.parse_meta().ok()) + .collect::>(); + + Ok(meta + .iter() + .find_map(|x| match x { + syn::Meta::List(y) if y.path.is_ident("mysql") => Some(x), + _ => None, + }) + .map(|x| ::from_meta(x)) + .transpose()? + .unwrap_or_default()) + }) + .collect::, _>>() + .map_err(|e: darling::Error| abort!(crate::Error::from(e))) + .unwrap(); + + let fields_names = fields + .named + .iter() + .zip(&fields_attrs) + .map(|(f, attrs)| { + let mut name = f.ident.as_ref().unwrap().to_string(); + + if let Some(ref r) = item_attrs.rename_all { + name = r.rename(&name); + } + + if let Some(ref n) = attrs.rename { + name = n.clone(); + } + + name + }) + .collect::>(); + + let field_ident = fields + .named + .iter() + .map(|f| f.ident.as_ref().unwrap()) + .collect::>(); + + let filed_name_constant = fields.named.iter().zip(&fields_names).map(|(f, name)| { + let ident = f.ident.as_ref().unwrap(); + let lit = syn::LitStr::new(&*name, f.span()); + let const_name = syn::Ident::new( + &format!("{}_FIELD", heck::AsShoutySnakeCase(ident.to_string())), + f.span(), + ); + + quote::quote!(const #const_name: &'static str = #lit;) + }); + + let take_field = fields + .named + .iter() + .zip(&fields_attrs) + .zip(&fields_names) + .enumerate() + .map(|(i, ((f, attrs), name))| { + let ident = f.ident.as_ref().unwrap(); + let ref ty = f.ty; + let lit = syn::LitStr::new(&name, ident.span()); + + let place = field_ident + .iter() + .zip(&fields_attrs) + .zip(&fields_names) + .take(i) + .map(|((f, attrs), name)| { + let lit = syn::LitStr::new(&name, f.span()); + if attrs.json { + quote::quote!( + row.place(*indexes.get(#lit).unwrap(), #f.rollback()) + ) + } else { + quote::quote!( + row.place(*indexes.get(#lit).unwrap(), #f.into()) + ) + } + }); + + let intermediate_ty = if attrs.json { + quote::quote!(<#crat::Deserialized<#ty> as FromValue>::Intermediate) + } else { + quote::quote!(<#ty as FromValue>::Intermediate) + }; + + quote::quote!( + let #ident = { + let val = match row.take_opt::(#lit) { + Some(Ok(x)) => match <#intermediate_ty as std::convert::TryFrom>::try_from(x) { + Ok(x) => Some(x), + Err(e) => { + row.place(*indexes.get(#lit).unwrap(), e.0); + None + } + }, + Some(_) => unreachable!("unable to convert Value to Value"), + None => None, + }; + + if let Some(val) = val { + val + } else { + #(#place;)* + return Err(FromRowError(row)); + } + } + ) + }) + .collect::>(); + + let set_field = fields + .named + .iter() + .zip(&fields_attrs) + .map(|(f, attrs)| { + let ident = f.ident.as_ref().unwrap(); + let ref ty = f.ty; + if attrs.json { + quote::quote!(#ident: #ident.commit().0) + } else { + quote::quote!(#ident: <<#ty as FromValue>::Intermediate as std::convert::Into<#ty>>::into(#ident)) + } + }) + .collect::>(); + + let new_tokens = quote::quote!( + impl #impl_generics #ident #ident_generics { + #table_name_constant + #(#filed_name_constant)* + } + + impl #impl_generics #crat::prelude::FromRow for #ident #ident_generics + #bounds { + fn from_row_opt( + mut row: #crat::Row, + ) -> std::result::Result + where + Self: Sized, + { + use #crat::prelude::*; + use #crat::Value; + use #crat::FromRowError; + + let columns = row.columns(); + let indexes = columns.iter().enumerate().fold( + std::collections::HashMap::new(), + |mut acc, (i, col)| { + acc.insert(col.name_str(), i); + acc + }, + ); + + #(#take_field;)* + + Ok(Self { + #(#set_field,)* + }) + } + } + ); + + tokens.append_all(new_tokens); + } +} + +#[cfg(test)] +mod tests { + #[test] + fn derive_from_row_named_struct() { + let code = r#" + #[derive(FromRow)] + struct Foo { + id: u64, + #[mysql(rename = "def", json)] + definition: serde_json::Value, + child: Option, + } + "#; + let input = syn::parse_str::(code).unwrap(); + let derived = super::super::impl_from_row(&input).unwrap(); + eprintln!("{}", derived); + } +} diff --git a/derive/src/from_value/enums/attrs/container.rs b/derive/src/from_value/enums/attrs/container.rs new file mode 100644 index 0000000..c03faf5 --- /dev/null +++ b/derive/src/from_value/enums/attrs/container.rs @@ -0,0 +1,202 @@ +use darling::{util::SpannedValue, FromMeta}; +use proc_macro2::{Span, TokenStream}; +use proc_macro_crate::{crate_name, FoundCrate}; +use quote::TokenStreamExt; + +#[derive(Default, FromMeta)] +pub struct Mysql { + #[darling(default)] + pub crate_name: Crate, + #[darling(default)] + pub rename_all: Option, + #[darling(default)] + pub allow_invalid_discriminants: bool, + #[darling(default)] + pub is_integer: SpannedValue, + #[darling(default)] + pub is_string: SpannedValue, +} + +pub enum Crate { + NotFound, + Multiple, + Itself, + Found(String), +} + +impl Default for Crate { + fn default() -> Self { + let mysql = crate_name("mysql"); + let mysql_async = crate_name("mysql_async"); + let mysql_common = crate_name("mysql_common"); + match (mysql, mysql_async, mysql_common) { + (Ok(_), Ok(_), _) => Self::Multiple, + (Ok(FoundCrate::Name(x)), _, _) + | (_, Ok(FoundCrate::Name(x)), _) + | (_, _, Ok(FoundCrate::Name(x))) => Self::Found(x), + (Ok(FoundCrate::Itself), _, _) + | (_, Ok(FoundCrate::Itself), _) + | (_, _, Ok(FoundCrate::Itself)) => Self::Itself, + (Err(_), Err(_), Err(_)) => Self::NotFound, + } + } +} + +impl FromMeta for Crate { + fn from_string(value: &str) -> darling::Result { + Ok(Self::Found(value.into())) + } +} + +pub enum RenameAll { + Lowercase, + Uppercase, + UpperCamelCase, + LowerCamelCase, + SnakeCase, + KebabCase, + ShoutySnakeCase, + ShoutyKebabCase, +} +impl RenameAll { + pub fn rename(&self, name: &str) -> String { + match self { + RenameAll::Lowercase => name.to_lowercase(), + RenameAll::Uppercase => name.to_uppercase(), + RenameAll::UpperCamelCase => heck::AsUpperCamelCase(name).to_string(), + RenameAll::LowerCamelCase => heck::AsLowerCamelCase(name).to_string(), + RenameAll::SnakeCase => heck::AsSnakeCase(name).to_string(), + RenameAll::KebabCase => heck::AsKebabCase(name).to_string(), + RenameAll::ShoutySnakeCase => heck::AsShoutySnakeCase(name).to_string(), + RenameAll::ShoutyKebabCase => heck::AsShoutyKebabCase(name).to_string(), + } + } +} + +impl FromMeta for RenameAll { + fn from_string(value: &str) -> darling::Result { + match value { + "lowercase" => Ok(Self::Lowercase), + "UPPERCASE" => Ok(Self::Uppercase), + "PascalCase" => Ok(Self::UpperCamelCase), + "camelCase" => Ok(Self::LowerCamelCase), + "snake_case" => Ok(Self::SnakeCase), + "kebab-case" => Ok(Self::KebabCase), + "SCREAMING_SNAKE_CASE" => Ok(Self::ShoutySnakeCase), + "SCREAMING-KEBAB-CASE" => Ok(Self::ShoutyKebabCase), + _ => Err(darling::Error::unknown_value(value)), + } + } +} + +#[derive(Default, FromMeta)] +#[darling(allow_unknown_fields)] +pub struct Repr(pub EnumRepr); + +pub enum EnumRepr { + I8(Span), + U8(Span), + I16(Span), + U16(Span), + I32(Span), + U32(Span), + I64(Span), + U64(Span), + I128(Span), + U128(Span), + ISize(Span), + USize(Span), +} + +impl EnumRepr { + const I8_IDENT: &str = "i8"; + const U8_IDENT: &str = "u8"; + const I16_IDENT: &str = "i16"; + const U16_IDENT: &str = "u16"; + const I32_IDENT: &str = "i32"; + const U32_IDENT: &str = "u32"; + const I64_IDENT: &str = "i64"; + const U64_IDENT: &str = "u64"; + const I128_IDENT: &str = "i128"; + const U128_IDENT: &str = "u128"; + const ISIZE_IDENT: &str = "isize"; + const USIZE_IDENT: &str = "usize"; + + pub fn span(&self) -> Span { + match self { + EnumRepr::I8(x) => *x, + EnumRepr::U8(x) => *x, + EnumRepr::I16(x) => *x, + EnumRepr::U16(x) => *x, + EnumRepr::I32(x) => *x, + EnumRepr::U32(x) => *x, + EnumRepr::I64(x) => *x, + EnumRepr::U64(x) => *x, + EnumRepr::I128(x) => *x, + EnumRepr::U128(x) => *x, + EnumRepr::ISize(x) => *x, + EnumRepr::USize(x) => *x, + } + } + + const fn ident(&self) -> &'static str { + match self { + EnumRepr::I8(_) => "i8", + EnumRepr::U8(_) => "u8", + EnumRepr::I16(_) => "i16", + EnumRepr::U16(_) => "u16", + EnumRepr::I32(_) => "i32", + EnumRepr::U32(_) => "u32", + EnumRepr::I64(_) => "i64", + EnumRepr::U64(_) => "u64", + EnumRepr::I128(_) => "i128", + EnumRepr::U128(_) => "u128", + EnumRepr::ISize(_) => "isize", + EnumRepr::USize(_) => "usize", + } + } + + fn from_ident(ident: &syn::Ident) -> Option { + match ident.to_string().as_str() { + Self::I8_IDENT => Some(EnumRepr::I8(ident.span())), + Self::U8_IDENT => Some(EnumRepr::U8(ident.span())), + Self::I16_IDENT => Some(EnumRepr::I16(ident.span())), + Self::U16_IDENT => Some(EnumRepr::U16(ident.span())), + Self::I32_IDENT => Some(EnumRepr::I32(ident.span())), + Self::U32_IDENT => Some(EnumRepr::U32(ident.span())), + Self::I64_IDENT => Some(EnumRepr::I64(ident.span())), + Self::U64_IDENT => Some(EnumRepr::U64(ident.span())), + Self::I128_IDENT => Some(EnumRepr::I128(ident.span())), + Self::U128_IDENT => Some(EnumRepr::U128(ident.span())), + Self::ISIZE_IDENT => Some(EnumRepr::ISize(ident.span())), + Self::USIZE_IDENT => Some(EnumRepr::USize(ident.span())), + _ => None, + } + } +} + +impl quote::ToTokens for EnumRepr { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append(syn::Ident::new(self.ident(), Span::call_site())) + } +} + +impl Default for EnumRepr { + fn default() -> Self { + Self::ISize(Span::call_site()) + } +} + +impl FromMeta for EnumRepr { + fn from_list(items: &[syn::NestedMeta]) -> darling::Result { + Ok(items + .into_iter() + .filter_map(|x| match x { + syn::NestedMeta::Meta(syn::Meta::Path(path)) => Some(path), + _ => None, + }) + .filter_map(|x| x.get_ident()) + .find_map(|x| Self::from_ident(x)) + .unwrap_or_default()) + } +} diff --git a/derive/src/from_value/enums/attrs/mod.rs b/derive/src/from_value/enums/attrs/mod.rs new file mode 100644 index 0000000..e76392a --- /dev/null +++ b/derive/src/from_value/enums/attrs/mod.rs @@ -0,0 +1,2 @@ +pub mod container; +pub mod variant; diff --git a/derive/src/from_value/enums/attrs/variant.rs b/derive/src/from_value/enums/attrs/variant.rs new file mode 100644 index 0000000..d41745a --- /dev/null +++ b/derive/src/from_value/enums/attrs/variant.rs @@ -0,0 +1,11 @@ +use darling::FromMeta; + +#[derive(Default, FromMeta)] +pub struct Mysql { + #[darling(default)] + pub rename: Option, + #[darling(default)] + pub explicit_invalid: bool, + #[darling(default)] + pub allow_invalid_discriminants: bool, +} diff --git a/derive/src/from_value/enums/misc.rs b/derive/src/from_value/enums/misc.rs new file mode 100644 index 0000000..0afebba --- /dev/null +++ b/derive/src/from_value/enums/misc.rs @@ -0,0 +1,17 @@ +use num_bigint::BigInt; +use syn::spanned::Spanned; + +pub fn get_discriminant(def: &syn::Expr) -> Result { + match def { + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Byte(x), + .. + }) => Ok(BigInt::from(x.value())), + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Int(x), + .. + }) => Ok(x.base10_parse().unwrap()), + syn::Expr::Group(syn::ExprGroup { ref expr, .. }) => get_discriminant(expr), + expr => Err(crate::Error::UnsupportedDiscriminant(expr.span())), + } +} diff --git a/derive/src/from_value/enums/mod.rs b/derive/src/from_value/enums/mod.rs new file mode 100644 index 0000000..99bff49 --- /dev/null +++ b/derive/src/from_value/enums/mod.rs @@ -0,0 +1,385 @@ +use std::cmp; + +use attrs::{ + container::{self, EnumRepr}, + variant, +}; +use darling::FromMeta; +use heck::AsSnakeCase; +use num_bigint::BigInt; +use proc_macro2::{Span, TokenStream}; +use proc_macro_error::abort; +use quote::{ToTokens, TokenStreamExt}; +use syn::spanned::Spanned; + +pub mod attrs; +mod misc; + +pub fn impl_from_value_for_enum( + attrs: &[syn::Attribute], + ident: &proc_macro2::Ident, + _generics: &syn::Generics, + data_enum: &syn::DataEnum, +) -> crate::Result { + let meta = attrs + .into_iter() + .filter_map(|attr| attr.parse_meta().ok()) + .collect::>(); + + let repr = meta + .iter() + .find_map(|x| match x { + syn::Meta::List(y) if y.path.is_ident("repr") => Some(x), + _ => None, + }) + .map(|x| ::from_meta(x)) + .transpose()? + .unwrap_or_default(); + + if matches!( + repr.0, + container::EnumRepr::U128(_) | container::EnumRepr::I128(_) + ) { + abort!(crate::Error::UnsupportedRepresentation(repr.0.span())); + } + + let item_attrs = meta + .iter() + .find_map(|x| match x { + syn::Meta::List(y) if y.path.is_ident("mysql") => Some(x), + _ => None, + }) + .map(|x| ::from_meta(x)) + .transpose()? + .unwrap_or_default(); + + if *item_attrs.is_integer && *item_attrs.is_string { + abort!(crate::Error::ConflictingsAttributes( + item_attrs.is_string.span(), + item_attrs.is_integer.span() + )); + } + + let mut variants = Vec::new(); + + let mut max_discriminant = BigInt::default(); + let mut min_discriminant = BigInt::default(); + + let mut next_discriminant = BigInt::default(); + for variant in &data_enum.variants { + if !matches!(variant.fields, syn::Fields::Unit) { + abort!(crate::Error::NonUnitVariant(variant.span())); + } + + let meta = variant + .attrs + .iter() + .filter_map(|attr| attr.parse_meta().ok()) + .collect::>(); + + let mut variant_attrs = meta + .iter() + .find_map(|x| match x { + syn::Meta::List(y) if y.path.is_ident("mysql") => Some(x), + _ => None, + }) + .map(|x| ::from_meta(x)) + .transpose()? + .unwrap_or_default(); + + let discriminant = variant + .discriminant + .as_ref() + .map(|(_, e)| misc::get_discriminant(e)) + .transpose()? + .unwrap_or(next_discriminant); + + if discriminant >= BigInt::from(u64::MAX) { + abort!(crate::Error::UnsupportedRepresentation(variant.span())); + } + + min_discriminant = cmp::min(min_discriminant, discriminant.clone()); + max_discriminant = cmp::max(max_discriminant, discriminant.clone()); + + next_discriminant = &discriminant + BigInt::from(1u8); + + if discriminant < BigInt::default() || discriminant > BigInt::from(u16::MAX) { + if !item_attrs.allow_invalid_discriminants + && !variant_attrs.allow_invalid_discriminants + && !*item_attrs.is_integer + && !*item_attrs.is_string + { + crate::warn::print_warning( + "negative discriminants for MySql enums are discouraging", + format!("#[mysql(allow_invalid_discriminants)]\nenum {} {{", ident), + "use the following annotation to suppress this warning", + ) + .unwrap(); + } + } else if discriminant == BigInt::default() { + if variant_attrs.explicit_invalid { + variant_attrs.rename = Some("".into()); + } else { + abort!(crate::Error::ExplicitInvalid(variant.span())) + } + } else { + variants.push(EnumVariant { + my_attrs: variant_attrs, + ident: variant.ident.clone(), + name: variant.ident.to_string(), + discriminant, + }) + } + } + + if min_discriminant >= BigInt::default() + && max_discriminant <= BigInt::from(u8::MAX) + && !*item_attrs.is_integer + && !*item_attrs.is_string + { + if !matches!(repr.0, EnumRepr::U8(_)) { + crate::warn::print_warning( + "enum representation is suboptimal. Consider the following annotation:", + format!("#[repr(u8)]\nenum {} {{", ident), + "use #[mysql(allow_suboptimal_repr)] to suppress this warning", + ) + .unwrap(); + } + } else if min_discriminant >= BigInt::default() + && max_discriminant <= BigInt::from(u16::MAX) + && !*item_attrs.is_integer + && !*item_attrs.is_string + { + if !matches!(repr.0, EnumRepr::U8(_)) { + crate::warn::print_warning( + "enum representation is suboptimal. Consider the following annotation:", + format!("#[repr(u16)]\nenum {} {{", ident), + "use #[mysql(allow_suboptimal_repr)] to suppress this warning", + ) + .unwrap(); + } + } + + let derived = Enum { + item_attrs, + name: ident.clone(), + variants, + repr: repr.0, + }; + + Ok(quote::quote! { #derived }) +} + +struct Enum { + item_attrs: container::Mysql, + name: proc_macro2::Ident, + variants: Vec, + repr: EnumRepr, +} + +impl ToTokens for Enum { + fn to_tokens(&self, tokens: &mut TokenStream) { + let Self { + item_attrs, + name: container_name, + variants, + repr, + } = self; + + let crat = match item_attrs.crate_name { + container::Crate::NotFound => abort!(crate::Error::NoCrateNameFound), + container::Crate::Multiple => abort!(crate::Error::MultipleCratesFound), + container::Crate::Itself => syn::Ident::new("crate", Span::call_site()), + container::Crate::Found(ref name) => syn::Ident::new(name, Span::call_site()), + }; + + let ir_name = syn::Ident::new(&format!("{container_name}Ir"), Span::call_site()); + let ir_mod_name = syn::Ident::new( + &format!("{}_ir", AsSnakeCase(container_name.to_string())), + Span::call_site(), + ); + + let branches = variants.iter().map( + |EnumVariant { + my_attrs, + name, + ident, + discriminant, + }| { + let mut name = name.clone(); + if let Some(ref rename) = item_attrs.rename_all { + name = rename.rename(&name); + } + if let Some(ref new_name) = my_attrs.rename { + name = new_name.clone(); + } + let s = syn::LitByteStr::new(name.as_bytes(), Span::call_site()); + let n = syn::LitInt::new(&discriminant.to_string(), Span::call_site()); + + if *item_attrs.is_integer { + quote::quote!( + #crat::Value::Int(#n) | #crat::Value::UInt(#n) => { + Ok(#ir_name(Parsed::Ready(#container_name::#ident))) + } + ) + } else if *item_attrs.is_string { + quote::quote!( + Value::Bytes(ref x) if x == #s => { + Ok(#ir_name(Parsed::Parsed(#container_name::#ident, v))) + } + ) + } else { + quote::quote!( + Value::Bytes(ref x) if x == #s => { + Ok(#ir_name(Parsed::Parsed(#container_name::#ident, v))) + } + #crat::Value::Int(#n) | #crat::Value::UInt(#n) => { + Ok(#ir_name(Parsed::Ready(#container_name::#ident))) + } + ) + } + }, + ); + + let to_value = if *item_attrs.is_string { + quote::quote!( + impl From<#container_name> for #crat::Value { + fn from(x: #container_name) -> Self { + #crat::Value::Int(x as #repr as i64) + } + } + ) + } else if *item_attrs.is_integer { + quote::quote!( + impl From<#container_name> for #crat::Value { + fn from(x: #container_name) -> Self { + match i64::try_from(x as #repr) { + Ok(x) => #crat::Value::Int(x), + _ => #crat::Value::UInt(x as #repr as u64), + } + } + } + ) + } else { + quote::quote!( + impl From<#container_name> for #crat::Value { + fn from(x: #container_name) -> Self { + #crat::Value::Int(x as #repr as i64) + } + } + ) + }; + + let new_tokens = quote::quote!( + mod #ir_mod_name { + use std::convert::TryFrom; + use super::#container_name; + use #crat::Value; + use #crat::FromValueError; + + pub struct #ir_name(Parsed); + + enum Parsed { + /// Type instance is ready without parsing. + Ready(#container_name), + /// Type instance is successfully parsed from this value. + Parsed(#container_name, Value), + } + + impl Parsed { + fn commit(self) -> #container_name { + match self { + Parsed::Ready(t) | Parsed::Parsed(t, _) => t, + } + } + + fn rollback(self) -> Value + { + match self { + Parsed::Ready(t) => t.into(), + Parsed::Parsed(_, v) => v, + } + } + } + + impl TryFrom for #ir_name { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { + match v { + #( #branches )* + v => Err(FromValueError(v)), + } + } + } + + impl From<#ir_name> for #container_name { + fn from(value: #ir_name) -> Self { + value.0.commit() + } + } + + impl From<#ir_name> for #crat::Value { + fn from(value: #ir_name) -> Self { + value.0.rollback() + } + } + } + + pub use #ir_mod_name::#ir_name; + + impl #crat::prelude::FromValue for #container_name { + type Intermediate = #ir_name; + } + + #to_value + ); + + tokens.append_all(new_tokens); + } + + fn to_token_stream(&self) -> TokenStream { + let mut tokens = TokenStream::new(); + self.to_tokens(&mut tokens); + tokens + } + + fn into_token_stream(self) -> TokenStream + where + Self: Sized, + { + self.to_token_stream() + } +} + +struct EnumVariant { + pub my_attrs: variant::Mysql, + pub name: String, + pub ident: syn::Ident, + pub discriminant: BigInt, +} + +#[cfg(test)] +mod tests { + #[test] + fn derive_enum() { + let code = r#" + #[derive(FromValue)] + #[mysql(rename_all = "kebab-case", crate_name = "mysql")] + #[repr(u32)] + enum Size { + #[mysql(explicit_invalid)] + Invalid, + XSmall = 1, + Small, + Medium, + Large, + #[mysql(rename = "XL")] + XLarge, + } + "#; + let input = syn::parse_str::(code).unwrap(); + let derived = super::super::impl_from_value(&input).unwrap(); + eprintln!("{}", derived); + } +} diff --git a/derive/src/from_value/mod.rs b/derive/src/from_value/mod.rs new file mode 100644 index 0000000..08b9bf1 --- /dev/null +++ b/derive/src/from_value/mod.rs @@ -0,0 +1,30 @@ +use proc_macro2::TokenStream; + +pub mod enums; +pub mod structs; + +pub fn impl_from_value(input: &syn::DeriveInput) -> crate::Result { + match input.data { + syn::Data::Struct(ref data_struct) => structs::impl_from_value_for_struct( + &input.attrs, + &input.ident, + &input.generics, + data_struct, + ), + syn::Data::Enum(ref data_enum) => { + enums::impl_from_value_for_enum(&input.attrs, &input.ident, &input.generics, data_enum) + } + syn::Data::Union(ref data_union) => { + impl_from_value_for_union(&input.attrs, &input.ident, &input.generics, data_union) + } + } +} + +fn impl_from_value_for_union( + _attrs: &[syn::Attribute], + _ident: &proc_macro2::Ident, + _generics: &syn::Generics, + _data_union: &syn::DataUnion, +) -> crate::Result { + Err(crate::Error::UnionsNotSupported(_ident.span())) +} diff --git a/derive/src/from_value/structs/attrs/container.rs b/derive/src/from_value/structs/attrs/container.rs new file mode 100644 index 0000000..1baa125 --- /dev/null +++ b/derive/src/from_value/structs/attrs/container.rs @@ -0,0 +1,57 @@ +use std::str::FromStr; + +use darling::{util::SpannedValue, FromMeta}; +use proc_macro::LexError; +use syn::{parse::Parse, punctuated::Punctuated}; + +use crate::from_value::enums::attrs::container::Crate; + +#[derive(Default, FromMeta)] +pub struct Mysql { + #[darling(default)] + pub crate_name: Crate, + #[darling(default)] + pub bound: Option, + #[darling(default)] + pub deserialize_with: Option>, + #[darling(default)] + pub serialize_with: Option>, +} + +#[derive(Debug)] +pub struct FnPath(pub syn::Path); + +impl Parse for FnPath { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + syn::Path::parse(input).map(Self) + } +} + +impl FromMeta for FnPath { + fn from_string(value: &str) -> darling::Result { + syn::parse::( + FromStr::from_str(value) + .map_err(|e: LexError| darling::Error::unsupported_format(&e.to_string()))?, + ) + .map_err(|e| darling::Error::unsupported_format(&e.to_string())) + .map(Self) + } +} + +pub struct Bound(pub Punctuated); + +impl Parse for Bound { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + Punctuated::::parse_terminated(input).map(Self) + } +} + +impl FromMeta for Bound { + fn from_string(value: &str) -> darling::Result { + syn::parse::( + FromStr::from_str(value) + .map_err(|e: LexError| darling::Error::unsupported_format(&e.to_string()))?, + ) + .map_err(|e| darling::Error::unsupported_format(&e.to_string())) + } +} diff --git a/derive/src/from_value/structs/attrs/mod.rs b/derive/src/from_value/structs/attrs/mod.rs new file mode 100644 index 0000000..18581c4 --- /dev/null +++ b/derive/src/from_value/structs/attrs/mod.rs @@ -0,0 +1 @@ +pub mod container; diff --git a/derive/src/from_value/structs/mod.rs b/derive/src/from_value/structs/mod.rs new file mode 100644 index 0000000..b0bc8b9 --- /dev/null +++ b/derive/src/from_value/structs/mod.rs @@ -0,0 +1,372 @@ +use darling::FromMeta; +use heck::AsSnakeCase; +use proc_macro2::{Span, TokenStream}; +use proc_macro_error::abort; +use quote::{ToTokens, TokenStreamExt}; + +use super::enums::attrs::container::Crate; + +pub mod attrs; + +pub fn impl_from_value_for_struct( + attrs: &[syn::Attribute], + ident: &proc_macro2::Ident, + generics: &syn::Generics, + data_struct: &syn::DataStruct, +) -> crate::Result { + let fields = match &data_struct.fields { + syn::Fields::Named(_) => { + return Err(crate::Error::NamedFieldsNotSupported( + data_struct.struct_token.span, + )) + } + syn::Fields::Unnamed(fields) => { + if fields.unnamed.len() != 1 { + return Err(crate::Error::NotANewTypeStruct( + data_struct.struct_token.span, + )); + } else { + fields + } + } + syn::Fields::Unit => { + return Err(crate::Error::UnitStructsNotSupported( + data_struct.struct_token.span, + )) + } + }; + + let meta = attrs + .into_iter() + .filter_map(|attr| attr.parse_meta().ok()) + .collect::>(); + + let item_attrs = meta + .iter() + .find_map(|x| match x { + syn::Meta::List(y) if y.path.is_ident("mysql") => Some(x), + _ => None, + }) + .map(|x| ::from_meta(x)) + .transpose()? + .unwrap_or_default(); + + if let Some(ref x) = item_attrs.serialize_with { + if item_attrs.deserialize_with.is_none() { + abort!(crate::Error::AttributeRequired( + x.span(), + "deserialize_with" + )) + } + } + + if generics.params.is_empty() { + let derived = NewTypeNoGenerics { + ident, + field: fields.unnamed.first().unwrap(), + item_attrs, + }; + Ok(quote::quote! { #derived }) + } else { + let derived = NewType { + ident, + field: fields.unnamed.first().unwrap(), + item_attrs, + generics, + }; + Ok(quote::quote! { #derived }) + } +} + +struct NewTypeNoGenerics<'a> { + ident: &'a proc_macro2::Ident, + item_attrs: attrs::container::Mysql, + field: &'a syn::Field, +} + +impl ToTokens for NewTypeNoGenerics<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let container_name = self.ident; + let field_type = &self.field.ty; + + let crat = match self.item_attrs.crate_name { + Crate::NotFound => abort!(crate::Error::NoCrateNameFound), + Crate::Multiple => abort!(crate::Error::MultipleCratesFound), + Crate::Itself => syn::Ident::new("crate", Span::call_site()), + Crate::Found(ref name) => syn::Ident::new(name, Span::call_site()), + }; + + let ir_name = syn::Ident::new(&format!("{container_name}Ir"), Span::call_site()); + let ir_mod_name = syn::Ident::new( + &format!("{}_ir", AsSnakeCase(container_name.to_string())), + Span::call_site(), + ); + + let serialize_with = match self.item_attrs.serialize_with { + Some(ref x) => { + let path = &x.0; + Some(quote::quote!( + impl From<#ir_name> for #crat::Value + { + fn from(x: #ir_name) -> Self { + #path(x.0) + } + } + )) + } + None => None, + }; + + let new_tokens = if let Some(x) = &self.item_attrs.deserialize_with { + let path = &x.0; + quote::quote!( + mod #ir_mod_name { + use super::*; + pub struct #ir_name(pub #field_type); + + impl std::convert::TryFrom<#crat::Value> for #ir_name { + type Error = #crat::FromValueError; + + fn try_from(v: #crat::Value) -> Result { + #path(v).map(Self) + } + } + + impl std::convert::From<#ir_name> for #container_name { + fn from(x: #ir_name) -> #container_name { + #container_name(x.0) + } + } + + #serialize_with + } + + pub use #ir_mod_name::#ir_name; + + impl #crat::prelude::FromValue for #container_name { + type Intermediate = #ir_name; + } + ) + } else { + quote::quote!( + mod #ir_mod_name { + use super::#container_name; + use #crat::prelude::FromValue; + use #crat::Value; + use std::convert::TryFrom; + + #[derive(Debug, Clone, Copy, Eq, PartialEq)] + pub struct #ir_name(T::Intermediate); + + impl TryFrom for #ir_name<#field_type> { + type Error = <<#field_type as FromValue>::Intermediate as TryFrom>::Error; + + fn try_from(value: Value) -> Result { + <#field_type as FromValue>::Intermediate::try_from(value).map(Self) + } + } + + impl From<#ir_name<#field_type>> for #container_name { + fn from(ir: #ir_name<#field_type>) -> Self { + Self(ir.0.into()) + } + } + + impl From<#ir_name<#field_type>> for Value + where + <#field_type as FromValue>::Intermediate: Into, + { + fn from(ir: #ir_name<#field_type>) -> Self { + ir.0.into() + } + } + } + + pub use #ir_mod_name::#ir_name; + + impl #crat::prelude::FromValue for #container_name { + type Intermediate = #ir_name<#field_type>; + } + ) + }; + + tokens.append_all(new_tokens); + } +} + +struct NewType<'a> { + ident: &'a proc_macro2::Ident, + item_attrs: attrs::container::Mysql, + field: &'a syn::Field, + generics: &'a syn::Generics, +} + +impl ToTokens for NewType<'_> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let container_name = self.ident; + let field_type = &self.field.ty; + let generics = self.generics.params.iter(); + + let crat = match self.item_attrs.crate_name { + Crate::NotFound => abort!(crate::Error::NoCrateNameFound), + Crate::Multiple => abort!(crate::Error::MultipleCratesFound), + Crate::Itself => syn::Ident::new("crate", Span::call_site()), + Crate::Found(ref name) => syn::Ident::new(name, Span::call_site()), + }; + + let ir_name = syn::Ident::new(&format!("{container_name}Ir"), Span::call_site()); + let ir_mod_name = syn::Ident::new( + &format!("{}_ir", AsSnakeCase(container_name.to_string())), + Span::call_site(), + ); + + let impl_generics = (generics.len() > 0).then(|| { + let generics = self.generics.params.iter(); + quote::quote!(#(#generics,)*) + }); + let ident_generics = (generics.len() > 0).then(|| { + let generics = self.generics.params.iter().map(|g| match g { + syn::GenericParam::Type(x) => { + let ident = &x.ident; + quote::quote!(#ident) + } + syn::GenericParam::Lifetime(x) => { + let lifetime = &x.lifetime; + quote::quote!(#lifetime) + } + syn::GenericParam::Const(x) => { + let ident = &x.ident; + quote::quote!(#ident) + } + }); + quote::quote!(#(#generics,)*) + }); + + let additional_bounds = { + let additional_bounds = self.item_attrs.bound.iter().map(|x| x.0.iter()).flatten(); + quote::quote!(#(#additional_bounds,)*) + }; + + let from_value_bound = quote::quote!(#field_type: #crat::prelude::FromValue,); + let into_value_bound = quote::quote!(<#field_type as #crat::prelude::FromValue>::Intermediate: Into<#crat::Value>,); + + let serialize_with = match self.item_attrs.serialize_with { + Some(ref x) => { + let path = &x.0; + Some(quote::quote!( + impl<#impl_generics> From<#ir_name<#ident_generics>> for #crat::Value + where + #additional_bounds + { + fn from(x: #ir_name<#ident_generics>) -> Self { + #path(x.0) + } + } + )) + } + None => None, + }; + + let new_tokens = if let Some(x) = &self.item_attrs.deserialize_with { + let path = &x.0; + quote::quote!( + mod #ir_mod_name { + use super::*; + pub struct #ir_name<#ident_generics>(pub #field_type); + + impl<#impl_generics> std::convert::TryFrom<#crat::Value> for #ir_name<#ident_generics> + where #additional_bounds { + type Error = #crat::FromValueError; + + fn try_from(v: #crat::Value) -> Result { + #path(v) + } + } + + #serialize_with + } + + pub use #ir_mod_name::#ir_name; + + impl <#impl_generics> #crat::prelude::FromValue for #container_name <#ident_generics> + where #additional_bounds { + type Intermediate = #ir_name<#ident_generics>; + } + ) + } else { + quote::quote!( + mod #ir_mod_name { + use #crat::prelude::FromValue; + + pub struct #ir_name(pub T::Intermediate); + } + + pub use #ir_mod_name::#ir_name; + + impl<#impl_generics> std::convert::TryFrom<#crat::Value> for #ir_name<#field_type> + where + #additional_bounds + #from_value_bound + { + type Error = <<#field_type as #crat::prelude::FromValue>::Intermediate as std::convert::TryFrom<#crat::Value>>::Error; + + fn try_from(value: #crat::Value) -> Result { + <#field_type as #crat::prelude::FromValue>::Intermediate::try_from(value).map(Self) + } + } + + impl<#impl_generics> From<#ir_name<#field_type>> for #container_name<#ident_generics> + where + #additional_bounds + #from_value_bound + { + fn from(ir: #ir_name<#field_type>) -> Self { + Self(ir.0.into()) + } + } + + + impl<#impl_generics> From<#ir_name<#field_type>> for #crat::Value + where + #additional_bounds + #from_value_bound + #into_value_bound + { + fn from(ir: #ir_name<#field_type>) -> Self { + ir.0.into() + } + } + + impl <#impl_generics> #crat::prelude::FromValue for #container_name <#ident_generics> + where + #additional_bounds + #from_value_bound { + type Intermediate = #ir_name<#field_type>; + } + ) + }; + tokens.append_all(new_tokens); + } +} + +#[cfg(test)] +mod tests { + #[test] + fn derive_struct() { + let code = r#" + #[derive(FromValue)] + struct A(i32); + "#; + let input = syn::parse_str::(code).unwrap(); + let derived = super::super::impl_from_value(&input).unwrap(); + eprintln!("{}", derived); + + let code = r#" + #[derive(FromValue)] + struct A(T); + "#; + let input = syn::parse_str::(code).unwrap(); + let derived = super::super::impl_from_value(&input).unwrap(); + eprintln!("{}", derived); + } +} diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000..0645328 --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,337 @@ +extern crate proc_macro; + +use proc_macro_error::abort; + +use crate::error::Error; +type Result = std::result::Result; + +mod error; +mod warn; + +mod from_row; +mod from_value; + +/// Derives `FromValue`. +/// +/// Supported derivations: +/// +/// * for enum – you should carefully read the corresponding section of MySql documentation +/// * for newtypes (see [New Type Idiom][1]) – given that the wrapped type itself satisfies +/// `FromValue` +/// +/// ## Enums +/// +/// ### Container attributes: +/// +/// * `#[mysql(crate_name = "some_name")]` – overrides an attemt to guess a crate that provides +/// required traits +/// * `#[mysql(rename_all = ...)]` – rename all the variants according to the given case +/// convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", +/// "snake_case", "SCREAMING_SNAKE_CASE", "kebab-case", "SCREAMING-KEBAB-CASE" +/// * `#[mysql(is_integer)]` – tells derive macro that the value is an integer rather than MySql +/// ENUM. Macro won't warn if variants are sparse or greater than u16 and will not try to parse +/// textual representation. +/// * `#[mysql(is_string)]` – tells derive macro that the value is a string rather than MySql +/// ENUM. Macro won't warn if variants are sparse or greater than u16 and will not try to parse +/// +/// ### Example +/// +/// Given `ENUM('x-small', 'small', 'medium', 'large', 'x-large')` on MySql side: +/// +/// ```no_run +/// # use mysql_common_derive::FromValue; +/// # use mysql_common::{row::Row, row::convert::from_row}; +/// #[derive(FromValue)] +/// #[mysql(rename_all = "kebab-case")] +/// #[repr(u8)] +/// enum Size { +/// XSmall = 1, +/// Small, +/// Medium, +/// Large, +/// XLarge, +/// } +/// +/// fn assert_from_row_works(x: Row) -> Size { +/// from_row(x) +/// } +/// +/// # fn main() {} +/// ``` +/// +/// ## Newtypes +/// +/// It is expected, that wrapper value satisfies `FromValue` or `deserialize_with` is given. +/// Also note, that to support `FromRow` the wrapped value must satisfy `Into` or +/// `serialize_with` must be given. +/// +/// ### Container attributes: +/// +/// * `#[mysql(crate_name = "some_name")]` – overrides an attemt to guess a crate to import types from +/// * `#[mysql(bound = "Foo: Bar, Baz: Quux")]` – use the following additional bounds +/// * `#[mysql(deserialize_with = "some::path")]` – use the following function to deserialize +/// the wrapped value. Expected signature is `fn (Value) -> Result`. +/// * `#[mysql(serialize_with = "some::path")]` – use the following function to serialize +/// the wrapped value. Expected signature is `fn (Wrapped) -> Value`. +/// +/// ### Example +/// +/// ```no_run +/// # use mysql_common::{row::Row, row::convert::from_row, prelude::FromValue, value::Value, value::convert::{from_value, FromValueError}}; +/// +/// /// Dummy complex type with additional bounds on FromValue impl. +/// struct ComplexTypeToWrap<'a, 'b, const N: usize, T, U, V>([(&'a T, &'b U, V); N]); +/// +/// struct FakeIr; +/// +/// impl TryFrom for FakeIr { +/// // ... +/// # type Error = FromValueError; +/// # fn try_from(v: Value) -> Result { +/// # unimplemented!(); +/// # } +/// } +/// +/// impl<'a, 'b: 'a, const N: usize, T: 'a, U: From, V: From> From for ComplexTypeToWrap<'a, 'b, N, T, U, V> { +/// // ... +/// # fn from(x: FakeIr) -> Self { +/// # unimplemented!(); +/// # } +/// } +/// +/// impl From for Value { +/// // ... +/// # fn from(x: FakeIr) -> Self { +/// # unimplemented!(); +/// # } +/// } +/// +/// impl<'a, 'b: 'a, const N: usize, T: 'a, U: From, V: From> FromValue for ComplexTypeToWrap<'a, 'b, N, T, U, V> { +/// type Intermediate = FakeIr; +/// } +/// +/// fn neg_de(v: Value) -> Result { +/// match v { +/// Value::Int(x) => Ok(-x), +/// Value::UInt(x) => Ok(-(x as i64)), +/// x => Err(FromValueError(x)), +/// } +/// } +/// +/// fn neg_ser(x: i64) -> Value { +/// Value::Int(-x) +/// } +/// +/// #[derive(FromValue)] +/// #[mysql(deserialize_with = "neg_de", serialize_with = "neg_ser")] +/// struct Neg(i64); +/// +/// #[derive(FromValue)] +/// struct Inch(i32); +/// +/// #[derive(FromValue)] +/// struct Foo(Option); +/// +/// #[derive(FromValue)] +/// #[mysql(bound = "'b: 'a, T: 'a, U: From, V: From")] +/// struct Bar<'a, 'b, const N: usize, T, U, V>(ComplexTypeToWrap<'a, 'b, N, T, U, V>); +/// +/// fn assert_from_row_works<'a, 'b, const N: usize, T, U, V>(x: Row) -> (Inch, Neg, Foo, Bar<'a, 'b, N, T, U, V>) +/// where 'b: 'a, T: 'a, U: From, V: From, +/// { +/// from_row(x) +/// } +/// +/// # fn main() {} +/// ``` +/// +/// [1]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html +#[proc_macro_derive(FromValue, attributes(mysql))] +#[proc_macro_error::proc_macro_error] +pub fn from_value(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input: syn::DeriveInput = syn::parse(input).unwrap(); + match from_value::impl_from_value(&input) { + Ok(gen) => gen.into(), + Err(e) => abort!(e), + } +} + +/// Derives `FromRow`. +/// +/// Also defines some constants on the struct: +/// +/// * `const TABLE_NAME: &str` – if `table_name` is given +/// * `const {}_FIELD: &str` – for each struct field (`{}` is a SCREAMING_SNAKE_CASE representation +/// of a field name (not a column name)) +/// +/// Supported derivations: +/// +/// * for a struct with named fields – field name will be used as a column name to search for a value +/// +/// ### Container attributes: +/// +/// * `#[mysql(crate_name = "some_name")]` – overrides an attemt to guess a crate that provides +/// required traits +/// * `#[mysql(rename_all = ...)]` – rename all column names according to the given case +/// convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase", +/// "snake_case", "SCREAMING_SNAKE_CASE", "kebab-case", "SCREAMING-KEBAB-CASE" +/// * `#[mysql(table_name = "some_name")]` – defines `pub const TABLE_NAME: &str` on the struct +/// +/// ### Field attributes: +/// +/// * `#[mysql(rename = "some_name")]` – overrides column name of a field +/// * `#[mysql(json)]` - column will be interpreted as a JSON string containing +/// a value of a field type +/// +/// ### Example +/// +/// ``` +/// # use mysql_common_derive::FromRow; +/// # use mysql_common::{ +/// # constants::ColumnType, +/// # packets::Column, +/// # row::{Row, new_row}, +/// # row::convert::from_row, +/// # value::Value, +/// # }; +/// #[derive(Debug, PartialEq, Eq, FromRow)] +/// #[mysql(table_name = "Foos")] +/// struct Foo { +/// id: u64, +/// #[mysql(json, rename = "def")] +/// definition: Bar, +/// child: Option, +/// } +/// +/// #[derive(Debug, serde::Deserialize, PartialEq, Eq)] +/// enum Bar { +/// Left, +/// Right, +/// } +/// +/// /// Returns the following row: +/// /// +/// /// ``` +/// /// +----+-----------+-------+ +/// /// | id | def | child | +/// /// +----+-----------+-------+ +/// /// | 42 | '"Right"' | NULL | +/// /// +----+-----------+-------+ +/// /// ``` +/// fn get_row() -> Row { +/// // ... +/// # let values = vec![Value::Int(42), Value::Bytes(b"\"Right\"".as_slice().into()), Value::NULL]; +/// # let columns = vec![ +/// # Column::new(ColumnType::MYSQL_TYPE_LONG).with_name(b"id"), +/// # Column::new(ColumnType::MYSQL_TYPE_BLOB).with_name(b"def"), +/// # Column::new(ColumnType::MYSQL_TYPE_NULL).with_name(b"child"), +/// # ]; +/// # new_row(values, columns.into_boxed_slice().into()) +/// } +/// +/// # fn main() { +/// let foo = from_row::(get_row()); +/// assert_eq!(foo, Foo { id: 42, definition: Bar::Right, child: None }); +/// assert_eq!(Foo::TABLE_NAME, "Foos"); +/// assert_eq!(Foo::ID_FIELD, "id"); +/// assert_eq!(Foo::DEFINITION_FIELD, "def"); +/// assert_eq!(Foo::CHILD_FIELD, "child"); +/// # } +/// ``` +/// +/// ## Newtypes +/// +/// It is expected, that wrapper value satisfies `FromValue` or `deserialize_with` is given. +/// Also note, that to support `FromRow` the wrapped value must satisfy `Into` or +/// `serialize_with` must be given. +/// +/// ### Container attributes: +/// +/// * `#[mysql(crate_name = "some_name")]` – overrides an attemt to guess a crate to import types from +/// * `#[mysql(bound = "Foo: Bar, Baz: Quux")]` – use the following additional bounds +/// * `#[mysql(deserialize_with = "some::path")]` – use the following function to deserialize +/// the wrapped value. Expected signature is `fn (Value) -> Result`. +/// * `#[mysql(serialize_with = "some::path")]` – use the following function to serialize +/// the wrapped value. Expected signature is `fn (Wrapped) -> Value`. +/// +/// ### Example +/// +/// ```no_run +/// # use mysql_common::{row::Row, row::convert::from_row, prelude::FromValue, value::Value, value::convert::{from_value, FromValueError}}; +/// +/// /// Dummy complex type with additional bounds on FromValue impl. +/// struct ComplexTypeToWrap<'a, 'b, const N: usize, T, U, V>([(&'a T, &'b U, V); N]); +/// +/// struct FakeIr; +/// +/// impl TryFrom for FakeIr { +/// // ... +/// # type Error = FromValueError; +/// # fn try_from(v: Value) -> Result { +/// # unimplemented!(); +/// # } +/// } +/// +/// impl<'a, 'b: 'a, const N: usize, T: 'a, U: From, V: From> From for ComplexTypeToWrap<'a, 'b, N, T, U, V> { +/// // ... +/// # fn from(x: FakeIr) -> Self { +/// # unimplemented!(); +/// # } +/// } +/// +/// impl From for Value { +/// // ... +/// # fn from(x: FakeIr) -> Self { +/// # unimplemented!(); +/// # } +/// } +/// +/// impl<'a, 'b: 'a, const N: usize, T: 'a, U: From, V: From> FromValue for ComplexTypeToWrap<'a, 'b, N, T, U, V> { +/// type Intermediate = FakeIr; +/// } +/// +/// fn neg_de(v: Value) -> Result { +/// match v { +/// Value::Int(x) => Ok(-x), +/// Value::UInt(x) => Ok(-(x as i64)), +/// x => Err(FromValueError(x)), +/// } +/// } +/// +/// fn neg_ser(x: i64) -> Value { +/// Value::Int(-x) +/// } +/// +/// #[derive(FromValue)] +/// #[mysql(deserialize_with = "neg_de", serialize_with = "neg_ser")] +/// struct Neg(i64); +/// +/// #[derive(FromValue)] +/// struct Inch(i32); +/// +/// #[derive(FromValue)] +/// struct Foo(Option); +/// +/// #[derive(FromValue)] +/// #[mysql(bound = "'b: 'a, T: 'a, U: From, V: From")] +/// struct Bar<'a, 'b, const N: usize, T, U, V>(ComplexTypeToWrap<'a, 'b, N, T, U, V>); +/// +/// fn assert_from_row_works<'a, 'b, const N: usize, T, U, V>(x: Row) -> (Inch, Neg, Foo, Bar<'a, 'b, N, T, U, V>) +/// where 'b: 'a, T: 'a, U: From, V: From, +/// { +/// from_row(x) +/// } +/// +/// # fn main() {} +/// ``` +/// +/// [1]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html +#[proc_macro_derive(FromRow, attributes(mysql))] +#[proc_macro_error::proc_macro_error] +pub fn from_row(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input: syn::DeriveInput = syn::parse(input).unwrap(); + match from_row::impl_from_row(&input) { + Ok(gen) => gen.into(), + Err(e) => abort!(e), + } +} diff --git a/derive/src/warn.rs b/derive/src/warn.rs new file mode 100644 index 0000000..8456d64 --- /dev/null +++ b/derive/src/warn.rs @@ -0,0 +1,54 @@ +// Shamelessly stolen from Aleph-Alpha/ts-rs. +// MIT License Copyright (c) 2020 Aleph Alpha GmbH + +use std::{fmt::Display, io::Write}; + +use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; + +// Sadly, it is impossible to raise a warning in a proc macro. +// This function prints a message which looks like a compiler warning. +pub fn print_warning( + title: impl Display, + content: impl Display, + note: impl Display, +) -> std::io::Result<()> { + let make_color = |color: Color, bold: bool| { + let mut spec = ColorSpec::new(); + spec.set_fg(Some(color)).set_bold(bold).set_intense(true); + spec + }; + + let yellow_bold = make_color(Color::Yellow, true); + let white_bold = make_color(Color::White, true); + let white = make_color(Color::White, false); + let blue = make_color(Color::Blue, true); + + let writer = BufferWriter::stderr(ColorChoice::Auto); + let mut buffer = writer.buffer(); + + buffer.set_color(&yellow_bold)?; + write!(&mut buffer, "warning")?; + buffer.set_color(&white_bold)?; + writeln!(&mut buffer, ": {}", title)?; + + buffer.set_color(&blue)?; + writeln!(&mut buffer, " | ")?; + + let content = format!("{content}"); + for line in content.split("\n") { + write!(&mut buffer, " | ")?; + buffer.set_color(&white)?; + writeln!(&mut buffer, "{}", line)?; + } + + buffer.set_color(&blue)?; + writeln!(&mut buffer, " | ")?; + + write!(&mut buffer, " = ")?; + buffer.set_color(&white_bold)?; + write!(&mut buffer, "note: ")?; + buffer.set_color(&white)?; + writeln!(&mut buffer, "{}", note)?; + + writer.print(&buffer) +} diff --git a/src/lib.rs b/src/lib.rs index 05c2792..a2be0bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,9 +76,12 @@ //! | `time03` | Enables `time` v0.3.x types support | 🟢 | //! | `uuid` | Enables `Uuid` type support | 🟢 | //! | `frunk` | Enables `FromRow` for `frunk::Hlist!` types | 🟢 | +//1 | `derive` | Enables [`FromValue` derive macro][2] | 🔴 | //! //! [1]: https://dev.mysql.com/doc/internals/en/binary-protocol-value.html -#![cfg_attr(feature = "nightly", feature(test, const_fn))] +//! [2]: https://docs.rs/mysql-common-derive +#![cfg_attr(feature = "nightly", feature(test))] +#![cfg_attr(docsrs, feature(doc_cfg))] // The `test` feature is required to compile tests. // It'll bind test binaries to an official C++ impl of MySql decimals (see build.rs) @@ -116,10 +119,36 @@ pub use time03; #[cfg(feature = "uuid")] pub use uuid; +#[cfg(feature = "derive")] +#[allow(unused_imports)] +#[macro_use] +extern crate mysql_common_derive; + pub use num_bigint; pub use serde; pub use serde_json; +pub use value::convert::FromValueError; +pub use value::Value; + +pub use row::convert::FromRowError; +pub use row::Row; + +pub use value::json::{Deserialized, Serialized}; + +pub mod prelude { + #[cfg(feature = "derive")] + #[doc(inline)] + pub use mysql_common_derive::FromValue; + + #[cfg(feature = "derive")] + #[doc(inline)] + pub use mysql_common_derive::FromRow; + + pub use crate::row::{convert::FromRow, ColumnIndex}; + pub use crate::value::convert::{FromValue, ToValue}; +} + /// This macro is a convenient way to pass named parameters to a statement. /// /// ```ignore diff --git a/src/packets/mod.rs b/src/packets/mod.rs index c8ee34e..6f576c2 100644 --- a/src/packets/mod.rs +++ b/src/packets/mod.rs @@ -1941,7 +1941,7 @@ impl StmtPacket { /// Null-bitmap. /// -/// http://dev.mysql.com/doc/internals/en/null-bitmap.html +/// #[derive(Debug, Clone, Eq, PartialEq)] pub struct NullBitmap = Vec>(U, PhantomData); diff --git a/src/params.rs b/src/params.rs index c3ed25c..3c04f16 100644 --- a/src/params.rs +++ b/src/params.rs @@ -38,13 +38,29 @@ impl Error for MissingNamedParameterError { } /// Representations of parameters of a prepared statement. -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, PartialEq)] pub enum Params { Empty, Named(HashMap, Value>), Positional(Vec), } +impl fmt::Debug for Params { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Empty => write!(f, "Empty"), + Self::Named(arg0) => { + let arg0 = arg0 + .iter() + .map(|(k, v)| (String::from_utf8_lossy(k), v)) + .collect::>(); + f.debug_tuple("Named").field(&arg0).finish() + } + Self::Positional(arg0) => f.debug_tuple("Positional").field(arg0).finish(), + } + } +} + impl Params { /// Will convert named parameters into positional assuming order passed in `named_params` /// attribute. diff --git a/src/row/convert/frunk.rs b/src/row/convert/frunk.rs index ad4d907..2f2a226 100644 --- a/src/row/convert/frunk.rs +++ b/src/row/convert/frunk.rs @@ -17,7 +17,7 @@ use super::{FromRow, FromRowError}; use crate::{ row::{new_row_raw, Row}, value::{ - convert::{ConvIr, FromValue, FromValueError}, + convert::{FromValue, FromValueError}, Value, }, }; @@ -37,6 +37,7 @@ impl FromRow for HNil { impl FromRow for HCons where H: FromValue, + H::Intermediate: Into, T: FromRow + sealed::HlistFromRow, T: HList, { @@ -76,6 +77,7 @@ mod sealed { impl HlistFromRow for HCons where H: FromValue, + H::Intermediate: Into, T: HlistFromRow, T: HList, { @@ -102,12 +104,12 @@ mod sealed { let tail = match T::hlist_from_row_opt(values) { Ok(t) => t, Err(mut values) => { - values.insert(0, Some(ir.rollback())); + values.insert(0, Some(Into::::into(ir))); return Err(values); } }; - Ok(h_cons(ir.commit(), tail)) + Ok(h_cons(Into::::into(ir), tail)) } } } diff --git a/src/row/convert/mod.rs b/src/row/convert/mod.rs index a1fa993..4aaa2e0 100644 --- a/src/row/convert/mod.rs +++ b/src/row/convert/mod.rs @@ -8,7 +8,10 @@ use crate::{ row::Row, - value::convert::{ConvIr, FromValue, FromValueError}, + value::{ + convert::{FromValue, FromValueError}, + Value, + }, }; use std::{any::type_name, error::Error, fmt}; @@ -112,7 +115,7 @@ macro_rules! take_or_place { match $t::get_intermediate(value) { Ok(ir) => ir, Err(FromValueError(value)) => { - $($row.place($idx, $ir.rollback());)* + $($row.place($idx, Into::::into($ir));)* $row.place($index, value); return Err(FromRowError($row)); }, @@ -142,7 +145,7 @@ where { fn from_row_opt(mut row: Row) -> Result { if row.len() == 1 { - Ok(take_or_place!(row, 0, T).commit()) + Ok(take_or_place!(row, 0, T).into()) } else { Err(FromRowError(row)) } @@ -153,8 +156,12 @@ impl FromRow for (T1,) where T1: FromValue, { - fn from_row_opt(row: Row) -> Result<(T1,), FromRowError> { - T1::from_row_opt(row).map(|t| (t,)) + fn from_row_opt(mut row: Row) -> Result<(T1,), FromRowError> { + if row.len() == 1 { + Ok((take_or_place!(row, 0, T1).into(),)) + } else { + Err(FromRowError(row)) + } } } @@ -162,6 +169,7 @@ impl FromRow for (T1, T2) where T1: FromValue, T2: FromValue, + T1::Intermediate: Into, { fn from_row_opt(mut row: Row) -> Result<(T1, T2), FromRowError> { if row.len() != 2 { @@ -169,7 +177,7 @@ where } let ir1 = take_or_place!(row, 0, T1); let ir2 = take_or_place!(row, 1, T2, [0, ir1]); - Ok((ir1.commit(), ir2.commit())) + Ok((Into::::into(ir1), Into::::into(ir2))) } } @@ -178,6 +186,8 @@ where T1: FromValue, T2: FromValue, T3: FromValue, + T1::Intermediate: Into, + T2::Intermediate: Into, { fn from_row_opt(mut row: Row) -> Result<(T1, T2, T3), FromRowError> { if row.len() != 3 { @@ -186,7 +196,7 @@ where let ir1 = take_or_place!(row, 0, T1); let ir2 = take_or_place!(row, 1, T2, [0, ir1]); let ir3 = take_or_place!(row, 2, T3, [0, ir1], [1, ir2]); - Ok((ir1.commit(), ir2.commit(), ir3.commit())) + Ok((Into::::into(ir1), Into::::into(ir2), ir3.into())) } } @@ -196,6 +206,9 @@ where T2: FromValue, T3: FromValue, T4: FromValue, + T1::Intermediate: Into, + T2::Intermediate: Into, + T3::Intermediate: Into, { fn from_row_opt(mut row: Row) -> Result<(T1, T2, T3, T4), FromRowError> { if row.len() != 4 { @@ -205,7 +218,12 @@ where let ir2 = take_or_place!(row, 1, T2, [0, ir1]); let ir3 = take_or_place!(row, 2, T3, [0, ir1], [1, ir2]); let ir4 = take_or_place!(row, 3, T4, [0, ir1], [1, ir2], [2, ir3]); - Ok((ir1.commit(), ir2.commit(), ir3.commit(), ir4.commit())) + Ok(( + Into::::into(ir1), + Into::::into(ir2), + Into::::into(ir3), + ir4.into(), + )) } } @@ -216,6 +234,10 @@ where T3: FromValue, T4: FromValue, T5: FromValue, + T1::Intermediate: Into, + T2::Intermediate: Into, + T3::Intermediate: Into, + T4::Intermediate: Into, { fn from_row_opt(mut row: Row) -> Result<(T1, T2, T3, T4, T5), FromRowError> { if row.len() != 5 { @@ -227,11 +249,11 @@ where let ir4 = take_or_place!(row, 3, T4, [0, ir1], [1, ir2], [2, ir3]); let ir5 = take_or_place!(row, 4, T5, [0, ir1], [1, ir2], [2, ir3], [3, ir4]); Ok(( - ir1.commit(), - ir2.commit(), - ir3.commit(), - ir4.commit(), - ir5.commit(), + Into::::into(ir1), + Into::::into(ir2), + Into::::into(ir3), + Into::::into(ir4), + ir5.into(), )) } } @@ -244,6 +266,11 @@ where T4: FromValue, T5: FromValue, T6: FromValue, + T1::Intermediate: Into, + T2::Intermediate: Into, + T3::Intermediate: Into, + T4::Intermediate: Into, + T5::Intermediate: Into, { fn from_row_opt(mut row: Row) -> Result<(T1, T2, T3, T4, T5, T6), FromRowError> { if row.len() != 6 { @@ -256,12 +283,12 @@ where let ir5 = take_or_place!(row, 4, T5, [0, ir1], [1, ir2], [2, ir3], [3, ir4]); let ir6 = take_or_place!(row, 5, T6, [0, ir1], [1, ir2], [2, ir3], [3, ir4], [4, ir5]); Ok(( - ir1.commit(), - ir2.commit(), - ir3.commit(), - ir4.commit(), - ir5.commit(), - ir6.commit(), + Into::::into(ir1), + Into::::into(ir2), + Into::::into(ir3), + Into::::into(ir4), + Into::::into(ir5), + ir6.into(), )) } } @@ -275,6 +302,12 @@ where T5: FromValue, T6: FromValue, T7: FromValue, + T1::Intermediate: Into, + T2::Intermediate: Into, + T3::Intermediate: Into, + T4::Intermediate: Into, + T5::Intermediate: Into, + T6::Intermediate: Into, { fn from_row_opt(mut row: Row) -> Result<(T1, T2, T3, T4, T5, T6, T7), FromRowError> { if row.len() != 7 { @@ -298,13 +331,13 @@ where [5, ir6] ); Ok(( - ir1.commit(), - ir2.commit(), - ir3.commit(), - ir4.commit(), - ir5.commit(), - ir6.commit(), - ir7.commit(), + Into::::into(ir1), + Into::::into(ir2), + Into::::into(ir3), + Into::::into(ir4), + Into::::into(ir5), + Into::::into(ir6), + ir7.into(), )) } } @@ -319,6 +352,13 @@ where T6: FromValue, T7: FromValue, T8: FromValue, + T1::Intermediate: Into, + T2::Intermediate: Into, + T3::Intermediate: Into, + T4::Intermediate: Into, + T5::Intermediate: Into, + T6::Intermediate: Into, + T7::Intermediate: Into, { fn from_row_opt(mut row: Row) -> Result<(T1, T2, T3, T4, T5, T6, T7, T8), FromRowError> { if row.len() != 8 { @@ -354,14 +394,14 @@ where [6, ir7] ); Ok(( - ir1.commit(), - ir2.commit(), - ir3.commit(), - ir4.commit(), - ir5.commit(), - ir6.commit(), - ir7.commit(), - ir8.commit(), + Into::::into(ir1), + Into::::into(ir2), + Into::::into(ir3), + Into::::into(ir4), + Into::::into(ir5), + Into::::into(ir6), + Into::::into(ir7), + ir8.into(), )) } } @@ -377,6 +417,14 @@ where T7: FromValue, T8: FromValue, T9: FromValue, + T1::Intermediate: Into, + T2::Intermediate: Into, + T3::Intermediate: Into, + T4::Intermediate: Into, + T5::Intermediate: Into, + T6::Intermediate: Into, + T7::Intermediate: Into, + T8::Intermediate: Into, { fn from_row_opt(mut row: Row) -> Result<(T1, T2, T3, T4, T5, T6, T7, T8, T9), FromRowError> { if row.len() != 9 { @@ -425,15 +473,15 @@ where [7, ir8] ); Ok(( - ir1.commit(), - ir2.commit(), - ir3.commit(), - ir4.commit(), - ir5.commit(), - ir6.commit(), - ir7.commit(), - ir8.commit(), - ir9.commit(), + Into::::into(ir1), + Into::::into(ir2), + Into::::into(ir3), + Into::::into(ir4), + Into::::into(ir5), + Into::::into(ir6), + Into::::into(ir7), + Into::::into(ir8), + ir9.into(), )) } } @@ -450,6 +498,15 @@ where T8: FromValue, T9: FromValue, T10: FromValue, + T1::Intermediate: Into, + T2::Intermediate: Into, + T3::Intermediate: Into, + T4::Intermediate: Into, + T5::Intermediate: Into, + T6::Intermediate: Into, + T7::Intermediate: Into, + T8::Intermediate: Into, + T9::Intermediate: Into, { fn from_row_opt( mut row: Row, @@ -514,16 +571,16 @@ where [8, ir9] ); Ok(( - ir1.commit(), - ir2.commit(), - ir3.commit(), - ir4.commit(), - ir5.commit(), - ir6.commit(), - ir7.commit(), - ir8.commit(), - ir9.commit(), - ir10.commit(), + Into::::into(ir1), + Into::::into(ir2), + Into::::into(ir3), + Into::::into(ir4), + Into::::into(ir5), + Into::::into(ir6), + Into::::into(ir7), + Into::::into(ir8), + Into::::into(ir9), + ir10.into(), )) } } @@ -542,6 +599,16 @@ where T9: FromValue, T10: FromValue, T11: FromValue, + T1::Intermediate: Into, + T2::Intermediate: Into, + T3::Intermediate: Into, + T4::Intermediate: Into, + T5::Intermediate: Into, + T6::Intermediate: Into, + T7::Intermediate: Into, + T8::Intermediate: Into, + T9::Intermediate: Into, + T10::Intermediate: Into, { fn from_row_opt( mut row: Row, @@ -621,17 +688,17 @@ where [9, ir10] ); Ok(( - ir1.commit(), - ir2.commit(), - ir3.commit(), - ir4.commit(), - ir5.commit(), - ir6.commit(), - ir7.commit(), - ir8.commit(), - ir9.commit(), - ir10.commit(), - ir11.commit(), + Into::::into(ir1), + Into::::into(ir2), + Into::::into(ir3), + Into::::into(ir4), + Into::::into(ir5), + Into::::into(ir6), + Into::::into(ir7), + Into::::into(ir8), + Into::::into(ir9), + Into::::into(ir10), + ir11.into(), )) } } @@ -651,6 +718,17 @@ where T10: FromValue, T11: FromValue, T12: FromValue, + T1::Intermediate: Into, + T2::Intermediate: Into, + T3::Intermediate: Into, + T4::Intermediate: Into, + T5::Intermediate: Into, + T6::Intermediate: Into, + T7::Intermediate: Into, + T8::Intermediate: Into, + T9::Intermediate: Into, + T10::Intermediate: Into, + T11::Intermediate: Into, { fn from_row_opt( mut row: Row, @@ -746,18 +824,18 @@ where [10, ir11] ); Ok(( - ir1.commit(), - ir2.commit(), - ir3.commit(), - ir4.commit(), - ir5.commit(), - ir6.commit(), - ir7.commit(), - ir8.commit(), - ir9.commit(), - ir10.commit(), - ir11.commit(), - ir12.commit(), + Into::::into(ir1), + Into::::into(ir2), + Into::::into(ir3), + Into::::into(ir4), + Into::::into(ir5), + Into::::into(ir6), + Into::::into(ir7), + Into::::into(ir8), + Into::::into(ir9), + Into::::into(ir10), + Into::::into(ir11), + ir12.into(), )) } } diff --git a/src/value/convert/bigdecimal.rs b/src/value/convert/bigdecimal.rs index 4f0a88a..c86d0c0 100644 --- a/src/value/convert/bigdecimal.rs +++ b/src/value/convert/bigdecimal.rs @@ -10,54 +10,50 @@ #![cfg(feature = "bigdecimal")] -use std::convert::TryInto; +use std::convert::{TryFrom, TryInto}; use bigdecimal::BigDecimal; -use super::{ConvIr, FromValue, FromValueError, ParseIr, Value}; +use super::{FromValue, FromValueError, ParseIr, Value}; -impl ConvIr for ParseIr { - fn new(v: Value) -> Result { +impl TryFrom for ParseIr { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { match v { - Value::Int(x) => Ok(ParseIr { - value: Value::Int(x), - output: x.into(), - }), - Value::UInt(x) => Ok(ParseIr { - value: Value::UInt(x), - output: x.into(), - }), - Value::Float(x) => Ok(ParseIr { - value: Value::Float(x), - output: x.try_into().map_err(|_| FromValueError(Value::Float(x)))?, - }), - Value::Double(x) => Ok(ParseIr { - value: Value::Double(x), - output: x.try_into().map_err(|_| FromValueError(Value::Double(x)))?, - }), - Value::Bytes(bytes) => match BigDecimal::parse_bytes(&*bytes, 10) { - Some(x) => Ok(ParseIr { - value: Value::Bytes(bytes), - output: x, - }), - None => Err(FromValueError(Value::Bytes(bytes))), + Value::Int(x) => Ok(ParseIr(x.into(), v)), + Value::UInt(x) => Ok(ParseIr(x.into(), v)), + Value::Float(x) => match x.try_into() { + Ok(x) => Ok(ParseIr(x, v)), + Err(_) => Err(FromValueError(v)), + }, + Value::Double(x) => match x.try_into() { + Ok(x) => Ok(ParseIr(x, v)), + Err(_) => Err(FromValueError(v)), }, - v => Err(FromValueError(v)), + Value::Bytes(ref bytes) => match BigDecimal::parse_bytes(bytes, 10) { + Some(x) => Ok(ParseIr(x, v)), + None => Err(FromValueError(v)), + }, + _ => Err(FromValueError(v)), } } - fn commit(self) -> BigDecimal { - self.output +} + +impl From> for BigDecimal { + fn from(value: ParseIr) -> Self { + value.commit() } - fn rollback(self) -> Value { - self.value +} + +impl From> for Value { + fn from(value: ParseIr) -> Self { + value.rollback() } } impl FromValue for BigDecimal { type Intermediate = ParseIr; - fn from_value(v: Value) -> BigDecimal { - <_>::from_value_opt(v).expect("Could not retrieve BigDecimal from Value") - } } impl From for Value { diff --git a/src/value/convert/bigdecimal03.rs b/src/value/convert/bigdecimal03.rs index fcb5c16..63482aa 100644 --- a/src/value/convert/bigdecimal03.rs +++ b/src/value/convert/bigdecimal03.rs @@ -10,54 +10,50 @@ #![cfg(feature = "bigdecimal03")] -use std::convert::TryInto; +use std::convert::{TryFrom, TryInto}; use bigdecimal03::BigDecimal; -use super::{ConvIr, FromValue, FromValueError, ParseIr, Value}; +use super::{FromValue, FromValueError, ParseIr, Value}; -impl ConvIr for ParseIr { - fn new(v: Value) -> Result { +impl TryFrom for ParseIr { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { match v { - Value::Int(x) => Ok(ParseIr { - value: Value::Int(x), - output: x.into(), - }), - Value::UInt(x) => Ok(ParseIr { - value: Value::UInt(x), - output: x.into(), - }), - Value::Float(x) => Ok(ParseIr { - value: Value::Float(x), - output: x.try_into().map_err(|_| FromValueError(Value::Float(x)))?, - }), - Value::Double(x) => Ok(ParseIr { - value: Value::Double(x), - output: x.try_into().map_err(|_| FromValueError(Value::Double(x)))?, - }), - Value::Bytes(bytes) => match BigDecimal::parse_bytes(&*bytes, 10) { - Some(x) => Ok(ParseIr { - value: Value::Bytes(bytes), - output: x, - }), - None => Err(FromValueError(Value::Bytes(bytes))), + Value::Int(x) => Ok(ParseIr(x.into(), v)), + Value::UInt(x) => Ok(ParseIr(x.into(), v)), + Value::Float(x) => match x.try_into() { + Ok(x) => Ok(ParseIr(x, v)), + Err(_) => Err(FromValueError(v)), + }, + Value::Double(x) => match x.try_into() { + Ok(x) => Ok(ParseIr(x, v)), + Err(_) => Err(FromValueError(v)), }, - v => Err(FromValueError(v)), + Value::Bytes(ref bytes) => match BigDecimal::parse_bytes(bytes, 10) { + Some(x) => Ok(ParseIr(x, v)), + None => Err(FromValueError(v)), + }, + _ => Err(FromValueError(v)), } } - fn commit(self) -> BigDecimal { - self.output +} + +impl From> for BigDecimal { + fn from(value: ParseIr) -> Self { + value.commit() } - fn rollback(self) -> Value { - self.value +} + +impl From> for Value { + fn from(value: ParseIr) -> Self { + value.rollback() } } impl FromValue for BigDecimal { type Intermediate = ParseIr; - fn from_value(v: Value) -> BigDecimal { - <_>::from_value_opt(v).expect("Could not retrieve BigDecimal from Value") - } } impl From for Value { diff --git a/src/value/convert/bigint.rs b/src/value/convert/bigint.rs index 978d74b..8724cb2 100644 --- a/src/value/convert/bigint.rs +++ b/src/value/convert/bigint.rs @@ -8,45 +8,43 @@ //! This module implements conversion from/to `Value` for `BigInt` and `BigUint` types. +use std::convert::TryFrom; + use num_bigint::{BigInt, BigUint}; use num_traits::{FromPrimitive, ToPrimitive}; -use super::{ConvIr, FromValue, FromValueError, ParseIr, Value}; +use super::{FromValue, FromValueError, ParseIr, Value}; + +impl TryFrom for ParseIr { + type Error = FromValueError; -impl ConvIr for ParseIr { - fn new(v: Value) -> Result { + fn try_from(v: Value) -> Result { match v { - Value::Int(x) => Ok(ParseIr { - value: Value::Int(x), - output: x.into(), - }), - Value::UInt(x) => Ok(ParseIr { - value: Value::UInt(x), - output: x.into(), - }), - Value::Bytes(bytes) => match BigInt::parse_bytes(&*bytes, 10) { - Some(x) => Ok(ParseIr { - value: Value::Bytes(bytes), - output: x, - }), - None => Err(FromValueError(Value::Bytes(bytes))), + Value::Int(x) => Ok(ParseIr(x.into(), v)), + Value::UInt(x) => Ok(ParseIr(x.into(), v)), + Value::Bytes(ref bytes) => match BigInt::parse_bytes(bytes, 10) { + Some(x) => Ok(ParseIr(x, v)), + None => Err(FromValueError(v)), }, - v => Err(FromValueError(v)), + _ => Err(FromValueError(v)), } } - fn commit(self) -> BigInt { - self.output +} + +impl From> for BigInt { + fn from(value: ParseIr) -> Self { + value.commit() } - fn rollback(self) -> Value { - self.value +} + +impl From> for Value { + fn from(value: ParseIr) -> Self { + value.rollback() } } impl FromValue for BigInt { type Intermediate = ParseIr; - fn from_value(v: Value) -> BigInt { - <_>::from_value_opt(v).expect("Could not retrieve BigInt from Value") - } } impl From for Value { @@ -61,46 +59,42 @@ impl From for Value { } } -impl ConvIr for ParseIr { - fn new(v: Value) -> Result { +impl TryFrom for ParseIr { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { match v { Value::Int(x) => { - if let Some(parsed) = <_>::from_i64(x) { - Ok(ParseIr { - value: Value::Int(x), - output: parsed, - }) + if let Some(x) = BigUint::from_i64(x) { + Ok(ParseIr(x, v)) } else { - Err(FromValueError(Value::Int(x))) + Err(FromValueError(v)) } } - Value::UInt(x) => Ok(ParseIr { - value: Value::UInt(x), - output: x.into(), - }), - Value::Bytes(bytes) => match BigUint::parse_bytes(&*bytes, 10) { - Some(x) => Ok(ParseIr { - value: Value::Bytes(bytes), - output: x, - }), - None => Err(FromValueError(Value::Bytes(bytes))), + Value::UInt(x) => Ok(ParseIr(x.into(), v)), + Value::Bytes(ref bytes) => match BigUint::parse_bytes(bytes, 10) { + Some(x) => Ok(ParseIr(x, v)), + None => Err(FromValueError(v)), }, - v => Err(FromValueError(v)), + _ => Err(FromValueError(v)), } } - fn commit(self) -> BigUint { - self.output +} + +impl From> for BigUint { + fn from(value: ParseIr) -> Self { + value.commit() } - fn rollback(self) -> Value { - self.value +} + +impl From> for Value { + fn from(value: ParseIr) -> Self { + value.rollback() } } impl FromValue for BigUint { type Intermediate = ParseIr; - fn from_value(v: Value) -> BigUint { - <_>::from_value_opt(v).expect("Could not retrieve BigUint from Value") - } } impl From for Value { diff --git a/src/value/convert/chrono.rs b/src/value/convert/chrono.rs index 7cb954d..9032c40 100644 --- a/src/value/convert/chrono.rs +++ b/src/value/convert/chrono.rs @@ -10,17 +10,29 @@ #![cfg(feature = "chrono")] +use std::convert::TryFrom; + use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike}; use crate::value::Value; use super::{ - parse_mysql_datetime_string, parse_mysql_time_string, ConvIr, FromValueError, ParseIr, + parse_mysql_datetime_string, parse_mysql_time_string, FromValue, FromValueError, ParseIr, }; -impl ConvIr for ParseIr { - fn new(value: Value) -> Result, FromValueError> { - let result = match value { +impl FromValue for NaiveDate { + type Intermediate = ParseIr; +} + +impl FromValue for NaiveDateTime { + type Intermediate = ParseIr; +} + +impl TryFrom for ParseIr { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { + match v { Value::Date(year, month, day, hour, minute, second, micros) => { let date = NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()); let time = NaiveTime::from_hms_micro_opt( @@ -29,48 +41,37 @@ impl ConvIr for ParseIr { second.into(), micros, ); - Ok(( - date, - time, - Value::Date(year, month, day, hour, minute, second, micros), - )) + if let Some((date, time)) = date.zip(time) { + Ok(ParseIr(NaiveDateTime::new(date, time), v)) + } else { + Err(FromValueError(v)) + } } - Value::Bytes(bytes) => { + Value::Bytes(ref bytes) => { if let Some((year, month, day, hour, minute, second, micros)) = - parse_mysql_datetime_string(&*bytes) + parse_mysql_datetime_string(bytes) { let date = NaiveDate::from_ymd_opt(year as i32, month, day); let time = NaiveTime::from_hms_micro_opt(hour, minute, second, micros); - Ok((date, time, Value::Bytes(bytes))) + if let Some((date, time)) = date.zip(time) { + Ok(ParseIr(NaiveDateTime::new(date, time), v)) + } else { + Err(FromValueError(v)) + } } else { - Err(FromValueError(Value::Bytes(bytes))) + Err(FromValueError(v)) } } - v => Err(FromValueError(v)), - }; - - let (date, time, value) = result?; - - if let (Some(date), Some(time)) = (date, time) { - Ok(ParseIr { - value, - output: NaiveDateTime::new(date, time), - }) - } else { - Err(FromValueError(value)) + _ => Err(FromValueError(v)), } } - fn commit(self) -> NaiveDateTime { - self.output - } - fn rollback(self) -> Value { - self.value - } } -impl ConvIr for ParseIr { - fn new(value: Value) -> Result, FromValueError> { - let result = match value { +impl TryFrom for ParseIr { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { + let result = match v { Value::Date(year, month, day, hour, minute, second, micros) => { let date = NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()); Ok(( @@ -92,29 +93,25 @@ impl ConvIr for ParseIr { let (date, value) = result?; if let Some(output) = date { - Ok(ParseIr { value, output }) + Ok(ParseIr(output, value)) } else { Err(FromValueError(value)) } } - fn commit(self) -> NaiveDate { - self.output - } - fn rollback(self) -> Value { - self.value - } } -impl ConvIr for ParseIr { - fn new(value: Value) -> Result, FromValueError> { - let result = match value { +impl TryFrom for ParseIr { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { + let result = match v { Value::Time(false, 0, h, m, s, u) => { let time = NaiveTime::from_hms_micro_opt(h.into(), m.into(), s.into(), u); Ok((time, Value::Time(false, 0, h, m, s, u))) } Value::Bytes(bytes) => { if let Some((false, h, m, s, u)) = parse_mysql_time_string(&*bytes) { - let time = NaiveTime::from_hms_micro_opt(h, m, s, u); + let time = NaiveTime::from_hms_micro_opt(h, m as u32, s as u32, u); Ok((time, Value::Bytes(bytes))) } else { Err(FromValueError(Value::Bytes(bytes))) @@ -126,65 +123,46 @@ impl ConvIr for ParseIr { let (time, value) = result?; if let Some(output) = time { - Ok(ParseIr { value, output }) + Ok(ParseIr(output, value)) } else { Err(FromValueError(value)) } } - fn commit(self) -> NaiveTime { - self.output +} + +impl From> for NaiveDateTime { + fn from(value: ParseIr) -> Self { + value.commit() } - fn rollback(self) -> Value { - self.value +} + +impl From> for NaiveDate { + fn from(value: ParseIr) -> Self { + value.commit() } } -#[cfg(feature = "chrono")] -impl ConvIr for ParseIr { - fn new(v: Value) -> Result, FromValueError> { - match v { - Value::Time(is_neg, days, hours, minutes, seconds, microseconds) => { - let duration = chrono::Duration::days(days.into()) - + chrono::Duration::hours(hours.into()) - + chrono::Duration::minutes(minutes.into()) - + chrono::Duration::seconds(seconds.into()) - + chrono::Duration::microseconds(microseconds.into()); - Ok(ParseIr { - value: Value::Time(is_neg, days, hours, minutes, seconds, microseconds), - output: if is_neg { -duration } else { duration }, - }) - } - Value::Bytes(val_bytes) => { - // Parse the string using `parse_mysql_time_string` - // instead of `parse_mysql_time_string_with_time` here, - // as it may contain an hour value that's outside of a day's normal 0-23 hour range. - let duration = match parse_mysql_time_string(&*val_bytes) { - Some((is_neg, hours, minutes, seconds, microseconds)) => { - let duration = chrono::Duration::hours(hours.into()) - + chrono::Duration::minutes(minutes.into()) - + chrono::Duration::seconds(seconds.into()) - + chrono::Duration::microseconds(microseconds.into()); - if is_neg { - -duration - } else { - duration - } - } - _ => return Err(FromValueError(Value::Bytes(val_bytes))), - }; - Ok(ParseIr { - value: Value::Bytes(val_bytes), - output: duration, - }) - } - v => Err(FromValueError(v)), - } +impl From> for NaiveTime { + fn from(value: ParseIr) -> Self { + value.commit() } - fn commit(self) -> chrono::Duration { - self.output +} + +impl From> for Value { + fn from(value: ParseIr) -> Self { + value.rollback() } - fn rollback(self) -> Value { - self.value +} + +impl From> for Value { + fn from(value: ParseIr) -> Self { + value.rollback() + } +} + +impl From> for Value { + fn from(value: ParseIr) -> Self { + value.rollback() } } @@ -226,8 +204,3 @@ impl From for Value { ) } } - -impl_from_value!(NaiveDateTime, ParseIr); -impl_from_value!(NaiveDate, ParseIr); -impl_from_value!(NaiveTime, ParseIr); -impl_from_value!(chrono::Duration, ParseIr); diff --git a/src/value/convert/decimal.rs b/src/value/convert/decimal.rs index ca80129..f4e0f35 100644 --- a/src/value/convert/decimal.rs +++ b/src/value/convert/decimal.rs @@ -12,47 +12,46 @@ use rust_decimal::Decimal; -use std::str::{from_utf8, FromStr}; +use std::{ + convert::TryFrom, + str::{from_utf8, FromStr}, +}; -use super::{ConvIr, FromValue, FromValueError, ParseIr, Value}; +use super::{FromValue, FromValueError, ParseIr, Value}; -impl ConvIr for ParseIr { - fn new(v: Value) -> Result { +impl TryFrom for ParseIr { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { match v { - Value::Int(x) => Ok(ParseIr { - value: Value::Int(x), - output: x.into(), - }), - Value::UInt(x) => Ok(ParseIr { - value: Value::UInt(x), - output: x.into(), - }), - Value::Bytes(bytes) => match from_utf8(&*bytes) { + Value::Int(x) => Ok(ParseIr(x.into(), v)), + Value::UInt(x) => Ok(ParseIr(x.into(), v)), + Value::Bytes(ref bytes) => match from_utf8(bytes) { Ok(x) => match Decimal::from_str(x) { - Ok(x) => Ok(ParseIr { - value: Value::Bytes(bytes), - output: x, - }), - Err(_) => Err(FromValueError(Value::Bytes(bytes))), + Ok(x) => Ok(ParseIr(x, v)), + Err(_) => Err(FromValueError(v)), }, - Err(_) => Err(FromValueError(Value::Bytes(bytes))), + Err(_) => Err(FromValueError(v)), }, - v => Err(FromValueError(v)), + _ => Err(FromValueError(v)), } } - fn commit(self) -> Decimal { - self.output +} + +impl From> for Decimal { + fn from(value: ParseIr) -> Self { + value.commit() } - fn rollback(self) -> Value { - self.value +} + +impl From> for Value { + fn from(value: ParseIr) -> Self { + value.rollback() } } impl FromValue for Decimal { type Intermediate = ParseIr; - fn from_value(v: Value) -> Decimal { - <_>::from_value_opt(v).expect("Could not retrieve Decimal from Value") - } } impl From for Value { diff --git a/src/value/convert/mod.rs b/src/value/convert/mod.rs index 3b972fc..d061452 100644 --- a/src/value/convert/mod.rs +++ b/src/value/convert/mod.rs @@ -7,21 +7,18 @@ // modified, or distributed except according to those terms. use lexical::parse; -use num_traits::{FromPrimitive, ToPrimitive}; +use num_traits::ToPrimitive; use regex::bytes::Regex; -use std::{any::type_name, error::Error, fmt, str::from_utf8, time::Duration}; +use std::{ + any::type_name, + borrow::Cow, + convert::{TryFrom, TryInto}, + time::Duration, +}; use crate::value::Value; -macro_rules! impl_from_value { - ($ty:ty, $ir:ty) => { - impl crate::value::convert::FromValue for $ty { - type Intermediate = $ir; - } - }; -} - pub mod bigdecimal; pub mod bigdecimal03; pub mod bigint; @@ -102,422 +99,442 @@ fn parse_mysql_datetime_string(bytes: &[u8]) -> Option<(u32, u32, u32, u32, u32, } /// `FromValue` conversion error. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, thiserror::Error)] +#[error("Couldn't convert the value `{:?}` to a desired type", _0)] pub struct FromValueError(pub Value); -impl fmt::Display for FromValueError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Couldn't convert the value `{:?}` to a desired type", - self.0 - ) - } -} - -impl Error for FromValueError { - fn description(&self) -> &str { - "Couldn't convert the value to a desired type" - } -} - -/// Basic operations on `FromValue` conversion intermediate result. -/// -/// See [`FromValue`](trait.FromValue.html) -pub trait ConvIr: Sized { - fn new(v: Value) -> Result; - fn commit(self) -> T; - fn rollback(self) -> Value; -} - -/// Implement this trait to convert value to something. +/// Implement this trait to convert a value to some type. /// -/// `FromRow` requires ability to cheaply rollback `FromValue` conversion. This ability is -/// provided via `Intermediate` associated type. -/// -/// Example implementation: -/// -/// ```ignore -/// #[derive(Debug)] -/// pub struct StringIr { -/// bytes: Vec, -/// } -/// -/// impl ConvIr for StringIr { -/// fn new(v: Value) -> MyResult { -/// match v { -/// Value::Bytes(bytes) => match from_utf8(&*bytes) { -/// Ok(_) => Ok(StringIr { bytes: bytes }), -/// Err(_) => Err(Error::FromValueError(Value::Bytes(bytes))), -/// }, -/// v => Err(Error::FromValueError(v)), -/// } -/// } -/// fn commit(self) -> String { -/// unsafe { String::from_utf8_unchecked(self.bytes) } -/// } -/// fn rollback(self) -> Value { -/// Value::Bytes(self.bytes) -/// } -/// } -/// -/// impl FromValue for String { -/// type Intermediate = StringIr; -/// } -/// ``` +/// The `FromRow` trait requires an ability to rollback this conversion to an original `Value` +/// instance. Thats the reason why there is the `Intermediate` type – consider implementing +/// `Into` for your `Intermediate` type if you want `FromRow` to work with your type. pub trait FromValue: Sized { - type Intermediate: ConvIr; + type Intermediate: TryFrom + Into; /// Will panic if could not convert `v` to `Self`. fn from_value(v: Value) -> Self { match Self::from_value_opt(v) { Ok(this) => this, - Err(_) => panic!("Could not retrieve {} from Value", type_name::()), + Err(e) => panic!("Could not retrieve `{}`: {e}", type_name::(),), } } /// Will return `Err(Error::FromValueError(v))` if could not convert `v` to `Self`. fn from_value_opt(v: Value) -> Result { - let ir = Self::Intermediate::new(v)?; - Ok(ir.commit()) + Self::Intermediate::try_from(v).map(Into::into) } /// Will return `Err(Error::FromValueError(v))` if `v` is not convertible to `Self`. fn get_intermediate(v: Value) -> Result { - Self::Intermediate::new(v) + Self::Intermediate::try_from(v) } } -/// Will panic if could not convert `v` to `T` -pub fn from_value(v: Value) -> T { - FromValue::from_value(v) +/// Intermediate result for a type that requires parsing. +#[derive(Debug, Clone, PartialEq)] +pub struct ParseIr(pub T, pub Value); + +impl ParseIr { + pub fn commit(self) -> T { + self.0 + } + + pub fn rollback(self) -> Value { + self.1 + } } -/// Will return `Err(FromValueError(v))` if could not convert `v` to `T` -pub fn from_value_opt(v: Value) -> Result { - FromValue::from_value_opt(v) +/// Intermediate result for a type that optionally requires parsing. +#[derive(Debug, Clone, PartialEq)] +pub enum ParseIrOpt { + /// Type instance is ready without parsing. + Ready(T), + /// Type instance is successfully parsed from this value. + Parsed(T, Value), +} + +impl ParseIrOpt { + pub fn commit(self) -> T { + match self { + ParseIrOpt::Ready(t) | ParseIrOpt::Parsed(t, _) => t, + } + } + + pub fn rollback(self) -> Value + where + T: Into, + { + match self { + ParseIrOpt::Ready(t) => t.into(), + ParseIrOpt::Parsed(_, v) => v, + } + } } macro_rules! impl_from_value_num { - ($t:ident) => { - impl ConvIr<$t> for ParseIr<$t> { - fn new(v: Value) -> Result, FromValueError> { + ($ty:ident) => { + impl TryFrom for ParseIrOpt<$ty> { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { match v { - Value::Int(x) => { - if let Some(output) = $t::from_i64(x) { - Ok(ParseIr { - value: Value::Int(x), - output, - }) - } else { - Err(FromValueError(Value::Int(x))) - } - } - Value::UInt(x) => { - if let Some(output) = $t::from_u64(x) { - Ok(ParseIr { - value: Value::UInt(x), - output, - }) - } else { - Err(FromValueError(Value::UInt(x))) - } - } + Value::Int(x) => $ty::try_from(x) + .map(ParseIrOpt::Ready) + .map_err(|_| FromValueError(Value::Int(x))), + Value::UInt(x) => $ty::try_from(x) + .map(ParseIrOpt::Ready) + .map_err(|_| FromValueError(Value::UInt(x))), Value::Bytes(bytes) => match parse(&*bytes) { - Ok(x) => Ok(ParseIr { - value: Value::Bytes(bytes), - output: x, - }), + Ok(x) => Ok(ParseIrOpt::Parsed(x, Value::Bytes(bytes))), _ => Err(FromValueError(Value::Bytes(bytes))), }, v => Err(FromValueError(v)), } } - fn commit(self) -> $t { - self.output + } + + impl From> for $ty { + fn from(value: ParseIrOpt<$ty>) -> Self { + value.commit() } - fn rollback(self) -> Value { - self.value + } + + impl From> for Value { + fn from(value: ParseIrOpt<$ty>) -> Self { + value.rollback() } } - impl_from_value!($t, ParseIr<$t>); + impl FromValue for $ty { + type Intermediate = ParseIrOpt<$ty>; + } }; } -/// Intermediate result of a Value-to-Option conversion. -#[derive(Debug, Clone, PartialEq)] -pub struct OptionIr { - value: Option, - ir: Option, -} +impl_from_value_num!(i8); +impl_from_value_num!(u8); +impl_from_value_num!(i16); +impl_from_value_num!(u16); +impl_from_value_num!(i32); +impl_from_value_num!(u32); +impl_from_value_num!(i64); +impl_from_value_num!(u64); +impl_from_value_num!(isize); +impl_from_value_num!(usize); +impl_from_value_num!(i128); +impl_from_value_num!(u128); -impl ConvIr> for OptionIr -where - T: FromValue, - Ir: ConvIr, -{ - fn new(v: Value) -> Result, FromValueError> { +impl TryFrom for ParseIrOpt { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { match v { - Value::NULL => Ok(OptionIr { - value: Some(Value::NULL), - ir: None, - }), - v => match T::get_intermediate(v) { - Ok(ir) => Ok(OptionIr { - value: None, - ir: Some(ir), - }), - Err(err) => Err(err), + Value::Int(0) => Ok(ParseIrOpt::Ready(false)), + Value::Int(1) => Ok(ParseIrOpt::Ready(true)), + Value::Bytes(ref bytes) => match bytes.as_slice() { + [b'0'] => Ok(ParseIrOpt::Parsed(false, v)), + [b'1'] => Ok(ParseIrOpt::Parsed(true, v)), + _ => Err(FromValueError(v)), }, + v => Err(FromValueError(v)), } } - fn commit(self) -> Option { - self.ir.map(|ir| ir.commit()) +} + +impl From> for bool { + fn from(value: ParseIrOpt) -> Self { + value.commit() } - fn rollback(self) -> Value { - let OptionIr { value, ir } = self; - match value { - Some(v) => v, - None => match ir { - Some(ir) => ir.rollback(), - None => unreachable!(), - }, - } +} + +impl From> for Value { + fn from(value: ParseIrOpt) -> Self { + value.rollback() } } -impl FromValue for Option -where - T: FromValue, -{ - type Intermediate = OptionIr; +impl FromValue for bool { + type Intermediate = ParseIrOpt; } -impl ConvIr for Value { - fn new(v: Value) -> Result { - Ok(v) - } +impl TryFrom for ParseIrOpt { + type Error = FromValueError; - fn commit(self) -> Value { - self + fn try_from(v: Value) -> Result { + match v { + Value::Float(x) => Ok(ParseIrOpt::Ready(x)), + Value::Bytes(bytes) => match parse(&*bytes) { + Ok(x) => Ok(ParseIrOpt::Parsed(x, Value::Bytes(bytes))), + _ => Err(FromValueError(Value::Bytes(bytes))), + }, + v => Err(FromValueError(v)), + } } +} - fn rollback(self) -> Value { - self +impl From> for f32 { + fn from(value: ParseIrOpt) -> Self { + value.commit() } } -impl FromValue for Value { - type Intermediate = Value; - fn from_value(v: Value) -> Value { - v - } - fn from_value_opt(v: Value) -> Result { - Ok(v) +impl From> for Value { + fn from(value: ParseIrOpt) -> Self { + value.rollback() } } -impl ConvIr for Vec { - fn new(v: Value) -> Result, FromValueError> { +impl FromValue for f32 { + type Intermediate = ParseIrOpt; +} + +impl TryFrom for ParseIrOpt { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { match v { - Value::Bytes(bytes) => match from_utf8(&*bytes) { - Ok(_) => Ok(bytes), - Err(_) => Err(FromValueError(Value::Bytes(bytes))), + Value::Double(x) => Ok(ParseIrOpt::Ready(x)), + Value::Float(x) => Ok(ParseIrOpt::Ready(x.into())), + Value::Bytes(bytes) => match parse(&*bytes) { + Ok(x) => Ok(ParseIrOpt::Parsed(x, Value::Bytes(bytes))), + _ => Err(FromValueError(Value::Bytes(bytes))), }, v => Err(FromValueError(v)), } } - fn commit(self) -> String { - unsafe { String::from_utf8_unchecked(self) } +} + +impl From> for f64 { + fn from(value: ParseIrOpt) -> Self { + value.commit() } - fn rollback(self) -> Value { - Value::Bytes(self) +} + +impl From> for Value { + fn from(value: ParseIrOpt) -> Self { + value.rollback() } } -/// Intermediate result of a Value-to-Integer conversion. -#[derive(Debug, Clone, PartialEq)] -pub struct ParseIr { - value: Value, - output: T, +impl FromValue for f64 { + type Intermediate = ParseIrOpt; } -impl ConvIr for ParseIr { - fn new(v: Value) -> Result, FromValueError> { +fn mysql_time_to_duration( + days: u32, + hours: u8, + minutes: u8, + seconds: u8, + microseconds: u32, +) -> Duration { + let nanos = (microseconds as u32) * 1000; + let secs = u64::from(seconds) + + u64::from(minutes) * 60 + + u64::from(hours) * 60 * 60 + + u64::from(days) * 60 * 60 * 24; + Duration::new(secs, nanos) +} + +impl TryFrom for ParseIrOpt { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { match v { - Value::Int(x) => Ok(ParseIr { - value: Value::Int(x), - output: x, - }), - Value::UInt(x) if x <= ::std::i64::MAX as u64 => Ok(ParseIr { - value: Value::UInt(x), - output: x as i64, - }), - Value::Bytes(bytes) => match parse(&*bytes) { - Ok(x) => Ok(ParseIr { - value: Value::Bytes(bytes), - output: x, - }), - _ => Err(FromValueError(Value::Bytes(bytes))), - }, + Value::Time(false, days, hours, minutes, seconds, microseconds) => { + let duration = mysql_time_to_duration(days, hours, minutes, seconds, microseconds); + Ok(ParseIrOpt::Parsed(duration, v)) + } + Value::Bytes(ref val_bytes) => { + let duration = match parse_mysql_time_string(&*val_bytes) { + Some((false, hours, minutes, seconds, microseconds)) => { + let days = hours / 24; + let hours = (hours % 24) as u8; + mysql_time_to_duration(days, hours, minutes, seconds, microseconds) + } + _ => return Err(FromValueError(v)), + }; + Ok(ParseIrOpt::Parsed(duration, v)) + } v => Err(FromValueError(v)), } } - fn commit(self) -> i64 { - self.output +} + +impl From> for Duration { + fn from(value: ParseIrOpt) -> Self { + value.commit() } - fn rollback(self) -> Value { - self.value +} + +impl From> for Value { + fn from(value: ParseIrOpt) -> Self { + value.rollback() } } -impl ConvIr for ParseIr { - fn new(v: Value) -> Result, FromValueError> { +impl FromValue for Duration { + type Intermediate = ParseIrOpt; +} + +impl TryFrom for String { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { match v { - Value::Int(x) if x >= 0 => Ok(ParseIr { - value: Value::Int(x), - output: x as u64, - }), - Value::UInt(x) => Ok(ParseIr { - value: Value::UInt(x), - output: x, - }), - Value::Bytes(bytes) => match parse(&*bytes) { - Ok(x) => Ok(ParseIr { - value: Value::Bytes(bytes), - output: x, - }), - _ => Err(FromValueError(Value::Bytes(bytes))), + Value::Bytes(bytes) => match String::from_utf8(bytes) { + Ok(x) => Ok(x), + Err(e) => Err(FromValueError(Value::Bytes(e.into_bytes()))), }, v => Err(FromValueError(v)), } } - fn commit(self) -> u64 { - self.output - } - fn rollback(self) -> Value { - self.value - } } -impl ConvIr for ParseIr { - fn new(v: Value) -> Result, FromValueError> { +impl FromValue for String { + type Intermediate = String; +} + +impl TryFrom for Vec { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { match v { - Value::Float(x) => Ok(ParseIr { - value: Value::Float(x), - output: x, - }), - Value::Bytes(bytes) => { - let val = parse(&*bytes).ok(); - match val { - Some(x) => Ok(ParseIr { - value: Value::Bytes(bytes), - output: x, - }), - None => Err(FromValueError(Value::Bytes(bytes))), - } - } + Value::Bytes(bytes) => Ok(bytes), v => Err(FromValueError(v)), } } - fn commit(self) -> f32 { - self.output +} + +impl FromValue for Vec { + type Intermediate = Vec; +} + +/// Intermediate result of a Value-to-Option conversion. +#[derive(Debug, Clone, PartialEq)] +pub enum OptionIr2 { + None, + Some(T::Intermediate), +} + +impl TryFrom for OptionIr2 { + type Error = <::Intermediate as TryFrom>::Error; + + fn try_from(value: Value) -> Result { + match value { + Value::NULL => Ok(Self::None), + v => ::Intermediate::try_from(v).map(Self::Some), + } } - fn rollback(self) -> Value { - self.value +} + +impl From> for Option { + fn from(ir: OptionIr2) -> Self { + match ir { + OptionIr2::None => None, + OptionIr2::Some(ir) => Some(ir.into()), + } } } -impl ConvIr for ParseIr { - fn new(v: Value) -> Result, FromValueError> { - match v { - Value::Double(x) => Ok(ParseIr { - value: Value::Double(x), - output: x, - }), - Value::Float(x) => { - let double = x.into(); - Ok(ParseIr { - value: Value::Double(double), - output: double, - }) - } - Value::Bytes(bytes) => { - let val = parse(&*bytes).ok(); - match val { - Some(x) => Ok(ParseIr { - value: Value::Bytes(bytes), - output: x, - }), - _ => Err(FromValueError(Value::Bytes(bytes))), - } - } - v => Err(FromValueError(v)), +impl From> for Value +where + ::Intermediate: Into, +{ + fn from(ir: OptionIr2) -> Self { + match ir { + OptionIr2::None => Value::NULL, + OptionIr2::Some(ir) => ir.into(), } } - fn commit(self) -> f64 { - self.output +} + +impl FromValue for Option { + type Intermediate = OptionIr2; +} + +// TODO: rustc is unable to conclude that Infallible equals FromValueError +#[derive(Debug, Clone, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct ValueIr(pub Value); + +impl TryFrom for ValueIr { + type Error = FromValueError; + + fn try_from(value: Value) -> Result { + Ok(ValueIr(value)) } - fn rollback(self) -> Value { - self.value +} + +impl From for Value { + fn from(value: ValueIr) -> Self { + value.0 } } -impl ConvIr for ParseIr { - fn new(v: Value) -> Result, FromValueError> { +impl FromValue for Value { + type Intermediate = ValueIr; +} + +/// Will panic if could not convert `v` to `T` +pub fn from_value(v: Value) -> T { + FromValue::from_value(v) +} + +/// Will return `Err(FromValueError(v))` if could not convert `v` to `T` +pub fn from_value_opt(v: Value) -> Result { + FromValue::from_value_opt(v) +} + +impl TryFrom for Cow<'static, str> { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { match v { - Value::Int(0) => Ok(ParseIr { - value: Value::Int(0), - output: false, - }), - Value::Int(1) => Ok(ParseIr { - value: Value::Int(1), - output: true, - }), - Value::Bytes(bytes) => { - if bytes.len() == 1 { - match bytes[0] { - 0x30 => Ok(ParseIr { - value: Value::Bytes(bytes), - output: false, - }), - 0x31 => Ok(ParseIr { - value: Value::Bytes(bytes), - output: true, - }), - _ => Err(FromValueError(Value::Bytes(bytes))), - } - } else { - Err(FromValueError(Value::Bytes(bytes))) - } - } + Value::Bytes(bytes) => match String::from_utf8(bytes) { + Ok(x) => Ok(Cow::Owned(x)), + Err(e) => Err(FromValueError(Value::Bytes(e.into_bytes()))), + }, v => Err(FromValueError(v)), } } - fn commit(self) -> bool { - self.output - } - fn rollback(self) -> Value { - self.value - } } -impl ConvIr> for Vec { - fn new(v: Value) -> Result, FromValueError> { +impl FromValue for Cow<'static, str> { + type Intermediate = String; +} + +impl TryFrom for Cow<'static, [u8]> { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { match v { - Value::Bytes(bytes) => Ok(bytes), + Value::Bytes(x) => Ok(Cow::Owned(x)), v => Err(FromValueError(v)), } } - fn commit(self) -> Vec { - self - } - fn rollback(self) -> Value { - Value::Bytes(self) +} + +impl FromValue for Cow<'static, [u8]> { + type Intermediate = Cow<'static, [u8]>; +} + +impl TryFrom for [u8; N] { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { + match v { + Value::Bytes(bytes) => bytes + .try_into() + .map_err(|x| FromValueError(Value::Bytes(x))), + v => Err(FromValueError(v)), + } } } +impl FromValue for [u8; N] { + type Intermediate = [u8; N]; +} + fn parse_micros(micros_bytes: &[u8]) -> u32 { let mut micros = parse(micros_bytes).unwrap(); @@ -537,7 +554,7 @@ fn parse_micros(micros_bytes: &[u8]) -> u32 { } /// Returns (is_neg, hours, minutes, seconds, microseconds) -fn parse_mysql_time_string(mut bytes: &[u8]) -> Option<(bool, u32, u32, u32, u32)> { +fn parse_mysql_time_string(mut bytes: &[u8]) -> Option<(bool, u32, u8, u8, u32)> { #[derive(PartialEq, Eq, PartialOrd, Ord)] #[repr(u8)] enum TimeKind { @@ -586,47 +603,6 @@ fn parse_mysql_time_string(mut bytes: &[u8]) -> Option<(bool, u32, u32, u32, u32 )) } -impl ConvIr for ParseIr { - fn new(v: Value) -> Result, FromValueError> { - match v { - Value::Time(false, days, hours, minutes, seconds, microseconds) => { - let nanos = (microseconds as u32) * 1000; - let secs = u64::from(seconds) - + u64::from(minutes) * 60 - + u64::from(hours) * 60 * 60 - + u64::from(days) * 60 * 60 * 24; - Ok(ParseIr { - value: Value::Time(false, days, hours, minutes, seconds, microseconds), - output: Duration::new(secs, nanos), - }) - } - Value::Bytes(val_bytes) => { - let duration = match parse_mysql_time_string(&*val_bytes) { - Some((false, hours, minutes, seconds, microseconds)) => { - let nanos = microseconds * 1000; - let secs = u64::from(seconds) - + u64::from(minutes) * 60 - + u64::from(hours) * 60 * 60; - Duration::new(secs, nanos) - } - _ => return Err(FromValueError(Value::Bytes(val_bytes))), - }; - Ok(ParseIr { - value: Value::Bytes(val_bytes), - output: duration, - }) - } - v => Err(FromValueError(v)), - } - } - fn commit(self) -> Duration { - self.output - } - fn rollback(self) -> Value { - self.value - } -} - impl From for Value { fn from(x: Duration) -> Value { let mut secs_total = x.as_secs(); @@ -648,25 +624,6 @@ impl From for Value { } } -impl_from_value!(String, Vec); -impl_from_value!(Vec, Vec); -impl_from_value!(bool, ParseIr); -impl_from_value!(i64, ParseIr); -impl_from_value!(u64, ParseIr); -impl_from_value!(f32, ParseIr); -impl_from_value!(f64, ParseIr); -impl_from_value!(Duration, ParseIr); -impl_from_value_num!(i8); -impl_from_value_num!(u8); -impl_from_value_num!(i16); -impl_from_value_num!(u16); -impl_from_value_num!(i32); -impl_from_value_num!(u32); -impl_from_value_num!(isize); -impl_from_value_num!(usize); -impl_from_value_num!(i128); -impl_from_value_num!(u128); - pub trait ToValue { fn to_value(&self) -> Value; } @@ -779,56 +736,31 @@ impl<'a> From<&'a str> for Value { } } +impl<'a, T: ToOwned> From> for Value +where + T::Owned: Into, + &'a T: Into, +{ + fn from(x: Cow<'a, T>) -> Value { + match x { + Cow::Borrowed(x) => x.into(), + Cow::Owned(x) => x.into(), + } + } +} + impl From for Value { fn from(x: String) -> Value { Value::Bytes(x.into_bytes()) } } -macro_rules! from_array_impl { - ($n:expr) => { - impl From<[u8; $n]> for Value { - fn from(x: [u8; $n]) -> Value { - Value::from(&x[..]) - } - } - }; +impl From<[u8; N]> for Value { + fn from(x: [u8; N]) -> Value { + Value::Bytes(x.to_vec()) + } } -from_array_impl!(0); -from_array_impl!(1); -from_array_impl!(2); -from_array_impl!(3); -from_array_impl!(4); -from_array_impl!(5); -from_array_impl!(6); -from_array_impl!(7); -from_array_impl!(8); -from_array_impl!(9); -from_array_impl!(10); -from_array_impl!(11); -from_array_impl!(12); -from_array_impl!(13); -from_array_impl!(14); -from_array_impl!(15); -from_array_impl!(16); -from_array_impl!(17); -from_array_impl!(18); -from_array_impl!(19); -from_array_impl!(20); -from_array_impl!(21); -from_array_impl!(22); -from_array_impl!(23); -from_array_impl!(24); -from_array_impl!(25); -from_array_impl!(26); -from_array_impl!(27); -from_array_impl!(28); -from_array_impl!(29); -from_array_impl!(30); -from_array_impl!(31); -from_array_impl!(32); - #[cfg(test)] mod tests { use super::*; @@ -897,8 +829,8 @@ mod tests { fn parse_mysql_time_string_parses_correctly( sign in 0..2, h in 0u32..900, - m in 0u32..59, - s in 0u32..59, + m in 0u8..59, + s in 0u8..59, have_us in 0..2, us in 0u32..1000000, ) { diff --git a/src/value/convert/time.rs b/src/value/convert/time.rs index 94fc17f..59907c3 100644 --- a/src/value/convert/time.rs +++ b/src/value/convert/time.rs @@ -10,110 +10,129 @@ #![cfg(feature = "time")] -use std::str::from_utf8; +use std::{convert::TryFrom, str::from_utf8}; use time::{Date, ParseError, PrimitiveDateTime, Time}; use crate::value::Value; -use super::{parse_mysql_time_string, ConvIr, FromValueError, ParseIr}; - -impl ConvIr for ParseIr { - fn new(value: Value) -> Result, FromValueError> { - match value { - Value::Date(year, month, day, hour, minute, second, micros) => Ok(ParseIr { - value: Value::Date(year, month, day, hour, minute, second, micros), - output: match create_primitive_date_time( - year, month, day, hour, minute, second, micros, - ) { - Some(datetime) => datetime, - None => return Err(FromValueError(value)), - }, - }), - Value::Bytes(bytes) => match parse_mysql_datetime_string_with_time(&*bytes) { - Ok(output) => Ok(ParseIr { - value: Value::Bytes(bytes), - output, - }), - Err(_) => Err(FromValueError(Value::Bytes(bytes))), +use super::{parse_mysql_time_string, FromValue, FromValueError, ParseIr}; + +impl TryFrom for ParseIr { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { + match v { + Value::Date(year, month, day, hour, minute, second, micros) => { + match create_primitive_date_time(year, month, day, hour, minute, second, micros) { + Some(x) => Ok(ParseIr(x, v)), + None => Err(FromValueError(v)), + } + } + Value::Bytes(ref bytes) => match parse_mysql_datetime_string_with_time(bytes) { + Ok(x) => Ok(ParseIr(x, v)), + Err(_) => Err(FromValueError(v)), }, - v => Err(FromValueError(v)), + _ => Err(FromValueError(v)), } } - fn commit(self) -> PrimitiveDateTime { - self.output +} + +impl From> for PrimitiveDateTime { + fn from(value: ParseIr) -> Self { + value.commit() } - fn rollback(self) -> Value { - self.value +} + +impl From> for Value { + fn from(value: ParseIr) -> Self { + value.rollback() } } -/// Converts a MySQL `DATE` value to a `time::Date`. -impl ConvIr for ParseIr { - fn new(value: Value) -> Result, FromValueError> { - match value { - Value::Date(year, month, day, hour, minute, second, micros) => Ok(ParseIr { - value: Value::Date(year, month, day, hour, minute, second, micros), - output: match Date::try_from_ymd(year as i32, month, day) { - Ok(date) => date, - Err(_) => return Err(FromValueError(value)), - }, - }), - Value::Bytes(bytes) => { - match from_utf8(&*bytes) +impl FromValue for PrimitiveDateTime { + type Intermediate = ParseIr; +} + +impl TryFrom for ParseIr { + type Error = FromValueError; + + fn try_from(v: Value) -> Result { + match v { + Value::Date(year, month, day, _, _, _, _) => { + match Date::try_from_ymd(year as i32, month, day) { + Ok(x) => Ok(ParseIr(x, v)), + Err(_) => Err(FromValueError(v)), + } + } + Value::Bytes(ref bytes) => { + match from_utf8(bytes) .ok() .and_then(|s| time::parse(s, "%Y-%m-%d").ok()) { - Some(output) => Ok(ParseIr { - value: Value::Bytes(bytes), - output, - }), - None => Err(FromValueError(Value::Bytes(bytes))), + Some(x) => Ok(ParseIr(x, v)), + None => Err(FromValueError(v)), } } v => Err(FromValueError(v)), } } - fn commit(self) -> Date { - self.output +} + +impl From> for Date { + fn from(value: ParseIr) -> Self { + value.commit() } - fn rollback(self) -> Value { - self.value +} + +impl From> for Value { + fn from(value: ParseIr) -> Self { + value.rollback() } } -/// Converts a MySQL `TIME` value to a `time::Time`. -/// Note: `time::Time` only allows for time values in the 00:00:00 - 23:59:59 range. -/// If you're expecting `TIME` values in MySQL's `TIME` value range of -838:59:59 - 838:59:59, -/// use time::Duration instead. -impl ConvIr