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