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