diff --git a/sqlx-macros/src/derives/attributes.rs b/sqlx-macros/src/derives/attributes.rs index 40412e98..dad7ec7a 100644 --- a/sqlx-macros/src/derives/attributes.rs +++ b/sqlx-macros/src/derives/attributes.rs @@ -33,6 +33,8 @@ pub enum RenameAll { UpperCase, ScreamingSnakeCase, KebabCase, + MixedCase, + CamelCase, } pub struct SqlxContainerAttributes { @@ -77,7 +79,8 @@ pub fn parse_container_attributes(input: &[Attribute]) -> syn::Result RenameAll::UpperCase, "SCREAMING_SNAKE_CASE" => RenameAll::ScreamingSnakeCase, "kebab-case" => RenameAll::KebabCase, - + "mixedCase" => RenameAll::MixedCase, + "CamelCase" => RenameAll::CamelCase, _ => fail!(meta, "unexpected value for rename_all"), }; diff --git a/sqlx-macros/src/derives/mod.rs b/sqlx-macros/src/derives/mod.rs index 0785c894..994f826e 100644 --- a/sqlx-macros/src/derives/mod.rs +++ b/sqlx-macros/src/derives/mod.rs @@ -10,7 +10,7 @@ pub(crate) use r#type::expand_derive_type; pub(crate) use row::expand_derive_from_row; use self::attributes::RenameAll; -use heck::{KebabCase, ShoutySnakeCase, SnakeCase}; +use heck::{KebabCase, ShoutySnakeCase, SnakeCase, CamelCase, MixedCase}; use std::iter::FromIterator; use syn::DeriveInput; @@ -35,5 +35,8 @@ pub(crate) fn rename_all(s: &str, pattern: RenameAll) -> String { RenameAll::UpperCase => s.to_uppercase(), RenameAll::ScreamingSnakeCase => s.to_shouty_snake_case(), RenameAll::KebabCase => s.to_kebab_case(), + RenameAll::MixedCase => s.to_mixed_case(), + RenameAll::CamelCase => s.to_camel_case(), + } } diff --git a/sqlx-macros/src/derives/row.rs b/sqlx-macros/src/derives/row.rs index 2409c7fb..90d17785 100644 --- a/sqlx-macros/src/derives/row.rs +++ b/sqlx-macros/src/derives/row.rs @@ -5,7 +5,7 @@ use syn::{ Fields, FieldsNamed, FieldsUnnamed, Lifetime, Stmt, }; -use super::attributes::parse_child_attributes; +use super::{attributes::{parse_child_attributes, parse_container_attributes}, rename_all}; pub fn expand_derive_from_row(input: &DeriveInput) -> syn::Result { match &input.data { @@ -69,13 +69,21 @@ fn expand_derive_from_row_struct( let (impl_generics, _, where_clause) = generics.split_for_impl(); + let container_attributes = parse_container_attributes(&input.attrs)?; + let reads = fields.iter().filter_map(|field| -> Option { + + let id = &field.ident.as_ref()?; let attributes = parse_child_attributes(&field.attrs).unwrap(); - let id_s = match attributes.rename { - Some(rename) => rename, - None => id.to_string().trim_start_matches("r#").to_owned(), - }; + let id_s = attributes.rename + .or_else(|| Some(id.to_string().trim_start_matches("r#").to_owned())) + .map(|s| match container_attributes.rename_all { + Some(pattern) => rename_all(&s, pattern), + None => s + }) + .unwrap(); + let ty = &field.ty; if attributes.default { diff --git a/tests/postgres/derives.rs b/tests/postgres/derives.rs index a218db3e..199d89a9 100644 --- a/tests/postgres/derives.rs +++ b/tests/postgres/derives.rs @@ -67,13 +67,29 @@ enum ColorScreamingSnake { } #[derive(PartialEq, Debug, sqlx::Type)] -#[sqlx(rename = "color-kebab-case")] +#[sqlx(rename = "color_kebab_case")] #[sqlx(rename_all = "kebab-case")] enum ColorKebabCase { RedGreen, BlueBlack, } +#[derive(PartialEq, Debug, sqlx::Type)] +#[sqlx(rename = "color_mixed_case")] +#[sqlx(rename_all = "mixedCase")] +enum ColorMixedCase { + RedGreen, + BlueBlack, +} + +#[derive(PartialEq, Debug, sqlx::Type)] +#[sqlx(rename = "color_camel_case")] +#[sqlx(rename_all = "CamelCase")] +enum ColorCamelCase { + RedGreen, + BlueBlack, +} + // "Strong" enum can map to a custom type #[derive(PartialEq, Debug, sqlx::Type)] #[sqlx(rename = "mood")] @@ -141,13 +157,19 @@ DROP TYPE IF EXISTS color_lower CASCADE; DROP TYPE IF EXISTS color_snake CASCADE; DROP TYPE IF EXISTS color_upper CASCADE; DROP TYPE IF EXISTS color_screaming_snake CASCADE; -DROP TYPE IF EXISTS "color-kebab-case" CASCADE; +DROP TYPE IF EXISTS color_kebab_case CASCADE; +DROP TYPE IF EXISTS color_mixed_case CASCADE; +DROP TYPE IF EXISTS color_camel_case CASCADE; + CREATE TYPE color_lower AS ENUM ( 'red', 'green', 'blue' ); CREATE TYPE color_snake AS ENUM ( 'red_green', 'blue_black' ); CREATE TYPE color_upper AS ENUM ( 'RED', 'GREEN', 'BLUE' ); CREATE TYPE color_screaming_snake AS ENUM ( 'RED_GREEN', 'BLUE_BLACK' ); -CREATE TYPE "color-kebab-case" AS ENUM ( 'red-green', 'blue-black' ); +CREATE TYPE color_kebab_case" AS ENUM ( 'red-green', 'blue-black' ); +CREATE TYPE color_mixed_case AS ENUM ( 'redGreen', 'blueBlack' ); +CREATE TYPE color_camel_case AS ENUM ( 'RedGreen', 'BlueBlack' ); + CREATE TABLE people ( id serial PRIMARY KEY, @@ -276,7 +298,7 @@ SELECT id, mood FROM people WHERE id = $1 let rec: (bool, ColorKebabCase) = sqlx::query_as( " - SELECT $1 = 'red-green'::\"color-kebab-case\", $1 + SELECT $1 = 'red-green'::color_kebab_case, $1 ", ) .bind(&ColorKebabCase::RedGreen) @@ -286,6 +308,30 @@ SELECT id, mood FROM people WHERE id = $1 assert!(rec.0); assert_eq!(rec.1, ColorKebabCase::RedGreen); + let rec: (bool, ColorMixedCase) = sqlx::query_as( + " + SELECT $1 = 'redGreen'::color_mixed_case, $1 + ", + ) + .bind(&ColorMixedCase::RedGreen) + .fetch_one(&mut conn) + .await?; + + assert!(rec.0); + assert_eq!(rec.1, ColorMixedCase::RedGreen); + + let rec: (bool, ColorCamelCase) = sqlx::query_as( + " + SELECT $1 = 'RedGreen'::color_camel_case, $1 + ", + ) + .bind(&ColorCamelCase::RedGreen) + .fetch_one(&mut conn) + .await?; + + assert!(rec.0); + assert_eq!(rec.1, ColorCamelCase::RedGreen); + Ok(()) } @@ -426,6 +472,34 @@ async fn test_from_row_with_rename() -> anyhow::Result<()> { Ok(()) } +#[cfg(feature = "macros")] +#[sqlx_macros::test] +async fn test_from_row_with_rename_all() -> anyhow::Result<()> { + #[derive(Debug, sqlx::FromRow)] + #[sqlx(rename_all = "mixedCase")] + struct AccountKeyword { + user_id: i32, + user_name: String, + user_surname: String, + } + + let mut conn = new::().await?; + + let account: AccountKeyword = sqlx::query_as( + r#"SELECT * from (VALUES (1, 'foo', 'bar')) accounts(userId, userName, userSurname)"# + ) + .fetch_one(&mut conn) + .await?; + println!("{:?}", account); + + assert_eq!(1, account.user_id); + assert_eq!("foo", account.user_name); + assert_eq!("bar8", account.user_surname); + + + Ok(()) +} + #[cfg(feature = "macros")] #[sqlx_macros::test] async fn test_from_row_tuple() -> anyhow::Result<()> {