rocket_codegen/attribute/param/
parse.rs1use unicode_xid::UnicodeXID;
2use devise::{Diagnostic, ext::SpanDiagnosticExt};
3use proc_macro2::Span;
4
5use crate::name::Name;
6use crate::proc_macro_ext::StringLit;
7use crate::attribute::param::{Parameter, Dynamic};
8use crate::http::uri::fmt::{Part, Kind, Path};
9
10#[derive(Debug)]
11pub struct Error<'a> {
12 segment: &'a str,
13 span: Span,
14 source: &'a str,
15 source_span: Span,
16 kind: ErrorKind,
17}
18
19#[derive(Debug, PartialEq, Eq, Copy, Clone)]
20pub enum ErrorKind {
21 Empty,
22 BadIdent,
23 Ignored,
24 EarlyTrailing,
25 NoTrailing,
26 Static,
27}
28
29impl Dynamic {
30 pub fn parse<P: Part>(
31 segment: &str,
32 span: Span,
33 ) -> Result<Self, Error<'_>> {
34 match Parameter::parse::<P>(&segment, span)? {
35 Parameter::Dynamic(d) | Parameter::Ignored(d) => Ok(d),
36 Parameter::Guard(g) => Ok(g.source),
37 Parameter::Static(_) => Err(Error::new(segment, span, ErrorKind::Static)),
38 }
39 }
40}
41
42impl Parameter {
43 pub fn parse<P: Part>(
44 segment: &str,
45 source_span: Span,
46 ) -> Result<Self, Error<'_>> {
47 let mut trailing = false;
48
49 if segment.starts_with('<') && segment.ends_with('>') {
51 let mut name = &segment[1..(segment.len() - 1)];
52 if name.ends_with("..") {
53 trailing = true;
54 name = &name[..(name.len() - 2)];
55 }
56
57 let span = subspan(name, segment, source_span);
58 if name.is_empty() {
59 return Err(Error::new(name, source_span, ErrorKind::Empty));
60 } else if !is_valid_ident(name) {
61 return Err(Error::new(name, span, ErrorKind::BadIdent));
62 }
63
64 let dynamic = Dynamic { name: Name::new(name, span), trailing, index: 0 };
65 if dynamic.is_wild() && P::KIND != Kind::Path {
66 return Err(Error::new(name, span, ErrorKind::Ignored));
67 } else if dynamic.is_wild() {
68 return Ok(Parameter::Ignored(dynamic));
69 } else {
70 return Ok(Parameter::Dynamic(dynamic));
71 }
72 } else if segment.is_empty() {
73 return Err(Error::new(segment, source_span, ErrorKind::Empty));
74 } else if segment.starts_with('<') {
75 let candidate = candidate_from_malformed(segment);
76 source_span.warning("`segment` starts with `<` but does not end with `>`")
77 .help(format!("perhaps you meant the dynamic parameter `<{}>`?", candidate))
78 .emit_as_item_tokens();
79 } else if segment.contains('>') || segment.contains('<') {
80 source_span.warning("`segment` contains `<` or `>` but is not a dynamic parameter")
81 .emit_as_item_tokens();
82 }
83
84 Ok(Parameter::Static(Name::new(segment, source_span)))
85 }
86
87 pub fn parse_many<P: Part>(
88 source: &str,
89 source_span: Span,
90 ) -> impl Iterator<Item = Result<Self, Error<'_>>> {
91 let mut trailing: Option<(&str, Span)> = None;
92
93 source.split(P::DELIMITER)
95 .filter(|s| !s.is_empty())
96 .enumerate()
97 .map(move |(i, segment)| {
98 if let Some((trail, span)) = trailing {
99 let error = Error::new(trail, span, ErrorKind::EarlyTrailing)
100 .source(source, source_span);
101
102 return Err(error);
103 }
104
105 let segment_span = subspan(segment, source, source_span);
106 let mut parsed = Self::parse::<P>(segment, segment_span)
107 .map_err(|e| e.source(source, source_span))?;
108
109 if let Some(ref mut d) = parsed.dynamic_mut() {
110 if d.trailing {
111 trailing = Some((segment, segment_span));
112 }
113
114 d.index = i;
115 }
116
117 Ok(parsed)
118 })
119 }
120}
121
122impl std::fmt::Display for ErrorKind {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 match self {
125 ErrorKind::Empty => "parameters cannot be empty".fmt(f),
126 ErrorKind::BadIdent => "invalid identifier".fmt(f),
127 ErrorKind::Ignored => "parameter must be named".fmt(f),
128 ErrorKind::NoTrailing => "parameter cannot be trailing".fmt(f),
129 ErrorKind::EarlyTrailing => "unexpected text after trailing parameter".fmt(f),
130 ErrorKind::Static => "unexpected static parameter".fmt(f),
131 }
132 }
133}
134
135impl<'a> Error<'a> {
136 pub fn new(segment: &str, span: Span, kind: ErrorKind) -> Error<'_> {
137 Error { segment, source: segment, span, source_span: span, kind }
138 }
139
140 pub fn source(mut self, source: &'a str, span: Span) -> Self {
141 self.source = source;
142 self.source_span = span;
143 self
144 }
145}
146
147impl From<Error<'_>> for Diagnostic {
148 fn from(error: Error<'_>) -> Self {
149 match error.kind {
150 ErrorKind::Empty => error.span.error(error.kind.to_string()),
151 ErrorKind::BadIdent => {
152 let candidate = candidate_from_malformed(error.segment);
153 error.span.error(format!("{}: `{}`", error.kind, error.segment))
154 .help("dynamic parameters must be valid identifiers")
155 .help(format!("did you mean `<{}>`?", candidate))
156 }
157 ErrorKind::Ignored => {
158 error.span.error(error.kind.to_string())
159 .help("use a name such as `_guard` or `_param`")
160 }
161 ErrorKind::EarlyTrailing => {
162 trailspan(error.segment, error.source, error.source_span)
163 .error(error.kind.to_string())
164 .help("a trailing parameter must be the final component")
165 .span_note(error.span, "trailing param is here")
166 }
167 ErrorKind::NoTrailing => {
168 let candidate = candidate_from_malformed(error.segment);
169 error.span.error(error.kind.to_string())
170 .help(format!("did you mean `<{}>`?", candidate))
171 }
172 ErrorKind::Static => {
173 let candidate = candidate_from_malformed(error.segment);
174 error.span.error(error.kind.to_string())
175 .help(format!("parameter must be dynamic: `<{}>`", candidate))
176 }
177 }
178 }
179}
180
181impl devise::FromMeta for Dynamic {
182 fn from_meta(meta: &devise::MetaItem) -> devise::Result<Self> {
183 let string = StringLit::from_meta(meta)?;
184 let span = string.subspan(1..string.len() + 1);
185 let param = Dynamic::parse::<Path>(&string, span)?;
186
187 if param.is_wild() {
188 return Err(Error::new(&string, span, ErrorKind::Ignored).into());
189 } else if param.trailing {
190 return Err(Error::new(&string, span, ErrorKind::NoTrailing).into());
191 } else {
192 Ok(param)
193 }
194 }
195}
196
197fn subspan(needle: &str, haystack: &str, span: Span) -> Span {
198 let index = needle.as_ptr() as usize - haystack.as_ptr() as usize;
199 StringLit::new(haystack, span).subspan(index..index + needle.len())
200}
201
202fn trailspan(needle: &str, haystack: &str, span: Span) -> Span {
203 let index = needle.as_ptr() as usize - haystack.as_ptr() as usize;
204 let lit = StringLit::new(haystack, span);
205 if needle.as_ptr() as usize > haystack.as_ptr() as usize {
206 lit.subspan((index - 1)..)
207 } else {
208 lit.subspan(index..)
209 }
210}
211
212fn candidate_from_malformed(segment: &str) -> String {
213 let candidate = segment.chars().enumerate()
214 .filter(|(i, c)| *i == 0 && is_ident_start(*c) || *i != 0 && is_ident_continue(*c))
215 .map(|(_, c)| c)
216 .collect::<String>();
217
218 if candidate.is_empty() {
219 "param".into()
220 } else {
221 candidate
222 }
223}
224
225#[inline]
226fn is_ident_start(c: char) -> bool {
227 c.is_ascii_alphabetic()
228 || c == '_'
229 || (c > '\x7f' && UnicodeXID::is_xid_start(c))
230}
231
232#[inline]
233fn is_ident_continue(c: char) -> bool {
234 c.is_ascii_alphanumeric()
235 || c == '_'
236 || (c > '\x7f' && UnicodeXID::is_xid_continue(c))
237}
238
239fn is_valid_ident(string: &str) -> bool {
240 let mut chars = string.chars();
241 match chars.next() {
242 Some(c) => is_ident_start(c) && chars.all(is_ident_continue),
243 None => false
244 }
245}