Merge pull request #857 from malobre/feature/derive-transparent
Derive transparent
This commit is contained in:
commit
774016d5c7
35
postgres-derive-test/src/compile-fail/invalid-transparent.rs
Normal file
35
postgres-derive-test/src/compile-fail/invalid-transparent.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use postgres_types::{FromSql, ToSql};
|
||||
|
||||
#[derive(ToSql, Debug)]
|
||||
#[postgres(transparent)]
|
||||
struct ToSqlTransparentStruct {
|
||||
a: i32
|
||||
}
|
||||
|
||||
#[derive(FromSql, Debug)]
|
||||
#[postgres(transparent)]
|
||||
struct FromSqlTransparentStruct {
|
||||
a: i32
|
||||
}
|
||||
|
||||
#[derive(ToSql, Debug)]
|
||||
#[postgres(transparent)]
|
||||
enum ToSqlTransparentEnum {
|
||||
Foo
|
||||
}
|
||||
|
||||
#[derive(FromSql, Debug)]
|
||||
#[postgres(transparent)]
|
||||
enum FromSqlTransparentEnum {
|
||||
Foo
|
||||
}
|
||||
|
||||
#[derive(ToSql, Debug)]
|
||||
#[postgres(transparent)]
|
||||
struct ToSqlTransparentTwoFieldTupleStruct(i32, i32);
|
||||
|
||||
#[derive(FromSql, Debug)]
|
||||
#[postgres(transparent)]
|
||||
struct FromSqlTransparentTwoFieldTupleStruct(i32, i32);
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,49 @@
|
||||
error: #[postgres(transparent)] may only be applied to single field tuple structs
|
||||
--> src/compile-fail/invalid-transparent.rs:4:1
|
||||
|
|
||||
4 | / #[postgres(transparent)]
|
||||
5 | | struct ToSqlTransparentStruct {
|
||||
6 | | a: i32
|
||||
7 | | }
|
||||
| |_^
|
||||
|
||||
error: #[postgres(transparent)] may only be applied to single field tuple structs
|
||||
--> src/compile-fail/invalid-transparent.rs:10:1
|
||||
|
|
||||
10 | / #[postgres(transparent)]
|
||||
11 | | struct FromSqlTransparentStruct {
|
||||
12 | | a: i32
|
||||
13 | | }
|
||||
| |_^
|
||||
|
||||
error: #[postgres(transparent)] may only be applied to single field tuple structs
|
||||
--> src/compile-fail/invalid-transparent.rs:16:1
|
||||
|
|
||||
16 | / #[postgres(transparent)]
|
||||
17 | | enum ToSqlTransparentEnum {
|
||||
18 | | Foo
|
||||
19 | | }
|
||||
| |_^
|
||||
|
||||
error: #[postgres(transparent)] may only be applied to single field tuple structs
|
||||
--> src/compile-fail/invalid-transparent.rs:22:1
|
||||
|
|
||||
22 | / #[postgres(transparent)]
|
||||
23 | | enum FromSqlTransparentEnum {
|
||||
24 | | Foo
|
||||
25 | | }
|
||||
| |_^
|
||||
|
||||
error: #[postgres(transparent)] may only be applied to single field tuple structs
|
||||
--> src/compile-fail/invalid-transparent.rs:28:1
|
||||
|
|
||||
28 | / #[postgres(transparent)]
|
||||
29 | | struct ToSqlTransparentTwoFieldTupleStruct(i32, i32);
|
||||
| |_____________________________________________________^
|
||||
|
||||
error: #[postgres(transparent)] may only be applied to single field tuple structs
|
||||
--> src/compile-fail/invalid-transparent.rs:32:1
|
||||
|
|
||||
32 | / #[postgres(transparent)]
|
||||
33 | | struct FromSqlTransparentTwoFieldTupleStruct(i32, i32);
|
||||
| |_______________________________________________________^
|
@ -7,6 +7,7 @@ use std::fmt;
|
||||
mod composites;
|
||||
mod domains;
|
||||
mod enums;
|
||||
mod transparent;
|
||||
|
||||
pub fn test_type<T, S>(conn: &mut Client, sql_type: &str, checks: &[(T, S)])
|
||||
where
|
||||
|
18
postgres-derive-test/src/transparent.rs
Normal file
18
postgres-derive-test/src/transparent.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use postgres::{Client, NoTls};
|
||||
use postgres_types::{FromSql, ToSql};
|
||||
|
||||
#[test]
|
||||
fn round_trip() {
|
||||
#[derive(FromSql, ToSql, Debug, PartialEq)]
|
||||
#[postgres(transparent)]
|
||||
struct UserId(i32);
|
||||
|
||||
assert_eq!(
|
||||
Client::connect("user=postgres host=localhost port=5433", NoTls)
|
||||
.unwrap()
|
||||
.query_one("SELECT $1::integer", &[&UserId(123)])
|
||||
.unwrap()
|
||||
.get::<_, UserId>(0),
|
||||
UserId(123)
|
||||
);
|
||||
}
|
@ -6,6 +6,14 @@ use syn::Ident;
|
||||
use crate::composites::Field;
|
||||
use crate::enums::Variant;
|
||||
|
||||
pub fn transparent_body(field: &syn::Field) -> TokenStream {
|
||||
let ty = &field.ty;
|
||||
|
||||
quote! {
|
||||
<#ty as ::postgres_types::ToSql>::accepts(type_)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn domain_body(name: &str, field: &syn::Field) -> TokenStream {
|
||||
let ty = &field.ty;
|
||||
|
||||
|
@ -11,9 +11,36 @@ use crate::overrides::Overrides;
|
||||
pub fn expand_derive_fromsql(input: DeriveInput) -> Result<TokenStream, Error> {
|
||||
let overrides = Overrides::extract(&input.attrs)?;
|
||||
|
||||
if overrides.name.is_some() && overrides.transparent {
|
||||
return Err(Error::new_spanned(
|
||||
&input,
|
||||
"#[postgres(transparent)] is not allowed with #[postgres(name = \"...\")]",
|
||||
));
|
||||
}
|
||||
|
||||
let name = overrides.name.unwrap_or_else(|| input.ident.to_string());
|
||||
|
||||
let (accepts_body, to_sql_body) = match input.data {
|
||||
let (accepts_body, to_sql_body) = if overrides.transparent {
|
||||
match input.data {
|
||||
Data::Struct(DataStruct {
|
||||
fields: Fields::Unnamed(ref fields),
|
||||
..
|
||||
}) if fields.unnamed.len() == 1 => {
|
||||
let field = fields.unnamed.first().unwrap();
|
||||
(
|
||||
accepts::transparent_body(field),
|
||||
transparent_body(&input.ident, field),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
input,
|
||||
"#[postgres(transparent)] may only be applied to single field tuple structs",
|
||||
))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match input.data {
|
||||
Data::Enum(ref data) => {
|
||||
let variants = data
|
||||
.variants
|
||||
@ -55,6 +82,7 @@ pub fn expand_derive_fromsql(input: DeriveInput) -> Result<TokenStream, Error> {
|
||||
"#[derive(FromSql)] may only be applied to structs, single field tuple structs, and enums",
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let ident = &input.ident;
|
||||
@ -77,6 +105,13 @@ pub fn expand_derive_fromsql(input: DeriveInput) -> Result<TokenStream, Error> {
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn transparent_body(ident: &Ident, field: &syn::Field) -> TokenStream {
|
||||
let ty = &field.ty;
|
||||
quote! {
|
||||
<#ty as postgres_types::FromSql>::from_sql(_type, buf).map(#ident)
|
||||
}
|
||||
}
|
||||
|
||||
fn enum_body(ident: &Ident, variants: &[Variant]) -> TokenStream {
|
||||
let variant_names = variants.iter().map(|v| &v.name);
|
||||
let idents = iter::repeat(ident);
|
||||
|
@ -4,6 +4,7 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
mod accepts;
|
||||
mod composites;
|
||||
@ -14,7 +15,8 @@ mod tosql;
|
||||
|
||||
#[proc_macro_derive(ToSql, attributes(postgres))]
|
||||
pub fn derive_tosql(input: TokenStream) -> TokenStream {
|
||||
let input = syn::parse(input).unwrap();
|
||||
let input = parse_macro_input!(input);
|
||||
|
||||
tosql::expand_derive_tosql(input)
|
||||
.unwrap_or_else(|e| e.to_compile_error())
|
||||
.into()
|
||||
@ -22,7 +24,8 @@ pub fn derive_tosql(input: TokenStream) -> TokenStream {
|
||||
|
||||
#[proc_macro_derive(FromSql, attributes(postgres))]
|
||||
pub fn derive_fromsql(input: TokenStream) -> TokenStream {
|
||||
let input = syn::parse(input).unwrap();
|
||||
let input = parse_macro_input!(input);
|
||||
|
||||
fromsql::expand_derive_fromsql(input)
|
||||
.unwrap_or_else(|e| e.to_compile_error())
|
||||
.into()
|
||||
|
@ -2,17 +2,18 @@ use syn::{Attribute, Error, Lit, Meta, NestedMeta};
|
||||
|
||||
pub struct Overrides {
|
||||
pub name: Option<String>,
|
||||
pub transparent: bool,
|
||||
}
|
||||
|
||||
impl Overrides {
|
||||
pub fn extract(attrs: &[Attribute]) -> Result<Overrides, Error> {
|
||||
let mut overrides = Overrides { name: None };
|
||||
let mut overrides = Overrides {
|
||||
name: None,
|
||||
transparent: false,
|
||||
};
|
||||
|
||||
for attr in attrs {
|
||||
let attr = match attr.parse_meta() {
|
||||
Ok(meta) => meta,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let attr = attr.parse_meta()?;
|
||||
|
||||
if !attr.path().is_ident("postgres") {
|
||||
continue;
|
||||
@ -39,7 +40,14 @@ impl Overrides {
|
||||
|
||||
overrides.name = Some(value);
|
||||
}
|
||||
bad => return Err(Error::new_spanned(bad, "expected a name-value meta item")),
|
||||
NestedMeta::Meta(Meta::Path(ref path)) => {
|
||||
if !path.is_ident("transparent") {
|
||||
return Err(Error::new_spanned(path, "unknown override"));
|
||||
}
|
||||
|
||||
overrides.transparent = true;
|
||||
}
|
||||
bad => return Err(Error::new_spanned(bad, "unknown attribute")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,46 +11,73 @@ use crate::overrides::Overrides;
|
||||
pub fn expand_derive_tosql(input: DeriveInput) -> Result<TokenStream, Error> {
|
||||
let overrides = Overrides::extract(&input.attrs)?;
|
||||
|
||||
if overrides.name.is_some() && overrides.transparent {
|
||||
return Err(Error::new_spanned(
|
||||
&input,
|
||||
"#[postgres(transparent)] is not allowed with #[postgres(name = \"...\")]",
|
||||
));
|
||||
}
|
||||
|
||||
let name = overrides.name.unwrap_or_else(|| input.ident.to_string());
|
||||
|
||||
let (accepts_body, to_sql_body) = match input.data {
|
||||
Data::Enum(ref data) => {
|
||||
let variants = data
|
||||
.variants
|
||||
.iter()
|
||||
.map(Variant::parse)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
(
|
||||
accepts::enum_body(&name, &variants),
|
||||
enum_body(&input.ident, &variants),
|
||||
)
|
||||
let (accepts_body, to_sql_body) = if overrides.transparent {
|
||||
match input.data {
|
||||
Data::Struct(DataStruct {
|
||||
fields: Fields::Unnamed(ref fields),
|
||||
..
|
||||
}) if fields.unnamed.len() == 1 => {
|
||||
let field = fields.unnamed.first().unwrap();
|
||||
|
||||
(accepts::transparent_body(field), transparent_body())
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
input,
|
||||
"#[postgres(transparent)] may only be applied to single field tuple structs",
|
||||
));
|
||||
}
|
||||
}
|
||||
Data::Struct(DataStruct {
|
||||
fields: Fields::Unnamed(ref fields),
|
||||
..
|
||||
}) if fields.unnamed.len() == 1 => {
|
||||
let field = fields.unnamed.first().unwrap();
|
||||
(accepts::domain_body(&name, field), domain_body())
|
||||
}
|
||||
Data::Struct(DataStruct {
|
||||
fields: Fields::Named(ref fields),
|
||||
..
|
||||
}) => {
|
||||
let fields = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(Field::parse)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
(
|
||||
accepts::composite_body(&name, "ToSql", &fields),
|
||||
composite_body(&fields),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
input,
|
||||
"#[derive(ToSql)] may only be applied to structs, single field tuple structs, and enums",
|
||||
));
|
||||
} else {
|
||||
match input.data {
|
||||
Data::Enum(ref data) => {
|
||||
let variants = data
|
||||
.variants
|
||||
.iter()
|
||||
.map(Variant::parse)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
(
|
||||
accepts::enum_body(&name, &variants),
|
||||
enum_body(&input.ident, &variants),
|
||||
)
|
||||
}
|
||||
Data::Struct(DataStruct {
|
||||
fields: Fields::Unnamed(ref fields),
|
||||
..
|
||||
}) if fields.unnamed.len() == 1 => {
|
||||
let field = fields.unnamed.first().unwrap();
|
||||
|
||||
(accepts::domain_body(&name, field), domain_body())
|
||||
}
|
||||
Data::Struct(DataStruct {
|
||||
fields: Fields::Named(ref fields),
|
||||
..
|
||||
}) => {
|
||||
let fields = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(Field::parse)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
(
|
||||
accepts::composite_body(&name, "ToSql", &fields),
|
||||
composite_body(&fields),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::new_spanned(
|
||||
input,
|
||||
"#[derive(ToSql)] may only be applied to structs, single field tuple structs, and enums",
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -78,6 +105,12 @@ pub fn expand_derive_tosql(input: DeriveInput) -> Result<TokenStream, Error> {
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn transparent_body() -> TokenStream {
|
||||
quote! {
|
||||
postgres_types::ToSql::to_sql(&self.0, _type, buf)
|
||||
}
|
||||
}
|
||||
|
||||
fn enum_body(ident: &Ident, variants: &[Variant]) -> TokenStream {
|
||||
let idents = iter::repeat(ident);
|
||||
let variant_idents = variants.iter().map(|v| &v.ident);
|
||||
|
@ -55,6 +55,21 @@
|
||||
//! struct SessionId(Vec<u8>);
|
||||
//! ```
|
||||
//!
|
||||
//! ## Newtypes
|
||||
//!
|
||||
//! The `#[postgres(transparent)]` attribute can be used on a single-field tuple struct to create a
|
||||
//! Rust-only wrapper type that will use the [`ToSql`] & [`FromSql`] implementation of the inner
|
||||
//! value :
|
||||
//! ```rust
|
||||
//! # #[cfg(feature = "derive")]
|
||||
//! use postgres_types::{ToSql, FromSql};
|
||||
//!
|
||||
//! # #[cfg(feature = "derive")]
|
||||
//! #[derive(Debug, ToSql, FromSql)]
|
||||
//! #[postgres(transparent)]
|
||||
//! struct UserId(i32);
|
||||
//! ```
|
||||
//!
|
||||
//! ## Composites
|
||||
//!
|
||||
//! Postgres composite types correspond to structs in Rust:
|
||||
|
Loading…
Reference in New Issue
Block a user