diff --git a/Cargo.toml b/Cargo.toml index 9748736..fa2fad8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,10 +33,11 @@ poem-openapi = { version = "3.0", features = [ typed-builder = { version = "0.18.0", optional = true } welds = { version = "0.2.1", default-features = false, optional = true} +proc-macro-error = "1.0.4" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [package.metadata.workspaces] -independent = true \ No newline at end of file +independent = true diff --git a/src/lib.rs b/src/lib.rs index 13b6898..4e760dc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,14 +5,13 @@ mod view_model; use proc_macro::TokenStream; use proc_macro2::{Group, Ident, TokenTree}; +use proc_macro_error::{abort, proc_macro_error}; use quote::quote; use syn::Attribute; -// TODO: Replace panics with a compile_error - /// Derives any number of models that are a subset of the struct deriving this macro. There are two types of models possible.
/// # view -///A selective subset of fields from the original model of the same types. +///A selective subset of fields from the original model of the same types. /// ///**Arguements:** ///- `name` - The name of the struct the generate (**Required**, **Must be first** e.g. `MyStruct`) @@ -49,7 +48,7 @@ use syn::Attribute; ///- `omit` - A *list* of field names in the original structure to omit (**Required**, e.g. `fields(field1, field2, ...)`) ///- `derive` - A *list* of derivables (in scope) to derive on the generated struct (e.g. `derive(Clone, Debug, thiserror::Error)`) ///- `default_derives` - A *bool*, if `true` *(default)* then the a list of derives will be additionally derived. Otherwise, `false` to avoid this (e.g. `default_derives = false`) -/// +/// ///**Example:** ///```rust /// // Original @@ -62,7 +61,7 @@ use syn::Attribute; /// password: String, /// } ///``` -/// +/// ///Generates: ///```rust /// #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] // <-- Default derives (when *not* disabled) @@ -72,8 +71,9 @@ use syn::Attribute; /// password: Option, /// } ///``` -/// +/// /// For more information, read the crate level documentation. +#[proc_macro_error] #[proc_macro_derive(Models, attributes(view, patch))] pub fn models(input: TokenStream) -> TokenStream { let ast = syn::parse_macro_input!(input as syn::DeriveInput); @@ -123,6 +123,8 @@ fn get_attribute_name(attr: &Attribute) -> Option<&Ident> { attr.meta.path().segments.first().map(|v| &v.ident) } +// "Invalid or missing `{name}` argument, expected a group of args, e.g. `{name}(...)`" + /// Extract a group for a given identifier, e.g. `name(...)`. The `(...)` part is returned. fn take_ident_group(name: &str, args: &mut Vec) -> Option { for (i, tk) in args.iter().enumerate() { @@ -137,9 +139,7 @@ fn take_ident_group(name: &str, args: &mut Vec) -> Option { } return Some(g); }, - _ => panic!( - "Invalid or missing `{name}` argument, expected a group of args, e.g. `{name}(...)`" - ), + e => abort!(e, "Invalid or missing `{name}` argument, expected a group of args, e.g. `{name}(...)`"), }, _ => {} } @@ -159,8 +159,8 @@ fn take_ident_bool(name: &str, args: &mut Vec) -> Option { .unwrap_or("Nothing".to_string()); let b = match value == "true" || value == "false" { true => value == "true", - false => panic!( - "Invalid or missing `{name}` argument, expected a bool, e.g. `{name} = true`" + false => abort!( + tk, "Invalid or missing `{name}` argument, expected a bool, e.g. `{name} = true`" ) }; @@ -172,7 +172,8 @@ fn take_ident_bool(name: &str, args: &mut Vec) -> Option { } return Some(b); } - _ => panic!( + _ => abort!( + tk, "Invalid or missing `{name}` argument, expected a bool, e.g. `{name} = true`" ), }, @@ -190,20 +191,22 @@ fn extract_idents(group: Group) -> Vec { .filter_map(|tt| match tt { TokenTree::Ident(ident) => Some(ident), TokenTree::Punct(v) if v.as_char() == ',' => None, - tt => panic!("Invalid syntax, expected a field identifier, got {}`", tt), + tt => abort!(tt, "Invalid syntax, expected a field identifier, got {tt}`"), }) .collect() } -/// Panics on unexpected args to show that they arent valid -fn panic_unexpected_args(names: Vec<&str>, args: &[TokenTree]) { +/// Aborts on unexpected args to show that they arent valid +fn abort_unexpected_args(names: Vec<&str>, args: &[TokenTree]) { for tk in args.iter() { match tk { TokenTree::Ident(v) if names.contains(&v.to_string().as_str()) => {} TokenTree::Ident(v) => { - panic!( + abort!( + v, "Unknown argument `{}`, all known arguments are {:?}", - v, names + v, + names ) } _ => {} diff --git a/src/patch_model.rs b/src/patch_model.rs index 5527ca2..1682108 100644 --- a/src/patch_model.rs +++ b/src/patch_model.rs @@ -1,7 +1,9 @@ use crate::*; + use proc_macro2::{Group, Ident, TokenStream, TokenTree}; use quote::quote; use syn::{Attribute, DeriveInput, Type}; +use proc_macro_error::abort; struct PatchModelArgs { name: Ident, @@ -59,7 +61,7 @@ pub fn impl_patch_model( &oai_f_attributes, )); }), - _ => panic!("Patch Models can only be derived for structs"), + _ => abort!(attr, "Patch Models can only be derived for structs"), }; let derives = get_derive(default_derives, derives.iter().collect()); @@ -254,8 +256,8 @@ fn parse_patch_arg(attr: &Attribute) -> PatchModelArgs { let name = match &tks[0] { TokenTree::Ident(v) => v.clone(), - _ => { - panic!("First argument must be an identifier (name) of the struct for the view") + x => { + abort!(x, "First argument must be an identifier (name) of the struct for the view") } }; @@ -273,7 +275,7 @@ fn parse_patch_arg(attr: &Attribute) -> PatchModelArgs { let omit = parse_omit(&mut args_slice); let derives = parse_derives(&mut args_slice); let default_derives = parse_default_derives(&mut args_slice); - panic_unexpected_args(vec!["fields", "derive", "default_derives"], &args_slice); + abort_unexpected_args(vec!["fields", "derive", "default_derives"], &args_slice); PatchModelArgs { name, diff --git a/src/view_model.rs b/src/view_model.rs index 25fcd1c..a00961f 100644 --- a/src/view_model.rs +++ b/src/view_model.rs @@ -1,5 +1,3 @@ -use core::panic; - use crate::*; use proc_macro2::{Ident, TokenTree}; use quote::quote; @@ -56,7 +54,7 @@ pub fn impl_view_model( } }) .collect(), - _ => panic!("PatchModel can only be derived for structs"), + _ => abort!(attr, "Patch Model can only be derived for structs"), }; let derives = get_derive(default_derives, derives.iter().collect()); @@ -91,19 +89,19 @@ fn parse_view_attributes(attr: &Attribute) -> ViewModelArgs { let name = match &tks[0] { TokenTree::Ident(v) => v.clone(), - _ => { - panic!("First argument must be an identifier (name) of the struct for the view") + x => { + abort!(x, "First argument must be an identifier (name) of the struct for the view") } }; if tks.len() < 3 { - panic!("Invalid syntax, expected at least one argument"); + abort!(attr, "Invalid syntax, expected at least one argument"); } let mut args_slice = tks[2..].to_vec(); - let fields = parse_fields(&mut args_slice); + let fields = parse_fields(&mut args_slice, attr); let derives = parse_derives(&mut args_slice); let default_derives = parse_default_derives(&mut args_slice); - panic_unexpected_args(vec!["fields", "derive", "default_derives"], &args_slice); + abort_unexpected_args(vec!["fields", "derive", "default_derives"], &args_slice); ViewModelArgs { name, @@ -113,12 +111,12 @@ fn parse_view_attributes(attr: &Attribute) -> ViewModelArgs { } } -/// Parse a list of identifiers equal to fields we want in the model. Panics if none are found. -fn parse_fields(args: &mut Vec) -> Vec { +/// Parse a list of identifiers equal to fields we want in the model. Aborts if none are found. +fn parse_fields(args: &mut Vec, attr_spanned: &Attribute) -> Vec { // Extract the fields args and ensuring it is a key-value pair of Ident and Group let fields: Group = match take_ident_group("fields", args) { Some(g) => g, - None => panic!("Missing args, expected `fields(...)"), + None => abort!(attr_spanned, "Missing args, expected `fields(...)"), }; // Parse the fields argument into a TokenStream, skip checking for commas coz lazy