Skip to content

Commit

Permalink
feat: switch to aborting with spans for errors
Browse files Browse the repository at this point in the history
  • Loading branch information
NexRX committed Dec 22, 2023
1 parent 71e0b54 commit c66cb1c
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 33 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
independent = true
37 changes: 20 additions & 17 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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. <br/>
/// # 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`)
Expand Down Expand Up @@ -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
Expand All @@ -62,7 +61,7 @@ use syn::Attribute;
/// password: String,
/// }
///```
///
///
///Generates:
///```rust
/// #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] // <-- Default derives (when *not* disabled)
Expand All @@ -72,8 +71,9 @@ use syn::Attribute;
/// password: Option<String>,
/// }
///```
///
///
/// 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);
Expand Down Expand Up @@ -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<TokenTree>) -> Option<Group> {
for (i, tk) in args.iter().enumerate() {
Expand All @@ -137,9 +139,7 @@ fn take_ident_group(name: &str, args: &mut Vec<TokenTree>) -> Option<Group> {
}
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}(...)`"),
},
_ => {}
}
Expand All @@ -159,8 +159,8 @@ fn take_ident_bool(name: &str, args: &mut Vec<TokenTree>) -> Option<bool> {
.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`"
)
};

Expand All @@ -172,7 +172,8 @@ fn take_ident_bool(name: &str, args: &mut Vec<TokenTree>) -> Option<bool> {
}
return Some(b);
}
_ => panic!(
_ => abort!(
tk,
"Invalid or missing `{name}` argument, expected a bool, e.g. `{name} = true`"
),
},
Expand All @@ -190,20 +191,22 @@ fn extract_idents(group: Group) -> Vec<Ident> {
.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
)
}
_ => {}
Expand Down
10 changes: 6 additions & 4 deletions src/patch_model.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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")
}
};

Expand All @@ -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,
Expand Down
20 changes: 9 additions & 11 deletions src/view_model.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use core::panic;

use crate::*;
use proc_macro2::{Ident, TokenTree};
use quote::quote;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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,
Expand All @@ -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<TokenTree>) -> Vec<Ident> {
/// Parse a list of identifiers equal to fields we want in the model. Aborts if none are found.
fn parse_fields(args: &mut Vec<TokenTree>, attr_spanned: &Attribute) -> Vec<Ident> {
// 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
Expand Down

0 comments on commit c66cb1c

Please sign in to comment.