Skip to content

Commit f09d450

Browse files
committed
Add a container-level rename_all attribute to FromSql/ToSql.
It works both with composite types' fields and enum variants, although arguably it will be the most useful for the latter as that's where the naming conventions between Rust and a typical PostgreSQL schema differ. Right now it supports the following casing styles: * `"camelCase"` * `"kebab-case"` * `"lowercase"` * `"PascalCase"` * `"SCREAMING-KEBAB-CASE"` * `"SCREAMING_SNAKE_CASE"` * `"snake_case"` * `"UPPERCASE"` mimicking Serde.
1 parent 71668b7 commit f09d450

File tree

12 files changed

+280
-80
lines changed

12 files changed

+280
-80
lines changed

postgres-derive-test/src/composites.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,48 @@ fn name_overrides() {
8989
);
9090
}
9191

92+
#[test]
93+
fn rename_all_overrides() {
94+
#[derive(FromSql, ToSql, Debug, PartialEq)]
95+
#[postgres(name = "inventory_item", rename_all = "SCREAMING_SNAKE_CASE")]
96+
struct InventoryItem {
97+
name: String,
98+
supplier_id: i32,
99+
price: Option<f64>,
100+
}
101+
102+
let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap();
103+
conn.batch_execute(
104+
"CREATE TYPE pg_temp.inventory_item AS (
105+
\"NAME\" TEXT,
106+
\"SUPPLIER_ID\" INT,
107+
\"PRICE\" DOUBLE PRECISION
108+
);",
109+
)
110+
.unwrap();
111+
112+
let item = InventoryItem {
113+
name: "foobar".to_owned(),
114+
supplier_id: 100,
115+
price: Some(15.50),
116+
};
117+
118+
let item_null = InventoryItem {
119+
name: "foobar".to_owned(),
120+
supplier_id: 100,
121+
price: None,
122+
};
123+
124+
test_type(
125+
&mut conn,
126+
"inventory_item",
127+
&[
128+
(item, "ROW('foobar', 100, 15.50)"),
129+
(item_null, "ROW('foobar', 100, NULL)"),
130+
],
131+
);
132+
}
133+
92134
#[test]
93135
fn wrong_name() {
94136
#[derive(FromSql, ToSql, Debug, PartialEq)]

postgres-derive-test/src/enums.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,35 @@ fn name_overrides() {
5353
);
5454
}
5555

