use eure_document::document::EureDocument;
use eure_document::eure;
use eure_document::identifier::Identifier;
use eure_document::parse::{ParseError, ParseErrorKind};
use eure_document::value::ObjectKey;
use eure_schema::convert::{ConversionError, document_to_schema};
use eure_schema::interop::VariantRepr;
use eure_schema::{
ArraySchema, Bound, FloatSchema, IntegerSchema, MapSchema, SchemaDocument, SchemaMetadata,
SchemaNodeContent, SchemaNodeId, TextSchema, UnknownFieldsPolicy,
};
use num_bigint::BigInt;
fn ident(s: &str) -> Identifier {
s.parse().expect("Invalid identifier")
}
#[track_caller]
fn convert(doc: &EureDocument) -> SchemaDocument {
let (schema, _source_map) = document_to_schema(doc).expect("Failed to convert to schema");
schema
}
#[track_caller]
fn assert_record1<F1>(schema: &SchemaDocument, node_id: SchemaNodeId, field1: (&str, F1))
where
F1: Fn(&SchemaDocument, SchemaNodeId),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Record(record) = &node.content {
let (name1, check1) = field1;
let field = record
.properties
.get(name1)
.unwrap_or_else(|| panic!("Field '{}' missing", name1));
check1(schema, field.schema);
} else {
panic!("Expected Record type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_record2<F1, F2>(
schema: &SchemaDocument,
node_id: SchemaNodeId,
field1: (&str, F1),
field2: (&str, F2),
) where
F1: Fn(&SchemaDocument, SchemaNodeId),
F2: Fn(&SchemaDocument, SchemaNodeId),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Record(record) = &node.content {
assert_eq!(record.properties.len(), 2);
let (name1, check1) = field1;
let (name2, check2) = field2;
let f1 = record
.properties
.get(name1)
.unwrap_or_else(|| panic!("Field '{}' missing", name1));
let f2 = record
.properties
.get(name2)
.unwrap_or_else(|| panic!("Field '{}' missing", name2));
check1(schema, f1.schema);
check2(schema, f2.schema);
} else {
panic!("Expected Record type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_record3<F1, F2, F3>(
schema: &SchemaDocument,
node_id: SchemaNodeId,
field1: (&str, F1),
field2: (&str, F2),
field3: (&str, F3),
) where
F1: Fn(&SchemaDocument, SchemaNodeId),
F2: Fn(&SchemaDocument, SchemaNodeId),
F3: Fn(&SchemaDocument, SchemaNodeId),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Record(record) = &node.content {
assert_eq!(record.properties.len(), 3);
let (name1, check1) = field1;
let (name2, check2) = field2;
let (name3, check3) = field3;
let f1 = record
.properties
.get(name1)
.unwrap_or_else(|| panic!("Field '{}' missing", name1));
let f2 = record
.properties
.get(name2)
.unwrap_or_else(|| panic!("Field '{}' missing", name2));
let f3 = record
.properties
.get(name3)
.unwrap_or_else(|| panic!("Field '{}' missing", name3));
check1(schema, f1.schema);
check2(schema, f2.schema);
check3(schema, f3.schema);
} else {
panic!("Expected Record type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_record5<F1, F2, F3, F4, F5>(
schema: &SchemaDocument,
node_id: SchemaNodeId,
field1: (&str, F1),
field2: (&str, F2),
field3: (&str, F3),
field4: (&str, F4),
field5: (&str, F5),
) where
F1: Fn(&SchemaDocument, SchemaNodeId),
F2: Fn(&SchemaDocument, SchemaNodeId),
F3: Fn(&SchemaDocument, SchemaNodeId),
F4: Fn(&SchemaDocument, SchemaNodeId),
F5: Fn(&SchemaDocument, SchemaNodeId),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Record(record) = &node.content {
assert_eq!(record.properties.len(), 5);
let (name1, check1) = field1;
let (name2, check2) = field2;
let (name3, check3) = field3;
let (name4, check4) = field4;
let (name5, check5) = field5;
let f1 = record
.properties
.get(name1)
.unwrap_or_else(|| panic!("Field '{}' missing", name1));
let f2 = record
.properties
.get(name2)
.unwrap_or_else(|| panic!("Field '{}' missing", name2));
let f3 = record
.properties
.get(name3)
.unwrap_or_else(|| panic!("Field '{}' missing", name3));
let f4 = record
.properties
.get(name4)
.unwrap_or_else(|| panic!("Field '{}' missing", name4));
let f5 = record
.properties
.get(name5)
.unwrap_or_else(|| panic!("Field '{}' missing", name5));
check1(schema, f1.schema);
check2(schema, f2.schema);
check3(schema, f3.schema);
check4(schema, f4.schema);
check5(schema, f5.schema);
} else {
panic!("Expected Record type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_field_optional(schema: &SchemaDocument, node_id: SchemaNodeId, field_name: &str) {
let node = schema.node(node_id);
if let SchemaNodeContent::Record(record) = &node.content {
let field = record
.properties
.get(field_name)
.unwrap_or_else(|| panic!("Field '{}' missing", field_name));
assert!(
field.optional,
"Expected field '{}' to be optional",
field_name
);
} else {
panic!("Expected Record type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_text(schema: &SchemaDocument, node_id: SchemaNodeId) {
let node = schema.node(node_id);
if let SchemaNodeContent::Text(text_schema) = &node.content {
assert!(
text_schema.language.is_none(),
"Expected text with no language constraint, got {:?}",
text_schema.language
);
} else {
panic!("Expected Text type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_text_language(schema: &SchemaDocument, node_id: SchemaNodeId, expected: &str) {
let node = schema.node(node_id);
if let SchemaNodeContent::Text(text_schema) = &node.content {
assert_eq!(
text_schema.language.as_deref(),
Some(expected),
"Expected language '{}', got {:?}",
expected,
text_schema.language
);
} else {
panic!("Expected Text type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_text_with<F>(schema: &SchemaDocument, node_id: SchemaNodeId, check: F)
where
F: Fn(&TextSchema),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Text(text_schema) = &node.content {
check(text_schema);
} else {
panic!("Expected Text type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_integer(schema: &SchemaDocument, node_id: SchemaNodeId) {
let node = schema.node(node_id);
assert!(
matches!(node.content, SchemaNodeContent::Integer(_)),
"Expected Integer type, got {:?}",
node.content
);
}
#[track_caller]
fn assert_integer_with<F>(schema: &SchemaDocument, node_id: SchemaNodeId, check: F)
where
F: Fn(&IntegerSchema),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Integer(int_schema) = &node.content {
check(int_schema);
} else {
panic!("Expected Integer type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_float(schema: &SchemaDocument, node_id: SchemaNodeId) {
let node = schema.node(node_id);
assert!(
matches!(node.content, SchemaNodeContent::Float(_)),
"Expected Float type, got {:?}",
node.content
);
}
#[track_caller]
fn assert_float_with<F>(schema: &SchemaDocument, node_id: SchemaNodeId, check: F)
where
F: Fn(&FloatSchema),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Float(float_schema) = &node.content {
check(float_schema);
} else {
panic!("Expected Float type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_boolean(schema: &SchemaDocument, node_id: SchemaNodeId) {
let node = schema.node(node_id);
assert!(
matches!(node.content, SchemaNodeContent::Boolean),
"Expected Boolean type, got {:?}",
node.content
);
}
#[track_caller]
fn assert_null(schema: &SchemaDocument, node_id: SchemaNodeId) {
let node = schema.node(node_id);
assert!(
matches!(node.content, SchemaNodeContent::Null),
"Expected Null type, got {:?}",
node.content
);
}
#[track_caller]
fn assert_any(schema: &SchemaDocument, node_id: SchemaNodeId) {
let node = schema.node(node_id);
assert!(
matches!(node.content, SchemaNodeContent::Any),
"Expected Any type, got {:?}",
node.content
);
}
#[track_caller]
fn assert_array<F>(schema: &SchemaDocument, node_id: SchemaNodeId, item_check: F)
where
F: Fn(&SchemaDocument, SchemaNodeId),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Array(array_schema) = &node.content {
item_check(schema, array_schema.item);
} else {
panic!("Expected Array type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_array_with<F, G>(
schema: &SchemaDocument,
node_id: SchemaNodeId,
item_check: F,
constraint_check: G,
) where
F: Fn(&SchemaDocument, SchemaNodeId),
G: Fn(&ArraySchema),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Array(array_schema) = &node.content {
item_check(schema, array_schema.item);
constraint_check(array_schema);
} else {
panic!("Expected Array type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_map<K, V>(schema: &SchemaDocument, node_id: SchemaNodeId, key_check: K, value_check: V)
where
K: Fn(&SchemaDocument, SchemaNodeId),
V: Fn(&SchemaDocument, SchemaNodeId),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Map(map_schema) = &node.content {
key_check(schema, map_schema.key);
value_check(schema, map_schema.value);
} else {
panic!("Expected Map type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_map_with<K, V, C>(
schema: &SchemaDocument,
node_id: SchemaNodeId,
key_check: K,
value_check: V,
constraint_check: C,
) where
K: Fn(&SchemaDocument, SchemaNodeId),
V: Fn(&SchemaDocument, SchemaNodeId),
C: Fn(&MapSchema),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Map(map_schema) = &node.content {
key_check(schema, map_schema.key);
value_check(schema, map_schema.value);
constraint_check(map_schema);
} else {
panic!("Expected Map type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_tuple2<F1, F2>(schema: &SchemaDocument, node_id: SchemaNodeId, check1: F1, check2: F2)
where
F1: Fn(&SchemaDocument, SchemaNodeId),
F2: Fn(&SchemaDocument, SchemaNodeId),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Tuple(tuple_schema) = &node.content {
assert_eq!(tuple_schema.elements.len(), 2);
check1(schema, tuple_schema.elements[0]);
check2(schema, tuple_schema.elements[1]);
} else {
panic!("Expected Tuple type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_tuple3<F1, F2, F3>(
schema: &SchemaDocument,
node_id: SchemaNodeId,
check1: F1,
check2: F2,
check3: F3,
) where
F1: Fn(&SchemaDocument, SchemaNodeId),
F2: Fn(&SchemaDocument, SchemaNodeId),
F3: Fn(&SchemaDocument, SchemaNodeId),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Tuple(tuple_schema) = &node.content {
assert_eq!(tuple_schema.elements.len(), 3);
check1(schema, tuple_schema.elements[0]);
check2(schema, tuple_schema.elements[1]);
check3(schema, tuple_schema.elements[2]);
} else {
panic!("Expected Tuple type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_union2<F1, F2>(
schema: &SchemaDocument,
node_id: SchemaNodeId,
variant1: (&str, F1),
variant2: (&str, F2),
) where
F1: Fn(&SchemaDocument, SchemaNodeId),
F2: Fn(&SchemaDocument, SchemaNodeId),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Union(union_schema) = &node.content {
assert_eq!(union_schema.variants.len(), 2);
let (name1, check1) = variant1;
let (name2, check2) = variant2;
let id1 = union_schema
.variants
.get(name1)
.unwrap_or_else(|| panic!("Variant '{}' missing", name1));
let id2 = union_schema
.variants
.get(name2)
.unwrap_or_else(|| panic!("Variant '{}' missing", name2));
check1(schema, *id1);
check2(schema, *id2);
} else {
panic!("Expected Union type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_union3<F1, F2, F3>(
schema: &SchemaDocument,
node_id: SchemaNodeId,
variant1: (&str, F1),
variant2: (&str, F2),
variant3: (&str, F3),
) where
F1: Fn(&SchemaDocument, SchemaNodeId),
F2: Fn(&SchemaDocument, SchemaNodeId),
F3: Fn(&SchemaDocument, SchemaNodeId),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Union(union_schema) = &node.content {
assert_eq!(union_schema.variants.len(), 3);
let (name1, check1) = variant1;
let (name2, check2) = variant2;
let (name3, check3) = variant3;
let id1 = union_schema
.variants
.get(name1)
.unwrap_or_else(|| panic!("Variant '{}' missing", name1));
let id2 = union_schema
.variants
.get(name2)
.unwrap_or_else(|| panic!("Variant '{}' missing", name2));
let id3 = union_schema
.variants
.get(name3)
.unwrap_or_else(|| panic!("Variant '{}' missing", name3));
check1(schema, *id1);
check2(schema, *id2);
check3(schema, *id3);
} else {
panic!("Expected Union type, got {:?}", node.content);
}
}
#[track_caller]
fn assert_union4<F1, F2, F3, F4>(
schema: &SchemaDocument,
node_id: SchemaNodeId,
variant1: (&str, F1),
variant2: (&str, F2),
variant3: (&str, F3),
variant4: (&str, F4),
) where
F1: Fn(&SchemaDocument, SchemaNodeId),
F2: Fn(&SchemaDocument, SchemaNodeId),
F3: Fn(&SchemaDocument, SchemaNodeId),
F4: Fn(&SchemaDocument, SchemaNodeId),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Union(union_schema) = &node.content {
assert_eq!(union_schema.variants.len(), 4);
let (name1, check1) = variant1;
let (name2, check2) = variant2;
let (name3, check3) = variant3;
let (name4, check4) = variant4;
let id1 = union_schema
.variants
.get(name1)
.unwrap_or_else(|| panic!("Variant '{}' missing", name1));
let id2 = union_schema
.variants
.get(name2)
.unwrap_or_else(|| panic!("Variant '{}' missing", name2));
let id3 = union_schema
.variants
.get(name3)
.unwrap_or_else(|| panic!("Variant '{}' missing", name3));
let id4 = union_schema
.variants
.get(name4)
.unwrap_or_else(|| panic!("Variant '{}' missing", name4));
check1(schema, *id1);
check2(schema, *id2);
check3(schema, *id3);
check4(schema, *id4);
} else {
panic!("Expected Union type, got {:?}", node.content);
}
}
fn assert_union_repr<F>(schema: &SchemaDocument, node_id: SchemaNodeId, check: F)
where
F: Fn(Option<&VariantRepr>),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Union(union_schema) = &node.content {
check(union_schema.interop.variant_repr.as_ref());
} else {
panic!("Expected Union type, got {:?}", node.content);
}
}
fn assert_unknown_fields<F>(schema: &SchemaDocument, node_id: SchemaNodeId, check: F)
where
F: Fn(&UnknownFieldsPolicy),
{
let node = schema.node(node_id);
if let SchemaNodeContent::Record(record) = &node.content {
check(&record.unknown_fields);
} else {
panic!("Expected Record type, got {:?}", node.content);
}
}
fn assert_reference(schema: &SchemaDocument, node_id: SchemaNodeId, expected_name: &str) {
let node = schema.node(node_id);
if let SchemaNodeContent::Reference(type_ref) = &node.content {
assert!(
type_ref.namespace.is_none(),
"Expected local reference, got namespace {:?}",
type_ref.namespace
);
assert_eq!(
type_ref.name,
ident(expected_name),
"Expected reference to '{}', got '{:?}'",
expected_name,
type_ref.name
);
} else {
panic!("Expected Reference type, got {:?}", node.content);
}
}
fn assert_reference_external(
schema: &SchemaDocument,
node_id: SchemaNodeId,
expected_namespace: &str,
expected_name: &str,
) {
let node = schema.node(node_id);
if let SchemaNodeContent::Reference(type_ref) = &node.content {
assert_eq!(
type_ref.namespace.as_deref(),
Some(expected_namespace),
"Expected namespace '{}', got {:?}",
expected_namespace,
type_ref.namespace
);
assert_eq!(
type_ref.name,
ident(expected_name),
"Expected reference to '{}', got '{:?}'",
expected_name,
type_ref.name
);
} else {
panic!("Expected Reference type, got {:?}", node.content);
}
}
fn assert_metadata<F>(schema: &SchemaDocument, node_id: SchemaNodeId, check: F)
where
F: Fn(&SchemaMetadata),
{
let node = schema.node(node_id);
check(&node.metadata);
}
#[test]
fn test_string_type() {
let doc = eure!({
name = @code("text"),
});
let schema = convert(&doc);
assert_record1(&schema, schema.root, ("name", |s, id| assert_text(s, id)));
}
#[test]
fn test_float_type() {
let doc = eure!({
count = @code("float"),
});
let schema = convert(&doc);
assert_record1(&schema, schema.root, ("count", |s, id| assert_float(s, id)));
}
#[test]
fn test_integer_type() {
let doc = eure!({
age = @code("integer"),
});
let schema = convert(&doc);
assert_record1(&schema, schema.root, ("age", |s, id| assert_integer(s, id)));
}
#[test]
fn test_boolean_type() {
let doc = eure!({
active = @code("boolean"),
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("active", |s, id| assert_boolean(s, id)),
);
}
#[test]
fn test_null_type() {
let doc = eure!({
deleted = @code("null"),
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("deleted", |s, id| assert_null(s, id)),
);
}
#[test]
fn test_any_type() {
let doc = eure!({
data = @code("any"),
});
let schema = convert(&doc);
assert_record1(&schema, schema.root, ("data", |s, id| assert_any(s, id)));
}
#[test]
fn test_text_type() {
let doc = eure!({
content = @code("text"),
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("content", |s, id| assert_text(s, id)),
);
}
#[test]
fn test_text_with_language() {
let doc = eure!({
code = @code("text.rust"),
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("code", |s, id| assert_text_language(s, id, "rust")),
);
}
#[test]
fn test_text_with_constraints() {
let doc = eure!({
username {
%variant = @code("text"),
language = "plaintext",
"min-length" = 3,
"max-length" = 20,
pattern = "^[a-z]+$",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("username", |s, id| {
assert_text_with(s, id, |t| {
assert_eq!(t.language.as_deref(), Some("plaintext"));
assert_eq!(t.min_length, Some(3));
assert_eq!(t.max_length, Some(20));
assert!(t.pattern.is_some());
})
}),
);
}
#[test]
fn test_array_shorthand() {
let doc = eure!({
tags = [@code("text")],
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("tags", |s, id| assert_array(s, id, assert_text)),
);
}
#[test]
fn test_array_with_constraints() {
let doc = eure!({
tags {
%variant = @code("array"),
item = @code("text"),
"min-length" = 1,
"max-length" = 10,
unique = true,
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("tags", |s, id| {
assert_array_with(s, id, assert_text, |array_schema| {
assert_eq!(array_schema.min_length, Some(1));
assert_eq!(array_schema.max_length, Some(10));
assert!(array_schema.unique);
})
}),
);
}
#[test]
fn test_tuple_shorthand() {
let doc = eure!({
point = (@code("float"), @code("float")),
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("point", |s, id| {
assert_tuple2(s, id, assert_float, assert_float)
}),
);
}
#[test]
fn test_tuple_mixed_types() {
let doc = eure!({
entry = (@code("text"), @code("integer"), @code("boolean")),
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("entry", |s, id| {
assert_tuple3(s, id, assert_text, assert_integer, assert_boolean)
}),
);
}
#[test]
fn test_record_basic() {
let doc = eure!({
user {
name = @code("text"),
age = @code("integer"),
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("user", |s, id| {
assert_record2(s, id, ("name", assert_text), ("age", assert_integer))
}),
);
}
#[test]
fn test_union_type() {
let doc = eure!({
value {
%variant = @code("union"),
variants.string = @code("text"),
variants.float = @code("float"),
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("value", |s, id| {
assert_union2(s, id, ("string", assert_text), ("float", assert_float))
}),
);
}
#[test]
fn test_union_with_multiple_types() {
let doc = eure!({
data {
%variant = @code("union"),
variants.string = @code("text"),
variants.float = @code("float"),
variants.boolean = @code("boolean"),
variants.null = @code("null"),
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("data", |s, id| {
assert_union4(
s,
id,
("string", assert_text),
("float", assert_float),
("boolean", assert_boolean),
("null", assert_null),
)
}),
);
}
#[test]
fn test_union_with_record_variants() {
let doc = eure!({
%types.action {
%variant = @code("union"),
variants.click = { "x" => @code("float"), "y" => @code("float") },
variants.hover = { "element" => @code("text") },
},
});
let schema = convert(&doc);
assert!(schema.types.contains_key(&ident("action")));
let action_id = schema.types[&ident("action")];
assert_union2(
&schema,
action_id,
("click", |s, click_id| {
assert_record2(s, click_id, ("x", assert_float), ("y", assert_float))
}),
("hover", |s, hover_id| {
assert_record1(s, hover_id, ("element", |s, id| assert_text(s, id)))
}),
);
}
#[test]
fn test_union_with_untagged_repr() {
let doc = eure!({
%types.response {
%variant = @code("union"),
%interop = { "variant-repr" => "untagged" },
variants.success = { "data" => @code("any") },
variants.error = { "message" => @code("text") },
},
});
let schema = convert(&doc);
let response_id = schema.types[&ident("response")];
assert_union2(
&schema,
response_id,
("success", |s, success_id| {
assert_record1(s, success_id, ("data", assert_any));
}),
("error", |s, error_id| {
assert_record1(s, error_id, ("message", assert_text));
}),
);
assert_union_repr(&schema, response_id, |repr| {
assert!(matches!(repr, Some(VariantRepr::Untagged)));
});
}
#[test]
fn test_union_with_internal_tag() {
let doc = eure!({
%types.message {
%variant = @code("union"),
%interop."variant-repr".tag = "type",
variants.text = { "content" => @code("text") },
variants.image = { "url" => @code("text") },
},
});
let schema = convert(&doc);
let message_id = schema.types[&ident("message")];
assert_union_repr(&schema, message_id, |repr| {
if let Some(VariantRepr::Internal { tag }) = repr {
assert_eq!(tag, "type");
} else {
panic!("Expected VariantRepr::Internal, got {:?}", repr);
}
});
}
#[test]
fn test_union_with_adjacent_tag() {
let doc = eure!({
%types.event {
%variant = @code("union"),
%interop."variant-repr".tag = "kind",
%interop."variant-repr".content = "data",
variants.login = { "username" => @code("text") },
variants.logout = { "reason" => @code("text") },
},
});
let schema = convert(&doc);
let event_id = schema.types[&ident("event")];
assert_union_repr(&schema, event_id, |repr| {
if let Some(VariantRepr::Adjacent { tag, content }) = repr {
assert_eq!(tag, "kind");
assert_eq!(content, "data");
} else {
panic!("Expected VariantRepr::Adjacent, got {:?}", repr);
}
});
}
#[test]
fn test_union_default_untagged() {
let doc = eure!({
%types.status {
%variant = @code("union"),
variants.pending = { "message" => @code("text") },
variants.active = { "started_at" => @code("integer") },
},
});
let schema = convert(&doc);
let status_id = schema.types[&ident("status")];
assert_union_repr(&schema, status_id, |repr| {
assert!(
repr.is_none(),
"Expected no variant repr metadata, got {:?}",
repr
);
});
}
#[test]
fn test_union_with_three_variants() {
let doc = eure!({
%types."traffic-light" {
%variant = @code("union"),
variants.red = { "duration" => @code("integer") },
variants.yellow = { "duration" => @code("integer") },
variants.green = { "duration" => @code("integer") },
},
});
let schema = convert(&doc);
let light_id = schema.types[&ident("traffic-light")];
assert_union3(
&schema,
light_id,
("red", |s, red_id| {
assert_record1(s, red_id, ("duration", assert_integer));
}),
("yellow", |s, yellow_id| {
assert_record1(s, yellow_id, ("duration", assert_integer));
}),
("green", |s, green_id| {
assert_record1(s, green_id, ("duration", assert_integer));
}),
);
}
#[test]
fn test_custom_type_definition() {
let doc = eure!({
user = @code("$types.username"),
%types.username {
%variant = @code("text"),
"min-length" = 3,
"max-length" = 20,
},
});
let schema = convert(&doc);
assert!(schema.types.contains_key(&ident("username")));
assert_record1(
&schema,
schema.root,
("user", |s, id| assert_reference(s, id, "username")),
);
}
#[test]
fn test_string_with_length() {
let doc = eure!({
username {
%variant = @code("text"),
"min-length" = 3,
"max-length" = 20,
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("username", |s, id| {
assert_text_with(s, id, |string_schema| {
assert_eq!(string_schema.min_length, Some(3));
assert_eq!(string_schema.max_length, Some(20));
})
}),
);
}
#[test]
fn test_string_with_pattern() {
let doc = eure!({
email {
%variant = @code("text"),
pattern = "^[a-z]+@[a-z]+\\.[a-z]+$",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("email", |s, id| {
assert_text_with(s, id, |string_schema| {
assert!(string_schema.pattern.is_some());
})
}),
);
}
#[test]
fn test_integer_with_range() {
let doc = eure!({
age {
%variant = @code("integer"),
range = "[0, 150]",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("age", |s, id| {
assert_integer_with(s, id, |int_schema| {
assert_eq!(int_schema.min, Bound::Inclusive(BigInt::from(0)));
assert_eq!(int_schema.max, Bound::Inclusive(BigInt::from(150)));
})
}),
);
}
#[test]
fn test_integer_with_multiple_of() {
let doc = eure!({
even {
%variant = @code("integer"),
"multiple-of" = 2,
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("even", |s, id| {
assert_integer_with(s, id, |int_schema| {
assert_eq!(int_schema.multiple_of, Some(BigInt::from(2)));
})
}),
);
}
#[test]
fn test_float_with_range() {
let doc = eure!({
probability {
%variant = @code("float"),
range = "[0.0, 1.0]",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("probability", |s, id| {
assert_float_with(s, id, |float_schema| {
assert_eq!(float_schema.min, Bound::Inclusive(0.0));
assert_eq!(float_schema.max, Bound::Inclusive(1.0));
})
}),
);
}
#[test]
fn test_map_type() {
let doc = eure!({
headers {
%variant = @code("map"),
key = @code("text"),
value = @code("text"),
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("headers", |s, id| {
assert_map(s, id, assert_text, assert_text)
}),
);
}
#[test]
fn test_map_with_constraints() {
let doc = eure!({
settings {
%variant = @code("map"),
key = @code("text"),
value = @code("any"),
"min-size" = 1,
"max-size" = 100,
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("settings", |s, id| {
assert_map_with(s, id, assert_text, assert_any, |map_schema| {
assert_eq!(map_schema.min_size, Some(1));
assert_eq!(map_schema.max_size, Some(100));
})
}),
);
}
#[test]
fn test_array_with_contains() {
let doc = eure!({
tags {
%variant = @code("array"),
item = @code("text"),
contains = "required",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("tags", |s, id| {
assert_array_with(s, id, assert_text, |array_schema| {
assert!(
array_schema.contains.is_some(),
"Expected contains to be Some"
);
})
}),
);
}
#[test]
fn test_nested_record() {
let doc = eure!({
user.profile {
name = @code("text"),
bio = @code("text"),
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("user", |s, user_id| {
assert_record1(
s,
user_id,
("profile", |s, profile_id| {
assert_record2(
s,
profile_id,
("name", |s, id| assert_text(s, id)),
("bio", |s, id| assert_text(s, id)),
)
}),
)
}),
);
}
#[test]
fn test_integer_range_rust_style_inclusive() {
let doc = eure!({
age {
%variant = @code("integer"),
range = "0..=150",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("age", |s, id| {
assert_integer_with(s, id, |int_schema| {
assert_eq!(int_schema.min, Bound::Inclusive(BigInt::from(0)));
assert_eq!(int_schema.max, Bound::Inclusive(BigInt::from(150)));
})
}),
);
}
#[test]
fn test_integer_range_rust_style_exclusive() {
let doc = eure!({
index {
%variant = @code("integer"),
range = "0..100",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("index", |s, id| {
assert_integer_with(s, id, |int_schema| {
assert_eq!(int_schema.min, Bound::Inclusive(BigInt::from(0)));
assert_eq!(int_schema.max, Bound::Exclusive(BigInt::from(100)));
})
}),
);
}
#[test]
fn test_integer_range_min_only() {
let doc = eure!({
positive {
%variant = @code("integer"),
range = "1..",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("positive", |s, id| {
assert_integer_with(s, id, |int_schema| {
assert_eq!(int_schema.min, Bound::Inclusive(BigInt::from(1)));
assert_eq!(int_schema.max, Bound::Unbounded);
})
}),
);
}
#[test]
fn test_integer_range_max_only() {
let doc = eure!({
small {
%variant = @code("integer"),
range = "..100",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("small", |s, id| {
assert_integer_with(s, id, |int_schema| {
assert_eq!(int_schema.min, Bound::Unbounded);
assert_eq!(int_schema.max, Bound::Exclusive(BigInt::from(100)));
})
}),
);
}
#[test]
fn test_integer_range_max_only_inclusive() {
let doc = eure!({
small {
%variant = @code("integer"),
range = "..=100",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("small", |s, id| {
assert_integer_with(s, id, |int_schema| {
assert_eq!(int_schema.min, Bound::Unbounded);
assert_eq!(int_schema.max, Bound::Inclusive(BigInt::from(100)));
})
}),
);
}
#[test]
fn test_integer_range_interval_exclusive() {
let doc = eure!({
value {
%variant = @code("integer"),
range = "(0, 100)",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("value", |s, id| {
assert_integer_with(s, id, |int_schema| {
assert_eq!(int_schema.min, Bound::Exclusive(BigInt::from(0)));
assert_eq!(int_schema.max, Bound::Exclusive(BigInt::from(100)));
})
}),
);
}
#[test]
fn test_integer_range_interval_mixed() {
let doc = eure!({
value {
%variant = @code("integer"),
range = "(0, 100]",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("value", |s, id| {
assert_integer_with(s, id, |int_schema| {
assert_eq!(int_schema.min, Bound::Exclusive(BigInt::from(0)));
assert_eq!(int_schema.max, Bound::Inclusive(BigInt::from(100)));
})
}),
);
}
#[test]
fn test_float_range_interval_half_open() {
let doc = eure!({
probability {
%variant = @code("float"),
range = "[0.0, 1.0)",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("probability", |s, id| {
assert_float_with(s, id, |float_schema| {
assert_eq!(float_schema.min, Bound::Inclusive(0.0));
assert_eq!(float_schema.max, Bound::Exclusive(1.0));
})
}),
);
}
#[test]
fn test_float_range_rust_style() {
let doc = eure!({
positive {
%variant = @code("float"),
range = "0.0..",
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("positive", |s, id| {
assert_float_with(s, id, |float_schema| {
assert_eq!(float_schema.min, Bound::Inclusive(0.0));
assert_eq!(float_schema.max, Bound::Unbounded);
})
}),
);
}
#[test]
fn test_optional_field() {
let doc = eure!({
name = @code("text"),
bio = @code("text"),
bio.%optional = true,
});
let schema = convert(&doc);
assert_field_optional(&schema, schema.root, "bio");
}
#[test]
fn test_metadata_description() {
let doc = eure!({
user = @code("any"),
user.%description = @code("User information"),
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("user", |s, id| {
assert_any(s, id);
assert_metadata(s, id, |metadata| {
assert!(metadata.description.is_some());
})
}),
);
}
#[test]
fn test_metadata_deprecated() {
let doc = eure!({
old_field = @code("text"),
old_field.%deprecated = true,
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("old_field", |s, id| {
assert_text(s, id);
assert_metadata(s, id, |metadata| {
assert!(metadata.deprecated);
})
}),
);
}
#[test]
fn test_metadata_default_value() {
let doc = eure!({
timeout = @code("integer"),
timeout.%default = 30,
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("timeout", |s, id| {
assert_integer(s, id);
assert_metadata(s, id, |metadata| {
assert!(metadata.default.is_some());
})
}),
);
}
#[test]
fn test_unknown_fields_policy_allow() {
let doc = eure!({
config {
%"unknown-fields" = "allow",
host = @code("text"),
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("config", |s, config_id| {
assert_record1(s, config_id, ("host", assert_text));
assert_unknown_fields(s, config_id, |policy| {
assert!(matches!(policy, UnknownFieldsPolicy::Allow));
});
}),
);
}
#[test]
fn test_unknown_fields_policy_deny() {
let doc = eure!({
config {
%"unknown-fields" = "deny",
host = @code("text"),
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("config", |s, config_id| {
assert_record1(s, config_id, ("host", assert_text));
assert_unknown_fields(s, config_id, |policy| {
assert!(matches!(policy, UnknownFieldsPolicy::Deny));
});
}),
);
}
#[test]
fn test_unknown_fields_policy_schema() {
let doc = eure!({
config {
%"unknown-fields" = @code("text"),
host = @code("text"),
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("config", |s, config_id| {
assert_record1(s, config_id, ("host", assert_text));
assert_unknown_fields(s, config_id, |policy| {
if let UnknownFieldsPolicy::Schema(schema_id) = policy {
assert_text(s, *schema_id);
} else {
panic!("Expected UnknownFieldsPolicy::Schema, got {:?}", policy);
}
});
}),
);
}
#[test]
fn test_text_language_variants() {
let doc = eure!({
rust = @code("text.rust"),
python = @code("text.python"),
sql = @code("text.sql"),
});
let schema = convert(&doc);
assert_record3(
&schema,
schema.root,
("rust", |s, id| assert_text_language(s, id, "rust")),
("python", |s, id| assert_text_language(s, id, "python")),
("sql", |s, id| assert_text_language(s, id, "sql")),
);
}
#[test]
fn test_type_reference() {
let doc = eure!({
%types.email = @code("text.email"),
contact = @code("$types.email"),
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("contact", |s, id| assert_reference(s, id, "email")),
);
}
#[test]
fn test_external_type_reference() {
let doc = eure!({
user = @code("$types.common.User"),
%types.common.User {
name = @code("text"),
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("user", |s, id| {
assert_reference_external(s, id, "common", "User")
}),
);
}
#[test]
fn test_circular_type_reference_is_rejected_when_non_productive() {
let doc = eure!({
%types.a = @code("$types.b"),
%types.b = @code("$types.a"),
data = @code("$types.a"),
});
let err = document_to_schema(&doc).expect_err("non-productive cycle must be rejected");
match err {
ConversionError::NonProductiveReferenceCycle(path) => {
assert!(path.contains("$types.a"));
assert!(path.contains("$types.b"));
}
other => panic!("Expected NonProductiveReferenceCycle, got {:?}", other),
}
}
#[test]
fn test_type_reference_chain() {
let doc = eure!({
data = @code("$types.user"),
%types."base-string" {
%variant = @code("text"),
"min-length" = 1,
"max-length" = 100,
},
%types.username = @code("$types.base-string"),
%types.user {
username = @code("$types.username"),
email = @code("text.email"),
},
});
let schema = convert(&doc);
assert!(schema.types.contains_key(&ident("base-string")));
assert!(schema.types.contains_key(&ident("username")));
assert!(schema.types.contains_key(&ident("user")));
assert_record1(
&schema,
schema.root,
("data", |s, id| assert_reference(s, id, "user")),
);
let user_id = schema.types[&ident("user")];
assert_record2(
&schema,
user_id,
("username", |s, id| assert_reference(s, id, "username")),
("email", |s, id| assert_text_language(s, id, "email")),
);
let username_id = schema.types[&ident("username")];
assert_reference(&schema, username_id, "base-string");
}
#[test]
fn test_complex_user_schema() {
let doc = eure!({
%types.username {
%variant = @code("text"),
"min-length" = 3,
"max-length" = 20,
pattern = "^[a-z0-9_]+$",
},
%types.role {
%variant = @code("union"),
variants.admin = "admin",
variants.user = "user",
variants.guest = "guest",
},
%types.user {
username = @code("$types.username"),
email = @code("text.email"),
age = @code("integer"),
age.%optional = true,
tags = [@code("text")],
role = @code("$types.role"),
},
});
let schema = convert(&doc);
assert!(schema.types.contains_key(&ident("username")));
assert!(schema.types.contains_key(&ident("user")));
assert!(schema.types.contains_key(&ident("role")));
let user_id = schema.types[&ident("user")];
assert_record5(
&schema,
user_id,
("username", |s, id| assert_reference(s, id, "username")),
("email", |s, id| assert_text_language(s, id, "email")),
("age", |s, id| assert_integer(s, id)),
("tags", |s, id| assert_array(s, id, assert_text)),
("role", |s, id| assert_reference(s, id, "role")),
);
}
#[test]
fn test_complex_api_schema() {
let doc = eure!({
%types."http-method" {
%variant = @code("union"),
variants.GET = "GET",
variants.POST = "POST",
variants.PUT = "PUT",
variants.DELETE = "DELETE",
variants.PATCH = "PATCH",
},
%types."api-request" {
method = @code("$types.http-method"),
path = @code("text"),
headers = @code("any"),
headers.%optional = true,
body = @code("any"),
body.%optional = true,
},
%types."api-response" {
%variant = @code("union"),
%interop = { "variant-repr" => "untagged" },
variants.success = { "status" => @code("integer"), "data" => @code("any") },
variants.error = { "status" => @code("integer"), "message" => @code("text") },
},
});
let schema = convert(&doc);
assert!(schema.types.contains_key(&ident("http-method")));
assert!(schema.types.contains_key(&ident("api-request")));
assert!(schema.types.contains_key(&ident("api-response")));
let response_id = schema.types[&ident("api-response")];
assert_union2(
&schema,
response_id,
("success", |s, success_id| {
assert_record2(
s,
success_id,
("status", assert_integer),
("data", assert_any),
);
}),
("error", |s, error_id| {
assert_record2(
s,
error_id,
("status", assert_integer),
("message", assert_text),
);
}),
);
assert_union_repr(&schema, response_id, |repr| {
assert!(matches!(repr, Some(VariantRepr::Untagged)));
});
}
#[test]
fn test_nested_types_and_arrays() {
let doc = eure!({
%types.address {
street = @code("text"),
city = @code("text"),
zip = @code("text"),
},
%types.person {
name = @code("text"),
addresses {
%variant = @code("array"),
item = @code("$types.address"),
"min-length" = 1,
},
},
});
let schema = convert(&doc);
let person_id = schema.types[&ident("person")];
assert_record2(
&schema,
person_id,
("name", |s, id| assert_text(s, id)),
("addresses", |s, id| {
assert_array_with(
s,
id,
|s, item_id| assert_reference(s, item_id, "address"),
|array_schema| {
assert_eq!(array_schema.min_length, Some(1));
},
);
}),
);
}
#[test]
fn test_array_of_custom_types_complex() {
let doc = eure!({
data = @code("$types.collection"),
%types.item {
%variant = @code("text"),
"min-length" = 1,
"max-length" = 100,
},
%types.collection {
%variant = @code("array"),
item = @code("$types.item"),
"min-length" = 1,
unique = true,
},
});
let schema = convert(&doc);
assert!(schema.types.contains_key(&ident("item")));
assert!(schema.types.contains_key(&ident("collection")));
assert_record1(
&schema,
schema.root,
("data", |s, id| assert_reference(s, id, "collection")),
);
let collection_id = schema.types[&ident("collection")];
assert_array_with(
&schema,
collection_id,
|s, item_id| assert_reference(s, item_id, "item"),
|array_schema| {
assert_eq!(array_schema.min_length, Some(1));
assert!(array_schema.unique);
},
);
}
#[test]
fn test_map_with_complex_types() {
let doc = eure!({
%types.address {
%variant = @code("text"),
"min-length" = 1,
"max-length" = 100,
},
locations {
%variant = @code("map"),
key = @code("text"),
value = @code("$types.address"),
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("locations", |s, id| {
assert_map(s, id, assert_text, |s, value_id| {
assert_reference(s, value_id, "address")
});
}),
);
}
#[test]
fn test_nested_maps() {
let doc = eure!({
nested {
%variant = @code("map"),
key = @code("text"),
value {
%variant = @code("map"),
key = @code("text"),
value = @code("integer"),
},
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("nested", |s, id| {
assert_map(s, id, assert_text, |s, value_id| {
assert_map(s, value_id, assert_text, assert_integer);
});
}),
);
}
#[test]
fn test_nested_union_types() {
let doc = eure!({
value {
%variant = @code("union"),
variants.string = @code("text"),
variants.integer = @code("integer"),
variants.array[] {
%variant = @code("union"),
variants.boolean = @code("boolean"),
variants.null = @code("null"),
},
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("value", |s, id| {
assert_union3(
s,
id,
("string", assert_text),
("integer", assert_integer),
("array", |s, array_id| {
assert_array(s, array_id, |s, item_id| {
assert_union2(
s,
item_id,
("boolean", assert_boolean),
("null", assert_null),
);
});
}),
);
}),
);
}
#[test]
fn test_empty_section_creates_empty_record() {
let doc = eure!({
config {},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("config", |s, id| {
let node = s.node(id);
if let SchemaNodeContent::Record(record) = &node.content {
assert!(
record.properties.is_empty(),
"Expected empty record, got {:?}",
record.properties
);
} else {
panic!("Expected Record type, got {:?}", node.content);
}
}),
);
}
#[test]
fn test_empty_array_schema() {
let doc = eure!({
items = [@code("any")],
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("items", |s, id| {
assert_array(s, id, assert_any);
}),
);
}
#[test]
fn test_invalid_type_reference() {
let doc = eure!({
user = @code("$types.nonexistent"),
});
let result = document_to_schema(&doc);
assert_eq!(
result.unwrap_err(),
ConversionError::UndefinedTypeReference("nonexistent".to_string())
);
}
#[test]
fn test_error_unknown_variant_type() {
let doc = eure!({
field {
%variant = @code("unknown_type"),
},
});
let result = document_to_schema(&doc);
let err = result.unwrap_err();
eprintln!("Actual error: {:?}", err);
assert!(matches!(
err,
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::UnknownVariant(_),
..
})
));
}
#[test]
fn test_error_invalid_integer_range_format() {
let doc = eure!({
field {
%variant = @code("integer"),
range = "not a range",
},
});
let result = document_to_schema(&doc);
assert_eq!(
result.unwrap_err(),
ConversionError::InvalidRangeString("not a range".to_string())
);
}
#[test]
fn test_error_invalid_float_range_format() {
let doc = eure!({
field {
%variant = @code("float"),
range = "invalid",
},
});
let result = document_to_schema(&doc);
assert_eq!(
result.unwrap_err(),
ConversionError::InvalidRangeString("invalid".to_string())
);
}
#[test]
fn test_error_invalid_type_path() {
let doc = eure!({
field = @code("unknown_primitive"),
});
let result = document_to_schema(&doc);
assert!(matches!(
result.unwrap_err(),
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::InvalidPattern { .. },
..
})
));
}
#[test]
fn test_error_invalid_extension_path() {
let doc = eure!({
field = @code("$unknown.type"),
});
let result = document_to_schema(&doc);
assert!(matches!(
result.unwrap_err(),
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::InvalidPattern { .. },
..
})
));
}
#[test]
fn test_error_map_missing_key() {
let doc = eure!({
field {
%variant = @code("map"),
value = @code("text"),
},
});
let result = document_to_schema(&doc);
assert!(matches!(
result.unwrap_err(),
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::MissingField(_),
..
})
));
}
#[test]
fn test_error_map_missing_value() {
let doc = eure!({
field {
%variant = @code("map"),
key = @code("text"),
},
});
let result = document_to_schema(&doc);
assert!(matches!(
result.unwrap_err(),
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::MissingField(_),
..
})
));
}
#[test]
fn test_error_array_missing_item() {
let doc = eure!({
field {
%variant = @code("array"),
"min-length" = 1,
},
});
let result = document_to_schema(&doc);
assert!(matches!(
result.unwrap_err(),
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::MissingField(_),
..
})
));
}
#[test]
fn test_error_invalid_variant_repr() {
let doc = eure!({
field {
%variant = @code("union"),
%interop = { "variant-repr" => "invalid_repr" },
variants.a = @code("text"),
},
});
let result = document_to_schema(&doc);
assert!(matches!(
result.unwrap_err(),
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::UnknownVariant(_),
..
})
));
}
#[test]
fn test_error_adjacent_repr_missing_tag() {
let doc = eure!({
field {
%variant = @code("union"),
variants.a = @code("text"),
%interop."variant-repr".content = "data",
},
});
let result = document_to_schema(&doc);
assert!(matches!(
result.unwrap_err(),
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::MissingField(_),
..
})
));
}
#[test]
fn test_error_legacy_variant_repr_extension_is_rejected() {
let doc = eure!({
field {
%variant = @code("union"),
%"variant-repr" = "untagged",
variants.a = @code("text"),
},
});
let result = document_to_schema(&doc);
assert!(matches!(
result.unwrap_err(),
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::InvalidPattern { .. },
..
})
));
}
#[test]
fn test_error_invalid_unknown_fields_policy() {
let doc = eure!({
record {
%"unknown-fields" = "invalid_policy",
name = @code("text"),
},
});
let result = document_to_schema(&doc);
assert!(matches!(
result.unwrap_err(),
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::UnknownVariant(_),
..
})
));
}
#[test]
fn test_error_array_with_multiple_items() {
let doc = eure!({
field = [@code("text"), @code("integer")],
});
let result = document_to_schema(&doc);
assert!(matches!(
result.unwrap_err(),
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::InvalidPattern { .. },
..
})
));
}
#[test]
fn test_error_invalid_range_interval_format() {
let doc = eure!({
field {
%variant = @code("integer"),
range = "[1, 2, 3]",
},
});
let result = document_to_schema(&doc);
assert_eq!(
result.unwrap_err(),
ConversionError::InvalidRangeString("[1, 2, 3]".to_string())
);
}
#[test]
fn test_error_literal_missing_value() {
let doc = eure!({
field {
%variant = @code("literal"),
other = "something",
},
});
let result = document_to_schema(&doc);
assert!(result.is_ok());
}
#[test]
fn test_error_types_not_map() {
let doc = eure!({
%types = "not a map",
});
let result = document_to_schema(&doc);
assert_eq!(
result.unwrap_err(),
ConversionError::InvalidExtensionValue {
extension: "types".to_string(),
path: "$types must be a map".to_string(),
}
);
}
#[test]
fn test_error_invalid_type_path_extra_segment() {
let doc = eure!({
field = @code("integer.foo"),
});
let result = document_to_schema(&doc);
assert!(matches!(
result.unwrap_err(),
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::InvalidPattern { .. },
..
})
));
}
#[test]
fn test_error_nested_variant_path() {
let doc = eure!({
response {
%variant = "ok.ok.err",
error_code = @code("integer"),
},
});
let result = document_to_schema(&doc);
assert!(matches!(
result.unwrap_err(),
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::UnknownVariant(_),
..
})
));
}
#[test]
fn test_variant_string_single_segment_valid() {
let doc = eure!({
field {
%variant = "text",
"min-length" = 1,
},
});
let schema = convert(&doc);
assert_record1(
&schema,
schema.root,
("field", |s: &SchemaDocument, id: SchemaNodeId| {
assert_text_with(s, id, |str_schema| {
assert_eq!(str_schema.min_length, Some(1));
});
}),
);
}
#[test]
fn test_error_variant_string_unknown() {
let doc = eure!({
field {
%variant = "unknown_type",
value = 123,
},
});
let result = document_to_schema(&doc);
assert!(matches!(
result.unwrap_err(),
ConversionError::ParseError(ParseError {
kind: ParseErrorKind::UnknownVariant(_),
..
})
));
}
#[test]
fn test_error_types_non_string_key() {
let doc = eure!({
%types.("a", "b") = @code("text"),
});
let result = document_to_schema(&doc);
match result.unwrap_err() {
ConversionError::InvalidTypeName(_) => {}
err => panic!("Expected InvalidTypeName error, got {:?}", err),
}
}
#[test]
fn test_error_types_integer_key() {
let doc = eure!({
%types.0 = @code("text"),
});
let result = document_to_schema(&doc);
assert_eq!(
result.unwrap_err(),
ConversionError::InvalidTypeName(ObjectKey::Number(0.into()))
);
}