From 1611c5fba99694a99824034f7ab739a84ab04a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Sat, 22 Jun 2024 09:13:47 +0200 Subject: [PATCH] xso-proc: validate XML names against rxml_validation::NcName That way we avoid a fallible conversion at runtime. --- xso-proc/Cargo.toml | 1 + xso-proc/src/lib.rs | 14 +++-------- xso-proc/src/meta.rs | 55 ++++++++++++++++++++++++++++++++++++----- xso/src/from_xml_doc.md | 12 +++++++++ 4 files changed, 65 insertions(+), 17 deletions(-) diff --git a/xso-proc/Cargo.toml b/xso-proc/Cargo.toml index 8aab8947..5f8ef5d0 100644 --- a/xso-proc/Cargo.toml +++ b/xso-proc/Cargo.toml @@ -18,6 +18,7 @@ proc-macro = true quote = "^1" syn = { version = "^2", features = ["full", "extra-traits"] } proc-macro2 = "^1" +rxml_validation = { version = "0.11.0", default-features = false, features = ["std"] } [features] panicking-into-impl = ["minidom"] diff --git a/xso-proc/src/lib.rs b/xso-proc/src/lib.rs index 8c363b92..c075c26b 100644 --- a/xso-proc/src/lib.rs +++ b/xso-proc/src/lib.rs @@ -77,8 +77,8 @@ fn from_xml_impl(input: Item) -> Result { let from_events_builder_ty_name = quote::format_ident!("{}FromEvents", ident); let state_ty_name = quote::format_ident!("{}FromEventsState", ident); - let unknown_attr_err = format!("Unknown attribute in {} element.", xml_name.value()); - let unknown_child_err = format!("Unknown child in {} element.", xml_name.value()); + let unknown_attr_err = format!("Unknown attribute in {} element.", xml_name.as_str()); + let unknown_child_err = format!("Unknown child in {} element.", xml_name.as_str()); let docstr = format!("Build a [`{}`] from XML events", ident); #[cfg_attr(not(feature = "minidom"), allow(unused_mut))] @@ -215,15 +215,7 @@ fn into_xml_impl(input: Item) -> Result { ::xso::exports::rxml::parser::EventMetrics::zero(), ( ::xso::exports::rxml::Namespace::from_str(#xml_namespace), - match ::xso::exports::rxml::NcName::try_from(#xml_name) { - ::core::result::Result::Ok(v) => v, - ::core::result::Result::Err(e) => { - self.0 = ::core::option::Option::None; - return ::core::option::Option::Some(::core::result::Result::Err(e.into())); - - } - - } + ::xso::exports::rxml::NcName::from(#xml_name), ), ::xso::exports::rxml::AttrMap::new(), ))) diff --git a/xso-proc/src/meta.rs b/xso-proc/src/meta.rs index 17401d4a..8e6ff6c0 100644 --- a/xso-proc/src/meta.rs +++ b/xso-proc/src/meta.rs @@ -9,20 +9,63 @@ //! This module is concerned with parsing attributes from the Rust "meta" //! annotations on structs, enums, enum variants and fields. -use proc_macro2::Span; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, quote_spanned}; use syn::{spanned::Spanned, *}; +use rxml_validation::NcName; + /// Type alias for a `#[xml(namespace = ..)]` attribute. /// /// This may, in the future, be replaced by an enum supporting multiple /// ways to specify a namespace. pub(crate) type NamespaceRef = Path; -/// Type alias for a `#[xml(name = ..)]` attribute. -/// -/// This may, in the future, be replaced by an enum supporting both `Path` and -/// `LitStr`. -pub(crate) type NameRef = LitStr; +/// Value for the `#[xml(name = .. )]` attribute. +#[derive(Debug)] +pub(crate) struct NameRef { + value: NcName, + span: Span, +} + +impl NameRef { + /// Access the XML name as str. + /// + /// *Note*: This function may vanish in the future if we ever support + /// non-literal XML names. + pub(crate) fn as_str(&self) -> &str { + self.value.as_str() + } +} + +impl syn::parse::Parse for NameRef { + fn parse(input: syn::parse::ParseStream<'_>) -> Result { + let s: LitStr = input.parse()?; + let span = s.span(); + match NcName::try_from(s.value()) { + Ok(value) => Ok(Self { value, span }), + Err(e) => Err(Error::new( + span, + format!("not a valid XML element name: {}", e), + )), + } + } +} + +impl quote::ToTokens for NameRef { + fn to_tokens(&self, tokens: &mut TokenStream) { + let value = self.value.as_str(); + let value = quote_spanned! { self.span=> #value }; + // SAFETY: self.0 is a known-good NcName, so converting it to an + // NcNameStr is known to be safe. + // NOTE: we cannot use `quote_spanned! { self.span=> }` for the unsafe + // block as that would then in fact trip a `#[deny(unsafe_code)]` lint + // at the use site of the macro. + tokens.extend(quote! { + unsafe { ::xso::exports::rxml::NcNameStr::from_str_unchecked(#value) } + }) + } +} /// Contents of an `#[xml(..)]` attribute on a struct, enum variant, or enum. #[derive(Debug)] diff --git a/xso/src/from_xml_doc.md b/xso/src/from_xml_doc.md index a4c8a814..3c4fd0e6 100644 --- a/xso/src/from_xml_doc.md +++ b/xso/src/from_xml_doc.md @@ -34,6 +34,18 @@ All key-value pairs interpreted by these derive macros must be wrapped in a | `namespace` | *path* | The path to a `&'static str` which holds the XML namespace to match. | | `name` | *string literal* | The XML element name to match. | +Note that the `name` value must be a valid XML element name, without colons. +The namespace prefix, if any, is assigned automatically at serialisation time +and cannot be overridden. The following will thus not compile: + +```compile_fail +# use xso::FromXml; +# static MY_NAMESPACE: &'static str = "urn:example"; +#[derive(FromXml, Debug, PartialEq)] +#[xml(namespace = MY_NAMESPACE, name = "fnord:foo")] // colon not allowed +struct Foo; +``` + ## Limitations Supports only empty structs currently. For example, the following will not