56+
#[test]
57+
fn rename_all_overrides() {
58+
#[derive(Debug, ToSql, FromSql, PartialEq)]
59+
#[postgres(name = "mood", rename_all = "snake_case")]
60+
enum Mood {
61+
Sad,
62+
#[postgres(name = "okay")]
63+
Ok,
64+
Happy,
65+
}
66+
67+
let mut conn = Client::connect("user=postgres host=localhost port=5433", NoTls).unwrap();
68+
conn.execute(
69+
"CREATE TYPE pg_temp.mood AS ENUM ('sad', 'okay', 'happy')",
70+
&[],
71+
)
72+
.unwrap();
73+
74+
test_type(
75+
&mut conn,
76+
"mood",
77+
&[
78+
(Mood::Sad, "'sad'"),
79+
(Mood::Ok, "'okay'"),
80+
(Mood::Happy, "'happy'"),
81+
],
82+
);
83+
}
84+
5685
#[test]
5786
fn wrong_name() {
5887
#[derive(Debug, ToSql, FromSql, PartialEq)]

postgres-derive/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ proc-macro = true
1212
test = false
1313

1414
[dependencies]
15+
convert_case = "0.6"
16+
itertools = "0.10"
1517
syn = "1.0"
1618
proc-macro2 = "1.0"
19+
strum = "0.24"
20+
strum_macros = "0.24"
1721
quote = "1.0"

postgres-derive/src/composites.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use syn::{
44
TypeParamBound,
55
};
66

7-
use crate::overrides::Overrides;
7+
use crate::{field_variant_overrides::FieldVariantOverrides, struct_overrides::StructOverrides};
88

99
pub struct Field {
1010
pub name: String,
@@ -13,17 +13,20 @@ pub struct Field {
1313
}
1414

1515
impl Field {
16-
pub fn parse(raw: &syn::Field) -> Result<Field, Error> {
17-
let overrides = Overrides::extract(&raw.attrs)?;
16+
pub fn parse(struct_overrides: &StructOverrides, raw: &syn::Field) -> Result<Field, Error> {
17+
use convert_case::Casing;
18+
19+
let mut overrides = FieldVariantOverrides::extract(&raw.attrs)?;
1820

1921
let ident = raw.ident.as_ref().unwrap().clone();
2022
Ok(Field {
21-
name: overrides.name.unwrap_or_else(|| {
23+
name: overrides.name.take().unwrap_or_else(|| {
2224
let name = ident.to_string();
23-
match name.strip_prefix("r#") {
24-
Some(name) => name.to_string(),
25-
None => name,
26-
}
25+
let name = name.strip_prefix("r#").map(String::from).unwrap_or(name);
26+
struct_overrides
27+
.rename_all
28+
.map(|case| name.to_case(case))
29+
.unwrap_or(name)
2730
}),
2831
ident,
2932
type_: raw.ty.clone(),

postgres-derive/src/enums.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
use syn::{Error, Fields, Ident};
22

3-
use crate::overrides::Overrides;
3+
use crate::{field_variant_overrides::FieldVariantOverrides, struct_overrides::StructOverrides};
44

55
pub struct Variant {
66
pub ident: Ident,
77
pub name: String,
88
}
99

1010
impl Variant {
11-
pub fn parse(raw: &syn::Variant) -> Result<Variant, Error> {
11+
pub fn parse(struct_overrides: &StructOverrides, raw: &syn::Variant) -> Result<Variant, Error> {
12+
use convert_case::Casing;
13+
1214
match raw.fields {
1315
Fields::Unit => {}
1416
_ => {
@@ -19,10 +21,17 @@ impl Variant {
1921
}
2022
}
2123

22-
let overrides = Overrides::extract(&raw.attrs)?;
24+
let mut overrides = FieldVariantOverrides::extract(&raw.attrs)?;
2325
Ok(Variant {
2426
ident: raw.ident.clone(),
25-
name: overrides.name.unwrap_or_else(|| raw.ident.to_string()),
27+
name: overrides.name.take().unwrap_or_else(|| {
28+
let name = raw.ident.to_string();
29+
let name = name.strip_prefix("r#").map(String::from).unwrap_or(name);
30+
struct_overrides
31+
.rename_all
32+
.map(|case| name.to_case(case))
33+
.unwrap_or(name)
34+
}),
2635
})
2736
}
2837
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use syn::{Attribute, Error, Lit, Meta, NestedMeta};
2+
3+
#[derive(Default)]
4+
pub struct FieldVariantOverrides {
5+
pub name: Option<String>,
6+
}
7+
8+
impl FieldVariantOverrides {
9+
pub fn extract(attrs: &[Attribute]) -> Result<FieldVariantOverrides, Error> {
10+
let mut overrides: FieldVariantOverrides = Default::default();
11+
12+
for attr in attrs {
13+
let attr = attr.parse_meta()?;
14+
15+
if !attr.path().is_ident("postgres") {
16+
continue;
17+
}
18+
19+
let list = match attr {
20+
Meta::List(ref list) => list,
21+
bad => return Err(Error::new_spanned(bad, "expected a #[postgres(...)]")),
22+
};
23+
24+
for item in &list.nested {
25+
match item {
26+
NestedMeta::Meta(Meta::NameValue(meta)) => {
27+
if meta.path.is_ident("name") {
28+
let value = match &meta.lit {
29+
Lit::Str(s) => s.value(),
30+
bad => {
31+
return Err(Error::new_spanned(
32+
bad,
33+
"expected a string literal",
34+
))
35+
}
36+
};
37+
overrides.name = Some(value);
38+
} else {
39+
return Err(Error::new_spanned(&meta.path, "unknown override"));
40+
}
41+
}
42+
bad => return Err(Error::new_spanned(bad, "unknown attribute")),
43+
}
44+
}
45+
}
46+
47+
Ok(overrides)
48+
}
49+
}

postgres-derive/src/fromsql.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ use crate::accepts;
1212
use crate::composites::Field;
1313
use crate::composites::{append_generic_bound, new_derive_path};
1414
use crate::enums::Variant;
15-
use crate::overrides::Overrides;
15+
use crate::struct_overrides::StructOverrides;
1616

1717
pub fn expand_derive_fromsql(input: DeriveInput) -> Result<TokenStream, Error> {
18-
let overrides = Overrides::extract(&input.attrs)?;
18+
let mut overrides = StructOverrides::extract(&input.attrs)?;
1919

2020
if overrides.name.is_some() && overrides.transparent {
2121
return Err(Error::new_spanned(
@@ -24,7 +24,10 @@ pub fn expand_derive_fromsql(input: DeriveInput) -> Result<TokenStream, Error> {
2424
));
2525
}
2626

27-
let name = overrides.name.unwrap_or_else(|| input.ident.to_string());
27+
let name = overrides
28+
.name
29+
.take()
30+
.unwrap_or_else(|| input.ident.to_string());
2831

2932
let (accepts_body, to_sql_body) = if overrides.transparent {
3033
match input.data {
@@ -51,7 +54,7 @@ pub fn expand_derive_fromsql(input: DeriveInput) -> Result<TokenStream, Error> {
5154
let variants = data
5255
.variants
5356
.iter()
54-
.map(Variant::parse)
57+
.map(|variant| Variant::parse(&overrides, variant))
5558
.collect::<Result<Vec<_>, _>>()?;
5659
(
5760
accepts::enum_body(&name, &variants),
@@ -75,7 +78,7 @@ pub fn expand_derive_fromsql(input: DeriveInput) -> Result<TokenStream, Error> {
7578
let fields = fields
7679
.named
7780
.iter()
78-
.map(Field::parse)
81+
.map(|field| Field::parse(&overrides, field))
7982
.collect::<Result<Vec<_>, _>>()?;
8083
(
8184
accepts::composite_body(&name, "FromSql", &fields),

postgres-derive/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ use syn::parse_macro_input;
99
mod accepts;
1010
mod composites;
1111
mod enums;
12+
mod field_variant_overrides;
1213
mod fromsql;
13-
mod overrides;
14+
mod rename_rule;
15+
mod struct_overrides;
1416
mod tosql;
1517

1618
#[proc_macro_derive(ToSql, attributes(postgres))]

postgres-derive/src/overrides.rs

Lines changed: 0 additions & 57 deletions
This file was deleted.

postgres-derive/src/rename_rule.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use strum_macros::{Display, EnumIter, EnumString};
2+
3+
#[derive(Clone, Copy, Display, EnumIter, EnumString)]
4+
pub enum RenameRule {
5+
#[strum(serialize = "camelCase")]
6+
Camel,
7+
#[strum(serialize = "kebab-case")]
8+
Kebab,
9+
#[strum(serialize = "lowercase")]
10+
Lower,
11+
#[strum(serialize = "PascalCase")]
12+
Pascal,
13+
#[strum(serialize = "SCREAMING-KEBAB-CASE")]
14+
ScreamingKebab,
15+
#[strum(serialize = "SCREAMING_SNAKE_CASE")]
16+
ScreamingSnake,
17+
#[strum(serialize = "snake_case")]
18+
Snake,
19+
#[strum(serialize = "UPPERCASE")]
20+
Upper,
21+
}
22+
23+
impl From<RenameRule> for convert_case::Case {
24+
fn from(rule: RenameRule) -> Self {
25+
match rule {
26+
RenameRule::Camel => Self::Camel,
27+
RenameRule::Kebab => Self::Kebab,
28+
RenameRule::Lower => Self::Lower,
29+
RenameRule::Pascal => Self::Pascal,
30+
RenameRule::ScreamingKebab => Self::UpperKebab,
31+
RenameRule::ScreamingSnake => Self::UpperSnake,
32+
RenameRule::Snake => Self::Snake,
33+
RenameRule::Upper => Self::Upper,
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)