Skip to content

Commit

Permalink
chore(craft-macros): Improve the robustness of extracting attributes. (
Browse files Browse the repository at this point in the history
…#937)

* chore(craft-macros): When `self.0` is shadowed, it is convenient to directly call the `self.deref()` method.

* chore(craft-macros): Improve the robustness of extracting attributes.

* chore(craft-macros): Add test

* chore(craft-macros): fix test

* chore(craft-macros): fix REGEX_STR

* chore(craft-macros): fix REGEX_STR
  • Loading branch information
andeya authored Sep 29, 2024
1 parent 03b9250 commit cde829d
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 19 deletions.
3 changes: 2 additions & 1 deletion crates/craft-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ proc-macro-crate.workspace = true
proc-macro2.workspace = true
quote.workspace = true
syn = { workspace = true, features = ["full", "parsing"] }
regex.workspace = true

[dev-dependencies]
salvo = { path = "../salvo", features = ["oapi"] }
tokio.workspace = true

[lints]
workspace = true
workspace = true
2 changes: 1 addition & 1 deletion crates/craft-macros/examples/example-add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl Opts {
}
/// doc line 5
/// doc line 6
#[craft(endpoint(responses((status_code = 400, description = "Wrong request parameters."))))]
#[craft(endpoint(responses((status_code = 400, description = "[(Wrong)] request parameters."))))]
pub fn add3(left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(*left + *right).to_string()
}
Expand Down
70 changes: 53 additions & 17 deletions crates/craft-macros/src/craft.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::utils::salvo_crate;
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use regex::Regex;
use syn::parse::Parser;
use syn::{
parse_quote, Attribute, FnArg, Generics, Ident, ImplItem, ImplItemFn, Item, Token, Type,
Expand Down Expand Up @@ -28,36 +29,43 @@ pub(crate) fn generate(input: Item) -> syn::Result<TokenStream> {
}
}

const REGEX_STR: &'static str = r#"(?s)#\s*\[\s*(::)?\s*([A-Za-z_][A-Za-z0-9_]*\s*::\s*)*\s*craft\s*\(\s*(?P<name>handler|endpoint)\s*(?P<content>\(.*\))?\s*\)\s*\]"#;

fn take_method_macro(item_fn: &mut ImplItemFn) -> syn::Result<Option<Attribute>> {
let mut index: Option<usize> = None;
let mut new_attr: Option<Attribute> = None;
let re = Regex::new(REGEX_STR).unwrap();
for (idx, attr) in &mut item_fn.attrs.iter().enumerate() {
if !(match attr.path().segments.last() {
Some(segment) => segment.ident == "craft",
None => false,
}) {
continue;
}
if let Some((_, last)) = attr.to_token_stream().to_string().split_once("craft(") {
if let Some(last) = last.strip_suffix(")]") {
let ts: Option<TokenStream> = if last == "handler" || last.starts_with("handler(") {
Some(format!("#[{}::{last}]", salvo_crate()).parse()?)
} else if last == "endpoint" || last.starts_with("endpoint(") {
Some(format!("#[{}::oapi::{last}]", salvo_crate()).parse()?)
} else {
None
let attr_str = attr.to_token_stream().to_string().trim().to_owned();
if let Some(caps) = re.captures(&attr_str) {
if let Some(name) = caps.name("name") {
let name = name.as_str();
let content = caps
.name("content")
.map(|c| c.as_str().to_string())
.unwrap_or_default();
let ts: TokenStream = match name {
"handler" => format!("#[{}::{name}{content}]", salvo_crate()).parse()?,
"endpoint" => format!("#[{}::oapi::{name}{content}]", salvo_crate()).parse()?,
_ => {
unreachable!()
}
};
if let Some(ts) = ts {
new_attr = Attribute::parse_outer.parse2(ts)?.into_iter().next();
index = Some(idx);
continue;
}
new_attr = Attribute::parse_outer.parse2(ts)?.into_iter().next();
index = Some(idx);
continue;
}
}
return Err(syn::Error::new_spanned(
item_fn,
"The attribute macro #[craft] on a method must be filled with sub-attributes, such as '#[craft(handler)]', '#[craft(endpoint)]', or '#[craft(endpoint(...))]'.",
));
item_fn,
format!("The attribute macro `{attr_str}` on a method must be filled with sub-attributes, such as '#[craft(handler)]', '#[craft(endpoint)]', or '#[craft(endpoint(...))]'."),
));
}
if let Some(index) = index {
item_fn.attrs.remove(index);
Expand Down Expand Up @@ -133,7 +141,8 @@ fn rewrite_method(
parse_quote! {
#vis fn #method_name(#receiver) -> impl #handler {
pub struct handle #impl_generics(::std::sync::Arc<#self_ty>) #where_clause;
impl #impl_generics ::std::ops::Deref for handle #impl_generics #where_clause{
use ::std::ops::Deref;
impl #impl_generics Deref for handle #impl_generics #where_clause{
type Target = #self_ty;

fn deref(&self) -> &Self::Target {
Expand All @@ -155,3 +164,30 @@ fn rewrite_method(
*method = new_method;
Ok(())
}

#[cfg(test)]
mod tests {
use super::REGEX_STR;
use regex::Regex;

#[test]
fn extract_attribute() {
let re = Regex::new(REGEX_STR).unwrap();

let texts = vec![
r###"#[:: craft(endpoint(responses((status_code = 400, description = "[(Wrong)] request parameters."))))]"###,
r###"#[ xx ::craft(handler())]"###,
r###"#[::xx::craft(endpoint(simple_text))] "###,
r###"#[craft(handler)]"###,
];
for text in texts {
for caps in re.captures_iter(text) {
println!(
"name={}, content={:?}",
caps.name("name").unwrap().as_str(),
caps.name("content").map(|c| c.as_str().to_owned())
)
}
}
}
}

0 comments on commit cde829d

Please sign in to comment.