rocket_codegen/attribute/route/
parse.rs1use devise::{Spanned, SpanWrapped, Result, FromMeta};
2use devise::ext::{SpanDiagnosticExt, TypeExt};
3use indexmap::{IndexSet, IndexMap};
4use proc_macro2::Span;
5
6use crate::proc_macro_ext::Diagnostics;
7use crate::http_codegen::{Method, MediaType};
8use crate::attribute::param::{Parameter, Dynamic, Guard};
9use crate::syn_ext::FnArgExt;
10use crate::name::Name;
11use crate::http::ext::IntoOwned;
12use crate::http::uri::{Origin, fmt};
13
14#[derive(Debug)]
16pub struct Route {
17 pub attr: Attribute,
19 pub path_params: Vec<Parameter>,
21 pub query_params: Vec<Parameter>,
23 pub data_guard: Option<Guard>,
25 pub request_guards: Vec<Guard>,
27 pub handler: syn::ItemFn,
29 pub arguments: Arguments,
31}
32
33type ArgumentMap = IndexMap<Name, (syn::Ident, syn::Type)>;
34
35#[derive(Debug)]
36pub struct Arguments {
37 pub span: Span,
38 pub map: ArgumentMap
39}
40
41#[derive(Debug, FromMeta)]
43pub struct Attribute {
44 #[meta(naked)]
45 pub method: SpanWrapped<Method>,
46 pub uri: RouteUri,
47 pub data: Option<SpanWrapped<Dynamic>>,
48 pub format: Option<MediaType>,
49 pub rank: Option<isize>,
50}
51
52#[derive(Debug, FromMeta)]
54pub struct MethodAttribute {
55 #[meta(naked)]
56 pub uri: RouteUri,
57 pub data: Option<SpanWrapped<Dynamic>>,
58 pub format: Option<MediaType>,
59 pub rank: Option<isize>,
60}
61
62#[derive(Debug)]
63pub struct RouteUri {
64 origin: Origin<'static>,
65 path_span: Span,
66 query_span: Option<Span>,
67}
68
69impl FromMeta for RouteUri {
70 fn from_meta(meta: &devise::MetaItem) -> Result<Self> {
71 let string = crate::proc_macro_ext::StringLit::from_meta(meta)?;
72
73 let origin = Origin::parse_route(&string)
74 .map_err(|e| {
75 let span = string.subspan(e.index() + 1..(e.index() + 2));
76 span.error(format!("invalid route URI: {}", e))
77 .help("expected URI in origin form: \"/path/<param>\"")
78 })?;
79
80 if !origin.is_normalized() {
81 let normalized = origin.clone().into_normalized();
82 let span = origin.path().find("//")
83 .or_else(|| origin.query()
84 .and_then(|q| q.find("&&"))
85 .map(|i| origin.path().len() + 1 + i))
86 .map(|i| string.subspan((1 + i)..(1 + i + 2)))
87 .unwrap_or_else(|| string.span());
88
89 return Err(span.error("route URIs cannot contain empty segments")
90 .note(format!("expected \"{}\", found \"{}\"", normalized, origin)));
91 }
92
93 let path_span = string.subspan(1..origin.path().len() + 1);
94 let query_span = origin.query().map(|q| {
95 let len_to_q = 1 + origin.path().len() + 1;
96 let end_of_q = len_to_q + q.len();
97 string.subspan(len_to_q..end_of_q)
98 });
99
100 Ok(RouteUri { origin: origin.into_owned(), path_span, query_span })
101 }
102}
103
104impl Route {
105 pub fn upgrade_param(param: Parameter, args: &Arguments) -> Result<Parameter> {
106 if param.dynamic().is_none() {
107 return Ok(param);
108 }
109
110 let param = param.take_dynamic().expect("dynamic() => take_dynamic()");
111 Route::upgrade_dynamic(param, args).map(Parameter::Guard)
112 }
113
114 pub fn upgrade_dynamic(param: Dynamic, args: &Arguments) -> Result<Guard> {
115 if let Some((ident, ty)) = args.map.get(¶m.name) {
116 Ok(Guard::from(param, ident.clone(), ty.clone()))
117 } else {
118 let msg = format!("expected argument named `{}` here", param.name);
119 let diag = param.span().error("unused parameter").span_note(args.span, msg);
120 Err(diag)
121 }
122 }
123
124 pub fn from(attr: Attribute, handler: syn::ItemFn) -> Result<Route> {
125 let mut diags = Diagnostics::new();
127
128 if let Some(ref data) = attr.data {
130 if !attr.method.0.supports_payload() {
131 let msg = format!("'{}' does not typically support payloads", attr.method.0);
132 data.full_span.warning("`data` used with non-payload-supporting method")
134 .span_note(attr.method.span, msg)
135 .emit_as_item_tokens();
136 }
137 }
138
139 let span = handler.sig.paren_token.span.join();
141 let mut arguments = Arguments { map: ArgumentMap::new(), span };
142 for arg in &handler.sig.inputs {
143 if let Some((ident, ty)) = arg.typed() {
144 let value = (ident.clone(), ty.with_stripped_lifetimes());
145 arguments.map.insert(Name::from(ident), value);
146 } else {
147 let span = arg.span();
148 let diag = if arg.wild().is_some() {
149 span.error("handler arguments must be named")
150 .help("to name an ignored handler argument, use `_name`")
151 } else {
152 span.error("handler arguments must be of the form `ident: Type`")
153 };
154
155 diags.push(diag);
156 }
157 }
158
159 let (source, span) = (attr.uri.path(), attr.uri.path_span);
161 let path_params = Parameter::parse_many::<fmt::Path>(source.as_str(), span)
162 .map(|p| Route::upgrade_param(p?, &arguments))
163 .filter_map(|p| p.map_err(|e| diags.push(e)).ok())
164 .collect::<Vec<_>>();
165
166 let query_params = match (attr.uri.query(), attr.uri.query_span) {
168 (Some(q), Some(span)) => Parameter::parse_many::<fmt::Query>(q.as_str(), span)
169 .map(|p| Route::upgrade_param(p?, &arguments))
170 .filter_map(|p| p.map_err(|e| diags.push(e)).ok())
171 .collect::<Vec<_>>(),
172 _ => vec![]
173 };
174
175 let data_guard = attr.data.clone()
177 .map(|p| Route::upgrade_dynamic(p.value, &arguments))
178 .and_then(|p| p.map_err(|e| diags.push(e)).ok());
179
180 let all_dyn_params = path_params.iter().filter_map(|p| p.dynamic())
182 .chain(query_params.iter().filter_map(|p| p.dynamic()))
183 .chain(data_guard.as_ref().map(|g| &g.source).into_iter());
184
185 let mut dyn_params: IndexSet<&Dynamic> = IndexSet::new();
187 for p in all_dyn_params {
188 if let Some(prev) = dyn_params.replace(p) {
189 diags.push(p.span().error(format!("duplicate parameter: `{}`", p.name))
190 .span_note(prev.span(), "previous parameter with the same name here"))
191 }
192 }
193
194 let request_guards = arguments.map.iter()
196 .filter(|(name, _)| {
197 let mut all_other_guards = path_params.iter().filter_map(|p| p.guard())
198 .chain(query_params.iter().filter_map(|p| p.guard()))
199 .chain(data_guard.as_ref().into_iter());
200
201 all_other_guards.all(|g| &g.name != *name)
202 })
203 .enumerate()
204 .map(|(index, (name, (ident, ty)))| Guard {
205 source: Dynamic { index, name: name.clone(), trailing: false },
206 fn_ident: ident.clone(),
207 ty: ty.clone(),
208 })
209 .collect();
210
211 diags.head_err_or(Route {
212 attr, path_params, query_params, data_guard, request_guards,
213 handler, arguments,
214 })
215 }
216}
217
218impl std::ops::Deref for RouteUri {
219 type Target = Origin<'static>;
220
221 fn deref(&self) -> &Self::Target {
222 &self.origin
223 }
224}