#![recursion_limit="256"]
extern crate proc_macro;
use quote::quote;
use darling::FromDeriveInput;
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(validate))]
struct JsonBodyOpts {
#[darling(default)]
context: Option<syn::Path>,
}
#[proc_macro_derive(JsonBody, attributes(validate))]
pub fn derive_jsonbody(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast = syn::parse_macro_input!(input as syn::DeriveInput);
let name = &ast.ident;
let opts = JsonBodyOpts::from_derive_input(&ast).unwrap();
let (ctx_line, validate_call) = if let Some(ctx) = opts.context {
(
quote! {
let ctx: & #ctx = req.rocket().state::<#ctx>().expect("context state not found");
},
quote! { obj.validate_with_args(ctx) }
)
} else {
(
quote! {},
quote! { obj.validate() }
)
};
let gen = quote! {
#[rocket::async_trait]
impl<'r> rocket::data::FromData<'r> for #name {
type Error = ();
async fn from_data(
req: &'r rocket::Request<'_>,
data: rocket::data::Data<'r>
) -> rocket::data::Outcome<'r, Self> {
if req.content_type() != Some(&rocket::http::ContentType::new("application", "json")) {
return rocket::data::Outcome::Forward((data, rocket::http::Status::Continue));
}
let json_outcome = rocket::serde::json::Json::<Self>::from_data(req, data).await;
let obj = match json_outcome {
rocket::data::Outcome::Success(json) => json.into_inner(),
rocket::data::Outcome::Error(_) => {
req.local_cache(|| rocketjson::error::JsonBodyError::JsonValidationError);
return rocket::data::Outcome::Error((rocket::http::Status::BadRequest, ()));
},
rocket::data::Outcome::Forward(f) => return rocket::data::Outcome::Forward(f),
};
#ctx_line
if let Err(errors) = #validate_call {
req.local_cache(|| rocketjson::error::JsonBodyError::ValidationError(errors));
return rocket::data::Outcome::Error((rocket::http::Status::BadRequest, ()));
}
rocket::data::Outcome::Success(obj)
}
}
};
gen.into()
}