meta.rs

  1// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7//! # Parse Rust attributes
  8//!
  9//! This module is concerned with parsing attributes from the Rust "meta"
 10//! annotations on structs, enums, enum variants and fields.
 11
 12use proc_macro2::{Span, TokenStream};
 13use quote::{quote, quote_spanned};
 14use syn::{meta::ParseNestedMeta, spanned::Spanned, *};
 15
 16use rxml_validation::NcName;
 17
 18/// XML core namespace URI (for the `xml:` prefix)
 19pub const XMLNS_XML: &str = "http://www.w3.org/XML/1998/namespace";
 20/// XML namespace URI (for the `xmlns:` prefix)
 21pub const XMLNS_XMLNS: &str = "http://www.w3.org/2000/xmlns/";
 22
 23/// Value for the `#[xml(namespace = ..)]` attribute.
 24#[derive(Debug)]
 25pub(crate) enum NamespaceRef {
 26    /// The XML namespace is specified as a string literal.
 27    LitStr(LitStr),
 28
 29    /// The XML namespace is specified as a path.
 30    Path(Path),
 31}
 32
 33impl NamespaceRef {
 34    fn fudge(value: &str, span: Span) -> Self {
 35        Self::LitStr(LitStr::new(value, span))
 36    }
 37}
 38
 39impl syn::parse::Parse for NamespaceRef {
 40    fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
 41        if input.peek(syn::LitStr) {
 42            Ok(Self::LitStr(input.parse()?))
 43        } else {
 44            Ok(Self::Path(input.parse()?))
 45        }
 46    }
 47}
 48
 49impl quote::ToTokens for NamespaceRef {
 50    fn to_tokens(&self, tokens: &mut TokenStream) {
 51        match self {
 52            Self::LitStr(ref lit) => lit.to_tokens(tokens),
 53            Self::Path(ref path) => path.to_tokens(tokens),
 54        }
 55    }
 56}
 57
 58/// Value for the `#[xml(name = .. )]` attribute.
 59#[derive(Debug)]
 60pub(crate) enum NameRef {
 61    /// The XML name is specified as a string literal.
 62    Literal {
 63        /// The validated XML name.
 64        value: NcName,
 65
 66        /// The span of the original [`syn::LitStr`].
 67        span: Span,
 68    },
 69
 70    /// The XML name is specified as a path.
 71    Path(Path),
 72}
 73
 74impl syn::parse::Parse for NameRef {
 75    fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
 76        if input.peek(syn::LitStr) {
 77            let s: LitStr = input.parse()?;
 78            let span = s.span();
 79            match NcName::try_from(s.value()) {
 80                Ok(value) => Ok(Self::Literal { value, span }),
 81                Err(e) => Err(Error::new(span, format!("not a valid XML name: {}", e))),
 82            }
 83        } else {
 84            let p: Path = input.parse()?;
 85            Ok(Self::Path(p))
 86        }
 87    }
 88}
 89
 90impl quote::ToTokens for NameRef {
 91    fn to_tokens(&self, tokens: &mut TokenStream) {
 92        match self {
 93            Self::Literal { ref value, span } => {
 94                let span = *span;
 95                let value = value.as_str();
 96                let value = quote_spanned! { span=> #value };
 97                // SAFETY: self.0 is a known-good NcName, so converting it to an
 98                // NcNameStr is known to be safe.
 99                // NOTE: we cannot use `quote_spanned! { self.span=> }` for the unsafe
100                // block as that would then in fact trip a `#[deny(unsafe_code)]` lint
101                // at the use site of the macro.
102                tokens.extend(quote! {
103                    unsafe { ::xso::exports::rxml::NcNameStr::from_str_unchecked(#value) }
104                })
105            }
106            Self::Path(ref path) => path.to_tokens(tokens),
107        }
108    }
109}
110
111/// Represents a boolean flag from a `#[xml(..)]` attribute meta.
112#[derive(Clone, Copy, Debug)]
113pub(crate) enum Flag {
114    /// The flag is not set.
115    Absent,
116
117    /// The flag was set.
118    Present(
119        /// The span of the syntax element which enabled the flag.
120        ///
121        /// This is used to generate useful error messages by pointing at the
122        /// specific place the flag was activated.
123        #[allow(dead_code)]
124        Span,
125    ),
126}
127
128impl Flag {
129    /// Return true if the flag is set, false otherwise.
130    pub(crate) fn is_set(&self) -> bool {
131        match self {
132            Self::Absent => false,
133            Self::Present(_) => true,
134        }
135    }
136}
137
138impl<T: Spanned> From<T> for Flag {
139    fn from(other: T) -> Flag {
140        Flag::Present(other.span())
141    }
142}
143
144/// Contents of an `#[xml(..)]` attribute on a struct, enum variant, or enum.
145#[derive(Debug)]
146pub(crate) struct XmlCompoundMeta {
147    /// The span of the `#[xml(..)]` meta from which this was parsed.
148    ///
149    /// This is useful for error messages.
150    pub(crate) span: Span,
151
152    /// The value assigned to `namespace` inside `#[xml(..)]`, if any.
153    pub(crate) namespace: Option<NamespaceRef>,
154
155    /// The value assigned to `name` inside `#[xml(..)]`, if any.
156    pub(crate) name: Option<NameRef>,
157
158    /// The debug flag.
159    pub(crate) debug: Flag,
160
161    /// The value assigned to `builder` inside `#[xml(..)]`, if any.
162    pub(crate) builder: Option<Ident>,
163
164    /// The value assigned to `iterator` inside `#[xml(..)]`, if any.
165    pub(crate) iterator: Option<Ident>,
166}
167
168impl XmlCompoundMeta {
169    /// Parse the meta values from a `#[xml(..)]` attribute.
170    ///
171    /// Undefined options or options with incompatible values are rejected
172    /// with an appropriate compile-time error.
173    fn parse_from_attribute(attr: &Attribute) -> Result<Self> {
174        let mut namespace = None;
175        let mut name = None;
176        let mut builder = None;
177        let mut iterator = None;
178        let mut debug = Flag::Absent;
179
180        attr.parse_nested_meta(|meta| {
181            if meta.path.is_ident("name") {
182                if name.is_some() {
183                    return Err(Error::new_spanned(meta.path, "duplicate `name` key"));
184                }
185                name = Some(meta.value()?.parse()?);
186                Ok(())
187            } else if meta.path.is_ident("namespace") {
188                if namespace.is_some() {
189                    return Err(Error::new_spanned(meta.path, "duplicate `namespace` key"));
190                }
191                namespace = Some(meta.value()?.parse()?);
192                Ok(())
193            } else if meta.path.is_ident("debug") {
194                if debug.is_set() {
195                    return Err(Error::new_spanned(meta.path, "duplicate `debug` key"));
196                }
197                debug = (&meta.path).into();
198                Ok(())
199            } else if meta.path.is_ident("builder") {
200                if builder.is_some() {
201                    return Err(Error::new_spanned(meta.path, "duplicate `builder` key"));
202                }
203                builder = Some(meta.value()?.parse()?);
204                Ok(())
205            } else if meta.path.is_ident("iterator") {
206                if iterator.is_some() {
207                    return Err(Error::new_spanned(meta.path, "duplicate `iterator` key"));
208                }
209                iterator = Some(meta.value()?.parse()?);
210                Ok(())
211            } else {
212                Err(Error::new_spanned(meta.path, "unsupported key"))
213            }
214        })?;
215
216        Ok(Self {
217            span: attr.span(),
218            namespace,
219            name,
220            debug,
221            builder,
222            iterator,
223        })
224    }
225
226    /// Search through `attrs` for a single `#[xml(..)]` attribute and parse
227    /// it.
228    ///
229    /// Undefined options or options with incompatible values are rejected
230    /// with an appropriate compile-time error.
231    ///
232    /// If more than one `#[xml(..)]` attribute is found, an error is
233    /// emitted.
234    ///
235    /// If no `#[xml(..)]` attribute is found, `None` is returned.
236    pub(crate) fn try_parse_from_attributes(attrs: &[Attribute]) -> Result<Option<Self>> {
237        let mut result = None;
238        for attr in attrs {
239            if !attr.path().is_ident("xml") {
240                continue;
241            }
242            if result.is_some() {
243                return Err(syn::Error::new_spanned(
244                    attr.path(),
245                    "only one #[xml(..)] per struct or enum variant allowed",
246                ));
247            }
248            result = Some(Self::parse_from_attribute(attr)?);
249        }
250        Ok(result)
251    }
252
253    /// Search through `attrs` for a single `#[xml(..)]` attribute and parse
254    /// it.
255    ///
256    /// Undefined options or options with incompatible values are rejected
257    /// with an appropriate compile-time error.
258    ///
259    /// If more than one or no `#[xml(..)]` attribute is found, an error is
260    /// emitted.
261    pub(crate) fn parse_from_attributes(attrs: &[Attribute]) -> Result<Self> {
262        match Self::try_parse_from_attributes(attrs)? {
263            Some(v) => Ok(v),
264            None => Err(syn::Error::new(
265                Span::call_site(),
266                "#[xml(..)] attribute required on struct or enum variant",
267            )),
268        }
269    }
270}
271
272/// Parse an XML name while resolving built-in namespace prefixes.
273fn parse_prefixed_name(
274    value: syn::parse::ParseStream<'_>,
275) -> Result<(Option<NamespaceRef>, NameRef)> {
276    if !value.peek(LitStr) {
277        // if we don't have a string literal next, we delegate to the default
278        // `NameRef` parser.
279        return Ok((None, value.parse()?));
280    }
281
282    let name: LitStr = value.parse()?;
283    let name_span = name.span();
284    let (prefix, name) = match name
285        .value()
286        .try_into()
287        .and_then(|name: rxml_validation::Name| name.split_name())
288    {
289        Ok(v) => v,
290        Err(e) => {
291            return Err(Error::new(
292                name_span,
293                format!("not a valid XML name: {}", e),
294            ))
295        }
296    };
297    let name = NameRef::Literal {
298        value: name,
299        span: name_span,
300    };
301    if let Some(prefix) = prefix {
302        let namespace_uri = match prefix.as_str() {
303            "xml" => XMLNS_XML,
304            "xmlns" => XMLNS_XMLNS,
305            other => return Err(Error::new(
306                name_span,
307                format!("prefix `{}` is not a built-in prefix and cannot be used. specify the desired namespace using the `namespace` key instead.", other)
308            )),
309        };
310        Ok((Some(NamespaceRef::fudge(namespace_uri, name_span)), name))
311    } else {
312        Ok((None, name))
313    }
314}
315
316/// Contents of an `#[xml(..)]` attribute on a struct or enum variant member.
317#[derive(Debug)]
318pub(crate) enum XmlFieldMeta {
319    /// `#[xml(attribute)]`, `#[xml(attribute = ..)]` or `#[xml(attribute(..))]`
320    Attribute {
321        /// The span of the `#[xml(attribute)]` meta from which this was parsed.
322        ///
323        /// This is useful for error messages.
324        span: Span,
325
326        /// The XML namespace supplied.
327        namespace: Option<NamespaceRef>,
328
329        /// The XML name supplied.
330        name: Option<NameRef>,
331
332        /// The `default` flag.
333        default_: Flag,
334    },
335
336    /// `#[xml(text)]`
337    Text {
338        /// The path to the optional codec type.
339        codec: Option<Type>,
340    },
341
342    /// `#[xml(child)`
343    Child {
344        /// The `default` flag.
345        default_: Flag,
346    },
347}
348
349impl XmlFieldMeta {
350    /// Parse a `#[xml(attribute(..))]` meta.
351    ///
352    /// That meta can have three distinct syntax styles:
353    /// - argument-less: `#[xml(attribute)]`
354    /// - shorthand: `#[xml(attribute = ..)]`
355    /// - full: `#[xml(attribute(..))]`
356    fn attribute_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
357        if meta.input.peek(Token![=]) {
358            // shorthand syntax
359            let (namespace, name) = parse_prefixed_name(meta.value()?)?;
360            Ok(Self::Attribute {
361                span: meta.path.span(),
362                name: Some(name),
363                namespace,
364                default_: Flag::Absent,
365            })
366        } else if meta.input.peek(syn::token::Paren) {
367            // full syntax
368            let mut name: Option<NameRef> = None;
369            let mut namespace: Option<NamespaceRef> = None;
370            let mut default_ = Flag::Absent;
371            meta.parse_nested_meta(|meta| {
372                if meta.path.is_ident("name") {
373                    if name.is_some() {
374                        return Err(Error::new_spanned(meta.path, "duplicate `name` key"));
375                    }
376                    let value = meta.value()?;
377                    let name_span = value.span();
378                    let (new_namespace, new_name) = parse_prefixed_name(value)?;
379                    if let Some(new_namespace) = new_namespace {
380                        if namespace.is_some() {
381                            return Err(Error::new(
382                                name_span,
383                                "cannot combine `namespace` key with prefixed `name`",
384                            ));
385                        }
386                        namespace = Some(new_namespace);
387                    }
388                    name = Some(new_name);
389                    Ok(())
390                } else if meta.path.is_ident("namespace") {
391                    if namespace.is_some() {
392                        return Err(Error::new_spanned(
393                            meta.path,
394                            "duplicate `namespace` key or `name` key has prefix",
395                        ));
396                    }
397                    namespace = Some(meta.value()?.parse()?);
398                    Ok(())
399                } else if meta.path.is_ident("default") {
400                    if default_.is_set() {
401                        return Err(Error::new_spanned(meta.path, "duplicate `default` key"));
402                    }
403                    default_ = (&meta.path).into();
404                    Ok(())
405                } else {
406                    Err(Error::new_spanned(meta.path, "unsupported key"))
407                }
408            })?;
409            Ok(Self::Attribute {
410                span: meta.path.span(),
411                name,
412                namespace,
413                default_,
414            })
415        } else {
416            // argument-less syntax
417            Ok(Self::Attribute {
418                span: meta.path.span(),
419                name: None,
420                namespace: None,
421                default_: Flag::Absent,
422            })
423        }
424    }
425
426    /// Parse a `#[xml(text)]` meta.
427    fn text_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
428        let mut codec: Option<Type> = None;
429        if meta.input.peek(Token![=]) {
430            Ok(Self::Text {
431                codec: Some(meta.value()?.parse()?),
432            })
433        } else if meta.input.peek(syn::token::Paren) {
434            meta.parse_nested_meta(|meta| {
435                if meta.path.is_ident("codec") {
436                    if codec.is_some() {
437                        return Err(Error::new_spanned(meta.path, "duplicate `codec` key"));
438                    }
439                    codec = Some(meta.value()?.parse()?);
440                    Ok(())
441                } else {
442                    Err(Error::new_spanned(meta.path, "unsupported key"))
443                }
444            })?;
445            Ok(Self::Text { codec })
446        } else {
447            Ok(Self::Text { codec: None })
448        }
449    }
450
451    /// Parse a `#[xml(child)]` meta.
452    fn child_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
453        if meta.input.peek(syn::token::Paren) {
454            let mut default_ = Flag::Absent;
455            meta.parse_nested_meta(|meta| {
456                if meta.path.is_ident("default") {
457                    if default_.is_set() {
458                        return Err(Error::new_spanned(meta.path, "duplicate `default` key"));
459                    }
460                    default_ = (&meta.path).into();
461                    Ok(())
462                } else {
463                    Err(Error::new_spanned(meta.path, "unsupported key"))
464                }
465            })?;
466            Ok(Self::Child { default_ })
467        } else {
468            Ok(Self::Child {
469                default_: Flag::Absent,
470            })
471        }
472    }
473
474    /// Parse [`Self`] from a nestd meta, switching on the identifier
475    /// of that nested meta.
476    fn parse_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
477        if meta.path.is_ident("attribute") {
478            Self::attribute_from_meta(meta)
479        } else if meta.path.is_ident("text") {
480            Self::text_from_meta(meta)
481        } else if meta.path.is_ident("child") {
482            Self::child_from_meta(meta)
483        } else {
484            Err(Error::new_spanned(meta.path, "unsupported field meta"))
485        }
486    }
487
488    /// Parse an `#[xml(..)]` meta on a field.
489    ///
490    /// This switches based on the first identifier within the `#[xml(..)]`
491    /// meta and generates an enum variant accordingly.
492    ///
493    /// Only a single nested meta is allowed; more than one will be
494    /// rejected with an appropriate compile-time error.
495    ///
496    /// If no meta is contained at all, a compile-time error is generated.
497    ///
498    /// Undefined options or options with incompatible values are rejected
499    /// with an appropriate compile-time error.
500    pub(crate) fn parse_from_attribute(attr: &Attribute) -> Result<Self> {
501        let mut result: Option<Self> = None;
502
503        attr.parse_nested_meta(|meta| {
504            if result.is_some() {
505                return Err(Error::new_spanned(
506                    meta.path,
507                    "multiple field type specifiers are not supported",
508                ));
509            }
510
511            result = Some(Self::parse_from_meta(meta)?);
512            Ok(())
513        })?;
514
515        if let Some(result) = result {
516            Ok(result)
517        } else {
518            Err(Error::new_spanned(
519                attr,
520                "missing field type specifier within `#[xml(..)]`",
521            ))
522        }
523    }
524
525    /// Find and parse a `#[xml(..)]` meta on a field.
526    ///
527    /// This invokes [`Self::parse_from_attribute`] internally on the first
528    /// encountered `#[xml(..)]` meta.
529    ///
530    /// If not exactly one `#[xml(..)]` meta is encountered, an error is
531    /// returned. The error is spanned to `err_span`.
532    pub(crate) fn parse_from_attributes(attrs: &[Attribute], err_span: &Span) -> Result<Self> {
533        let mut result: Option<Self> = None;
534        for attr in attrs {
535            if !attr.path().is_ident("xml") {
536                continue;
537            }
538
539            if result.is_some() {
540                return Err(Error::new_spanned(
541                    attr,
542                    "only one #[xml(..)] attribute per field allowed.",
543                ));
544            }
545
546            result = Some(Self::parse_from_attribute(attr)?);
547        }
548
549        if let Some(result) = result {
550            Ok(result)
551        } else {
552            Err(Error::new(*err_span, "missing #[xml(..)] meta on field"))
553        }
554    }
555}