rocket_codegen/
http_codegen.rs1use quote::ToTokens;
2use devise::{FromMeta, MetaItem, Result, ext::{Split2, PathExt, SpanDiagnosticExt}};
3use proc_macro2::{TokenStream, Span};
4
5use crate::http;
6
7#[derive(Debug)]
8pub struct ContentType(pub http::ContentType);
9
10#[derive(Debug)]
11pub struct Status(pub http::Status);
12
13#[derive(Debug)]
14pub struct MediaType(pub http::MediaType);
15
16#[derive(Debug, Copy, Clone)]
17pub struct Method(pub http::Method);
18
19#[derive(Clone, Debug)]
20pub struct Optional<T>(pub Option<T>);
21
22#[derive(Debug)]
23pub struct Origin<'a>(pub &'a http::uri::Origin<'a>, pub Span);
24
25#[derive(Debug)]
26pub struct Absolute<'a>(pub &'a http::uri::Absolute<'a>, pub Span);
27
28#[derive(Debug)]
29pub struct Authority<'a>(pub &'a http::uri::Authority<'a>, pub Span);
30
31#[derive(Debug)]
32pub struct Reference<'a>(pub &'a http::uri::Reference<'a>, pub Span);
33
34#[derive(Debug)]
35pub struct Asterisk(pub http::uri::Asterisk, pub Span);
36
37impl FromMeta for Status {
38 fn from_meta(meta: &MetaItem) -> Result<Self> {
39 let num = usize::from_meta(meta)?;
40 if num < 100 || num >= 600 {
41 return Err(meta.value_span().error("status must be in range [100, 599]"));
42 }
43
44 Ok(Status(http::Status::new(num as u16)))
45 }
46}
47
48impl ToTokens for Status {
49 fn to_tokens(&self, tokens: &mut TokenStream) {
50 let code = self.0.code;
51 tokens.extend(quote!(rocket::http::Status { code: #code }));
52 }
53}
54
55impl FromMeta for ContentType {
56 fn from_meta(meta: &MetaItem) -> Result<Self> {
57 http::ContentType::parse_flexible(&String::from_meta(meta)?)
58 .map(ContentType)
59 .ok_or_else(|| meta.value_span().error("invalid or unknown content type"))
60 }
61}
62
63impl ToTokens for ContentType {
64 fn to_tokens(&self, tokens: &mut TokenStream) {
65 let http_media_type = self.0.media_type().clone();
66 let media_type = MediaType(http_media_type);
67 tokens.extend(quote!(::rocket::http::ContentType(#media_type)));
68 }
69}
70
71impl FromMeta for MediaType {
72 fn from_meta(meta: &MetaItem) -> Result<Self> {
73 let mt = http::MediaType::parse_flexible(&String::from_meta(meta)?)
74 .ok_or_else(|| meta.value_span().error("invalid or unknown media type"))?;
75
76 if !mt.is_known() {
77 meta.value_span()
79 .warning(format!("'{}' is not a known media type", mt))
80 .emit_as_item_tokens();
81 }
82
83 Ok(MediaType(mt))
84 }
85}
86
87impl ToTokens for MediaType {
88 fn to_tokens(&self, tokens: &mut TokenStream) {
89 let (top, sub) = (self.0.top().as_str(), self.0.sub().as_str());
90 let (keys, values) = self.0.params().map(|(k, v)| (k.as_str(), v)).split2();
91 let http = quote!(::rocket::http);
92
93 tokens.extend(quote! {
94 #http::MediaType::const_new(#top, #sub, &[#((#keys, #values)),*])
95 });
96 }
97}
98
99const VALID_METHODS_STR: &str = "`GET`, `PUT`, `POST`, `DELETE`, `HEAD`, \
100 `PATCH`, `OPTIONS`";
101
102const VALID_METHODS: &[http::Method] = &[
103 http::Method::Get, http::Method::Put, http::Method::Post,
104 http::Method::Delete, http::Method::Head, http::Method::Patch,
105 http::Method::Options,
106];
107
108impl FromMeta for Method {
109 fn from_meta(meta: &MetaItem) -> Result<Self> {
110 let span = meta.value_span();
111 let help_text = format!("method must be one of: {}", VALID_METHODS_STR);
112
113 if let MetaItem::Path(path) = meta {
114 if let Some(ident) = path.last_ident() {
115 let method = ident.to_string().parse()
116 .map_err(|_| span.error("invalid HTTP method").help(&*help_text))?;
117
118 if !VALID_METHODS.contains(&method) {
119 return Err(span.error("invalid HTTP method for route handlers")
120 .help(&*help_text));
121 }
122
123 return Ok(Method(method));
124 }
125 }
126
127 Err(span.error(format!("expected identifier, found {}", meta.description()))
128 .help(&*help_text))
129 }
130}
131
132impl ToTokens for Method {
133 fn to_tokens(&self, tokens: &mut TokenStream) {
134 let method_tokens = match self.0 {
135 http::Method::Get => quote!(::rocket::http::Method::Get),
136 http::Method::Put => quote!(::rocket::http::Method::Put),
137 http::Method::Post => quote!(::rocket::http::Method::Post),
138 http::Method::Delete => quote!(::rocket::http::Method::Delete),
139 http::Method::Options => quote!(::rocket::http::Method::Options),
140 http::Method::Head => quote!(::rocket::http::Method::Head),
141 http::Method::Trace => quote!(::rocket::http::Method::Trace),
142 http::Method::Connect => quote!(::rocket::http::Method::Connect),
143 http::Method::Patch => quote!(::rocket::http::Method::Patch),
144 };
145
146 tokens.extend(method_tokens);
147 }
148}
149
150impl<T: ToTokens> ToTokens for Optional<T> {
151 fn to_tokens(&self, tokens: &mut TokenStream) {
152 use crate::exports::{_Some, _None};
153 use devise::Spanned;
154
155 let opt_tokens = match self.0 {
156 Some(ref val) => quote_spanned!(val.span() => #_Some(#val)),
157 None => quote!(#_None)
158 };
159
160 tokens.extend(opt_tokens);
161 }
162}
163
164impl ToTokens for Origin<'_> {
165 fn to_tokens(&self, tokens: &mut TokenStream) {
166 let (origin, span) = (self.0, self.1);
167 let origin = origin.clone().into_normalized();
168 define_spanned_export!(span => _uri);
169
170 let path = origin.path().as_str();
171 let query = Optional(origin.query().map(|q| q.as_str()));
172 tokens.extend(quote_spanned! { span =>
173 #_uri::Origin::const_new(#path, #query)
174 });
175 }
176}
177
178impl ToTokens for Absolute<'_> {
179 fn to_tokens(&self, tokens: &mut TokenStream) {
180 let (absolute, span) = (self.0, self.1);
181 define_spanned_export!(span => _uri);
182 let absolute = absolute.clone().into_normalized();
183
184 let scheme = absolute.scheme();
185 let auth = Optional(absolute.authority().map(|a| Authority(a, span)));
186 let path = absolute.path().as_str();
187 let query = Optional(absolute.query().map(|q| q.as_str()));
188 tokens.extend(quote_spanned! { span =>
189 #_uri::Absolute::const_new(#scheme, #auth, #path, #query)
190 });
191 }
192}
193
194impl ToTokens for Authority<'_> {
195 fn to_tokens(&self, tokens: &mut TokenStream) {
196 let (authority, span) = (self.0, self.1);
197 define_spanned_export!(span => _uri);
198
199 let user_info = Optional(authority.user_info());
200 let host = authority.host();
201 let port = Optional(authority.port());
202 tokens.extend(quote_spanned! { span =>
203 #_uri::Authority::const_new(#user_info, #host, #port)
204 });
205 }
206}
207
208impl ToTokens for Reference<'_> {
209 fn to_tokens(&self, tokens: &mut TokenStream) {
210 let (reference, span) = (self.0, self.1);
211 define_spanned_export!(span => _uri);
212 let reference = reference.clone().into_normalized();
213
214 let scheme = Optional(reference.scheme());
215 let auth = Optional(reference.authority().map(|a| Authority(a, span)));
216 let path = reference.path().as_str();
217 let query = Optional(reference.query().map(|q| q.as_str()));
218 let frag = Optional(reference.fragment().map(|f| f.as_str()));
219 tokens.extend(quote_spanned! { span =>
220 #_uri::Reference::const_new(#scheme, #auth, #path, #query, #frag)
221 });
222 }
223}
224
225impl ToTokens for Asterisk {
226 fn to_tokens(&self, tokens: &mut TokenStream) {
227 define_spanned_export!(self.1 => _uri);
228 tokens.extend(quote_spanned!(self.1 => #_uri::Asterisk));
229 }
230}