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}