xso-proc: start making derive macros for FromXml and IntoXml
For now, these macros only support empty elements. Everything else will be rejected with a compile-time error.
This commit is contained in:
parent
9ec9a0f0c6
commit
0adfd1218b
10 changed files with 662 additions and 1 deletions
150
parsers/src/util/macro_tests.rs
Normal file
150
parsers/src/util/macro_tests.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#![deny(
|
||||
non_camel_case_types,
|
||||
non_snake_case,
|
||||
unsafe_code,
|
||||
unused_variables,
|
||||
unused_mut,
|
||||
dead_code
|
||||
)]
|
||||
|
||||
mod helpers {
|
||||
// we isolate the helpers into a module, because we do not want to have
|
||||
// them in scope below.
|
||||
// this is to ensure that the macros do not have hidden dependencies on
|
||||
// any specific names being imported.
|
||||
use minidom::Element;
|
||||
use xso::{error::FromElementError, transform, try_from_element, FromXml, IntoXml};
|
||||
|
||||
pub(super) fn roundtrip_full<T: IntoXml + FromXml + PartialEq + std::fmt::Debug + Clone>(
|
||||
s: &str,
|
||||
) {
|
||||
let initial: Element = s.parse().unwrap();
|
||||
let structural: T = match try_from_element(initial.clone()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("failed to parse from {:?}: {}", s, e),
|
||||
};
|
||||
let recovered =
|
||||
transform(structural.clone()).expect("roundtrip did not produce an element");
|
||||
assert_eq!(initial, recovered);
|
||||
let structural2: T = match try_from_element(recovered) {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("failed to parse from serialisation of {:?}: {}", s, e),
|
||||
};
|
||||
assert_eq!(structural, structural2);
|
||||
}
|
||||
|
||||
pub(super) fn parse_str<T: FromXml>(s: &str) -> Result<T, FromElementError> {
|
||||
let initial: Element = s.parse().unwrap();
|
||||
try_from_element(initial)
|
||||
}
|
||||
}
|
||||
|
||||
use self::helpers::{parse_str, roundtrip_full};
|
||||
|
||||
use xso::{FromXml, IntoXml};
|
||||
|
||||
// these are adverserial local names in order to trigger any issues with
|
||||
// unqualified names in the macro expansions.
|
||||
#[allow(dead_code, non_snake_case)]
|
||||
fn Err() {}
|
||||
#[allow(dead_code, non_snake_case)]
|
||||
fn Ok() {}
|
||||
#[allow(dead_code, non_snake_case)]
|
||||
fn Some() {}
|
||||
#[allow(dead_code, non_snake_case)]
|
||||
fn None() {}
|
||||
#[allow(dead_code)]
|
||||
type Option = ((),);
|
||||
#[allow(dead_code)]
|
||||
type Result = ((),);
|
||||
|
||||
static NS1: &str = "urn:example:ns1";
|
||||
|
||||
#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
|
||||
#[xml(namespace = NS1, name = "foo")]
|
||||
struct Empty;
|
||||
|
||||
#[test]
|
||||
fn empty_roundtrip() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
roundtrip_full::<Empty>("<foo xmlns='urn:example:ns1'/>");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_name_mismatch() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
match parse_str::<Empty>("<bar xmlns='urn:example:ns1'/>") {
|
||||
Err(xso::error::FromElementError::Mismatch(..)) => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_namespace_mismatch() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
match parse_str::<Empty>("<foo xmlns='urn:example:ns2'/>") {
|
||||
Err(xso::error::FromElementError::Mismatch(..)) => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_unexpected_attribute() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
match parse_str::<Empty>("<foo xmlns='urn:example:ns1' fnord='bar'/>") {
|
||||
Err(xso::error::FromElementError::Invalid(xso::error::Error::Other(e))) => {
|
||||
assert_eq!(e, "Unknown attribute in foo element.");
|
||||
}
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_unexpected_child() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
match parse_str::<Empty>("<foo xmlns='urn:example:ns1'><coucou/></foo>") {
|
||||
Err(xso::error::FromElementError::Invalid(xso::error::Error::Other(e))) => {
|
||||
assert_eq!(e, "Unknown child in foo element.");
|
||||
}
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_qname_check_has_precedence_over_attr_check() {
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
option::Option::{None, Some},
|
||||
result::Result::{Err, Ok},
|
||||
};
|
||||
match parse_str::<Empty>("<bar xmlns='urn:example:ns1' fnord='bar'/>") {
|
||||
Err(xso::error::FromElementError::Mismatch(..)) => (),
|
||||
other => panic!("unexpected result: {:?}", other),
|
||||
}
|
||||
}
|
||||
|
|
@ -10,3 +10,6 @@ pub(crate) mod text_node_codecs;
|
|||
/// Helper macros to parse and serialise more easily.
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
#[cfg(test)]
|
||||
mod macro_tests;
|
||||
|
|
|
|||
Loading…
Reference in a new issue