use quote::ToTokens; use devise::{FromMeta, MetaItem, Result, ext::{Split2, PathExt, SpanDiagnosticExt}}; use proc_macro2::{TokenStream, Span}; use crate::http; #[derive(Debug)] pub struct ContentType(pub http::ContentType); #[derive(Debug)] pub struct Status(pub http::Status); #[derive(Debug)] pub struct MediaType(pub http::MediaType); #[derive(Debug, Copy, Clone)] pub struct Method(pub http::Method); #[derive(Clone, Debug)] pub struct Optional(pub Option); #[derive(Debug)] pub struct Origin<'a>(pub &'a http::uri::Origin<'a>, pub Span); #[derive(Debug)] pub struct Absolute<'a>(pub &'a http::uri::Absolute<'a>, pub Span); #[derive(Debug)] pub struct Authority<'a>(pub &'a http::uri::Authority<'a>, pub Span); #[derive(Debug)] pub struct Reference<'a>(pub &'a http::uri::Reference<'a>, pub Span); #[derive(Debug)] pub struct Asterisk(pub http::uri::Asterisk, pub Span); impl FromMeta for Status { fn from_meta(meta: &MetaItem) -> Result { let num = usize::from_meta(meta)?; if num < 100 || num >= 600 { return Err(meta.value_span().error("status must be in range [100, 599]")); } Ok(Status(http::Status::new(num as u16))) } } impl ToTokens for Status { fn to_tokens(&self, tokens: &mut TokenStream) { let code = self.0.code; tokens.extend(quote!(rocket::http::Status { code: #code })); } } impl FromMeta for ContentType { fn from_meta(meta: &MetaItem) -> Result { http::ContentType::parse_flexible(&String::from_meta(meta)?) .map(ContentType) .ok_or_else(|| meta.value_span().error("invalid or unknown content type")) } } impl ToTokens for ContentType { fn to_tokens(&self, tokens: &mut TokenStream) { let http_media_type = self.0.media_type().clone(); let media_type = MediaType(http_media_type); tokens.extend(quote!(::rocket::http::ContentType(#media_type))); } } impl FromMeta for MediaType { fn from_meta(meta: &MetaItem) -> Result { let mt = http::MediaType::parse_flexible(&String::from_meta(meta)?) .ok_or_else(|| meta.value_span().error("invalid or unknown media type"))?; if !mt.is_known() { // FIXME(diag: warning) meta.value_span() .warning(format!("'{}' is not a known media type", mt)) .emit_as_item_tokens(); } Ok(MediaType(mt)) } } impl ToTokens for MediaType { fn to_tokens(&self, tokens: &mut TokenStream) { let (top, sub) = (self.0.top().as_str(), self.0.sub().as_str()); let (keys, values) = self.0.params().map(|(k, v)| (k.as_str(), v)).split2(); let http = quote!(::rocket::http); tokens.extend(quote! { #http::MediaType::const_new(#top, #sub, &[#((#keys, #values)),*]) }); } } const VALID_METHODS_STR: &str = "`GET`, `PUT`, `POST`, `DELETE`, `HEAD`, \ `PATCH`, `OPTIONS`"; const VALID_METHODS: &[http::Method] = &[ http::Method::Get, http::Method::Put, http::Method::Post, http::Method::Delete, http::Method::Head, http::Method::Patch, http::Method::Options, ]; impl FromMeta for Method { fn from_meta(meta: &MetaItem) -> Result { let span = meta.value_span(); let help_text = format!("method must be one of: {}", VALID_METHODS_STR); if let MetaItem::Path(path) = meta { if let Some(ident) = path.last_ident() { let method = ident.to_string().parse() .map_err(|_| span.error("invalid HTTP method").help(&*help_text))?; if !VALID_METHODS.contains(&method) { return Err(span.error("invalid HTTP method for route handlers") .help(&*help_text)); } return Ok(Method(method)); } } Err(span.error(format!("expected identifier, found {}", meta.description())) .help(&*help_text)) } } impl ToTokens for Method { fn to_tokens(&self, tokens: &mut TokenStream) { let method_tokens = match self.0 { http::Method::Get => quote!(::rocket::http::Method::Get), http::Method::Put => quote!(::rocket::http::Method::Put), http::Method::Post => quote!(::rocket::http::Method::Post), http::Method::Delete => quote!(::rocket::http::Method::Delete), http::Method::Options => quote!(::rocket::http::Method::Options), http::Method::Head => quote!(::rocket::http::Method::Head), http::Method::Trace => quote!(::rocket::http::Method::Trace), http::Method::Connect => quote!(::rocket::http::Method::Connect), http::Method::Patch => quote!(::rocket::http::Method::Patch), }; tokens.extend(method_tokens); } } impl ToTokens for Optional { fn to_tokens(&self, tokens: &mut TokenStream) { use crate::exports::{_Some, _None}; use devise::Spanned; let opt_tokens = match self.0 { Some(ref val) => quote_spanned!(val.span() => #_Some(#val)), None => quote!(#_None) }; tokens.extend(opt_tokens); } } impl ToTokens for Origin<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let (origin, span) = (self.0, self.1); let origin = origin.clone().into_normalized(); define_spanned_export!(span => _uri); let path = origin.path().as_str(); let query = Optional(origin.query().map(|q| q.as_str())); tokens.extend(quote_spanned! { span => #_uri::Origin::const_new(#path, #query) }); } } impl ToTokens for Absolute<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let (absolute, span) = (self.0, self.1); define_spanned_export!(span => _uri); let absolute = absolute.clone().into_normalized(); let scheme = absolute.scheme(); let auth = Optional(absolute.authority().map(|a| Authority(a, span))); let path = absolute.path().as_str(); let query = Optional(absolute.query().map(|q| q.as_str())); tokens.extend(quote_spanned! { span => #_uri::Absolute::const_new(#scheme, #auth, #path, #query) }); } } impl ToTokens for Authority<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let (authority, span) = (self.0, self.1); define_spanned_export!(span => _uri); let user_info = Optional(authority.user_info()); let host = authority.host(); let port = Optional(authority.port()); tokens.extend(quote_spanned! { span => #_uri::Authority::const_new(#user_info, #host, #port) }); } } impl ToTokens for Reference<'_> { fn to_tokens(&self, tokens: &mut TokenStream) { let (reference, span) = (self.0, self.1); define_spanned_export!(span => _uri); let reference = reference.clone().into_normalized(); let scheme = Optional(reference.scheme()); let auth = Optional(reference.authority().map(|a| Authority(a, span))); let path = reference.path().as_str(); let query = Optional(reference.query().map(|q| q.as_str())); let frag = Optional(reference.fragment().map(|f| f.as_str())); tokens.extend(quote_spanned! { span => #_uri::Reference::const_new(#scheme, #auth, #path, #query, #frag) }); } } impl ToTokens for Asterisk { fn to_tokens(&self, tokens: &mut TokenStream) { define_spanned_export!(self.1 => _uri); tokens.extend(quote_spanned!(self.1 => #_uri::Asterisk)); } }