use darling::{ast::NestedMeta, FromMeta};
use heck::ToUpperCamelCase as _;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
spanned::Spanned as _, Error, FnArg, Generics, Ident, ItemFn, Pat, PatType, ReturnType, Type,
Visibility,
};
#[derive(Debug, Default)]
struct Keys(Option<Vec<Ident>>);
impl FromMeta for Keys {
fn from_list(items: &[NestedMeta]) -> darling::Result<Self> {
let mut idents = Vec::new();
for item in items {
match item {
NestedMeta::Meta(syn::Meta::Path(path)) => {
if let Some(ident) = path.get_ident() {
idents.push(ident.clone());
} else {
return Err(darling::Error::custom("expected identifier").with_span(path));
}
}
_ => {
return Err(darling::Error::custom("expected identifier"));
}
}
}
Ok(Keys(Some(idents)))
}
}
#[derive(Debug, Default)]
pub enum OutputEq {
#[default]
None,
PartialEq,
Custom(syn::Path),
}
impl FromMeta for OutputEq {
fn from_word() -> darling::Result<Self> {
Ok(OutputEq::PartialEq)
}
fn from_value(value: &syn::Lit) -> darling::Result<Self> {
Err(darling::Error::unexpected_lit_type(value))
}
fn from_meta(item: &syn::Meta) -> darling::Result<Self> {
match item {
syn::Meta::Path(_) => Ok(OutputEq::PartialEq),
syn::Meta::NameValue(nv) => {
if let syn::Expr::Path(expr_path) = &nv.value {
Ok(OutputEq::Custom(expr_path.path.clone()))
} else {
Err(darling::Error::custom("expected path").with_span(&nv.value))
}
}
syn::Meta::List(_) => Err(darling::Error::unsupported_format("list")),
}
}
}
#[derive(Debug, Default)]
pub enum DebugFormat {
#[default]
Derive,
Custom(String),
}
impl FromMeta for DebugFormat {
fn from_string(value: &str) -> darling::Result<Self> {
Ok(DebugFormat::Custom(value.to_string()))
}
}
#[derive(Debug, Default, FromMeta)]
pub struct QueryAttr {
#[darling(default)]
output_eq: OutputEq,
#[darling(default)]
keys: Keys,
#[darling(default)]
name: Option<String>,
#[darling(default)]
singleton: bool,
#[darling(default)]
debug: DebugFormat,
}
struct Param {
name: Ident,
ty: Type,
}
struct ParsedFn {
vis: Visibility,
name: Ident,
generics: Generics,
params: Vec<Param>,
output_ty: Type,
}
#[derive(Debug, PartialEq)]
enum FormatSegment {
Literal(String),
Field { name: String, specifier: String },
TypeName,
}
fn parse_format_string(format: &str) -> Result<Vec<FormatSegment>, String> {
let mut segments = Vec::new();
let mut chars = format.chars().peekable();
let mut current_literal = String::new();
while let Some(c) = chars.next() {
match c {
'{' => {
if chars.peek() == Some(&'{') {
chars.next();
current_literal.push('{');
} else {
if !current_literal.is_empty() {
segments.push(FormatSegment::Literal(current_literal.clone()));
current_literal.clear();
}
let mut field_content = String::new();
let mut found_close = false;
for ch in chars.by_ref() {
if ch == '}' {
found_close = true;
break;
}
field_content.push(ch);
}
if !found_close {
return Err("unclosed `{` in format string".to_string());
}
let (name, specifier) = if let Some(colon_pos) = field_content.find(':') {
(
field_content[..colon_pos].to_string(),
field_content[colon_pos + 1..].to_string(),
)
} else {
(field_content, String::new())
};
if name.is_empty() {
return Err("empty field name in format string".to_string());
}
if name == "Self" {
if !specifier.is_empty() {
return Err("{Self} does not support format specifiers".to_string());
}
segments.push(FormatSegment::TypeName);
} else {
segments.push(FormatSegment::Field { name, specifier });
}
}
}
'}' => {
if chars.peek() == Some(&'}') {
chars.next();
current_literal.push('}');
} else {
return Err("unmatched `}` in format string".to_string());
}
}
_ => {
current_literal.push(c);
}
}
}
if !current_literal.is_empty() {
segments.push(FormatSegment::Literal(current_literal));
}
Ok(segments)
}
fn validate_format_fields(segments: &[FormatSegment], params: &[Param]) -> Result<(), Error> {
for segment in segments {
if let FormatSegment::Field { name, .. } = segment {
if !params.iter().any(|p| p.name == name.as_str()) {
return Err(Error::new(
proc_macro2::Span::call_site(),
format!("unknown field `{}` in debug format string", name),
));
}
}
}
Ok(())
}
fn generate_custom_debug(
struct_name: &Ident,
generics: &Generics,
format: &str,
params: &[Param],
) -> Result<TokenStream, Error> {
let segments =
parse_format_string(format).map_err(|e| Error::new(proc_macro2::Span::call_site(), e))?;
validate_format_fields(&segments, params)?;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let mut fmt_string = String::new();
let mut fmt_args: Vec<TokenStream> = Vec::new();
for segment in &segments {
match segment {
FormatSegment::Literal(text) => {
for c in text.chars() {
if c == '{' {
fmt_string.push_str("{{");
} else if c == '}' {
fmt_string.push_str("}}");
} else {
fmt_string.push(c);
}
}
}
FormatSegment::Field { name, specifier } => {
let field_ident = format_ident!("{}", name);
if specifier.is_empty() {
fmt_string.push_str("{}");
fmt_args.push(quote! { &self.#field_ident });
} else if specifier == "?" {
fmt_string.push_str("{:?}");
fmt_args.push(quote! { &self.#field_ident });
} else if specifier == "#?" {
fmt_string.push_str("{:#?}");
fmt_args.push(quote! { &self.#field_ident });
} else {
fmt_string.push('{');
fmt_string.push(':');
fmt_string.push_str(specifier);
fmt_string.push('}');
fmt_args.push(quote! { &self.#field_ident });
}
}
FormatSegment::TypeName => {
fmt_string.push_str(&struct_name.to_string());
}
}
}
Ok(quote! {
impl #impl_generics ::std::fmt::Debug for #struct_name #ty_generics #where_clause {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, #fmt_string #(, #fmt_args)*)
}
}
})
}
pub fn generate_query(attr: QueryAttr, input_fn: ItemFn) -> Result<TokenStream, Error> {
let parsed = parse_function(&input_fn)?;
let struct_name = match &attr.name {
Some(name) => format_ident!("{}", name),
None => format_ident!("{}", parsed.name.to_string().to_upper_camel_case()),
};
if attr.singleton && attr.keys.0.is_some() {
return Err(Error::new(
input_fn.sig.span(),
"`singleton` and `keys` are mutually exclusive",
));
}
let key_params: Vec<&Param> = if attr.singleton {
vec![]
} else {
match &attr.keys.0 {
None => {
parsed.params.iter().collect()
}
Some(keys) if keys.is_empty() => {
return Err(Error::new(
input_fn.sig.span(),
"empty `keys()` is not allowed; use `singleton` for queries with no cache key",
));
}
Some(keys) => {
for key in keys {
if !parsed.params.iter().any(|p| p.name == *key) {
return Err(Error::new(
key.span(),
format!("unknown parameter `{}` in keys", key),
));
}
}
parsed
.params
.iter()
.filter(|p| keys.contains(&p.name))
.collect()
}
}
};
let struct_def = generate_struct(&parsed, &struct_name, &key_params, &attr.debug)?;
let query_impl = generate_query_impl(&parsed, &struct_name, &attr)?;
Ok(quote! {
#input_fn
#struct_def
#query_impl
})
}
fn parse_function(input_fn: &ItemFn) -> Result<ParsedFn, Error> {
let vis = input_fn.vis.clone();
let name = input_fn.sig.ident.clone();
let generics = input_fn.sig.generics.clone();
let mut iter = input_fn.sig.inputs.iter();
let first_param = iter.next().ok_or_else(|| {
Error::new(
input_fn.sig.span(),
"query function must have `db: &impl Db` as first parameter",
)
})?;
validate_db_param(first_param)?;
let mut params = Vec::new();
for arg in iter {
match arg {
FnArg::Typed(pat_type) => {
let param = parse_param(pat_type)?;
params.push(param);
}
FnArg::Receiver(_) => {
return Err(Error::new(arg.span(), "query functions cannot have `self`"));
}
}
}
let output_ty = parse_return_type(&input_fn.sig.output)?;
Ok(ParsedFn {
vis,
name,
generics,
params,
output_ty,
})
}
fn validate_db_param(arg: &FnArg) -> Result<(), Error> {
match arg {
FnArg::Typed(_) => {
Ok(())
}
FnArg::Receiver(_) => Err(Error::new(
arg.span(),
"first parameter must be `db: &impl Db`, not `self`",
)),
}
}
fn parse_param(pat_type: &PatType) -> Result<Param, Error> {
let name = match &*pat_type.pat {
Pat::Ident(pat_ident) => pat_ident.ident.clone(),
_ => {
return Err(Error::new(
pat_type.pat.span(),
"expected simple identifier pattern",
))
}
};
let ty = (*pat_type.ty).clone();
Ok(Param { name, ty })
}
fn parse_return_type(ret: &ReturnType) -> Result<Type, Error> {
match ret {
ReturnType::Default => Err(Error::new(
ret.span(),
"query function must return `Result<T, QueryError>`",
)),
ReturnType::Type(_, ty) => {
extract_result_ok_type(ty)
}
}
}
fn extract_result_ok_type(ty: &Type) -> Result<Type, Error> {
if let Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
if segment.ident == "Result" {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
if let Some(syn::GenericArgument::Type(ok_ty)) = args.args.first() {
return Ok(ok_ty.clone());
}
}
}
}
}
Err(Error::new(
ty.span(),
"expected `Result<T, QueryError>` return type",
))
}
fn extract_arc_inner(ty: &Type) -> Option<Type> {
if let Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
if segment.ident == "Arc" {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
return Some(inner_ty.clone());
}
}
}
}
}
None
}
fn extract_type_idents(ty: &Type, idents: &mut std::collections::HashSet<Ident>) {
match ty {
Type::Path(type_path) => {
if type_path.qself.is_none() && type_path.path.segments.len() == 1 {
let segment = &type_path.path.segments[0];
idents.insert(segment.ident.clone());
}
for segment in &type_path.path.segments {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
for arg in &args.args {
if let syn::GenericArgument::Type(inner_ty) = arg {
extract_type_idents(inner_ty, idents);
}
}
}
}
}
Type::Reference(type_ref) => {
extract_type_idents(&type_ref.elem, idents);
}
Type::Slice(type_slice) => {
extract_type_idents(&type_slice.elem, idents);
}
Type::Array(type_array) => {
extract_type_idents(&type_array.elem, idents);
}
Type::Tuple(type_tuple) => {
for elem in &type_tuple.elems {
extract_type_idents(elem, idents);
}
}
Type::Paren(type_paren) => {
extract_type_idents(&type_paren.elem, idents);
}
Type::Group(type_group) => {
extract_type_idents(&type_group.elem, idents);
}
Type::Ptr(type_ptr) => {
extract_type_idents(&type_ptr.elem, idents);
}
Type::TraitObject(type_trait_object) => {
for bound in &type_trait_object.bounds {
if let syn::TypeParamBound::Trait(trait_bound) = bound {
for segment in &trait_bound.path.segments {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
for arg in &args.args {
if let syn::GenericArgument::Type(inner_ty) = arg {
extract_type_idents(inner_ty, idents);
}
}
}
}
}
}
}
Type::ImplTrait(type_impl_trait) => {
for bound in &type_impl_trait.bounds {
if let syn::TypeParamBound::Trait(trait_bound) = bound {
for segment in &trait_bound.path.segments {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
for arg in &args.args {
if let syn::GenericArgument::Type(inner_ty) = arg {
extract_type_idents(inner_ty, idents);
}
}
}
}
}
}
}
Type::BareFn(type_fn) => {
for input in &type_fn.inputs {
extract_type_idents(&input.ty, idents);
}
if let syn::ReturnType::Type(_, ret_ty) = &type_fn.output {
extract_type_idents(ret_ty, idents);
}
}
_ => {}
}
}
fn unused_type_params(generics: &Generics, params: &[Param]) -> Vec<Ident> {
let mut used_idents = std::collections::HashSet::new();
for param in params {
extract_type_idents(¶m.ty, &mut used_idents);
}
let mut unused = Vec::new();
for type_param in generics.type_params() {
if !used_idents.contains(&type_param.ident) {
unused.push(type_param.ident.clone());
}
}
unused
}
fn generate_struct(
parsed: &ParsedFn,
struct_name: &Ident,
key_params: &[&Param],
debug: &DebugFormat,
) -> Result<TokenStream, Error> {
let vis = &parsed.vis;
let generics = &parsed.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let unused_params = unused_type_params(generics, &parsed.params);
let has_type_params = generics.type_params().count() > 0;
let mut fields: Vec<_> = parsed
.params
.iter()
.map(|p| {
let name = &p.name;
let ty = &p.ty;
quote! { pub #name: #ty }
})
.collect();
let phantom_field_names: Vec<_> = unused_params
.iter()
.map(|p| format_ident!("_phantom_{}", p.to_string().to_lowercase()))
.collect();
for (phantom_name, type_param) in phantom_field_names.iter().zip(&unused_params) {
fields.push(quote! { #phantom_name: ::std::marker::PhantomData<#type_param> });
}
let field_names: Vec<_> = parsed.params.iter().map(|p| &p.name).collect();
let field_types: Vec<_> = parsed.params.iter().map(|p| &p.ty).collect();
let phantom_inits: Vec<_> = phantom_field_names
.iter()
.map(|name| quote! { #name: ::std::marker::PhantomData })
.collect();
let new_impl = if parsed.params.is_empty() && phantom_field_names.is_empty() {
quote! {
impl #impl_generics #struct_name #ty_generics #where_clause {
#vis fn new() -> Self {
Self {}
}
}
impl #impl_generics ::std::default::Default for #struct_name #ty_generics #where_clause {
fn default() -> Self {
Self::new()
}
}
}
} else if parsed.params.is_empty() {
quote! {
impl #impl_generics #struct_name #ty_generics #where_clause {
#vis fn new() -> Self {
Self { #( #phantom_inits ),* }
}
}
impl #impl_generics ::std::default::Default for #struct_name #ty_generics #where_clause {
fn default() -> Self {
Self::new()
}
}
}
} else if phantom_field_names.is_empty() {
quote! {
impl #impl_generics #struct_name #ty_generics #where_clause {
#vis fn new(#( #field_names: #field_types ),*) -> Self {
Self { #( #field_names ),* }
}
}
}
} else {
quote! {
impl #impl_generics #struct_name #ty_generics #where_clause {
#vis fn new(#( #field_names: #field_types ),*) -> Self {
Self { #( #field_names, )* #( #phantom_inits ),* }
}
}
}
};
let all_fields_are_keys = key_params.len() == parsed.params.len()
&& key_params
.iter()
.all(|kp| parsed.params.iter().any(|p| p.name == kp.name));
let needs_manual_impls = has_type_params;
let hash_eq_impl = if all_fields_are_keys && !needs_manual_impls {
quote! {}
} else {
let key_names: Vec<_> = if all_fields_are_keys {
parsed.params.iter().map(|p| &p.name).collect()
} else {
key_params.iter().map(|p| &p.name).collect()
};
let hash_body = if key_names.is_empty() {
quote! {}
} else {
quote! {
#( self.#key_names.hash(state); )*
}
};
let eq_body = if key_names.is_empty() {
quote! { true }
} else {
let comparisons: Vec<_> = key_names
.iter()
.map(|name| {
quote! { self.#name == other.#name }
})
.collect();
quote! { #( #comparisons )&&* }
};
quote! {
impl #impl_generics ::std::hash::Hash for #struct_name #ty_generics #where_clause {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
#hash_body
}
}
impl #impl_generics ::std::cmp::PartialEq for #struct_name #ty_generics #where_clause {
fn eq(&self, other: &Self) -> bool {
#eq_body
}
}
impl #impl_generics ::std::cmp::Eq for #struct_name #ty_generics #where_clause {}
}
};
let clone_impl = if needs_manual_impls {
let clone_fields: Vec<_> = field_names
.iter()
.map(|name| quote! { #name: self.#name.clone() })
.collect();
let phantom_clones: Vec<_> = phantom_field_names
.iter()
.map(|name| quote! { #name: ::std::marker::PhantomData })
.collect();
quote! {
impl #impl_generics ::std::clone::Clone for #struct_name #ty_generics #where_clause {
fn clone(&self) -> Self {
Self {
#( #clone_fields, )*
#( #phantom_clones ),*
}
}
}
}
} else {
quote! {}
};
let use_derive_debug = matches!(debug, DebugFormat::Derive);
let debug_impl = if needs_manual_impls && use_derive_debug {
let debug_fields: Vec<_> = parsed
.params
.iter()
.map(|p| {
let name = &p.name;
let name_str = name.to_string();
quote! { .field(#name_str, &self.#name) }
})
.collect();
let struct_name_str = struct_name.to_string();
quote! {
impl #impl_generics ::std::fmt::Debug for #struct_name #ty_generics #where_clause {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.debug_struct(#struct_name_str)
#( #debug_fields )*
.finish()
}
}
}
} else {
quote! {}
};
let derives = if needs_manual_impls {
quote! {}
} else {
match (all_fields_are_keys, use_derive_debug) {
(true, true) => quote! { #[derive(Clone, Debug, Hash, PartialEq, Eq)] },
(true, false) => quote! { #[derive(Clone, Hash, PartialEq, Eq)] },
(false, true) => quote! { #[derive(Clone, Debug)] },
(false, false) => quote! { #[derive(Clone)] },
}
};
let custom_debug_impl = match debug {
DebugFormat::Derive => quote! {},
DebugFormat::Custom(format) => {
generate_custom_debug(struct_name, generics, format, &parsed.params)?
}
};
Ok(quote! {
#derives
#vis struct #struct_name #impl_generics #where_clause {
#( #fields ),*
}
#new_impl
#clone_impl
#hash_eq_impl
#debug_impl
#custom_debug_impl
})
}
fn generate_query_impl(
parsed: &ParsedFn,
struct_name: &Ident,
attr: &QueryAttr,
) -> Result<TokenStream, Error> {
let output_ty = &parsed.output_ty;
let generics = &parsed.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let (actual_output_ty, query_body) = if let Some(inner_ty) = extract_arc_inner(output_ty) {
let fn_name = &parsed.name;
let field_names: Vec<_> = parsed.params.iter().map(|p| &p.name).collect();
(
quote! { #inner_ty },
quote! { #fn_name(db #(,self.#field_names )*) },
)
} else {
let fn_name = &parsed.name;
let field_names: Vec<_> = parsed.params.iter().map(|p| &p.name).collect();
(
quote! { #output_ty },
quote! { #fn_name(db #(,self.#field_names )*).map(::std::sync::Arc::new) },
)
};
let output_eq_impl = match &attr.output_eq {
OutputEq::None | OutputEq::PartialEq => quote! {
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
},
OutputEq::Custom(custom_fn) => quote! {
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
#custom_fn(old, new)
}
},
};
Ok(quote! {
impl #impl_generics ::query_flow::Query for #struct_name #ty_generics #where_clause {
type Output = #actual_output_ty;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
#query_body
}
#output_eq_impl
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
use syn::ItemFn;
fn normalize_tokens(tokens: TokenStream) -> String {
tokens
.to_string()
.split_whitespace()
.collect::<Vec<_>>()
.join(" ")
}
#[test]
fn test_query_macro_preserves_attributes() {
let input_fn: ItemFn = syn::parse_quote! {
#[allow(unused_variables)]
#[inline]
fn my_query(db: &impl Db, x: i32) -> Result<i32, QueryError> {
let unused = 42;
Ok(x * 2)
}
};
let attr = QueryAttr::default();
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
#[allow(unused_variables)]
#[inline]
fn my_query(db: &impl Db, x: i32) -> Result<i32, QueryError> {
let unused = 42;
Ok(x * 2)
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct MyQuery {
pub x: i32
}
impl MyQuery {
#[doc = r" Create a new query instance."]
fn new(x: i32) -> Self {
Self { x }
}
}
impl ::query_flow::Query for MyQuery {
type Output = i32;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
my_query(db, self.x).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_without_attributes() {
let input_fn: ItemFn = syn::parse_quote! {
fn simple(db: &impl Db, a: i32, b: i32) -> Result<i32, QueryError> {
Ok(a + b)
}
};
let attr = QueryAttr::default();
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn simple(db: &impl Db, a: i32, b: i32) -> Result<i32, QueryError> {
Ok(a + b)
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct Simple {
pub a: i32,
pub b: i32
}
impl Simple {
#[doc = r" Create a new query instance."]
fn new(a: i32, b: i32) -> Self {
Self { a, b }
}
}
impl ::query_flow::Query for Simple {
type Output = i32;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
simple(db, self.a, self.b).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_no_params() {
let input_fn: ItemFn = syn::parse_quote! {
fn no_params(db: &impl Db) -> Result<i32, QueryError> {
Ok(42)
}
};
let attr = QueryAttr::default();
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn no_params(db: &impl Db) -> Result<i32, QueryError> {
Ok(42)
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct NoParams {
}
impl NoParams {
#[doc = r" Create a new query instance."]
fn new() -> Self {
Self {}
}
}
impl ::std::default::Default for NoParams {
fn default() -> Self {
Self::new()
}
}
impl ::query_flow::Query for NoParams {
type Output = i32;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
no_params(db).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_arc_output() {
let input_fn: ItemFn = syn::parse_quote! {
fn returns_arc(db: &impl Db, x: i32) -> Result<Arc<String>, QueryError> {
Ok(Arc::new(x.to_string()))
}
};
let attr = QueryAttr::default();
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn returns_arc(db: &impl Db, x: i32) -> Result<Arc<String>, QueryError> {
Ok(Arc::new(x.to_string()))
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct ReturnsArc {
pub x: i32
}
impl ReturnsArc {
#[doc = r" Create a new query instance."]
fn new(x: i32) -> Self {
Self { x }
}
}
impl ::query_flow::Query for ReturnsArc {
type Output = String;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
returns_arc(db, self.x)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_keys_subset() {
let input_fn: ItemFn = syn::parse_quote! {
fn with_keys(db: &impl Db, a: i32, b: String, c: bool) -> Result<i32, QueryError> {
Ok(a)
}
};
let attr = QueryAttr {
output_eq: OutputEq::None,
keys: Keys(Some(vec![format_ident!("a")])),
name: None,
singleton: false,
debug: DebugFormat::Derive,
};
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn with_keys(db: &impl Db, a: i32, b: String, c: bool) -> Result<i32, QueryError> {
Ok(a)
}
#[derive(Clone, Debug)]
struct WithKeys {
pub a: i32,
pub b: String,
pub c: bool
}
impl WithKeys {
#[doc = r" Create a new query instance."]
fn new(a: i32, b: String, c: bool) -> Self {
Self { a, b, c }
}
}
impl ::std::hash::Hash for WithKeys {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
self.a.hash(state);
}
}
impl ::std::cmp::PartialEq for WithKeys {
fn eq(&self, other: &Self) -> bool {
self.a == other.a
}
}
impl ::std::cmp::Eq for WithKeys {}
impl ::query_flow::Query for WithKeys {
type Output = i32;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
with_keys(db, self.a, self.b, self.c).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_keys_multiple() {
let input_fn: ItemFn = syn::parse_quote! {
fn multi_keys(db: &impl Db, a: i32, b: String, c: bool) -> Result<i32, QueryError> {
Ok(a)
}
};
let attr = QueryAttr {
output_eq: OutputEq::None,
keys: Keys(Some(vec![format_ident!("a"), format_ident!("c")])),
name: None,
singleton: false,
debug: DebugFormat::Derive,
};
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn multi_keys(db: &impl Db, a: i32, b: String, c: bool) -> Result<i32, QueryError> {
Ok(a)
}
#[derive(Clone, Debug)]
struct MultiKeys {
pub a: i32,
pub b: String,
pub c: bool
}
impl MultiKeys {
#[doc = r" Create a new query instance."]
fn new(a: i32, b: String, c: bool) -> Self {
Self { a, b, c }
}
}
impl ::std::hash::Hash for MultiKeys {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
self.a.hash(state);
self.c.hash(state);
}
}
impl ::std::cmp::PartialEq for MultiKeys {
fn eq(&self, other: &Self) -> bool {
self.a == other.a && self.c == other.c
}
}
impl ::std::cmp::Eq for MultiKeys {}
impl ::query_flow::Query for MultiKeys {
type Output = i32;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
multi_keys(db, self.a, self.b, self.c).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_keys_all_explicit() {
let input_fn: ItemFn = syn::parse_quote! {
fn all_keys(db: &impl Db, a: i32, b: String) -> Result<i32, QueryError> {
Ok(a)
}
};
let attr = QueryAttr {
output_eq: OutputEq::None,
keys: Keys(Some(vec![format_ident!("a"), format_ident!("b")])),
name: None,
singleton: false,
debug: DebugFormat::Derive,
};
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn all_keys(db: &impl Db, a: i32, b: String) -> Result<i32, QueryError> {
Ok(a)
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct AllKeys {
pub a: i32,
pub b: String
}
impl AllKeys {
#[doc = r" Create a new query instance."]
fn new(a: i32, b: String) -> Self {
Self { a, b }
}
}
impl ::query_flow::Query for AllKeys {
type Output = i32;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
all_keys(db, self.a, self.b).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_keys_unknown_error() {
let input_fn: ItemFn = syn::parse_quote! {
fn bad_keys(db: &impl Db, a: i32) -> Result<i32, QueryError> {
Ok(a)
}
};
let attr = QueryAttr {
output_eq: OutputEq::None,
keys: Keys(Some(vec![format_ident!("unknown")])),
name: None,
singleton: false,
debug: DebugFormat::Derive,
};
let result = generate_query(attr, input_fn);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err
.to_string()
.contains("unknown parameter `unknown` in keys"));
}
#[test]
fn test_query_macro_keys_empty_error() {
let input_fn: ItemFn = syn::parse_quote! {
fn empty_keys(db: &impl Db, a: i32, b: String) -> Result<i32, QueryError> {
Ok(a)
}
};
let attr = QueryAttr {
output_eq: OutputEq::None,
keys: Keys(Some(vec![])),
name: None,
singleton: false,
debug: DebugFormat::Derive,
};
let result = generate_query(attr, input_fn);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("empty `keys()` is not allowed"));
}
#[test]
fn test_query_macro_singleton() {
let input_fn: ItemFn = syn::parse_quote! {
fn singleton_query(db: &impl Db, format: String) -> Result<String, QueryError> {
Ok(format!("result: {}", format))
}
};
let attr = QueryAttr {
output_eq: OutputEq::None,
keys: Keys(None),
name: None,
singleton: true,
debug: DebugFormat::Derive,
};
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn singleton_query(db: &impl Db, format: String) -> Result<String, QueryError> {
Ok(format!("result: {}", format))
}
#[derive(Clone, Debug)]
struct SingletonQuery {
pub format: String
}
impl SingletonQuery {
#[doc = r" Create a new query instance."]
fn new(format: String) -> Self {
Self { format }
}
}
impl ::std::hash::Hash for SingletonQuery {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
}
}
impl ::std::cmp::PartialEq for SingletonQuery {
fn eq(&self, other: &Self) -> bool {
true
}
}
impl ::std::cmp::Eq for SingletonQuery {}
impl ::query_flow::Query for SingletonQuery {
type Output = String;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
singleton_query(db, self.format).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_singleton_keys_mutually_exclusive() {
let input_fn: ItemFn = syn::parse_quote! {
fn bad_query(db: &impl Db, a: i32) -> Result<i32, QueryError> {
Ok(a)
}
};
let attr = QueryAttr {
output_eq: OutputEq::None,
keys: Keys(Some(vec![format_ident!("a")])),
name: None,
singleton: true,
debug: DebugFormat::Derive,
};
let result = generate_query(attr, input_fn);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err
.to_string()
.contains("`singleton` and `keys` are mutually exclusive"));
}
#[test]
fn test_query_macro_custom_debug() {
let input_fn: ItemFn = syn::parse_quote! {
fn fetch_user(db: &impl Db, id: u64, include_deleted: bool) -> Result<String, QueryError> {
Ok(format!("user {}", id))
}
};
let attr = QueryAttr {
output_eq: OutputEq::None,
keys: Keys(None),
name: None,
singleton: false,
debug: DebugFormat::Custom("Fetch({id})".to_string()),
};
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn fetch_user(db: &impl Db, id: u64, include_deleted: bool) -> Result<String, QueryError> {
Ok(format!("user {}", id))
}
#[derive(Clone, Hash, PartialEq, Eq)]
struct FetchUser {
pub id: u64,
pub include_deleted: bool
}
impl FetchUser {
#[doc = r" Create a new query instance."]
fn new(id: u64, include_deleted: bool) -> Self {
Self { id, include_deleted }
}
}
impl ::std::fmt::Debug for FetchUser {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "Fetch({})", &self.id)
}
}
impl ::query_flow::Query for FetchUser {
type Output = String;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
fetch_user(db, self.id, self.include_deleted).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_debug_unknown_field_error() {
let input_fn: ItemFn = syn::parse_quote! {
fn bad_debug(db: &impl Db, id: u64) -> Result<i32, QueryError> {
Ok(42)
}
};
let attr = QueryAttr {
output_eq: OutputEq::None,
keys: Keys(None),
name: None,
singleton: false,
debug: DebugFormat::Custom("Query({unknown_field})".to_string()),
};
let result = generate_query(attr, input_fn);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err
.to_string()
.contains("unknown field `unknown_field` in debug format string"));
}
#[test]
fn test_query_macro_debug_with_keys() {
let input_fn: ItemFn = syn::parse_quote! {
fn query_with_both(db: &impl Db, id: u64, opts: String) -> Result<i32, QueryError> {
Ok(42)
}
};
let attr = QueryAttr {
output_eq: OutputEq::None,
keys: Keys(Some(vec![format_ident!("id")])),
name: None,
singleton: false,
debug: DebugFormat::Custom("Query({id:?})".to_string()),
};
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn query_with_both(db: &impl Db, id: u64, opts: String) -> Result<i32, QueryError> {
Ok(42)
}
#[derive(Clone)]
struct QueryWithBoth {
pub id: u64,
pub opts: String
}
impl QueryWithBoth {
#[doc = r" Create a new query instance."]
fn new(id: u64, opts: String) -> Self {
Self { id, opts }
}
}
impl ::std::hash::Hash for QueryWithBoth {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl ::std::cmp::PartialEq for QueryWithBoth {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl ::std::cmp::Eq for QueryWithBoth {}
impl ::std::fmt::Debug for QueryWithBoth {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "Query({:?})", &self.id)
}
}
impl ::query_flow::Query for QueryWithBoth {
type Output = i32;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
query_with_both(db, self.id, self.opts).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_parse_format_string() {
let result = parse_format_string("Fetch({id})").unwrap();
assert_eq!(
result,
vec![
FormatSegment::Literal("Fetch(".to_string()),
FormatSegment::Field {
name: "id".to_string(),
specifier: String::new()
},
FormatSegment::Literal(")".to_string()),
]
);
let result = parse_format_string("Query({name:?})").unwrap();
assert_eq!(
result,
vec![
FormatSegment::Literal("Query(".to_string()),
FormatSegment::Field {
name: "name".to_string(),
specifier: "?".to_string()
},
FormatSegment::Literal(")".to_string()),
]
);
let result = parse_format_string("{{literal}} {field}").unwrap();
assert_eq!(
result,
vec![
FormatSegment::Literal("{literal} ".to_string()),
FormatSegment::Field {
name: "field".to_string(),
specifier: String::new()
},
]
);
let result = parse_format_string("{Self}({id})").unwrap();
assert_eq!(
result,
vec![
FormatSegment::TypeName,
FormatSegment::Literal("(".to_string()),
FormatSegment::Field {
name: "id".to_string(),
specifier: String::new()
},
FormatSegment::Literal(")".to_string()),
]
);
assert!(parse_format_string("unclosed {brace").is_err());
assert!(parse_format_string("unmatched }").is_err());
assert!(parse_format_string("empty {}").is_err());
assert!(parse_format_string("{Self:?}").is_err()); }
#[test]
fn test_query_macro_debug_with_self() {
let input_fn: ItemFn = syn::parse_quote! {
fn fetch_user(db: &impl Db, id: u64) -> Result<String, QueryError> {
Ok(format!("user {}", id))
}
};
let attr = QueryAttr {
output_eq: OutputEq::None,
keys: Keys(None),
name: None,
singleton: false,
debug: DebugFormat::Custom("{Self}({id})".to_string()),
};
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn fetch_user(db: &impl Db, id: u64) -> Result<String, QueryError> {
Ok(format!("user {}", id))
}
#[derive(Clone, Hash, PartialEq, Eq)]
struct FetchUser {
pub id: u64
}
impl FetchUser {
#[doc = r" Create a new query instance."]
fn new(id: u64) -> Self {
Self { id }
}
}
impl ::std::fmt::Debug for FetchUser {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "FetchUser({})", &self.id)
}
}
impl ::query_flow::Query for FetchUser {
type Output = String;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
fetch_user(db, self.id).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_simple_generic() {
let input_fn: ItemFn = syn::parse_quote! {
fn parse_value<T: Clone>(db: &impl Db, data: Vec<u8>) -> Result<T, QueryError> {
todo!()
}
};
let attr = QueryAttr::default();
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn parse_value<T: Clone>(db: &impl Db, data: Vec<u8>) -> Result<T, QueryError> {
todo!()
}
struct ParseValue<T: Clone> {
pub data: Vec<u8>,
_phantom_t: ::std::marker::PhantomData<T>
}
impl<T: Clone> ParseValue<T> {
#[doc = r" Create a new query instance."]
fn new(data: Vec<u8>) -> Self {
Self { data, _phantom_t: ::std::marker::PhantomData }
}
}
impl<T: Clone> ::std::clone::Clone for ParseValue<T> {
fn clone(&self) -> Self {
Self {
data: self.data.clone(),
_phantom_t: ::std::marker::PhantomData
}
}
}
impl<T: Clone> ::std::hash::Hash for ParseValue<T> {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
self.data.hash(state);
}
}
impl<T: Clone> ::std::cmp::PartialEq for ParseValue<T> {
fn eq(&self, other: &Self) -> bool {
self.data == other.data
}
}
impl<T: Clone> ::std::cmp::Eq for ParseValue<T> {}
impl<T: Clone> ::std::fmt::Debug for ParseValue<T> {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.debug_struct("ParseValue")
.field("data", &self.data)
.finish()
}
}
impl<T: Clone> ::query_flow::Query for ParseValue<T> {
type Output = T;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
parse_value(db, self.data).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_generic_with_where_clause() {
let input_fn: ItemFn = syn::parse_quote! {
fn transform<T, E>(db: &impl Db, input: String) -> Result<T, QueryError>
where
T: Clone + Default,
E: std::error::Error,
{
todo!()
}
};
let attr = QueryAttr::default();
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn transform<T, E>(db: &impl Db, input: String) -> Result<T, QueryError>
where
T: Clone + Default,
E: std::error::Error,
{
todo!()
}
struct Transform<T, E>
where
T: Clone + Default,
E: std::error::Error,
{
pub input: String,
_phantom_t: ::std::marker::PhantomData<T>,
_phantom_e: ::std::marker::PhantomData<E>
}
impl<T, E> Transform<T, E>
where
T: Clone + Default,
E: std::error::Error,
{
#[doc = r" Create a new query instance."]
fn new(input: String) -> Self {
Self { input, _phantom_t: ::std::marker::PhantomData, _phantom_e: ::std::marker::PhantomData }
}
}
impl<T, E> ::std::clone::Clone for Transform<T, E>
where
T: Clone + Default,
E: std::error::Error,
{
fn clone(&self) -> Self {
Self {
input: self.input.clone(),
_phantom_t: ::std::marker::PhantomData,
_phantom_e: ::std::marker::PhantomData
}
}
}
impl<T, E> ::std::hash::Hash for Transform<T, E>
where
T: Clone + Default,
E: std::error::Error,
{
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
self.input.hash(state);
}
}
impl<T, E> ::std::cmp::PartialEq for Transform<T, E>
where
T: Clone + Default,
E: std::error::Error,
{
fn eq(&self, other: &Self) -> bool {
self.input == other.input
}
}
impl<T, E> ::std::cmp::Eq for Transform<T, E>
where
T: Clone + Default,
E: std::error::Error,
{}
impl<T, E> ::std::fmt::Debug for Transform<T, E>
where
T: Clone + Default,
E: std::error::Error,
{
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.debug_struct("Transform")
.field("input", &self.input)
.finish()
}
}
impl<T, E> ::query_flow::Query for Transform<T, E>
where
T: Clone + Default,
E: std::error::Error,
{
type Output = T;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
transform(db, self.input).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_generic_used_in_field() {
let input_fn: ItemFn = syn::parse_quote! {
fn process<T: Clone>(db: &impl Db, value: T) -> Result<String, QueryError> {
todo!()
}
};
let attr = QueryAttr::default();
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn process<T: Clone>(db: &impl Db, value: T) -> Result<String, QueryError> {
todo!()
}
struct Process<T: Clone> {
pub value: T
}
impl<T: Clone> Process<T> {
#[doc = r" Create a new query instance."]
fn new(value: T) -> Self {
Self { value }
}
}
impl<T: Clone> ::std::clone::Clone for Process<T> {
fn clone(&self) -> Self {
Self {
value: self.value.clone(),
}
}
}
impl<T: Clone> ::std::hash::Hash for Process<T> {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
self.value.hash(state);
}
}
impl<T: Clone> ::std::cmp::PartialEq for Process<T> {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl<T: Clone> ::std::cmp::Eq for Process<T> {}
impl<T: Clone> ::std::fmt::Debug for Process<T> {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.debug_struct("Process")
.field("value", &self.value)
.finish()
}
}
impl<T: Clone> ::query_flow::Query for Process<T> {
type Output = String;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
process(db, self.value).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_generic_with_custom_debug() {
let input_fn: ItemFn = syn::parse_quote! {
fn fetch<T>(db: &impl Db, id: u64) -> Result<T, QueryError> {
todo!()
}
};
let attr = QueryAttr {
output_eq: OutputEq::None,
keys: Keys(None),
name: None,
singleton: false,
debug: DebugFormat::Custom("{Self}({id})".to_string()),
};
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn fetch<T>(db: &impl Db, id: u64) -> Result<T, QueryError> {
todo!()
}
struct Fetch<T> {
pub id: u64,
_phantom_t: ::std::marker::PhantomData<T>
}
impl<T> Fetch<T> {
#[doc = r" Create a new query instance."]
fn new(id: u64) -> Self {
Self { id, _phantom_t: ::std::marker::PhantomData }
}
}
impl<T> ::std::clone::Clone for Fetch<T> {
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
_phantom_t: ::std::marker::PhantomData
}
}
}
impl<T> ::std::hash::Hash for Fetch<T> {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl<T> ::std::cmp::PartialEq for Fetch<T> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<T> ::std::cmp::Eq for Fetch<T> {}
impl<T> ::std::fmt::Debug for Fetch<T> {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
write!(f, "Fetch({})", &self.id)
}
}
impl<T> ::query_flow::Query for Fetch<T> {
type Output = T;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
fetch(db, self.id).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_generic_partial_phantom() {
let input_fn: ItemFn = syn::parse_quote! {
fn convert<T, U>(db: &impl Db, input: T) -> Result<U, QueryError> {
todo!()
}
};
let attr = QueryAttr::default();
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn convert<T, U>(db: &impl Db, input: T) -> Result<U, QueryError> {
todo!()
}
struct Convert<T, U> {
pub input: T,
_phantom_u: ::std::marker::PhantomData<U>
}
impl<T, U> Convert<T, U> {
#[doc = r" Create a new query instance."]
fn new(input: T) -> Self {
Self { input, _phantom_u: ::std::marker::PhantomData }
}
}
impl<T, U> ::std::clone::Clone for Convert<T, U> {
fn clone(&self) -> Self {
Self {
input: self.input.clone(),
_phantom_u: ::std::marker::PhantomData
}
}
}
impl<T, U> ::std::hash::Hash for Convert<T, U> {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
self.input.hash(state);
}
}
impl<T, U> ::std::cmp::PartialEq for Convert<T, U> {
fn eq(&self, other: &Self) -> bool {
self.input == other.input
}
}
impl<T, U> ::std::cmp::Eq for Convert<T, U> {}
impl<T, U> ::std::fmt::Debug for Convert<T, U> {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.debug_struct("Convert")
.field("input", &self.input)
.finish()
}
}
impl<T, U> ::query_flow::Query for Convert<T, U> {
type Output = U;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
convert(db, self.input).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_generic_with_keys() {
let input_fn: ItemFn = syn::parse_quote! {
fn cached_parse<T>(db: &impl Db, id: u64, opts: String) -> Result<T, QueryError> {
todo!()
}
};
let attr = QueryAttr {
output_eq: OutputEq::None,
keys: Keys(Some(vec![format_ident!("id")])),
name: None,
singleton: false,
debug: DebugFormat::Derive,
};
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn cached_parse<T>(db: &impl Db, id: u64, opts: String) -> Result<T, QueryError> {
todo!()
}
struct CachedParse<T> {
pub id: u64,
pub opts: String,
_phantom_t: ::std::marker::PhantomData<T>
}
impl<T> CachedParse<T> {
#[doc = r" Create a new query instance."]
fn new(id: u64, opts: String) -> Self {
Self { id, opts, _phantom_t: ::std::marker::PhantomData }
}
}
impl<T> ::std::clone::Clone for CachedParse<T> {
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
opts: self.opts.clone(),
_phantom_t: ::std::marker::PhantomData
}
}
}
impl<T> ::std::hash::Hash for CachedParse<T> {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl<T> ::std::cmp::PartialEq for CachedParse<T> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<T> ::std::cmp::Eq for CachedParse<T> {}
impl<T> ::std::fmt::Debug for CachedParse<T> {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.debug_struct("CachedParse")
.field("id", &self.id)
.field("opts", &self.opts)
.finish()
}
}
impl<T> ::query_flow::Query for CachedParse<T> {
type Output = T;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
cached_parse(db, self.id, self.opts).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
#[test]
fn test_query_macro_no_params_with_generic() {
let input_fn: ItemFn = syn::parse_quote! {
fn get_default<T: Default>(db: &impl Db) -> Result<T, QueryError> {
Ok(T::default())
}
};
let attr = QueryAttr::default();
let output = generate_query(attr, input_fn).unwrap();
let expected = quote! {
fn get_default<T: Default>(db: &impl Db) -> Result<T, QueryError> {
Ok(T::default())
}
struct GetDefault<T: Default> {
_phantom_t: ::std::marker::PhantomData<T>
}
impl<T: Default> GetDefault<T> {
#[doc = r" Create a new query instance."]
fn new() -> Self {
Self { _phantom_t: ::std::marker::PhantomData }
}
}
impl<T: Default> ::std::default::Default for GetDefault<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Default> ::std::clone::Clone for GetDefault<T> {
fn clone(&self) -> Self {
Self {
_phantom_t: ::std::marker::PhantomData
}
}
}
impl<T: Default> ::std::hash::Hash for GetDefault<T> {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
}
}
impl<T: Default> ::std::cmp::PartialEq for GetDefault<T> {
fn eq(&self, other: &Self) -> bool {
true
}
}
impl<T: Default> ::std::cmp::Eq for GetDefault<T> {}
impl<T: Default> ::std::fmt::Debug for GetDefault<T> {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
f.debug_struct("GetDefault")
.finish()
}
}
impl<T: Default> ::query_flow::Query for GetDefault<T> {
type Output = T;
fn query(self, db: &impl ::query_flow::Db) -> ::std::result::Result<::std::sync::Arc<Self::Output>, ::query_flow::QueryError> {
get_default(db).map(::std::sync::Arc::new)
}
fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
old == new
}
}
};
assert_eq!(normalize_tokens(output), normalize_tokens(expected));
}
}