use darling::{Error, FromDeriveInput, FromField, FromMeta, ast, util};
#[derive(Debug, Clone)]
pub enum MethodFilterSpec {
Fields(Vec<syn::Ident>),
Raw(String),
}
#[derive(Debug, Clone)]
pub struct MethodSpec {
pub method_name: syn::Ident,
pub filter: MethodFilterSpec,
}
impl FromMeta for MethodSpec {
fn from_expr(expr: &syn::Expr) -> darling::Result<Self> {
let syn::Expr::Call(call) = expr else {
return Err(
Error::custom("expected `method_name(field1, field2, ...)`").with_span(expr)
);
};
let syn::Expr::Path(func_path) = call.func.as_ref() else {
return Err(Error::custom("expected a method name here").with_span(&call.func));
};
let method_name = func_path
.path
.get_ident()
.ok_or_else(|| {
Error::custom("method name must be a plain identifier").with_span(func_path)
})?
.clone();
if let [syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
})] = call.args.iter().collect::<Vec<_>>().as_slice()
{
return Ok(MethodSpec {
method_name,
filter: MethodFilterSpec::Raw(s.value()),
});
}
let fields = call
.args
.iter()
.map(|arg| {
let syn::Expr::Path(field_path) = arg else {
return Err(Error::custom("expected a field name").with_span(arg));
};
field_path.path.get_ident().cloned().ok_or_else(|| {
Error::custom("field name must be a plain identifier").with_span(field_path)
})
})
.collect::<darling::Result<Vec<_>>>()?;
if fields.is_empty() {
return Err(
Error::custom("expected at least one field name in parentheses").with_span(call),
);
}
Ok(MethodSpec {
method_name,
filter: MethodFilterSpec::Fields(fields),
})
}
}
#[derive(Debug, Clone)]
pub struct QuotedString(pub String);
impl FromMeta for QuotedString {
fn from_expr(expr: &syn::Expr) -> darling::Result<Self> {
match expr {
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}) => Ok(QuotedString(s.value())),
syn::Expr::Path(p) if p.path.get_ident().is_some() => Err(Error::custom(format!(
"expected a quoted string; write `\"{}\"` instead of `{}`",
p.path.get_ident().unwrap(),
p.path.get_ident().unwrap()
))
.with_span(expr)),
_ => Err(Error::custom("expected a quoted string, e.g. `\"users\"`").with_span(expr)),
}
}
}
#[derive(Debug, Clone)]
pub struct TypeExpr(pub syn::Type);
impl FromMeta for TypeExpr {
fn from_expr(expr: &syn::Expr) -> darling::Result<Self> {
let tokens = quote::ToTokens::to_token_stream(expr);
syn::parse2::<syn::Type>(tokens)
.map(TypeExpr)
.map_err(|e| Error::custom(format!("expected a type here: {e}")).with_span(expr))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Operator {
Eq,
Gt,
Lt,
Gte,
Lte,
Like,
Ilike,
In,
NotIn,
}
impl Operator {
pub fn sql_symbol(self) -> &'static str {
match self {
Operator::Eq => "=",
Operator::Gt => ">",
Operator::Lt => "<",
Operator::Gte => ">=",
Operator::Lte => "<=",
Operator::Like => "LIKE",
Operator::Ilike => "ILIKE",
Operator::In => unreachable!("`In` is rendered as `= ANY($n)`, not `<ident> <symbol>`"),
Operator::NotIn => {
unreachable!("`NotIn` is rendered as `!= ALL($n)`, not `<ident> <symbol>`")
}
}
}
}
impl FromMeta for Operator {
fn from_expr(expr: &syn::Expr) -> darling::Result<Self> {
let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(s),
..
}) = expr
else {
return Err(Error::custom("expected a quoted operator, e.g. `op = \"gt\"`")
.with_span(expr));
};
match s.value().as_str() {
"eq" => Ok(Operator::Eq),
"gt" => Ok(Operator::Gt),
"lt" => Ok(Operator::Lt),
"gte" => Ok(Operator::Gte),
"lte" => Ok(Operator::Lte),
"like" => Ok(Operator::Like),
"ilike" => Ok(Operator::Ilike),
"in" => Ok(Operator::In),
"not_in" => Ok(Operator::NotIn),
other => Err(Error::custom(format!(
"unknown operator `{other}`; expected one of: eq, gt, lt, gte, lte, like, ilike, in, not_in"
))
.with_span(expr)),
}
}
}
#[derive(Debug, Clone)]
pub struct OperatorList(pub Vec<Operator>);
impl FromMeta for OperatorList {
fn from_expr(expr: &syn::Expr) -> darling::Result<Self> {
match expr {
syn::Expr::Array(array) => {
let ops = array
.elems
.iter()
.map(Operator::from_expr)
.collect::<darling::Result<Vec<_>>>()?;
if ops.is_empty() {
return Err(
Error::custom("expected at least one operator in the list")
.with_span(array),
);
}
Ok(OperatorList(ops))
}
other => Operator::from_expr(other).map(|op| OperatorList(vec![op])),
}
}
}
#[derive(Debug, Clone, FromField)]
#[darling(attributes(table))]
pub struct FieldAttrs {
pub ident: Option<syn::Ident>,
pub ty: syn::Type,
#[darling(default)]
pub select: util::Flag,
#[darling(default)]
pub select_many: util::Flag,
#[darling(default)]
pub update: util::Flag,
#[darling(default)]
pub delete: util::Flag,
#[darling(default)]
pub upsert: util::Flag,
#[darling(default)]
pub as_type: Option<String>,
#[darling(default)]
pub op: Option<OperatorList>,
}
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(table), supports(struct_named))]
pub struct TableAttrs {
pub ident: syn::Ident,
pub data: ast::Data<util::Ignored, FieldAttrs>,
#[darling(default)]
pub name: Option<QuotedString>,
#[darling(default)]
pub spec_columns: Option<QuotedString>,
#[darling(default)]
pub return_type: Option<TypeExpr>,
#[darling(default)]
pub return_fields: Option<QuotedString>,
#[darling(default, multiple, rename = "select")]
pub custom_select: Vec<MethodSpec>,
#[darling(default, multiple, rename = "select_many")]
pub custom_select_many: Vec<MethodSpec>,
#[darling(default, multiple, rename = "delete")]
pub custom_delete: Vec<MethodSpec>,
#[darling(default, multiple, rename = "update")]
pub custom_update: Vec<MethodSpec>,
}