rocket_codegen/attribute/param/
parse.rs

1use 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        // Check if this is a dynamic param. If so, check its well-formedness.
50        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        // We check for empty segments when we parse an `Origin` in `FromMeta`.
94        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}