1 use quote::ToTokens; 2 use crate::proc_macro2::TokenStream as TokenStream2; 3 use devise::{FromMeta, MetaItem, Result, ext::{Split2, PathExt}}; 4 use crate::http::{self, ext::IntoOwned}; 5 use crate::http::uri::{Path, Query}; 6 use crate::attribute::segments::{parse_segments, parse_data_segment, Segment, Kind}; 7 8 use crate::proc_macro_ext::StringLit; 9 10 #[derive(Debug)] 11 pub struct ContentType(pub http::ContentType); 12 13 #[derive(Debug)] 14 pub struct Status(pub http::Status); 15 16 #[derive(Debug)] 17 pub struct MediaType(pub http::MediaType); 18 19 #[derive(Debug)] 20 pub struct Method(pub http::Method); 21 22 #[derive(Debug)] 23 pub struct Origin(pub http::uri::Origin<'static>); 24 25 #[derive(Clone, Debug)] 26 pub struct DataSegment(pub Segment); 27 28 #[derive(Clone, Debug)] 29 pub struct Optional<T>(pub Option<T>); 30 31 impl FromMeta for StringLit { from_meta(meta: MetaItem<'_>) -> Result<Self>32 fn from_meta(meta: MetaItem<'_>) -> Result<Self> { 33 Ok(StringLit::new(String::from_meta(meta)?, meta.value_span())) 34 } 35 } 36 37 #[derive(Debug)] 38 pub struct RoutePath { 39 pub origin: Origin, 40 pub path: Vec<Segment>, 41 pub query: Option<Vec<Segment>>, 42 } 43 44 impl FromMeta for Status { from_meta(meta: MetaItem<'_>) -> Result<Self>45 fn from_meta(meta: MetaItem<'_>) -> Result<Self> { 46 let num = usize::from_meta(meta)?; 47 if num < 100 || num >= 600 { 48 return Err(meta.value_span().error("status must be in range [100, 599]")); 49 } 50 51 Ok(Status(http::Status::raw(num as u16))) 52 } 53 } 54 55 impl ToTokens for Status { to_tokens(&self, tokens: &mut TokenStream2)56 fn to_tokens(&self, tokens: &mut TokenStream2) { 57 let (code, reason) = (self.0.code, self.0.reason); 58 tokens.extend(quote!(rocket::http::Status { code: #code, reason: #reason })); 59 } 60 } 61 62 impl FromMeta for ContentType { from_meta(meta: MetaItem<'_>) -> Result<Self>63 fn from_meta(meta: MetaItem<'_>) -> Result<Self> { 64 http::ContentType::parse_flexible(&String::from_meta(meta)?) 65 .map(ContentType) 66 .ok_or(meta.value_span().error("invalid or unknown content type")) 67 } 68 } 69 70 impl ToTokens for ContentType { to_tokens(&self, tokens: &mut TokenStream2)71 fn to_tokens(&self, tokens: &mut TokenStream2) { 72 // Yeah, yeah. (((((i))).kn0w())) 73 let media_type = MediaType((self.0).clone().0); 74 tokens.extend(quote!(::rocket::http::ContentType(#media_type))); 75 } 76 } 77 78 impl FromMeta for MediaType { from_meta(meta: MetaItem<'_>) -> Result<Self>79 fn from_meta(meta: MetaItem<'_>) -> Result<Self> { 80 let mt = http::MediaType::parse_flexible(&String::from_meta(meta)?) 81 .ok_or(meta.value_span().error("invalid or unknown media type"))?; 82 83 if !mt.is_known() { 84 meta.value_span() 85 .warning(format!("'{}' is not a known media type", mt)) 86 .emit(); 87 } 88 89 Ok(MediaType(mt)) 90 } 91 } 92 93 impl ToTokens for MediaType { to_tokens(&self, tokens: &mut TokenStream2)94 fn to_tokens(&self, tokens: &mut TokenStream2) { 95 use std::iter::repeat; 96 let (top, sub) = (self.0.top().as_str(), self.0.sub().as_str()); 97 let (keys, values) = self.0.params().split2(); 98 99 let cow = quote!(::std::borrow::Cow); 100 let (pub_http, http) = (quote!(::rocket::http), quote!(::rocket::http::private)); 101 let (http_, http__) = (repeat(&http), repeat(&http)); 102 let (cow_, cow__) = (repeat(&cow), repeat(&cow)); 103 104 // TODO: Produce less code when possible (for known media types). 105 tokens.extend(quote!(#pub_http::MediaType { 106 source: #http::Source::None, 107 top: #http::Indexed::Concrete(#cow::Borrowed(#top)), 108 sub: #http::Indexed::Concrete(#cow::Borrowed(#sub)), 109 params: #http::MediaParams::Static(&[ 110 #(( 111 #http_::Indexed::Concrete(#cow_::Borrowed(#keys)), 112 #http__::Indexed::Concrete(#cow__::Borrowed(#values)) 113 )),* 114 ]) 115 })) 116 } 117 } 118 119 const VALID_METHODS_STR: &str = "`GET`, `PUT`, `POST`, `DELETE`, `HEAD`, \ 120 `PATCH`, `OPTIONS`"; 121 122 const VALID_METHODS: &[http::Method] = &[ 123 http::Method::Get, http::Method::Put, http::Method::Post, 124 http::Method::Delete, http::Method::Head, http::Method::Patch, 125 http::Method::Options, 126 ]; 127 128 impl FromMeta for Method { from_meta(meta: MetaItem<'_>) -> Result<Self>129 fn from_meta(meta: MetaItem<'_>) -> Result<Self> { 130 let span = meta.value_span(); 131 let help_text = format!("method must be one of: {}", VALID_METHODS_STR); 132 133 if let MetaItem::Path(path) = meta { 134 if let Some(ident) = path.last_ident() { 135 let method = ident.to_string().parse() 136 .map_err(|_| span.error("invalid HTTP method").help(&*help_text))?; 137 138 if !VALID_METHODS.contains(&method) { 139 return Err(span.error("invalid HTTP method for route handlers") 140 .help(&*help_text)); 141 } 142 143 return Ok(Method(method)); 144 } 145 } 146 147 Err(span.error(format!("expected identifier, found {}", meta.description())) 148 .help(&*help_text)) 149 } 150 } 151 152 impl ToTokens for Method { to_tokens(&self, tokens: &mut TokenStream2)153 fn to_tokens(&self, tokens: &mut TokenStream2) { 154 let method_tokens = match self.0 { 155 http::Method::Get => quote!(::rocket::http::Method::Get), 156 http::Method::Put => quote!(::rocket::http::Method::Put), 157 http::Method::Post => quote!(::rocket::http::Method::Post), 158 http::Method::Delete => quote!(::rocket::http::Method::Delete), 159 http::Method::Options => quote!(::rocket::http::Method::Options), 160 http::Method::Head => quote!(::rocket::http::Method::Head), 161 http::Method::Trace => quote!(::rocket::http::Method::Trace), 162 http::Method::Connect => quote!(::rocket::http::Method::Connect), 163 http::Method::Patch => quote!(::rocket::http::Method::Patch), 164 }; 165 166 tokens.extend(method_tokens); 167 } 168 } 169 170 impl FromMeta for Origin { from_meta(meta: MetaItem<'_>) -> Result<Self>171 fn from_meta(meta: MetaItem<'_>) -> Result<Self> { 172 let string = StringLit::from_meta(meta)?; 173 174 let uri = http::uri::Origin::parse_route(&string) 175 .map_err(|e| { 176 let span = e.index() 177 .map(|i| string.subspan(i + 1..)) 178 .unwrap_or(string.span()); 179 180 span.error(format!("invalid path URI: {}", e)) 181 .help("expected path in origin form: \"/path/<param>\"") 182 })?; 183 184 if !uri.is_normalized() { 185 let normalized = uri.to_normalized(); 186 return Err(string.span().error("paths cannot contain empty segments") 187 .note(format!("expected '{}', found '{}'", normalized, uri))); 188 } 189 190 Ok(Origin(uri.into_owned())) 191 } 192 } 193 194 impl FromMeta for DataSegment { from_meta(meta: MetaItem<'_>) -> Result<Self>195 fn from_meta(meta: MetaItem<'_>) -> Result<Self> { 196 let string = StringLit::from_meta(meta)?; 197 let span = string.subspan(1..(string.len() + 1)); 198 199 let segment = parse_data_segment(&string, span)?; 200 if segment.kind != Kind::Single { 201 return Err(span.error("malformed parameter") 202 .help("parameter must be of the form '<param>'")); 203 } 204 205 Ok(DataSegment(segment)) 206 } 207 } 208 209 impl FromMeta for RoutePath { from_meta(meta: MetaItem<'_>) -> Result<Self>210 fn from_meta(meta: MetaItem<'_>) -> Result<Self> { 211 let (origin, string) = (Origin::from_meta(meta)?, StringLit::from_meta(meta)?); 212 let path_span = string.subspan(1..origin.0.path().len() + 1); 213 let path = parse_segments::<Path>(origin.0.path(), path_span); 214 215 let query = origin.0.query() 216 .map(|q| { 217 let len_to_q = 1 + origin.0.path().len() + 1; 218 let end_of_q = len_to_q + q.len(); 219 let query_span = string.subspan(len_to_q..end_of_q); 220 if q.starts_with('&') || q.contains("&&") || q.ends_with('&') { 221 // TODO: Show a help message with what's expected. 222 Err(query_span.error("query cannot contain empty segments").into()) 223 } else { 224 parse_segments::<Query>(q, query_span) 225 } 226 }).transpose(); 227 228 match (path, query) { 229 (Ok(path), Ok(query)) => Ok(RoutePath { origin, path, query }), 230 (Err(diag), Ok(_)) | (Ok(_), Err(diag)) => Err(diag.emit_head()), 231 (Err(d1), Err(d2)) => Err(d1.join(d2).emit_head()) 232 } 233 } 234 } 235 236 impl<T: ToTokens> ToTokens for Optional<T> { to_tokens(&self, tokens: &mut TokenStream2)237 fn to_tokens(&self, tokens: &mut TokenStream2) { 238 define_vars_and_mods!(_Some, _None); 239 let opt_tokens = match self.0 { 240 Some(ref val) => quote!(#_Some(#val)), 241 None => quote!(#_None) 242 }; 243 244 tokens.extend(opt_tokens); 245 } 246 } 247