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