blob: eb0fad039f09413484428c2a4d5e145d254de1d6 [file] [log] [blame]
use proc_macro2::{Span, TokenStream};
use syn::spanned::Spanned;
use syn::{Field as SynField, Ident, Index, Result, Type};
use crate::attrs::{parse_attributes, AttributeSpanWrapper, FieldAttr, SqlIdentifier};
pub struct Field {
pub ty: Type,
pub span: Span,
pub name: FieldName,
column_name: Option<AttributeSpanWrapper<SqlIdentifier>>,
pub sql_type: Option<AttributeSpanWrapper<Type>>,
pub serialize_as: Option<AttributeSpanWrapper<Type>>,
pub deserialize_as: Option<AttributeSpanWrapper<Type>>,
pub select_expression: Option<AttributeSpanWrapper<SelectExpr>>,
pub select_expression_type: Option<AttributeSpanWrapper<Type>>,
pub embed: Option<AttributeSpanWrapper<bool>>,
}
impl Field {
pub fn from_struct_field(field: &SynField, index: usize) -> Result<Self> {
let SynField {
ident, attrs, ty, ..
} = field;
let mut column_name = None;
let mut sql_type = None;
let mut serialize_as = None;
let mut deserialize_as = None;
let mut embed = None;
let mut select_expression = None;
let mut select_expression_type = None;
for attr in parse_attributes(attrs)? {
let attribute_span = attr.attribute_span;
let ident_span = attr.ident_span;
match attr.item {
FieldAttr::ColumnName(_, value) => {
column_name = Some(AttributeSpanWrapper {
item: value,
attribute_span,
ident_span,
})
}
FieldAttr::SqlType(_, value) => {
sql_type = Some(AttributeSpanWrapper {
item: Type::Path(value),
attribute_span,
ident_span,
})
}
FieldAttr::SerializeAs(_, value) => {
serialize_as = Some(AttributeSpanWrapper {
item: Type::Path(value),
attribute_span,
ident_span,
})
}
FieldAttr::DeserializeAs(_, value) => {
deserialize_as = Some(AttributeSpanWrapper {
item: Type::Path(value),
attribute_span,
ident_span,
})
}
FieldAttr::SelectExpression(_, value) => {
select_expression = Some(AttributeSpanWrapper {
item: value,
attribute_span,
ident_span,
})
}
FieldAttr::SelectExpressionType(_, value) => {
select_expression_type = Some(AttributeSpanWrapper {
item: value,
attribute_span,
ident_span,
})
}
FieldAttr::Embed(_) => {
embed = Some(AttributeSpanWrapper {
item: true,
attribute_span,
ident_span,
})
}
}
}
let name = match ident.clone() {
Some(x) => FieldName::Named(x),
None => FieldName::Unnamed(index.into()),
};
let span = match name {
FieldName::Named(ref ident) => ident.span(),
FieldName::Unnamed(_) => ty.span(),
};
Ok(Self {
ty: ty.clone(),
span,
name,
column_name,
sql_type,
serialize_as,
deserialize_as,
select_expression,
select_expression_type,
embed,
})
}
pub fn column_name(&self) -> Result<SqlIdentifier> {
let identifier = self.column_name.as_ref().map(|a| a.item.clone());
if let Some(identifier) = identifier {
Ok(identifier)
} else {
match self.name {
FieldName::Named(ref x) => Ok(x.into()),
FieldName::Unnamed(ref x) => Err(syn::Error::new(
x.span(),
"All fields of tuple structs must be annotated with `#[diesel(column_name)]`",
)),
}
}
}
pub fn ty_for_deserialize(&self) -> &Type {
if let Some(AttributeSpanWrapper { item: value, .. }) = &self.deserialize_as {
value
} else {
&self.ty
}
}
pub(crate) fn embed(&self) -> bool {
self.embed.as_ref().map(|a| a.item).unwrap_or(false)
}
}
pub enum FieldName {
Named(Ident),
Unnamed(Index),
}
impl quote::ToTokens for FieldName {
fn to_tokens(&self, tokens: &mut TokenStream) {
match *self {
FieldName::Named(ref x) => x.to_tokens(tokens),
FieldName::Unnamed(ref x) => x.to_tokens(tokens),
}
}
}
/// We use this instead of directly `syn::Expr` to reduce compilation time
///
/// `syn::Expr` does not properly support tuples when `syn/full` feature is
/// not enabled, and that feature slightly increases compilation time
#[allow(clippy::large_enum_variant)]
pub enum SelectExpr {
Expr(syn::Expr),
Tuple {
paren_token: syn::token::Paren,
content: proc_macro2::TokenStream,
},
}
impl quote::ToTokens for SelectExpr {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
SelectExpr::Expr(ref e) => e.to_tokens(tokens),
SelectExpr::Tuple {
ref paren_token,
ref content,
} => paren_token.surround(tokens, |tokens| content.to_tokens(tokens)),
}
}
}
impl syn::parse::Parse for SelectExpr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(syn::token::Paren) {
let content;
let paren_token = syn::parenthesized!(content in input);
Ok(Self::Tuple {
paren_token,
content: content.parse()?,
})
} else {
input.parse::<syn::Expr>().map(Self::Expr)
}
}
}