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::borrow::Cow;
13
14use proc_macro2::{Span, TokenStream};
15use quote::{quote, quote_spanned};
16use syn::{spanned::Spanned, *};
17
18use rxml_validation::NcName;
19
20/// Value for the `#[xml(namespace = ..)]` attribute.
21#[derive(Debug)]
22pub(crate) enum NamespaceRef {
23 /// The XML namespace is specified as a string literal.
24 LitStr(LitStr),
25
26 /// The XML namespace is specified as a path.
27 Path(Path),
28}
29
30impl syn::parse::Parse for NamespaceRef {
31 fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
32 if input.peek(syn::LitStr) {
33 Ok(Self::LitStr(input.parse()?))
34 } else {
35 Ok(Self::Path(input.parse()?))
36 }
37 }
38}
39
40impl quote::ToTokens for NamespaceRef {
41 fn to_tokens(&self, tokens: &mut TokenStream) {
42 match self {
43 Self::LitStr(ref lit) => lit.to_tokens(tokens),
44 Self::Path(ref path) => path.to_tokens(tokens),
45 }
46 }
47}
48
49/// Value for the `#[xml(name = .. )]` attribute.
50#[derive(Debug)]
51pub(crate) enum NameRef {
52 /// The XML name is specified as a string literal.
53 Literal {
54 /// The validated XML name.
55 value: NcName,
56
57 /// The span of the original [`syn::LitStr`].
58 span: Span,
59 },
60
61 /// The XML name is specified as a path.
62 Path(Path),
63}
64
65impl NameRef {
66 /// Access a representation of the XML name as str.
67 ///
68 /// If this name reference is a [`Self::Path`], this will return the name
69 /// of the rightmost identifier in the path.
70 ///
71 /// If this name reference is a [`Self::Literal`], this will return the
72 /// contents of the literal.
73 pub(crate) fn repr_to_string(&self) -> Cow<'_, str> {
74 match self {
75 Self::Literal { ref value, .. } => Cow::Borrowed(value.as_str()),
76 Self::Path(ref path) => path.segments.last().unwrap().ident.to_string().into(),
77 }
78 }
79}
80
81impl syn::parse::Parse for NameRef {
82 fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
83 if input.peek(syn::LitStr) {
84 let s: LitStr = input.parse()?;
85 let span = s.span();
86 match NcName::try_from(s.value()) {
87 Ok(value) => Ok(Self::Literal { value, span }),
88 Err(e) => Err(Error::new(
89 span,
90 format!("not a valid XML element name: {}", e),
91 )),
92 }
93 } else {
94 let p: Path = input.parse()?;
95 Ok(Self::Path(p))
96 }
97 }
98}
99
100impl quote::ToTokens for NameRef {
101 fn to_tokens(&self, tokens: &mut TokenStream) {
102 match self {
103 Self::Literal { ref value, span } => {
104 let span = *span;
105 let value = value.as_str();
106 let value = quote_spanned! { span=> #value };
107 // SAFETY: self.0 is a known-good NcName, so converting it to an
108 // NcNameStr is known to be safe.
109 // NOTE: we cannot use `quote_spanned! { self.span=> }` for the unsafe
110 // block as that would then in fact trip a `#[deny(unsafe_code)]` lint
111 // at the use site of the macro.
112 tokens.extend(quote! {
113 unsafe { ::xso::exports::rxml::NcNameStr::from_str_unchecked(#value) }
114 })
115 }
116 Self::Path(ref path) => path.to_tokens(tokens),
117 }
118 }
119}
120
121/// Contents of an `#[xml(..)]` attribute on a struct, enum variant, or enum.
122#[derive(Debug)]
123pub(crate) struct XmlCompoundMeta {
124 /// The span of the `#[xml(..)]` meta from which this was parsed.
125 ///
126 /// This is useful for error messages.
127 pub(crate) span: Span,
128
129 /// The value assigned to `namespace` inside `#[xml(..)]`, if any.
130 pub(crate) namespace: Option<NamespaceRef>,
131
132 /// The value assigned to `name` inside `#[xml(..)]`, if any.
133 pub(crate) name: Option<NameRef>,
134}
135
136impl XmlCompoundMeta {
137 /// Parse the meta values from a `#[xml(..)]` attribute.
138 ///
139 /// Undefined options or options with incompatible values are rejected
140 /// with an appropriate compile-time error.
141 fn parse_from_attribute(attr: &Attribute) -> Result<Self> {
142 let mut namespace = None;
143 let mut name = None;
144
145 attr.parse_nested_meta(|meta| {
146 if meta.path.is_ident("name") {
147 if name.is_some() {
148 return Err(Error::new_spanned(meta.path, "duplicate `name` key"));
149 }
150 name = Some(meta.value()?.parse()?);
151 Ok(())
152 } else if meta.path.is_ident("namespace") {
153 if namespace.is_some() {
154 return Err(Error::new_spanned(meta.path, "duplicate `namespace` key"));
155 }
156 namespace = Some(meta.value()?.parse()?);
157 Ok(())
158 } else {
159 Err(Error::new_spanned(meta.path, "unsupported key"))
160 }
161 })?;
162
163 Ok(Self {
164 span: attr.span(),
165 namespace,
166 name,
167 })
168 }
169
170 /// Search through `attrs` for a single `#[xml(..)]` attribute and parse
171 /// it.
172 ///
173 /// Undefined options or options with incompatible values are rejected
174 /// with an appropriate compile-time error.
175 ///
176 /// If more than one `#[xml(..)]` attribute is found, an error is
177 /// emitted.
178 ///
179 /// If no `#[xml(..)]` attribute is found, `None` is returned.
180 pub(crate) fn try_parse_from_attributes(attrs: &[Attribute]) -> Result<Option<Self>> {
181 let mut result = None;
182 for attr in attrs {
183 if !attr.path().is_ident("xml") {
184 continue;
185 }
186 if result.is_some() {
187 return Err(syn::Error::new_spanned(
188 attr.path(),
189 "only one #[xml(..)] per struct or enum variant allowed",
190 ));
191 }
192 result = Some(Self::parse_from_attribute(attr)?);
193 }
194 Ok(result)
195 }
196
197 /// Search through `attrs` for a single `#[xml(..)]` attribute and parse
198 /// it.
199 ///
200 /// Undefined options or options with incompatible values are rejected
201 /// with an appropriate compile-time error.
202 ///
203 /// If more than one or no `#[xml(..)]` attribute is found, an error is
204 /// emitted.
205 pub(crate) fn parse_from_attributes(attrs: &[Attribute]) -> Result<Self> {
206 match Self::try_parse_from_attributes(attrs)? {
207 Some(v) => Ok(v),
208 None => Err(syn::Error::new(
209 Span::call_site(),
210 "#[xml(..)] attribute required on struct or enum variant",
211 )),
212 }
213 }
214}