use xsd::{PrimitiveType, PrimitiveValue, Type, Value};
#[test]
fn datatype_names_and_iris() {
for (name, datatype) in xsd::TYPES.entries() {
assert_eq!(datatype.to_string(), *name);
assert_eq!(&name.parse::<Type>().unwrap(), *datatype);
#[cfg(feature = "alloc")]
{
let expected = format!("{}{name}", xsd::BASE_URI);
assert_eq!(datatype.iri_string(), expected);
match datatype {
Type::Primitive(t) => assert_eq!(t.iri_string(), expected),
Type::Decimal(t) => assert_eq!(t.iri_string(), expected),
Type::Other(_) => unreachable!(),
}
}
}
}
#[test]
fn custom_datatype_names_and_iris() {
for name in [
"unsignedInt",
"urn:example:datatype",
"https://example.com/datatype",
"http://www.w3.org/2001/XMLSchema#integer",
] {
let datatype = Type::from(name);
let primitive = PrimitiveType::Other(name.into());
assert_eq!(datatype.to_string(), name);
assert_eq!(primitive.to_string(), name);
#[cfg(feature = "alloc")]
{
let expected = if name == "unsignedInt" {
format!("{}{name}", xsd::BASE_URI)
} else {
name.to_owned()
};
assert_eq!(datatype.iri_string(), expected);
assert_eq!(primitive.iri_string(), expected);
}
}
}
#[test]
#[cfg(feature = "oxrdf")]
fn named_node_iri_round_trips() {
for iri in [
"http://www.w3.org/2001/XMLSchema#string",
"http://www.w3.org/2001/XMLSchema#int",
"http://www.w3.org/2001/XMLSchema#unsignedInt",
"urn:example:datatype",
] {
let datatype = Type::from(oxrdf::NamedNode::new(iri).unwrap());
assert_eq!(datatype.iri_string(), iri);
}
}
#[test]
fn scalar_lexical_round_trips() {
for (input, datatype, expected) in [
("1", xsd::BOOLEAN, "true"),
("false", xsd::BOOLEAN, "false"),
("12.5", xsd::DECIMAL, "12.5"),
("42", xsd::INTEGER, "42"),
("-9223372036854775808", xsd::LONG, "-9223372036854775808"),
("+42", xsd::INT, "42"),
("-32768", xsd::SHORT, "-32768"),
("127", xsd::BYTE, "127"),
("1.5", xsd::FLOAT, "1.5"),
("INF", xsd::DOUBLE, "INF"),
("-INF", xsd::DOUBLE, "-INF"),
("NaN", xsd::DOUBLE, "NaN"),
] {
let value = xsd::parse(input, &datatype).unwrap();
let rendered = value.to_string();
assert_eq!(rendered, expected);
assert_eq!(xsd::parse(rendered, datatype).unwrap(), value);
}
assert_primitive(PrimitiveValue::from("text<&>"), "text<&>");
assert_primitive(PrimitiveValue::from(42_i32), "42");
}
#[test]
#[cfg(feature = "jiff")]
fn temporal_lexical_round_trips() {
for (input, datatype) in [
("2026-12-31", xsd::DATE),
("2026-12-31T12:34:56", xsd::DATE_TIME),
("12:34:56.125", xsd::TIME),
("PT1.5S", xsd::DURATION),
("-PT1S", xsd::DURATION),
] {
let value = xsd::parse(input, &datatype).unwrap();
assert_eq!(value.to_string(), input);
assert_eq!(xsd::parse(value.to_string(), datatype).unwrap(), value);
}
}
#[test]
#[cfg(feature = "jiff")]
fn date_lexical_year_formatting() {
use xsd::primitive::Date;
for (year, expected) in [
(-9999, "-9999-01-02"),
(-2026, "-2026-01-02"),
(-1, "-0001-01-02"),
(1, "0001-01-02"),
(9999, "9999-01-02"),
] {
assert_primitive(
PrimitiveValue::Date(Date::new(year, 1, 2).unwrap()),
expected,
);
}
}
#[test]
#[cfg(feature = "jiff")]
fn datetime_lexical_year_formatting() {
use xsd::primitive::DateTime;
for (year, prefix) in [
(-9999, "-9999"),
(-2026, "-2026"),
(-1, "-0001"),
(1, "0001"),
(9999, "9999"),
] {
for (nanosecond, suffix) in [(0, ""), (125_000_000, ".125"), (1, ".000000001")] {
let value = DateTime::new(year, 1, 2, 12, 34, 56, nanosecond).unwrap();
assert_primitive(
PrimitiveValue::DateTime(value),
&format!("{prefix}-01-02T12:34:56{suffix}"),
);
}
}
}
#[test]
fn partial_calendar_lexical_forms() {
use PrimitiveValue::*;
for (value, expected) in [
(GYear(xsd::primitive::GYear::new(1)), "0001"),
(GYear(xsd::primitive::GYear::new(-1)), "-0001"),
(GYear(xsd::primitive::GYear::new(12345)), "12345"),
(GYear(xsd::primitive::GYear::new(i32::MIN)), "-2147483648"),
(
GYearMonth(xsd::primitive::GYearMonth::new(2026, 1).unwrap()),
"2026-01",
),
(
GYearMonth(xsd::primitive::GYearMonth::new(-1, 12).unwrap()),
"-0001-12",
),
(
GMonthDay(xsd::primitive::GMonthDay::new(2, 29).unwrap()),
"--02-29",
),
(GDay(xsd::primitive::GDay::new(1).unwrap()), "---01"),
(GMonth(xsd::primitive::GMonth::new(1).unwrap()), "--01"),
] {
assert_primitive(value, expected);
}
}
#[test]
#[cfg(feature = "alloc")]
fn binary_and_name_lexical_forms() {
use PrimitiveValue::*;
assert_primitive(HexBinary(vec![]), "");
assert_primitive(HexBinary(vec![0, 1, 15, 16, 171, 255]), "00010F10ABFF");
for (bytes, expected) in [
(b"".as_slice(), ""),
(b"f", "Zg=="),
(b"fo", "Zm8="),
(b"foo", "Zm9v"),
(b"foob", "Zm9vYg=="),
(b"fooba", "Zm9vYmE="),
(b"foobar", "Zm9vYmFy"),
(&[251, 255, 239], "+//v"),
] {
assert_primitive(Base64Binary(bytes.to_vec()), expected);
}
assert_primitive(AnyUri("urn:example:item".into()), "urn:example:item");
assert_primitive(QName("ex".into(), "item".into()), "ex:item");
assert_primitive(QName("".into(), "item".into()), "item");
}
#[test]
fn formatting_propagates_writer_errors() {
use core::fmt::Write;
let mut output = heapless::String::<2>::new();
assert!(write!(&mut output, "{}", PrimitiveValue::g_month(1).unwrap()).is_err());
#[cfg(feature = "jiff")]
{
let value = PrimitiveValue::Date(xsd::primitive::Date::new(-1, 1, 2).unwrap());
let mut short = heapless::String::<2>::new();
assert!(write!(&mut short, "{value}").is_err());
let mut year_only = heapless::String::<5>::new();
assert!(write!(&mut year_only, "{value}").is_err());
let value = PrimitiveValue::DateTime(
xsd::primitive::DateTime::new(-1, 1, 2, 12, 34, 56, 125_000_000).unwrap(),
);
let mut short = heapless::String::<2>::new();
assert!(write!(&mut short, "{value}").is_err());
let mut date_only = heapless::String::<11>::new();
assert!(write!(&mut date_only, "{value}").is_err());
let mut whole_seconds = heapless::String::<20>::new();
assert!(write!(&mut whole_seconds, "{value}").is_err());
}
#[cfg(feature = "alloc")]
for value in [
PrimitiveValue::HexBinary(vec![0, 255]),
PrimitiveValue::Base64Binary(vec![0, 255]),
] {
let mut output = heapless::String::<2>::new();
assert!(write!(&mut output, "{value}").is_err());
}
}
fn assert_primitive(value: PrimitiveValue, expected: &str) {
assert_eq!(value.to_string(), expected);
assert_eq!(Value::from(value).to_string(), expected);
}