#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
use crate::entities::{GtsConfig, GtsEntity};
use serde_json::{Value, json};
#[test]
fn test_gts_store_query_result_default() {
let result = GtsStoreQueryResult {
error: String::new(),
count: 0,
limit: 100,
results: vec![],
};
assert_eq!(result.count, 0);
assert_eq!(result.limit, 100);
assert!(result.error.is_empty());
assert!(result.results.is_empty());
}
#[test]
fn test_gts_store_query_result_serialization() {
let result = GtsStoreQueryResult {
error: String::new(),
count: 2,
limit: 10,
results: vec![json!({"id": "test1"}), json!({"id": "test2"})],
};
let json_value = serde_json::to_value(&result).expect("test");
let json = json_value.as_object().expect("test");
assert_eq!(json.get("count").expect("test").as_u64().expect("test"), 2);
assert_eq!(json.get("limit").expect("test").as_u64().expect("test"), 10);
assert!(json.get("results").expect("test").is_array());
}
#[test]
fn test_gts_store_new_without_reader() {
let store: GtsStore = GtsStore::new();
assert_eq!(store.items().count(), 0);
}
#[test]
fn test_gts_store_register_entity() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
None,
);
let result = store.register(entity);
assert!(result.is_ok());
assert_eq!(store.items().count(), 1);
}
#[test]
fn test_gts_store_register_schema() {
let mut store = GtsStore::new();
let schema_content = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
let result = store.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_content);
assert!(result.is_ok());
let entity = store.get("gts.vendor.package.namespace.type.v1.0~");
assert!(entity.is_some());
assert!(entity.expect("test").is_schema);
}
#[test]
fn test_gts_store_register_schema_invalid_id() {
let mut store = GtsStore::new();
let schema_content = json!({
"type": "object"
});
let result = store.register_schema(
"gts.vendor.package.namespace.type.v1.0", &schema_content,
);
assert!(result.is_err());
match result {
Err(StoreError::InvalidTypeId(_)) => {}
_ => panic!("Expected InvalidTypeId error"),
}
}
#[test]
fn test_gts_store_get_schema_content() {
let mut store = GtsStore::new();
let schema_content = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_content)
.expect("test");
let result = store.get_schema_content("gts.vendor.package.namespace.type.v1.0~");
assert!(result.is_ok());
assert_eq!(result.expect("test"), schema_content);
}
#[test]
fn test_gts_store_get_schema_content_not_found() {
let mut store = GtsStore::new();
let result = store.get_schema_content("gts.vendor.package.namespace.type.v1.0~");
assert!(result.is_err());
match result {
Err(StoreError::SchemaNotFound(id)) => {
assert_eq!(id, "gts.vendor.package.namespace.type.v1.0~");
}
_ => panic!("Expected SchemaNotFound error"),
}
}
#[test]
fn test_gts_store_items_iterator() {
let mut store = GtsStore::new();
for i in 0..3 {
let schema_content = json!({
"$id": format!("gts.vendor.package.namespace.type.v{i}.0~"),
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
store
.register_schema(
&format!("gts.vendor.package.namespace.type.v{i}.0~"),
&schema_content,
)
.expect("test");
}
assert_eq!(store.items().count(), 3);
assert_eq!(store.items().count(), 3);
}
#[test]
fn test_gts_store_validate_instance_missing_schema() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
None,
);
store.register(entity).expect("test");
let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
assert!(result.is_err());
}
#[test]
fn test_gts_store_build_schema_graph() {
let mut store = GtsStore::new();
let schema_content = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_content)
.expect("test");
let graph = store.build_schema_graph("gts.vendor.package.namespace.type.v1.0~");
assert!(graph.is_object());
}
#[test]
fn test_gts_store_query_wildcard() {
let mut store = GtsStore::new();
for i in 0..3 {
let schema_content = json!({
"$id": format!("gts.vendor.package.namespace.type.v{i}.0~"),
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
let schema_id = format!("gts.vendor.package.namespace.type.v{i}.0~");
store
.register_schema(&schema_id, &schema_content)
.expect("test");
}
let result = store.query("gts.vendor.*", 10);
assert_eq!(result.count, 3);
assert_eq!(result.results.len(), 3);
}
#[test]
fn test_gts_store_query_with_limit() {
let mut store = GtsStore::new();
for i in 0..5 {
let schema_content = json!({
"$id": format!("gts.vendor.package.namespace.type.v{i}.0~"),
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
store
.register_schema(
&format!("gts.vendor.package.namespace.type.v{i}.0~"),
&schema_content,
)
.expect("test");
}
let result = store.query("gts.vendor.*", 2);
assert_eq!(result.results.len(), 2);
assert!(result.count >= 2);
}
#[test]
fn test_store_error_display() {
let error = StoreError::InstanceNotFound("test_id".to_owned());
assert!(error.to_string().contains("test_id"));
let error = StoreError::SchemaNotFound("schema_id".to_owned());
assert!(error.to_string().contains("schema_id"));
let error = StoreError::InvalidEntity("instance_id".to_owned());
assert!(error.to_string().contains("instance_id"));
}
#[test]
fn test_gts_store_cast() {
let mut store = GtsStore::new();
let schema_v1 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
let schema_v2 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string", "default": "test@example.com"}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.type.v1.1~", &schema_v2)
.expect("test");
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"type": "gts.vendor.package.namespace.type.v1.0~",
"name": "John"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.cast(
"gts.vendor.package.namespace.type.v1.0",
"gts.vendor.package.namespace.type.v1.1~",
);
let cast = result.expect("cast to a minor-compatible version should succeed");
let casted = cast
.casted_entity
.expect("a successful cast must produce a casted entity");
assert_eq!(
casted.get("name").and_then(Value::as_str),
Some("John"),
"the cast must carry the existing `name` value forward"
);
}
#[test]
fn test_gts_store_cast_missing_entity() {
let mut store = GtsStore::new();
let result = store.cast("nonexistent", "gts.vendor.package.namespace.type.v1.0~");
assert!(result.is_err());
}
#[test]
fn test_gts_store_cast_missing_schema() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
None,
);
store.register(entity).expect("test");
let result = store.cast("gts.vendor.package.namespace.type.v1.0", "nonexistent~");
assert!(result.is_err());
}
#[test]
fn test_gts_store_is_minor_compatible() {
let mut store = GtsStore::new();
let schema_v1 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
let schema_v2 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.type.v1.1~", &schema_v2)
.expect("test");
let result = store.is_minor_compatible(
"gts.vendor.package.namespace.type.v1.0~",
"gts.vendor.package.namespace.type.v1.1~",
);
assert!(result.is_backward_compatible);
}
#[test]
fn test_gts_store_get() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
None,
);
store.register(entity).expect("test");
let result = store.get("gts.vendor.package.namespace.type.v1.0");
assert!(result.is_some());
}
#[test]
fn test_gts_store_get_nonexistent() {
let mut store = GtsStore::new();
let result = store.get("nonexistent");
assert!(result.is_none());
}
#[test]
fn test_gts_store_query_exact_match() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let result = store.query("gts.vendor.package.namespace.type.v1.0~", 10);
assert_eq!(result.count, 1);
}
#[test]
fn test_gts_store_register_duplicate() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"name": "test"
});
let entity1 = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
None,
);
let entity2 = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
None,
);
store.register(entity1).expect("test");
let result = store.register(entity2);
assert!(result.is_ok());
}
#[test]
fn test_gts_store_validate_instance_success() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0~a.b.c.d.v1",
"type": "gts.vendor.package.namespace.type.v1.2~",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0~a.b.c.d.v1");
assert!(result.is_ok());
}
#[test]
fn test_gts_store_validate_instance_missing_entity() {
let mut store = GtsStore::new();
let result = store.validate_instance("nonexistent");
assert!(result.is_err());
}
#[test]
fn test_gts_store_validate_instance_no_schema() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
None,
);
store.register(entity).expect("test");
let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
let err = result.expect_err("an instance with no resolvable type_id must fail validation");
assert!(
matches!(err, StoreError::InvalidEntity(ref m) if m.contains("has no type_id")),
"expected InvalidEntity(\"...has no type_id\"), got: {err:?}"
);
}
#[test]
fn test_gts_store_register_schema_with_invalid_id() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "invalid",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
let result = store.register_schema("invalid", &schema);
assert!(result.is_err());
}
#[test]
fn test_gts_store_get_schema_content_missing() {
let mut store = GtsStore::new();
let result = store.get_schema_content("nonexistent~");
assert!(result.is_err());
}
#[test]
fn test_gts_store_query_empty() {
let store = GtsStore::new();
let result = store.query("gts.vendor.*", 10);
assert_eq!(result.count, 0);
assert_eq!(result.results.len(), 0);
}
#[test]
fn test_gts_store_items_empty() {
let store = GtsStore::new();
assert_eq!(store.items().count(), 0);
}
#[test]
fn test_gts_store_register_entity_without_id() {
let mut store = GtsStore::new();
let content = json!({
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
None,
None,
false,
String::new(),
None,
None,
);
let result = store.register(entity);
assert!(result.is_err());
}
#[test]
fn test_gts_store_build_schema_graph_missing() {
let mut store = GtsStore::new();
let graph = store.build_schema_graph("nonexistent~");
assert!(graph.is_object());
}
#[test]
fn test_gts_store_new_empty() {
let store = GtsStore::new();
assert_eq!(store.items().count(), 0);
}
#[test]
fn test_gts_store_cast_entity_without_schema() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
None,
);
store.register(entity).expect("test");
let result = store.cast(
"gts.vendor.package.namespace.type.v1.0",
"gts.vendor.package.namespace.type.v1.1~",
);
let err = result.expect_err("casting an instance with no type_id must fail");
assert!(
matches!(err, StoreError::InvalidEntity(ref m) if m.contains("has no type_id")),
"expected InvalidEntity(\"...has no type_id\"), got: {err:?}"
);
}
#[test]
fn test_gts_store_is_minor_compatible_missing_schemas() {
let mut store = GtsStore::new();
let result = store.is_minor_compatible("nonexistent1~", "nonexistent2~");
assert!(!result.is_backward_compatible);
}
#[test]
fn test_gts_store_validate_instance_with_refs() {
let mut store = GtsStore::new();
let base_schema = json!({
"$id": "gts://gts.vendor.package.namespace.base.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": {"type": "string"}
}
});
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{"$ref": "gts://gts.vendor.package.namespace.base.v1.0~"},
{
"type": "object",
"properties": {
"name": {"type": "string"}
}
}
]
});
store
.register_schema("gts.vendor.package.namespace.base.v1.0~", &base_schema)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"type": "gts.vendor.package.namespace.type.v1.0~",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
result.expect("a valid instance against an allOf+$ref schema should validate");
}
#[test]
fn test_gts_store_validate_instance_validation_failure() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"age": {"type": "number"}
},
"required": ["age"]
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"type": "gts.vendor.package.namespace.type.v1.0~",
"age": "not a number"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
assert!(result.is_err());
}
#[test]
fn test_gts_store_query_with_filters() {
let mut store = GtsStore::new();
for i in 0..5 {
let schema = json!({
"$id": format!("gts.vendor.package.namespace.type{i}.v1.0~"),
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
store
.register_schema(
&format!("gts.vendor.package.namespace.type{i}.v1.0~"),
&schema,
)
.expect("test");
}
let result = store.query("gts.vendor.package.namespace.type0.*", 10);
assert_eq!(result.count, 1);
}
#[test]
fn test_gts_store_register_multiple_schemas() {
let mut store = GtsStore::new();
for i in 0..10 {
let schema = json!({
"$id": format!("gts.vendor.package.namespace.type.v1.{i}~"),
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
let result = store.register_schema(
&format!("gts.vendor.package.namespace.type.v1.{i}~"),
&schema,
);
assert!(result.is_ok());
}
assert_eq!(store.items().count(), 10);
}
#[test]
fn test_gts_store_cast_with_validation() {
let mut store = GtsStore::new();
let schema_v1 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
});
let schema_v2 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string", "default": "test@example.com"}
},
"required": ["name"]
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.type.v1.1~", &schema_v2)
.expect("test");
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"type": "gts.vendor.package.namespace.type.v1.0~",
"name": "John"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.cast(
"gts.vendor.package.namespace.type.v1.0",
"gts.vendor.package.namespace.type.v1.1~",
);
let cast = result.expect("casting to a compatible minor version should succeed");
let casted = cast
.casted_entity
.expect("a successful cast must produce a casted entity");
assert_eq!(
casted.get("name").and_then(Value::as_str),
Some("John"),
"the cast must carry the existing required `name` value forward"
);
}
#[test]
fn test_gts_store_build_schema_graph_with_refs() {
let mut store = GtsStore::new();
let base_schema = json!({
"$id": "gts://gts.vendor.package.namespace.base.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": {"type": "string"}
}
});
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{"$ref": "gts://gts.vendor.package.namespace.base.v1.0~"}
]
});
store
.register_schema("gts.vendor.package.namespace.base.v1.0~", &base_schema)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let graph = store.build_schema_graph("gts.vendor.package.namespace.type.v1.0~");
assert!(graph.is_object());
}
#[test]
fn test_gts_store_get_schema_content_success() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let result = store.get_schema_content("gts.vendor.package.namespace.type.v1.0~");
assert!(result.is_ok());
assert_eq!(
result
.expect("test")
.get("type")
.expect("test")
.as_str()
.expect("test"),
"object"
);
}
#[test]
fn test_gts_store_register_entity_with_schema() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"type": "gts.vendor.package.namespace.type.v1.0~",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
let result = store.register(entity);
assert!(result.is_ok());
}
#[test]
fn test_gts_store_query_result_structure() {
let result = GtsStoreQueryResult {
error: String::new(),
count: 0,
limit: 100,
results: vec![],
};
assert_eq!(result.count, 0);
assert_eq!(result.limit, 100);
assert!(result.results.is_empty());
}
#[test]
fn test_gts_store_error_variants() {
let err1 = StoreError::InvalidEntity("bad entity".to_owned());
assert!(!err1.to_string().is_empty());
let err2 = StoreError::InvalidTypeId(GtsIdError::new("bad", "not a type id"));
assert!(!err2.to_string().is_empty());
}
#[test]
fn test_gts_store_register_schema_overwrite() {
let mut store = GtsStore::new();
let schema1 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
let schema2 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema1)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema2)
.expect("test");
let result = store.get_schema_content("gts.vendor.package.namespace.type.v1.0~");
assert!(result.is_ok());
let schema = result.expect("test");
assert!(
schema
.get("properties")
.expect("test")
.get("email")
.is_some()
);
}
#[test]
fn test_gts_store_cast_missing_source_schema() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
store
.register_schema("gts.vendor.package.namespace.type.v1.1~", &schema)
.expect("test");
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.cast(
"gts.vendor.package.namespace.type.v1.0",
"gts.vendor.package.namespace.type.v1.1~",
);
let err = result.expect_err("casting when the source schema is unregistered must fail");
assert!(
matches!(
err,
StoreError::SchemaNotFound(ref m)
if m.contains("gts.vendor.package.namespace.type.v1.0~")
),
"expected SchemaNotFound for the missing source schema, got: {err:?}"
);
}
#[test]
fn test_gts_store_query_multiple_patterns() {
let mut store = GtsStore::new();
let schema1 = json!({
"$id": "gts://gts.vendor1.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
let schema2 = json!({
"$id": "gts://gts.vendor2.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
store
.register_schema("gts.vendor1.package.namespace.type.v1.0~", &schema1)
.expect("test");
store
.register_schema("gts.vendor2.package.namespace.type.v1.0~", &schema2)
.expect("test");
let result1 = store.query("gts.vendor1.*", 10);
assert_eq!(result1.count, 1);
let result2 = store.query("gts.vendor2.*", 10);
assert_eq!(result2.count, 1);
let result3 = store.query("gts.*", 10);
assert_eq!(result3.count, 2);
}
#[test]
fn test_gts_store_validate_with_nested_refs() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.vendor.package.namespace.base.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": {"type": "string"}
}
});
let middle = json!({
"$id": "gts://gts.vendor.package.namespace.middle.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{"$ref": "gts://gts.vendor.package.namespace.base.v1.0~"},
{
"type": "object",
"properties": {
"name": {"type": "string"}
}
}
]
});
let top = json!({
"$id": "gts://gts.vendor.package.namespace.top.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{"$ref": "gts://gts.vendor.package.namespace.middle.v1.0~"},
{
"type": "object",
"properties": {
"email": {"type": "string"}
}
}
]
});
store
.register_schema("gts.vendor.package.namespace.base.v1.0~", &base)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.middle.v1.0~", &middle)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.top.v1.0~", &top)
.expect("test");
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.top.v1.0",
"name": "test",
"email": "test@example.com"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.top.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.validate_instance("gts.vendor.package.namespace.top.v1.0");
result.expect("a valid instance against a multi-level allOf+$ref chain should validate");
}
#[test]
fn test_gts_store_query_with_version_wildcard() {
let mut store = GtsStore::new();
for i in 0..3 {
let schema = json!({
"$id": format!("gts://gts.vendor.package.namespace.type.v{i}.0~"),
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
store
.register_schema(
&format!("gts.vendor.package.namespace.type.v{i}.0~"),
&schema,
)
.expect("test");
}
let result = store.query("gts.vendor.package.namespace.type.*", 10);
assert_eq!(result.count, 3);
}
#[test]
fn test_gts_store_cast_backward_incompatible() {
let mut store = GtsStore::new();
let schema_v1 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
let schema_v2 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v2.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "number"}
},
"required": ["name", "age"]
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.type.v2.0~", &schema_v2)
.expect("test");
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"name": "John"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.cast(
"gts.vendor.package.namespace.type.v1.0",
"gts.vendor.package.namespace.type.v2.0~",
);
let cast = result.expect("cast returns a compatibility report even when incompatible");
assert!(
!cast.is_backward_compatible,
"adding required `age` must make the cast backward-incompatible"
);
assert!(
!cast.backward_errors.is_empty(),
"backward incompatibility must be explained in backward_errors"
);
}
#[test]
fn test_gts_store_items_iterator_multiple() {
let mut store = GtsStore::new();
for i in 0..5 {
let schema = json!({
"$id": format!("gts.vendor.package.namespace.type{i}.v1.0~"),
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
store
.register_schema(
&format!("gts.vendor.package.namespace.type{i}.v1.0~"),
&schema,
)
.expect("test");
}
let count = store.items().count();
assert_eq!(count, 5);
}
#[test]
fn test_gts_store_compatibility_fully_compatible() {
let mut store = GtsStore::new();
let schema_v1 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
let schema_v2 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.type.v1.1~", &schema_v2)
.expect("test");
let result = store.is_minor_compatible(
"gts.vendor.package.namespace.type.v1.0~",
"gts.vendor.package.namespace.type.v1.1~",
);
assert!(result.is_backward_compatible);
}
#[test]
fn test_gts_store_build_schema_graph_complex() {
let mut store = GtsStore::new();
let base1 = json!({
"$id": "gts://gts.vendor.package.namespace.base1.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": {"type": "string"}
}
});
let base2 = json!({
"$id": "gts://gts.vendor.package.namespace.base2.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
let combined = json!({
"$id": "gts://gts.vendor.package.namespace.combined.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{"$ref": "gts://gts.vendor.package.namespace.base1.v1.0~"},
{"$ref": "gts://gts.vendor.package.namespace.base2.v1.0~"}
]
});
store
.register_schema("gts.vendor.package.namespace.base1.v1.0~", &base1)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.base2.v1.0~", &base2)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.combined.v1.0~", &combined)
.expect("test");
let graph = store.build_schema_graph("gts.vendor.package.namespace.combined.v1.0~");
assert!(graph.is_object());
}
#[test]
fn test_gts_store_register_invalid_json_entity() {
let mut store = GtsStore::new();
let content = json!({"name": "test"});
let entity = GtsEntity::new(
None,
None,
&content,
None,
None,
false,
String::new(),
None,
None,
);
let result = store.register(entity);
assert!(result.is_err());
}
#[test]
fn test_gts_store_validate_with_complex_schema() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string", "minLength": 1, "maxLength": 100},
"age": {"type": "integer", "minimum": 0, "maximum": 150},
"email": {"type": "string", "format": "email"},
"tags": {
"type": "array",
"items": {"type": "string"},
"minItems": 1
}
},
"required": ["name", "age"]
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"name": "John Doe",
"age": 30,
"email": "john@example.com",
"tags": ["developer", "rust"]
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
result.expect("a fully-valid instance against the complex schema should validate");
}
#[test]
fn test_gts_store_validate_missing_required_field() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
assert!(result.is_err());
}
#[test]
fn test_gts_store_schema_with_properties_only() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"name": {"type": "string"}
}
});
let result = store.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema);
assert!(result.is_ok());
}
#[test]
fn test_gts_store_query_no_results() {
let store = GtsStore::new();
let result = store.query("gts.nonexistent.*", 10);
assert_eq!(result.count, 0);
assert!(result.results.is_empty());
}
#[test]
fn test_gts_store_query_with_zero_limit() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let result = store.query("gts.vendor.*", 0);
assert_eq!(result.results.len(), 0);
}
#[test]
fn test_gts_store_cast_same_version() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.cast(
"gts.vendor.package.namespace.type.v1.0",
"gts.vendor.package.namespace.type.v1.0~",
);
let cast = result.expect("casting to the same version should succeed");
assert!(
cast.casted_entity.is_some(),
"a same-version cast must still produce a casted entity"
);
}
#[test]
fn test_gts_store_multiple_entities_same_schema() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let cfg = GtsConfig::default();
for i in 0..5 {
let content = json!({
"id": format!("gts.vendor.package.namespace.instance{i}.v1.0"),
"name": format!("test{i}")
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
}
let count = store.items().count();
assert!(count >= 5); }
#[test]
fn test_gts_store_get_schema_content_for_entity() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let result = store.get_schema_content("gts.vendor.package.namespace.type.v1.0~");
assert!(result.is_ok());
let retrieved = result.expect("test");
assert_eq!(
retrieved.get("type").expect("test").as_str().expect("test"),
"object"
);
}
#[test]
fn test_gts_store_compatibility_with_removed_properties() {
let mut store = GtsStore::new();
let schema_v1 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "number"},
"email": {"type": "string"}
}
});
let schema_v2 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "number"}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.type.v1.1~", &schema_v2)
.expect("test");
let result = store.is_minor_compatible(
"gts.vendor.package.namespace.type.v1.0~",
"gts.vendor.package.namespace.type.v1.1~",
);
assert!(result.is_forward_compatible);
}
#[test]
fn test_gts_store_build_schema_graph_single_schema() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let graph = store.build_schema_graph("gts.vendor.package.namespace.type.v1.0~");
assert!(graph.is_object());
}
#[test]
fn test_gts_store_register_schema_without_id() {
let mut store = GtsStore::new();
let schema = json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
let result = store.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema);
assert!(result.is_ok());
}
#[test]
fn test_gts_store_validate_with_unresolvable_ref() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{"$ref": "gts://gts.vendor.package.namespace.nonexistent.v1.0~"}
]
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("Unresolved $ref(s): gts://gts.vendor.package.namespace.nonexistent.v1.0~")
);
}
#[test]
fn test_gts_store_query_result_serialization_with_error() {
let result = GtsStoreQueryResult {
error: "Test error message".to_owned(),
count: 0,
limit: 10,
results: vec![],
};
let json_value = serde_json::to_value(&result).expect("test");
let json = json_value.as_object().expect("test");
assert_eq!(
json.get("error").expect("test").as_str().expect("test"),
"Test error message"
);
assert_eq!(json.get("count").expect("test").as_u64().expect("test"), 0);
}
#[test]
fn test_gts_store_cast_from_schema_entity() {
let mut store = GtsStore::new();
let schema_v1 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
let schema_v2 = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
.expect("test");
store
.register_schema("gts.vendor.package.namespace.type.v1.1~", &schema_v2)
.expect("test");
let result = store.cast(
"gts.vendor.package.namespace.type.v1.0~",
"gts.vendor.package.namespace.type.v1.1~",
);
let err = result.expect_err("casting from a schema id (not an instance) must be rejected");
assert!(
matches!(
err,
StoreError::InvalidEntity(ref m) if m.contains("is a schema, not an instance")
),
"expected InvalidEntity for a schema-as-source cast, got: {err:?}"
);
}
#[test]
fn test_gts_store_build_schema_graph_with_type_id() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.instance.v1.0",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let graph = store.build_schema_graph("gts.vendor.package.namespace.instance.v1.0");
assert!(graph.is_object());
let graph_obj = graph.as_object().expect("test");
assert!(graph_obj.contains_key("type_id") || graph_obj.contains_key("errors"));
}
#[test]
fn test_gts_store_query_with_filter_brackets() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
for i in 0..3 {
let content = json!({
"id": format!("gts.vendor.package.namespace.item{i}.v1.0~abc.app.custom.item{i}.v1.0"),
"name": format!("item{i}"),
"status": if i % 2 == 0 { "active" } else { "inactive" }
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
None,
);
store.register(entity).expect("test");
}
let result = store.query("gts.vendor.*[status=active]", 10);
assert!(result.count >= 1);
}
#[test]
fn test_gts_store_query_with_wildcard_filter() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
for i in 0..3 {
let content = if i == 0 {
json!({
"id": format!("gts.vendor.package.namespace.items.v1.0~a.b._.{i}.v1"),
"name": format!("item{i}"),
"category": null
})
} else {
json!({
"id": format!("gts.vendor.package.namespace.items.v1.0~c.d.e.{i}.v1"),
"name": format!("item{i}"),
"category": format!("cat{i}")
})
};
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
None,
);
store.register(entity).expect("test");
}
let mut all_entities = Vec::new();
for i in 0..3 {
let id1 = format!("gts.vendor.package.namespace.items.v1.0~a.b._.{i}.v1");
let id2 = format!("gts.vendor.package.namespace.items.v1.0~c.d.e.{i}.v1");
if let Some(entity) = store.get(&id1) {
all_entities.push((id1, entity.content.get("category").cloned()));
}
if i > 0
&& let Some(entity) = store.get(&id2)
{
all_entities.push((id2, entity.content.get("category").cloned()));
}
}
let non_null_count = all_entities
.iter()
.filter(|(_, cat)| cat.is_some() && cat.as_ref().unwrap() != &serde_json::Value::Null)
.count();
assert_eq!(non_null_count, 2);
}
#[test]
fn test_gts_store_query_invalid_wildcard_pattern() {
let store = GtsStore::new();
let result = store.query("gts.vendor*", 10);
assert!(!result.error.is_empty());
assert!(result.error.contains("wildcard"));
}
#[test]
fn test_gts_store_query_invalid_gts_id() {
let store = GtsStore::new();
let result = store.query("invalid-id", 10);
assert!(!result.error.is_empty());
}
#[test]
fn test_gts_store_query_gts_id_no_segments() {
let store = GtsStore::new();
let result = store.query("gts", 10);
assert!(!result.error.is_empty());
}
#[test]
fn test_gts_store_validate_instance_invalid_gts_id() {
let mut store = GtsStore::new();
let result = store.validate_instance("invalid-id");
let err = result.expect_err("validating an unregistered id must fail");
assert!(
matches!(err, StoreError::InstanceNotFound(ref m) if m.contains("invalid-id")),
"expected InstanceNotFound for an unregistered id, got: {err:?}"
);
}
#[test]
fn test_gts_store_validate_instance_invalid_schema() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "invalid_type"
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.instance.v1.0",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.validate_instance("gts.vendor.package.namespace.instance.v1.0");
assert!(result.is_err());
}
struct MockGtsReader {
entities: Vec<GtsEntity>,
index: usize,
}
impl MockGtsReader {
fn new(entities: Vec<GtsEntity>) -> Self {
MockGtsReader { entities, index: 0 }
}
}
impl GtsReader for MockGtsReader {
fn iter(&mut self) -> Box<dyn Iterator<Item = GtsEntity> + '_> {
Box::new(self.entities.clone().into_iter())
}
fn read_by_id(&self, entity_id: &str) -> Option<GtsEntity> {
self.entities
.iter()
.find(|e| e.effective_id().as_deref() == Some(entity_id))
.cloned()
}
fn reset(&mut self) {
self.index = 0;
}
}
#[test]
fn test_gts_store_with_reader() {
let cfg = GtsConfig::default();
let mut entities = Vec::new();
for i in 0..3 {
let content = json!({
"id": format!("gts.vendor.package.namespace.item{i}.v1.0"),
"name": format!("item{i}")
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
None,
);
entities.push(entity);
}
let reader = MockGtsReader::new(entities);
let store = GtsStore::with_reader(Box::new(reader));
assert_eq!(store.items().count(), 3);
}
#[test]
fn test_gts_store_get_from_reader() {
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.item.v1.0",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
None,
);
let reader = MockGtsReader::new(vec![entity]);
let mut store = GtsStore::with_reader(Box::new(reader));
let result = store.get("gts.vendor.package.namespace.item.v1.0");
assert!(result.is_some());
}
#[test]
fn test_gts_store_reader_without_gts_id() {
let content = json!({
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
None,
None,
false,
String::new(),
None,
None,
);
let reader = MockGtsReader::new(vec![entity]);
let store = GtsStore::with_reader(Box::new(reader));
assert_eq!(store.items().count(), 0);
}
#[test]
fn test_validate_schema_refs_valid_gts_uri() {
let schema = json!({
"$ref": "gts://gts.vendor.package.namespace.type.v1.0~"
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_ok());
}
#[test]
fn test_validate_schema_refs_valid_local_ref() {
let schema = json!({
"$ref": "#/definitions/MyType"
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_ok());
}
#[test]
fn test_validate_schema_refs_invalid_bare_gts_id() {
let schema = json!({
"$ref": "gts.vendor.package.namespace.type.v1.0~"
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("must be a local ref"));
assert!(err.contains("gts://"));
}
#[test]
fn test_validate_schema_refs_invalid_http_uri() {
let schema = json!({
"$ref": "https://example.com/schema.json"
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("must be a local ref"));
}
#[test]
fn test_validate_schema_refs_invalid_gts_id_in_uri() {
let schema = json!({
"$ref": "gts://invalid-gts-id"
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("must reference a GTS type id"));
}
#[test]
fn test_validate_schema_refs_nested() {
let schema = json!({
"properties": {
"user": {
"$ref": "gts://gts.vendor.package.namespace.user.v1.0~"
},
"order": {
"$ref": "invalid-ref"
}
}
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("properties.order.$ref"));
}
#[test]
fn test_validate_schema_refs_in_array() {
let schema = json!({
"allOf": [
{"$ref": "gts://gts.vendor.package.namespace.base.v1.0~"},
{"$ref": "not-valid-ref"}
]
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("allOf[1].$ref"));
}
#[test]
fn test_validate_schema_integration() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{"$ref": "gts.vendor.package.namespace.base.v1.0~"}
]
});
let result = store.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema);
assert!(result.is_ok());
let validation_result = store.validate_schema_refs("gts.vendor.package.namespace.type.v1.0~");
assert!(validation_result.is_err());
let err = validation_result.unwrap_err().to_string();
assert!(err.contains("must be a local ref") || err.contains("gts://"));
}
#[test]
fn test_validate_schema_refs_rejects_external_ref_without_gts_prefix() {
let schema = json!({
"$ref": "http://example.com/schema.json"
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("must be a local ref") || err.contains("GTS URI"),
"Error should mention local ref or GTS URI requirement"
);
}
#[test]
fn test_validate_schema_refs_rejects_malformed_gts_id_in_ref() {
let schema = json!({
"$ref": "gts://invalid-gts-id"
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("must reference a GTS type id"),
"Error should explain a GTS type id is required, got: {err}"
);
}
#[test]
fn test_validate_schema_refs_accepts_valid_gts_ref() {
let schema = json!({
"$ref": "gts://gts.vendor.package.namespace.type.v1.0~"
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_ok(), "Valid gts:// ref should be accepted");
}
#[test]
fn test_validate_schema_refs_accepts_local_json_pointer() {
let schema = json!({
"$ref": "#/definitions/Base"
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_ok(), "Local JSON Pointer ref should be accepted");
}
#[test]
fn test_validate_schema_refs_accepts_root_json_pointer() {
let schema = json!({
"$ref": "#"
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_ok(), "Root JSON Pointer ref should be accepted");
}
#[test]
fn test_validate_schema_refs_rejects_gts_colon_without_slashes() {
let schema = json!({
"$ref": "gts:gts.vendor.package.namespace.type.v1.0~"
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("must be a local ref") || err.contains("GTS URI"),
"Error should mention local ref or GTS URI requirement"
);
}
#[test]
fn test_validate_schema_refs_deeply_nested_invalid_ref() {
let schema = json!({
"properties": {
"level1": {
"properties": {
"level2": {
"properties": {
"level3": {
"$ref": "invalid-external-ref"
}
}
}
}
}
}
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("properties.level1.properties.level2.properties.level3.$ref"),
"Error should report the correct nested path"
);
}
#[test]
fn test_validate_schema_refs_mixed_valid_and_invalid() {
let schema = json!({
"allOf": [
{"$ref": "gts://gts.vendor.package.namespace.base.v1.0~"},
{"$ref": "#/definitions/Local"},
{"$ref": "invalid-ref"}
]
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_err(), "Should fail when any ref is invalid");
let err = result.unwrap_err().to_string();
assert!(
err.contains("allOf[2].$ref"),
"Should report the invalid ref path"
);
}
#[test]
fn test_validate_schema_refs_empty_string() {
let schema = json!({
"$ref": ""
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("must be a local ref") || err.contains("GTS URI"),
"Error should mention local ref or GTS URI requirement"
);
}
#[test]
fn test_validate_schema_refs_gts_prefix_but_empty_id() {
let schema = json!({
"$ref": "gts://"
});
let result = GtsStore::validate_ref_uris(&schema);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(
err.contains("must reference a GTS type id"),
"Error should explain a GTS type id is required, got: {err}"
);
}
#[test]
fn test_validate_schema_x_gts_refs_non_schema_id() {
let mut store = GtsStore::new();
let result = store.validate_schema_refs("gts.vendor.package.namespace.type.v1.0");
assert!(result.is_err());
match result {
Err(StoreError::InvalidTypeId(err)) => {
assert_eq!(err.input, "gts.vendor.package.namespace.type.v1.0");
}
_ => panic!("Expected InvalidTypeId error"),
}
}
#[test]
fn test_validate_schema_x_gts_refs_schema_not_found() {
let mut store = GtsStore::new();
let result = store.validate_schema_refs("gts.vendor.package.namespace.type.v1.0~");
assert!(result.is_err());
match result {
Err(StoreError::SchemaNotFound(id)) => {
assert_eq!(id, "gts.vendor.package.namespace.type.v1.0~");
}
_ => panic!("Expected SchemaNotFound error"),
}
}
#[test]
fn test_validate_schema_x_gts_refs_entity_not_schema() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0~",
"name": "test"
});
let gts_id = GtsId::try_new("gts.vendor.package.namespace.type.v1.0~").expect("test");
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
Some(gts_id),
false, String::new(),
None,
None,
);
store.register(entity).expect("test");
let result = store.validate_schema_refs("gts.vendor.package.namespace.type.v1.0~");
assert!(result.is_err());
match result {
Err(StoreError::InvalidEntity(msg)) => {
assert!(msg.contains("is not a schema"));
}
_ => panic!("Expected InvalidEntity error"),
}
}
#[test]
fn test_validate_schema_x_gts_refs_validation_error() {
let schema_content = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"invalidRef": {
"type": "string",
"x-gts-ref": "invalid-gts-id" }
}
});
let result = GtsStore::validate_schema_x_gts_refs(&schema_content);
assert!(result.is_err());
match result {
Err(StoreError::ValidationError(msg)) => {
assert!(msg.contains("x-gts-ref validation failed"));
}
_ => panic!("Expected ValidationError"),
}
}
#[test]
fn test_validate_schema_non_schema_id() {
let mut store = GtsStore::new();
let result = store.validate_schema_refs("gts.vendor.package.namespace.type.v1.0");
assert!(result.is_err());
match result {
Err(StoreError::InvalidTypeId(err)) => {
assert_eq!(err.input, "gts.vendor.package.namespace.type.v1.0");
}
_ => panic!("Expected InvalidTypeId error"),
}
}
#[test]
fn test_validate_schema_entity_not_schema() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0~",
"name": "test"
});
let gts_id = GtsId::try_new("gts.vendor.package.namespace.type.v1.0~").expect("test");
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
Some(gts_id),
false, String::new(),
None,
None,
);
store.register(entity).expect("test");
let result = store.validate_schema_refs("gts.vendor.package.namespace.type.v1.0~");
assert!(result.is_err());
match result {
Err(StoreError::InvalidEntity(msg)) => {
assert!(msg.contains("is not a schema"));
}
_ => panic!("Expected InvalidEntity error"),
}
}
#[test]
fn test_validate_schema_content_not_object() {
let mut store = GtsStore::new();
let schema_content = json!(["not", "an", "object"]);
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_content)
.expect("test");
let result = store.validate_schema_refs("gts.vendor.package.namespace.type.v1.0~");
assert!(result.is_err());
match result {
Err(StoreError::InvalidEntity(msg)) => {
assert!(msg.contains("is not a schema"));
}
_ => panic!("Expected InvalidEntity error"),
}
}
#[test]
fn test_validate_instance_schema_compilation_error() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let invalid_schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "invalid-type-value" });
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &invalid_schema)
.expect("test");
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0~a.b.c.d.v1",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0~a.b.c.d.v1");
assert!(result.is_err());
match result {
Err(StoreError::ValidationError(msg)) => {
assert!(msg.contains("Invalid schema"), "Actual: {msg}");
}
Err(e) => panic!("Expected ValidationError for invalid schema, got: {e:?}"),
_ => panic!("Expected an error"),
}
}
#[test]
fn test_validate_instance_validation_failed() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0~a.b.c.d.v1"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0~a.b.c.d.v1");
assert!(result.is_err());
match result {
Err(StoreError::ValidationError(msg)) => {
assert!(msg.contains("Validation failed"));
}
other => panic!("Expected ValidationError for failed validation, got: {other:?}"),
}
}
#[test]
fn test_validate_instance_x_gts_ref_validation_failed() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let schema = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"refField": {
"type": "string",
"x-gts-ref": "gts.vendor.package.namespace.other.v1.0~"
}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
.expect("test");
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0~a.b.c.d.v1",
"refField": "invalid-reference" });
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("test");
let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0~a.b.c.d.v1");
assert!(result.is_err());
match result {
Err(StoreError::ValidationError(msg)) => {
assert!(msg.contains("x-gts-ref validation failed"));
}
_ => panic!("Expected ValidationError for x-gts-ref validation"),
}
}
#[test]
fn test_cast_missing_schema_for_instance() {
let mut store = GtsStore::new();
let cfg = GtsConfig::default();
let content = json!({
"id": "gts.vendor.package.namespace.type.v1.0",
"name": "test"
});
let entity = GtsEntity::new(
None,
None,
&content,
Some(&cfg),
None,
false,
String::new(),
None,
None,
);
store.register(entity).expect("test");
let target_schema = json!({
"$id": "gts://gts.vendor.package.namespace.target.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object"
});
store
.register_schema("gts.vendor.package.namespace.target.v1.0~", &target_schema)
.expect("test");
let result = store.cast(
"gts.vendor.package.namespace.type.v1.0",
"gts.vendor.package.namespace.target.v1.0~",
);
assert!(result.is_err());
match result {
Err(StoreError::InvalidEntity(msg)) => {
assert!(msg.contains("gts.vendor.package.namespace.type.v1.0"));
}
_ => panic!("Expected InvalidEntity error"),
}
}
#[test]
fn test_op12_single_segment_schema_always_valid() {
let mut store = GtsStore::new();
let schema = json!({
"$id": "gts://gts.x.test.base.user.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["userId"],
"properties": {
"userId": {"type": "string"},
"email": {"type": "string"}
}
});
store
.register_schema("gts.x.test.base.user.v1~", &schema)
.expect("register");
let result = store.validate_schema_refs("gts.x.test.base.user.v1~");
assert!(
result.is_ok(),
"Single-segment schema should always pass chain validation"
);
}
#[test]
fn test_op12_derived_tightens_constraints_ok() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test12.base.user.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["userId", "email"],
"properties": {
"userId": {"type": "string", "format": "uuid"},
"email": {"type": "string", "format": "email"},
"tier": {"type": "string", "maxLength": 100}
}
});
store
.register_schema("gts.x.test12.base.user.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test12.base.user.v1~x.test12._.premium.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test12.base.user.v1~"},
{
"type": "object",
"properties": {
"tier": {"type": "string", "enum": ["gold", "platinum"]}
}
}
]
});
store
.register_schema("gts.x.test12.base.user.v1~x.test12._.premium.v1~", &derived)
.expect("register derived");
let result = store.validate_schema_refs("gts.x.test12.base.user.v1~x.test12._.premium.v1~");
assert!(
result.is_ok(),
"Derived that tightens constraints should pass: {result:?}"
);
}
#[test]
fn test_op12_derived_adds_property_ok() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test12.base.user.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["userId"],
"properties": {
"userId": {"type": "string"}
}
});
store
.register_schema("gts.x.test12.base.user.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test12.base.user.v1~x.test12._.extended.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test12.base.user.v1~"},
{
"type": "object",
"properties": {
"extra": {"type": "string"}
}
}
]
});
store
.register_schema(
"gts.x.test12.base.user.v1~x.test12._.extended.v1~",
&derived,
)
.expect("register derived");
let result = store.validate_schema_refs("gts.x.test12.base.user.v1~x.test12._.extended.v1~");
assert!(
result.is_ok(),
"Adding property to open base should pass: {result:?}"
);
}
#[test]
fn test_op12_additional_properties_false_violation() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test12.closed.account.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["accountId"],
"properties": {
"accountId": {"type": "string"},
"email": {"type": "string"}
},
"additionalProperties": false
});
store
.register_schema("gts.x.test12.closed.account.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test12.closed.account.v1~x.test12._.premium.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test12.closed.account.v1~"},
{
"type": "object",
"properties": {
"tier": {"type": "string"}
}
}
]
});
store
.register_schema(
"gts.x.test12.closed.account.v1~x.test12._.premium.v1~",
&derived,
)
.expect("register derived");
let result =
store.validate_schema_chain("gts.x.test12.closed.account.v1~x.test12._.premium.v1~");
assert!(
result.is_err(),
"Adding property when base has additionalProperties:false should fail"
);
}
#[test]
fn test_op12_loosened_max_length_fails() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test12.str.field.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"value": {"type": "string", "maxLength": 128}
}
});
store
.register_schema("gts.x.test12.str.field.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test12.str.field.v1~x.test12._.loose.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test12.str.field.v1~"},
{
"type": "object",
"properties": {
"value": {"type": "string", "maxLength": 256}
}
}
]
});
store
.register_schema("gts.x.test12.str.field.v1~x.test12._.loose.v1~", &derived)
.expect("register derived");
let result = store.validate_schema_chain("gts.x.test12.str.field.v1~x.test12._.loose.v1~");
assert!(result.is_err(), "Loosened maxLength should fail");
}
#[test]
fn test_op12_loosened_maximum_fails() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test12.num.field.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"size": {"type": "integer", "minimum": 0, "maximum": 100}
}
});
store
.register_schema("gts.x.test12.num.field.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test12.num.field.v1~x.test12._.loose.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test12.num.field.v1~"},
{
"type": "object",
"properties": {
"size": {"type": "integer", "minimum": 0, "maximum": 200}
}
}
]
});
store
.register_schema("gts.x.test12.num.field.v1~x.test12._.loose.v1~", &derived)
.expect("register derived");
let result = store.validate_schema_chain("gts.x.test12.num.field.v1~x.test12._.loose.v1~");
assert!(result.is_err(), "Loosened maximum should fail");
}
#[test]
fn test_op12_enum_expansion_fails() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test12.enum.status.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"status": {"type": "string", "enum": ["active", "inactive"]}
}
});
store
.register_schema("gts.x.test12.enum.status.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test12.enum.status.v1~x.test12._.expanded.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test12.enum.status.v1~"},
{
"type": "object",
"properties": {
"status": {"type": "string", "enum": ["active", "inactive", "archived"]}
}
}
]
});
store
.register_schema(
"gts.x.test12.enum.status.v1~x.test12._.expanded.v1~",
&derived,
)
.expect("register derived");
let result = store.validate_schema_chain("gts.x.test12.enum.status.v1~x.test12._.expanded.v1~");
assert!(result.is_err(), "Enum expansion should fail");
}
#[test]
fn test_op12_3level_progressive_tightening_ok() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test12.cascade.msg.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["msgId"],
"properties": {
"msgId": {"type": "string"},
"payload": {"type": "string", "maxLength": 1024}
}
});
store
.register_schema("gts.x.test12.cascade.msg.v1~", &base)
.expect("register base");
let l2 = json!({
"$id": "gts://gts.x.test12.cascade.msg.v1~x.test12._.sms.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test12.cascade.msg.v1~"},
{
"type": "object",
"properties": {
"payload": {"type": "string", "maxLength": 512}
}
}
]
});
store
.register_schema("gts.x.test12.cascade.msg.v1~x.test12._.sms.v1~", &l2)
.expect("register L2");
let l3 = json!({
"$id": "gts://gts.x.test12.cascade.msg.v1~x.test12._.sms.v1~x.test12._.short.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test12.cascade.msg.v1~x.test12._.sms.v1~"},
{
"type": "object",
"properties": {
"payload": {"type": "string", "maxLength": 256}
}
}
]
});
store
.register_schema(
"gts.x.test12.cascade.msg.v1~x.test12._.sms.v1~x.test12._.short.v1~",
&l3,
)
.expect("register L3");
let result = store.validate_schema_chain("gts.x.test12.cascade.msg.v1~x.test12._.sms.v1~");
assert!(result.is_ok(), "L2 tightening should pass: {result:?}");
let result = store.validate_schema_chain(
"gts.x.test12.cascade.msg.v1~x.test12._.sms.v1~x.test12._.short.v1~",
);
assert!(
result.is_ok(),
"L3 progressive tightening should pass: {result:?}"
);
}
#[test]
fn test_op12_3level_l3_violates_l2() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test12.hier.base.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"size": {"type": "integer", "minimum": 0, "maximum": 1000}
}
});
store
.register_schema("gts.x.test12.hier.base.v1~", &base)
.expect("register base");
let l2 = json!({
"$id": "gts://gts.x.test12.hier.base.v1~x.test12._.medium.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test12.hier.base.v1~"},
{
"type": "object",
"properties": {
"size": {"type": "integer", "minimum": 100, "maximum": 500}
}
}
]
});
store
.register_schema("gts.x.test12.hier.base.v1~x.test12._.medium.v1~", &l2)
.expect("register L2");
let l3 = json!({
"$id": "gts://gts.x.test12.hier.base.v1~x.test12._.medium.v1~x.test12._.bad.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test12.hier.base.v1~x.test12._.medium.v1~"},
{
"type": "object",
"properties": {
"size": {"type": "integer", "minimum": 100, "maximum": 800}
}
}
]
});
store
.register_schema(
"gts.x.test12.hier.base.v1~x.test12._.medium.v1~x.test12._.bad.v1~",
&l3,
)
.expect("register L3");
let result = store.validate_schema_chain("gts.x.test12.hier.base.v1~x.test12._.medium.v1~");
assert!(result.is_ok(), "L2 should pass: {result:?}");
let result = store
.validate_schema_chain("gts.x.test12.hier.base.v1~x.test12._.medium.v1~x.test12._.bad.v1~");
assert!(result.is_err(), "L3 loosening L2 maximum should fail");
}
#[test]
fn test_op12_property_disabled_fails() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test12.order.base.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["orderId", "customerId", "total"],
"properties": {
"orderId": {"type": "string"},
"customerId": {"type": "string"},
"total": {"type": "number", "minimum": 0}
}
});
store
.register_schema("gts.x.test12.order.base.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test12.order.base.v1~x.test12._.anon_order.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test12.order.base.v1~"},
{
"type": "object",
"properties": {
"customerId": false
}
}
]
});
store
.register_schema(
"gts.x.test12.order.base.v1~x.test12._.anon_order.v1~",
&derived,
)
.expect("register derived");
let result =
store.validate_schema_chain("gts.x.test12.order.base.v1~x.test12._.anon_order.v1~");
assert!(
result.is_err(),
"Disabling a property defined in base should fail"
);
}
#[test]
fn test_op12_direct_derived_loosens_additional_properties_to_true() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test.addl.closed.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": {"type": "string"}
},
"additionalProperties": false
});
store
.register_schema("gts.x.test.addl.closed.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test.addl.closed.v1~x.test._.open.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": {"type": "string"}
},
"additionalProperties": true
});
store
.register_schema("gts.x.test.addl.closed.v1~x.test._.open.v1~", &derived)
.expect("register derived");
let result = store.validate_schema_chain("gts.x.test.addl.closed.v1~x.test._.open.v1~");
assert!(
result.is_err(),
"Loosening additionalProperties from false to true should fail"
);
}
#[test]
fn test_op12_allof_overlay_additional_properties_true_stays_closed() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test.addl.closed3.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": {"type": "string"}
},
"additionalProperties": false
});
store
.register_schema("gts.x.test.addl.closed3.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test.addl.closed3.v1~x.test._.overlay.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test.addl.closed3.v1~"},
{
"type": "object",
"properties": {
"id": {"type": "string"}
},
"additionalProperties": true
}
]
});
store
.register_schema("gts.x.test.addl.closed3.v1~x.test._.overlay.v1~", &derived)
.expect("register derived");
let result = store.validate_schema_chain("gts.x.test.addl.closed3.v1~x.test._.overlay.v1~");
assert!(
result.is_ok(),
"additionalProperties: true in an allOf overlay does not loosen \
a closed base branch. Got: {result:?}"
);
}
#[test]
fn test_op12_derived_omits_additional_properties_inherits_closedness() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test.addl.closed2.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"id": {"type": "string"}
},
"additionalProperties": false
});
store
.register_schema("gts.x.test.addl.closed2.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test.addl.closed2.v1~x.test._.omit.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test.addl.closed2.v1~"}
]
});
store
.register_schema("gts.x.test.addl.closed2.v1~x.test._.omit.v1~", &derived)
.expect("register derived");
let result = store.validate_schema_chain("gts.x.test.addl.closed2.v1~x.test._.omit.v1~");
assert!(
result.is_ok(),
"Omitting additionalProperties when base has false is *not* \
loosening — closedness is inherited via $ref/allOf composition. \
Got: {result:?}"
);
}
#[test]
fn test_op12_descendant_nested_closed_schema_orphans_ancestor_property() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test12.nested_orphan.event.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"routing": {
"type": "object",
"properties": {
"source": {"type": "string"}
}
}
}
});
store
.register_schema("gts.x.test12.nested_orphan.event.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test12.nested_orphan.event.v1~x.test12._.child.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test12.nested_orphan.event.v1~"},
{
"type": "object",
"properties": {
"routing": {
"type": "object",
"additionalProperties": false,
"properties": {
"target": {"type": "string"}
}
}
}
}
]
});
store
.register_schema(
"gts.x.test12.nested_orphan.event.v1~x.test12._.child.v1~",
&derived,
)
.expect("register derived");
let result =
store.validate_schema_chain("gts.x.test12.nested_orphan.event.v1~x.test12._.child.v1~");
assert!(
result.is_err(),
"closed nested derived branch should not orphan ancestor routing.source: {result:?}"
);
}
#[test]
fn test_op12_derived_omits_const() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test.const.base.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"status": {"type": "string", "const": "active"}
}
});
store
.register_schema("gts.x.test.const.base.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test.const.base.v1~x.test._.loose.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{"$ref": "gts://gts.x.test.const.base.v1~"},
{
"properties": {
"status": {"type": "string"} }
}
]
});
store
.register_schema("gts.x.test.const.base.v1~x.test._.loose.v1~", &derived)
.expect("register derived");
let result = store.validate_schema_chain("gts.x.test.const.base.v1~x.test._.loose.v1~");
assert!(result.is_err(), "Omitting const should fail");
}
#[test]
fn test_op12_derived_omits_pattern() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test.pattern.base.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"email": {"type": "string", "pattern": "^[a-z]+@[a-z]+\\.[a-z]+$"}
}
});
store
.register_schema("gts.x.test.pattern.base.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test.pattern.base.v1~x.test._.loose.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{"$ref": "gts://gts.x.test.pattern.base.v1~"},
{
"properties": {
"email": {"type": "string"} }
}
]
});
store
.register_schema("gts.x.test.pattern.base.v1~x.test._.loose.v1~", &derived)
.expect("register derived");
let result = store.validate_schema_chain("gts.x.test.pattern.base.v1~x.test._.loose.v1~");
assert!(result.is_err(), "Omitting pattern should fail");
}
#[test]
fn test_op12_derived_omits_enum() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test.enum.base.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"role": {"type": "string", "enum": ["admin", "user"]}
}
});
store
.register_schema("gts.x.test.enum.base.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test.enum.base.v1~x.test._.loose.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{"$ref": "gts://gts.x.test.enum.base.v1~"},
{
"properties": {
"role": {"type": "string"} }
}
]
});
store
.register_schema("gts.x.test.enum.base.v1~x.test._.loose.v1~", &derived)
.expect("register derived");
let result = store.validate_schema_chain("gts.x.test.enum.base.v1~x.test._.loose.v1~");
assert!(result.is_err(), "Omitting enum should fail");
}
#[test]
fn test_op12_derived_omits_max_length() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test.maxlen.base.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": {"type": "string", "maxLength": 50}
}
});
store
.register_schema("gts.x.test.maxlen.base.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test.maxlen.base.v1~x.test._.loose.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{"$ref": "gts://gts.x.test.maxlen.base.v1~"},
{
"properties": {
"name": {"type": "string"} }
}
]
});
store
.register_schema("gts.x.test.maxlen.base.v1~x.test._.loose.v1~", &derived)
.expect("register derived");
let result = store.validate_schema_chain("gts.x.test.maxlen.base.v1~x.test._.loose.v1~");
assert!(result.is_err(), "Omitting maxLength should fail");
}
#[test]
fn test_op13_traits_all_resolved_passes() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test13.tr.base.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"topicRef": {"type": "string"},
"retention": {"type": "string"}
}
},
"properties": {"id": {"type": "string"}}
});
store
.register_schema("gts.x.test13.tr.base.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test13.tr.base.v1~x.test13._.leaf.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits": {
"topicRef": "gts.x.core.events.topic.v1~x.test._.orders.v1",
"retention": "P90D"
},
"allOf": [
{"$ref": "gts://gts.x.test13.tr.base.v1~"}
]
});
store
.register_schema("gts.x.test13.tr.base.v1~x.test13._.leaf.v1~", &derived)
.expect("register derived");
let result = store.validate_schema("gts.x.test13.tr.base.v1~x.test13._.leaf.v1~");
assert!(
result.is_ok(),
"All traits resolved should pass: {result:?}"
);
}
#[test]
fn test_op13_traits_defaults_fill_passes() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test13.dfl.base.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {
"type": "object",
"properties": {
"retention": {"type": "string", "default": "P30D"},
"topicRef": {"type": "string", "default": "default_topic"}
}
},
"properties": {"id": {"type": "string"}}
});
store
.register_schema("gts.x.test13.dfl.base.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test13.dfl.base.v1~x.test13._.leaf.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test13.dfl.base.v1~"},
{"type": "object"}
]
});
store
.register_schema("gts.x.test13.dfl.base.v1~x.test13._.leaf.v1~", &derived)
.expect("register derived");
let result = store.validate_schema("gts.x.test13.dfl.base.v1~x.test13._.leaf.v1~");
assert!(result.is_ok(), "Defaults should fill traits: {result:?}");
}
#[test]
fn test_op13_traits_missing_required_fails() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test13.mis.base.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {
"type": "object",
"properties": {
"topicRef": {"type": "string"},
"retention": {"type": "string", "default": "P30D"}
},
"required": ["topicRef"]
},
"properties": {"id": {"type": "string"}}
});
store
.register_schema("gts.x.test13.mis.base.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test13.mis.base.v1~x.test13._.leaf.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits": {"retention": "P90D"},
"allOf": [
{"$ref": "gts://gts.x.test13.mis.base.v1~"}
]
});
store
.register_schema("gts.x.test13.mis.base.v1~x.test13._.leaf.v1~", &derived)
.expect("register derived");
let result = store.validate_schema("gts.x.test13.mis.base.v1~x.test13._.leaf.v1~");
assert!(result.is_err(), "Missing topicRef should fail");
}
#[test]
fn test_op13_traits_wrong_type_fails() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test13.wt.base.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {
"type": "object",
"properties": {
"maxRetries": {"type": "integer", "minimum": 0, "default": 3}
}
},
"properties": {"id": {"type": "string"}}
});
store
.register_schema("gts.x.test13.wt.base.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test13.wt.base.v1~x.test13._.leaf.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits": {"maxRetries": "not_a_number"},
"allOf": [
{"$ref": "gts://gts.x.test13.wt.base.v1~"}
]
});
store
.register_schema("gts.x.test13.wt.base.v1~x.test13._.leaf.v1~", &derived)
.expect("register derived");
let result = store.validate_schema("gts.x.test13.wt.base.v1~x.test13._.leaf.v1~");
assert!(result.is_err(), "Wrong type should fail");
}
#[test]
fn test_op13_traits_no_traits_schema_passes() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test13.nt.base.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"id": {"type": "string"}}
});
store
.register_schema("gts.x.test13.nt.base.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test13.nt.base.v1~x.test13._.leaf.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test13.nt.base.v1~"},
{"type": "object", "properties": {"extra": {"type": "string"}}}
]
});
store
.register_schema("gts.x.test13.nt.base.v1~x.test13._.leaf.v1~", &derived)
.expect("register derived");
let result = store.validate_schema("gts.x.test13.nt.base.v1~x.test13._.leaf.v1~");
assert!(
result.is_ok(),
"No traits schema means nothing to validate: {result:?}"
);
}
#[test]
fn test_store_query_empty_expr() {
let store = GtsStore::new();
let result = store.query("", 10);
assert!(!result.error.is_empty());
}
#[test]
fn test_store_query_with_very_large_limit() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.test.package.namespace.foo.v1~",
&json!({
"$id": "gts://gts.test.package.namespace.foo.v1~",
"type": "object"
}),
)
.unwrap();
let result = store.query("gts.test.package.namespace.foo.v1~", 10000);
assert!(result.error.is_empty());
assert_eq!(result.count, 1);
}
#[test]
fn test_store_register_schema_validates_type_id() {
let mut store = GtsStore::new();
let type_id = "gts.test.package.namespace.minimal.v1~";
let result = store.register_schema(
type_id,
&json!({
"$id": format!("gts://{type_id}"),
"type": "object"
}),
);
assert!(result.is_ok());
let bad_id = "gts.test.bad.v1";
let result = store.register_schema(
bad_id,
&json!({
"$id": format!("gts://{bad_id}"),
"type": "object"
}),
);
assert!(result.is_err());
}
#[test]
fn test_store_build_schema_graph_with_nonexistent_id() {
let mut store = GtsStore::new();
let graph = store.build_schema_graph("gts.nonexistent.schema.v1~");
assert!(graph.is_object() || graph.is_null());
}
#[test]
fn test_store_error_debug_display() {
let err = StoreError::InstanceNotFound("test_id".to_owned());
let debug_str = format!("{err:?}");
assert!(debug_str.contains("InstanceNotFound"));
let display_str = format!("{err}");
assert!(display_str.contains("test_id"));
}
#[test]
fn test_store_error_variants() {
let err1 = StoreError::InvalidTypeId(GtsIdError::new("bad", "not a type id"));
assert!(format!("{err1}").contains("Invalid GTS type id"));
let err2 = StoreError::InvalidEntity("bad".to_owned());
assert!(format!("{err2:?}").contains("InvalidEntity"));
let err3 = StoreError::ValidationError("test error".to_owned());
assert!(format!("{err3}").contains("test error"));
let err4 = StoreError::CircularRef;
assert_eq!(err4.to_string(), "Circular $ref detected");
let err5 = StoreError::UnresolvedRefs(vec!["a".to_owned(), "b".to_owned()]);
assert!(
err5.to_string().contains("a, b"),
"UnresolvedRefs must render the joined ref list, got: {err5}"
);
}
#[test]
fn test_store_get_schema_content_returns_copy() {
let mut store = GtsStore::new();
let type_id = "gts.test.package.namespace.copy.v1~";
let schema = json!({
"$id": format!("gts://{type_id}"),
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"field": {"type": "string"}}
});
store.register_schema(type_id, &schema).unwrap();
let content1 = store.get_schema_content(type_id).unwrap();
let content2 = store.get_schema_content(type_id).unwrap();
assert_eq!(content1, content2);
}
#[test]
fn test_op13_traits_ref_based_trait_schema() {
let mut store = GtsStore::new();
let retention_trait = json!({
"$id": "gts://gts.x.test13.traits.retention.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"retention": {"type": "string", "default": "P30D"}
}
});
store
.register_schema("gts.x.test13.traits.retention.v1~", &retention_trait)
.expect("register retention trait");
let topic_trait = json!({
"$id": "gts://gts.x.test13.traits.topic.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"topicRef": {"type": "string"}
}
});
store
.register_schema("gts.x.test13.traits.topic.v1~", &topic_trait)
.expect("register topic trait");
let base = json!({
"$id": "gts://gts.x.test13.ref.base.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test13.traits.retention.v1~"},
{"$ref": "gts://gts.x.test13.traits.topic.v1~"}
]
},
"properties": {"id": {"type": "string"}}
});
store
.register_schema("gts.x.test13.ref.base.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test13.ref.base.v1~x.test13._.leaf.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits": {
"topicRef": "gts.x.core.events.topic.v1~x.test._.orders.v1",
"retention": "P90D"
},
"allOf": [
{"$ref": "gts://gts.x.test13.ref.base.v1~"}
]
});
store
.register_schema("gts.x.test13.ref.base.v1~x.test13._.leaf.v1~", &derived)
.expect("register derived");
let result = store.validate_schema("gts.x.test13.ref.base.v1~x.test13._.leaf.v1~");
assert!(
result.is_ok(),
"$ref trait schemas should resolve and validate: {result:?}"
);
}
#[test]
fn test_op13_traits_ref_to_nonexistent_schema() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test13.badref.base.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {
"type": "object",
"allOf": [
{"$ref": "gts://gts.x.test13.traits.nonexistent.v1~"}
]
},
"properties": {"id": {"type": "string"}}
});
store
.register_schema("gts.x.test13.badref.base.v1~", &base)
.expect("register base");
let derived = json!({
"$id": "gts://gts.x.test13.badref.base.v1~x.test13._.leaf.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits": {"foo": "bar"},
"allOf": [
{"$ref": "gts://gts.x.test13.badref.base.v1~"}
]
});
store
.register_schema("gts.x.test13.badref.base.v1~x.test13._.leaf.v1~", &derived)
.expect("register derived");
let result = store.validate_schema("gts.x.test13.badref.base.v1~x.test13._.leaf.v1~");
assert!(
result.is_err(),
"Unresolvable $ref should cause validation error"
);
}
#[test]
fn test_op13_redeclared_default_in_mid_allowed() {
let mut store = GtsStore::new();
let base = json!({
"$id": "gts://gts.x.test13.chdfl.event.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {
"type": "object",
"properties": {
"retention": {
"type": "string",
"default": "P30D"
},
"topicRef": {
"type": "string"
}
}
},
"properties": {"id": {"type": "string"}}
});
store
.register_schema("gts.x.test13.chdfl.event.v1~", &base)
.expect("register base");
let mid = json!({
"$id": "gts://gts.x.test13.chdfl.event.v1~x.test13._.chdfl_mid.v1~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {
"type": "object",
"properties": {
"retention": {
"type": "string",
"default": "P90D"
}
}
},
"x-gts-traits": {
"topicRef": "gts.x.core.events.topic.v1~x.test13._.orders.v1"
},
"allOf": [
{"$ref": "gts://gts.x.test13.chdfl.event.v1~"}
]
});
store
.register_schema("gts.x.test13.chdfl.event.v1~x.test13._.chdfl_mid.v1~", &mid)
.expect("register mid");
let result = store.validate_schema("gts.x.test13.chdfl.event.v1~x.test13._.chdfl_mid.v1~");
assert!(
result.is_ok(),
"Redeclared default in descendant should be allowed, got: {result:?}"
);
}
#[test]
fn test_effective_traits_walks_id_chain() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.cti.tr.base.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": { "type": "object", "properties": {
"retention": {"type": "string", "default": "P30D"},
"tier": {"type": "string"}
}},
"x-gts-traits": {"tier": "standard"}
}),
)
.unwrap();
store
.register_schema(
"gts.x.cti.tr.base.v1~x.cti._.leaf.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits": {"tier": "premium"}
}),
)
.unwrap();
let id = "gts.x.cti.tr.base.v1~x.cti._.leaf.v1~";
let traits = store.effective_traits(id).unwrap();
assert_eq!(
traits.resolved_trait_schemas.len(),
1,
"one x-gts-traits-schema in the chain"
);
assert_eq!(
traits.merged_traits["tier"], "premium",
"leaf value wins (RFC 7396)"
);
assert_eq!(
traits.values["retention"], "P30D",
"ancestor default is materialized"
);
assert_eq!(
traits.schema["$schema"], "http://json-schema.org/draft-07/schema#",
"leaf dialect is pinned into the composed trait schema"
);
}
#[allow(clippy::needless_pass_by_value)] #[allow(clippy::fn_params_excessive_bools)] fn assert_resolved_type(
rt: &crate::store::ResolvedType,
expected_id: &str,
expected_is_abstract: bool,
expected_is_final: bool,
expected_schema: Value,
expected_effective_traits: Value,
expected_effective_traits_schema: Value,
) {
assert_eq!(
rt.id,
crate::GtsTypeId::new(expected_id),
"ResolvedType.id mismatch"
);
assert_eq!(
rt.is_abstract, expected_is_abstract,
"ResolvedType.is_abstract mismatch"
);
assert_eq!(
rt.is_final, expected_is_final,
"ResolvedType.is_final mismatch"
);
assert_eq!(rt.schema, expected_schema, "ResolvedType.schema mismatch");
assert_eq!(
rt.effective_traits, expected_effective_traits,
"ResolvedType.effective_traits mismatch"
);
assert_eq!(
rt.effective_traits_schema, expected_effective_traits_schema,
"ResolvedType.effective_traits_schema mismatch"
);
}
#[test]
fn test_resolved_type_single_level_full_artifacts() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.rs.tr.base.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"id": {"type": "string"}},
"x-gts-traits-schema": {"type": "object", "properties": {
"tier": {"type": "string", "default": "standard"},
"retention": {"type": "string"}
}},
"x-gts-traits": {"tier": "gold", "retention": "P30D"}
}),
)
.unwrap();
let rt = store.validate_schema("gts.x.rs.tr.base.v1~").unwrap();
assert_resolved_type(
&rt,
"gts.x.rs.tr.base.v1~",
false,
false,
json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"id": {"type": "string"}},
"x-gts-traits-schema": {"type": "object", "properties": {
"tier": {"type": "string", "default": "standard"},
"retention": {"type": "string"}
}},
"x-gts-traits": {"tier": "gold", "retention": "P30D"}
}),
json!({"tier": "gold", "retention": "P30D"}),
json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"tier": {"type": "string", "default": "standard"},
"retention": {"type": "string"}
}
}),
);
}
#[test]
fn test_resolved_type_single_level_default_materialized() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.ep.tr.base.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"id": {"type": "string"}},
"x-gts-traits-schema": {"type": "object", "properties": {
"retention": {"type": "string", "default": "P30D"}
}}
}),
)
.unwrap();
let rt = store.validate_schema("gts.x.ep.tr.base.v1~").unwrap();
assert_resolved_type(
&rt,
"gts.x.ep.tr.base.v1~",
false,
false,
json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"id": {"type": "string"}},
"x-gts-traits-schema": {"type": "object", "properties": {
"retention": {"type": "string", "default": "P30D"}
}}
}),
json!({"retention": "P30D"}),
json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"retention": {"type": "string", "default": "P30D"}}
}),
);
}
#[test]
fn test_resolved_type_abstract_full_artifacts() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.p3.tr.base.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-abstract": true,
"properties": {"id": {"type": "string"}},
"x-gts-traits-schema": {"type": "object", "properties": {
"topicRef": {"type": "string"},
"tier": {"type": "string", "default": "standard"}
}, "required": ["topicRef"]}
}),
)
.unwrap();
let rt = store.validate_schema("gts.x.p3.tr.base.v1~").unwrap();
assert_resolved_type(
&rt,
"gts.x.p3.tr.base.v1~",
true,
false,
json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-abstract": true,
"properties": {"id": {"type": "string"}},
"x-gts-traits-schema": {"type": "object", "properties": {
"topicRef": {"type": "string"},
"tier": {"type": "string", "default": "standard"}
}, "required": ["topicRef"]}
}),
json!({"tier": "standard"}),
json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"topicRef": {"type": "string"},
"tier": {"type": "string", "default": "standard"}
},
"required": ["topicRef"]
}),
);
}
#[test]
fn test_resolved_type_final_flag() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.fin.tr.base.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-final": true,
"properties": {"id": {"type": "string"}}
}),
)
.unwrap();
let rt = store.validate_schema("gts.x.fin.tr.base.v1~").unwrap();
assert_resolved_type(
&rt,
"gts.x.fin.tr.base.v1~",
false,
true,
json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-final": true,
"properties": {"id": {"type": "string"}}
}),
json!({}),
json!({"$schema": "http://json-schema.org/draft-07/schema#"}),
);
}
#[test]
fn test_resolved_type_false_traits_schema() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.t5.tr.base.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"id": {"type": "string"}},
"x-gts-traits-schema": false
}),
)
.unwrap();
let rt = store.validate_schema("gts.x.t5.tr.base.v1~").unwrap();
assert_resolved_type(
&rt,
"gts.x.t5.tr.base.v1~",
false,
false,
json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"id": {"type": "string"}},
"x-gts-traits-schema": false
}),
json!({}),
json!(false),
);
}
#[test]
fn test_resolved_type_true_traits_schema() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.t6.tr.base.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": true,
"x-gts-traits": {"anything": 42}
}),
)
.unwrap();
let rt = store.validate_schema("gts.x.t6.tr.base.v1~").unwrap();
assert_resolved_type(
&rt,
"gts.x.t6.tr.base.v1~",
false,
false,
json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": true,
"x-gts-traits": {"anything": 42}
}),
json!({"anything": 42}),
json!(true),
);
}
#[test]
fn test_validate_payload_ok_and_reject() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.vp.tr.base.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["id"],
"properties": {"id": {"type": "string"}}
}),
)
.unwrap();
assert!(
store
.validate_payload("gts.x.vp.tr.base.v1~", &json!({"id": "x"}))
.is_ok()
);
assert!(
store
.validate_payload("gts.x.vp.tr.base.v1~", &json!({}))
.is_err()
);
}
#[test]
fn test_validate_payload_rejects_abstract_type() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.vp.tr.abs.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-abstract": true,
"properties": {"id": {"type": "string"}}
}),
)
.unwrap();
let err = store
.validate_payload("gts.x.vp.tr.abs.v1~", &json!({"id": "x"}))
.unwrap_err();
assert!(format!("{err}").contains("abstract"));
}
#[test]
fn test_schema_traits_ok_and_type_error() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.vt.tr.good.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {"type": "object", "properties": {
"maxRetries": {"type": "integer", "minimum": 0, "default": 3}
}},
"x-gts-traits": {"maxRetries": 5}
}),
)
.unwrap();
assert!(store.validate_schema("gts.x.vt.tr.good.v1~").is_ok());
store
.register_schema(
"gts.x.vt.tr.bad.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {"type": "object", "properties": {
"maxRetries": {"type": "integer", "minimum": 0, "default": 3}
}},
"x-gts-traits": {"maxRetries": "x"}
}),
)
.unwrap();
assert!(store.validate_schema("gts.x.vt.tr.bad.v1~").is_err());
}
#[test]
fn test_schema_traits_prohibited_by_false_schema() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.vt.tr.no_good.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": false
}),
)
.unwrap();
assert!(store.validate_schema("gts.x.vt.tr.no_good.v1~").is_ok());
store
.register_schema(
"gts.x.vt.tr.no_bad.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": false,
"x-gts-traits": {"any": 1}
}),
)
.unwrap();
assert!(store.validate_schema("gts.x.vt.tr.no_bad.v1~").is_err());
}
#[test]
fn test_trait_schema_resolves_local_defs_ref() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.dr.tr.good.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"id": {"type": "string"}},
"$defs": {
"Retention": {"type": "string", "enum": ["P30D", "P365D"]}
},
"x-gts-traits-schema": {
"type": "object",
"properties": {"retention": {"$ref": "#/$defs/Retention"}}
},
"x-gts-traits": {"retention": "P30D"}
}),
)
.unwrap();
assert!(
store.validate_schema("gts.x.dr.tr.good.v1~").is_ok(),
"valid trait value must pass once the $defs ref resolves"
);
store
.register_schema(
"gts.x.dr.tr.bad.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"id": {"type": "string"}},
"$defs": {
"Retention": {"type": "string", "enum": ["P30D", "P365D"]}
},
"x-gts-traits-schema": {
"type": "object",
"properties": {"retention": {"$ref": "#/$defs/Retention"}}
},
"x-gts-traits": {"retention": "NOPE"}
}),
)
.unwrap();
assert!(
store.validate_schema("gts.x.dr.tr.bad.v1~").is_err(),
"trait value violating the $defs-referenced enum must be rejected"
);
}
#[test]
fn test_trait_schema_cross_doc_fragment_ref_does_not_break_validation() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.cd.tr.base.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {
"type": "object",
"properties": {"retention": {"type": "string", "enum": ["P30D", "P365D"]}}
}
}),
)
.unwrap();
store
.register_schema(
"gts.x.cd.tr.base.v1~x.cd._.derived.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {
"allOf": [
{"$ref": "gts://gts.x.cd.tr.base.v1~#/x-gts-traits-schema"},
{"type": "object", "properties": {"tier": {"type": "string"}}}
]
},
"x-gts-traits": {"retention": "P30D", "tier": "gold"}
}),
)
.unwrap();
let id = "gts.x.cd.tr.base.v1~x.cd._.derived.v1~";
assert!(
store.validate_schema(id).is_ok(),
"valid trait values must pass despite the redundant cross-doc fragment ref"
);
store
.register_schema(
"gts.x.cd.tr.base.v1~x.cd._.bad.v1~",
&json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits-schema": {
"allOf": [
{"$ref": "gts://gts.x.cd.tr.base.v1~#/x-gts-traits-schema"},
{"type": "object", "properties": {"tier": {"type": "string"}}}
]
},
"x-gts-traits": {"retention": "NOPE"}
}),
)
.unwrap();
assert!(
store
.validate_schema("gts.x.cd.tr.base.v1~x.cd._.bad.v1~")
.is_err(),
"ancestor enum constraint must still be enforced"
);
}
fn register_chain_schema(store: &mut GtsStore, id: &str, extra: Value) {
let mut doc = serde_json::Map::new();
doc.insert(
"$schema".to_owned(),
json!("http://json-schema.org/draft-07/schema#"),
);
doc.insert("type".to_owned(), json!("object"));
if let Value::Object(m) = extra {
for (k, v) in m {
doc.insert(k, v);
}
}
store
.register_schema(id, &Value::Object(doc))
.unwrap_or_else(|e| panic!("register {id}: {e:?}"));
}
#[test]
fn test_op13_chain3_schema_at_base_only_conforms() {
let mut store = GtsStore::new();
let base = "gts.x.c3a.tr.base.v1~";
let mid = "gts.x.c3a.tr.base.v1~x.c3a._.mid.v1~";
let leaf = "gts.x.c3a.tr.base.v1~x.c3a._.mid.v1~x.c3a._.leaf.v1~";
register_chain_schema(
&mut store,
base,
json!({"x-gts-traits-schema": {
"type": "object",
"properties": {"retention": {"type": "string"}, "tier": {"type": "string"}}
}}),
);
register_chain_schema(&mut store, mid, json!({}));
register_chain_schema(
&mut store,
leaf,
json!({"x-gts-traits": {"retention": "P30D", "tier": "gold"}}),
);
let traits = store.effective_traits(leaf).expect("effective traits");
assert_eq!(
traits.resolved_trait_schemas.len(),
1,
"only the base contributes a trait-schema across the 3-level chain"
);
assert!(
store.validate_schema(leaf).is_ok(),
"leaf values conform to the base-declared trait-schema"
);
}
#[test]
fn test_op13_chain3_schema_at_base_only_rejects_wrong_type() {
let mut store = GtsStore::new();
let base = "gts.x.c3b.tr.base.v1~";
let mid = "gts.x.c3b.tr.base.v1~x.c3b._.mid.v1~";
let leaf = "gts.x.c3b.tr.base.v1~x.c3b._.mid.v1~x.c3b._.leaf.v1~";
register_chain_schema(
&mut store,
base,
json!({"x-gts-traits-schema": {
"type": "object",
"properties": {"retention": {"type": "string"}}
}}),
);
register_chain_schema(&mut store, mid, json!({}));
register_chain_schema(
&mut store,
leaf,
json!({"x-gts-traits": {"retention": 123}}),
);
let err = store.validate_schema(leaf).unwrap_err();
assert!(
format!("{err}").contains("trait validation failed"),
"wrong-typed leaf trait value must be rejected: {err}"
);
}
#[test]
fn test_op13_chain3_schema_composed_across_two_levels() {
let mut store = GtsStore::new();
let base = "gts.x.c3c.tr.base.v1~";
let mid = "gts.x.c3c.tr.base.v1~x.c3c._.mid.v1~";
let leaf = "gts.x.c3c.tr.base.v1~x.c3c._.mid.v1~x.c3c._.leaf.v1~";
register_chain_schema(
&mut store,
base,
json!({"x-gts-traits-schema": {
"type": "object",
"properties": {"retention": {"type": "string"}},
"required": ["retention"]
}}),
);
register_chain_schema(
&mut store,
mid,
json!({"x-gts-traits-schema": {
"type": "object",
"properties": {"tier": {"type": "string", "enum": ["gold", "silver"]}},
"required": ["tier"]
}}),
);
register_chain_schema(
&mut store,
leaf,
json!({"x-gts-traits": {"retention": "P30D", "tier": "gold"}}),
);
let traits = store.effective_traits(leaf).expect("effective traits");
assert_eq!(
traits.resolved_trait_schemas.len(),
2,
"base and mid each contribute a trait-schema"
);
assert!(
store.validate_schema(leaf).is_ok(),
"leaf satisfies both base and mid trait constraints"
);
let bad = "gts.x.c3c.tr.base.v1~x.c3c._.mid.v1~x.c3c._.bad.v1~";
register_chain_schema(
&mut store,
bad,
json!({"x-gts-traits": {"retention": "P30D", "tier": "bronze"}}),
);
assert!(
store.validate_schema(bad).is_err(),
"value violating the mid-level enum must be rejected"
);
}
#[test]
fn test_op13_chain_traits_schema_true_accepts_any_values() {
let mut store = GtsStore::new();
let solo = "gts.x.c3t.tr.solo.v1~";
register_chain_schema(
&mut store,
solo,
json!({"x-gts-traits-schema": true, "x-gts-traits": {"anything": 42, "x": "y"}}),
);
let traits = store.effective_traits(solo).expect("effective traits");
assert_eq!(
traits.resolved_trait_schemas.len(),
1,
"`true` is a present trait-schema contribution"
);
assert!(
store.validate_schema(solo).is_ok(),
"`true` trait-schema accepts arbitrary trait values"
);
let base = "gts.x.c3t.tr.base.v1~";
let mid = "gts.x.c3t.tr.base.v1~x.c3t._.mid.v1~";
let leaf = "gts.x.c3t.tr.base.v1~x.c3t._.mid.v1~x.c3t._.leaf.v1~";
register_chain_schema(&mut store, base, json!({"x-gts-traits-schema": true}));
register_chain_schema(&mut store, mid, json!({}));
register_chain_schema(
&mut store,
leaf,
json!({"x-gts-traits": {"whatever": [1, 2, 3]}}),
);
assert!(
store.validate_schema(leaf).is_ok(),
"`true` declared at the base accepts any leaf values across the chain"
);
}
#[test]
fn test_op13_chain3_false_at_base_prohibits_descendant_values() {
let mut store = GtsStore::new();
let base = "gts.x.c3f.tr.base.v1~";
let mid = "gts.x.c3f.tr.base.v1~x.c3f._.mid.v1~";
let leaf_ok = "gts.x.c3f.tr.base.v1~x.c3f._.mid.v1~x.c3f._.ok.v1~";
let leaf_bad = "gts.x.c3f.tr.base.v1~x.c3f._.mid.v1~x.c3f._.bad.v1~";
register_chain_schema(&mut store, base, json!({"x-gts-traits-schema": false}));
register_chain_schema(&mut store, mid, json!({}));
register_chain_schema(&mut store, leaf_ok, json!({}));
register_chain_schema(&mut store, leaf_bad, json!({"x-gts-traits": {"x": 1}}));
assert!(
store.validate_schema(leaf_ok).is_ok(),
"`false` trait-schema with no values is allowed"
);
let err = store.validate_schema(leaf_bad).unwrap_err();
assert!(
format!("{err}").contains("prohibited"),
"`false` in the chain must prohibit descendant trait values: {err}"
);
}
#[test]
fn test_op13_chain3_false_at_intermediate_overrides_real_base_schema() {
let mut store = GtsStore::new();
let base = "gts.x.c3fi.tr.base.v1~";
let mid = "gts.x.c3fi.tr.base.v1~x.c3fi._.mid.v1~";
let leaf = "gts.x.c3fi.tr.base.v1~x.c3fi._.mid.v1~x.c3fi._.leaf.v1~";
register_chain_schema(
&mut store,
base,
json!({"x-gts-traits-schema": {
"type": "object",
"properties": {"retention": {"type": "string"}}
}}),
);
register_chain_schema(&mut store, mid, json!({"x-gts-traits-schema": false}));
register_chain_schema(
&mut store,
leaf,
json!({"x-gts-traits": {"retention": "P30D"}}),
);
let err = store.validate_schema(leaf).unwrap_err();
assert!(
format!("{err}").contains("prohibited"),
"`false` at the mid level prohibits values even though the base schema would accept them: {err}"
);
}
#[test]
fn test_op13_chain3_schema_only_at_intermediate() {
let mut store = GtsStore::new();
let base = "gts.x.c3m.tr.base.v1~";
let mid = "gts.x.c3m.tr.base.v1~x.c3m._.mid.v1~";
let leaf_ok = "gts.x.c3m.tr.base.v1~x.c3m._.mid.v1~x.c3m._.ok.v1~";
let leaf_bad = "gts.x.c3m.tr.base.v1~x.c3m._.mid.v1~x.c3m._.bad.v1~";
register_chain_schema(&mut store, base, json!({}));
register_chain_schema(
&mut store,
mid,
json!({"x-gts-traits-schema": {
"type": "object",
"properties": {"tier": {"type": "string", "enum": ["a", "b"]}}
}}),
);
register_chain_schema(&mut store, leaf_ok, json!({"x-gts-traits": {"tier": "a"}}));
register_chain_schema(&mut store, leaf_bad, json!({"x-gts-traits": {"tier": "z"}}));
assert!(
store.validate_schema(leaf_ok).is_ok(),
"value conforming to the mid-only trait-schema passes"
);
assert!(
store.validate_schema(leaf_bad).is_err(),
"value violating the mid-only enum is rejected"
);
}
#[test]
fn test_op13_chain3_no_schema_anywhere() {
let mut store = GtsStore::new();
let base = "gts.x.c3n.tr.base.v1~";
let mid = "gts.x.c3n.tr.base.v1~x.c3n._.mid.v1~";
let leaf_ok = "gts.x.c3n.tr.base.v1~x.c3n._.mid.v1~x.c3n._.ok.v1~";
let leaf_bad = "gts.x.c3n.tr.base.v1~x.c3n._.mid.v1~x.c3n._.bad.v1~";
register_chain_schema(&mut store, base, json!({}));
register_chain_schema(&mut store, mid, json!({}));
register_chain_schema(&mut store, leaf_ok, json!({}));
register_chain_schema(&mut store, leaf_bad, json!({"x-gts-traits": {"foo": 1}}));
assert!(
store.validate_schema(leaf_ok).is_ok(),
"no trait-schema and no values anywhere in the chain is valid"
);
let err = store.validate_schema(leaf_bad).unwrap_err();
assert!(
format!("{err}").contains("no x-gts-traits-schema"),
"trait values with no trait-schema in the chain must be rejected: {err}"
);
}
#[test]
fn test_op13_chain4_merge_defaults_consts_nulls_via_validate_schema() {
let mut store = GtsStore::new();
let base = "gts.x.c4.tr.base.v1~";
let l1 = "gts.x.c4.tr.base.v1~x.c4._.l1.v1~";
let l2 = "gts.x.c4.tr.base.v1~x.c4._.l1.v1~x.c4._.l2.v1~";
let leaf = "gts.x.c4.tr.base.v1~x.c4._.l1.v1~x.c4._.l2.v1~x.c4._.leaf.v1~";
register_chain_schema(
&mut store,
base,
json!({
"x-gts-traits-schema": {"type": "object", "properties": {
"retention": {"type": "string", "default": "P30D"},
"tier": {"type": "string"},
"region": {"type": "string", "default": "us"},
"locked": {"type": "string", "const": "X"},
"optional": {"type": "string", "default": "d"}
}},
"x-gts-traits": {"tier": "standard", "region": "eu"}
}),
);
register_chain_schema(&mut store, l1, json!({"x-gts-traits": {"tier": "premium"}}));
register_chain_schema(&mut store, l2, json!({"x-gts-traits": {"region": null}}));
register_chain_schema(
&mut store,
leaf,
json!({"x-gts-traits": {"retention": "P90D"}}),
);
let rt = store
.validate_schema(leaf)
.expect("4-level chain must validate");
assert_eq!(
rt.effective_traits,
json!({
"retention": "P90D",
"tier": "premium",
"region": "us",
"locked": "X",
"optional": "d"
}),
"merge across 4 levels must honor leaf-wins, null-delete->default, const, and default"
);
}
#[test]
fn test_op13_trait_schema_allof_ref_resolves_default_and_enforces() {
let mut store = GtsStore::new();
register_chain_schema(
&mut store,
"gts.x.rd.tr.retention.v1~",
json!({"properties": {
"retention": {"type": "string", "enum": ["P30D", "P365D"], "default": "P30D"}
}}),
);
register_chain_schema(
&mut store,
"gts.x.rd.tr.base.v1~",
json!({"x-gts-traits-schema": {
"type": "object",
"allOf": [{"$ref": "gts://gts.x.rd.tr.retention.v1~"}]
}}),
);
let expected_traits_schema = json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"allOf": [{
"type": "object",
"properties": {
"retention": {"type": "string", "enum": ["P30D", "P365D"], "default": "P30D"}
}
}]
});
let dflt = "gts.x.rd.tr.base.v1~x.rd._.dflt.v1~";
register_chain_schema(&mut store, dflt, json!({}));
let rt = store
.validate_schema(dflt)
.expect("default from $ref must resolve");
assert_resolved_type(
&rt,
dflt,
false,
false,
json!({"$schema": "http://json-schema.org/draft-07/schema#", "type": "object"}),
json!({"retention": "P30D"}),
expected_traits_schema.clone(),
);
let ok = "gts.x.rd.tr.base.v1~x.rd._.ok.v1~";
register_chain_schema(
&mut store,
ok,
json!({"x-gts-traits": {"retention": "P365D"}}),
);
let rt = store
.validate_schema(ok)
.expect("value within the $ref'd enum must pass");
assert_resolved_type(
&rt,
ok,
false,
false,
json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"x-gts-traits": {"retention": "P365D"}
}),
json!({"retention": "P365D"}),
expected_traits_schema,
);
let bad = "gts.x.rd.tr.base.v1~x.rd._.bad.v1~";
register_chain_schema(
&mut store,
bad,
json!({"x-gts-traits": {"retention": "NOPE"}}),
);
assert!(
store.validate_schema(bad).is_err(),
"value violating the $ref'd enum must be rejected"
);
}
#[test]
fn test_op13_abstract_rejects_wrong_typed_trait_value() {
let mut store = GtsStore::new();
let id = "gts.x.abst.tr.wt.v1~";
register_chain_schema(
&mut store,
id,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {"maxRetries": {"type": "integer"}}
},
"x-gts-traits": {"maxRetries": "not_a_number"}
}),
);
let err = store.validate_schema(id).unwrap_err();
assert!(
format!("{err}").contains("trait validation failed"),
"abstract type must still type-check provided trait values: {err}"
);
}
#[test]
fn test_op13_abstract_rejects_incompatible_trait_schema_without_values() {
let mut store = GtsStore::new();
let base = "gts.x.abst.tr.incomp.v1~";
let child = "gts.x.abst.tr.incomp.v1~x.abst._.child.v1~";
register_chain_schema(
&mut store,
base,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {"retention": {"type": "string"}}
}
}),
);
register_chain_schema(
&mut store,
child,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {"retention": {"type": "integer"}}
}
}),
);
let err = store.validate_schema(child).unwrap_err();
assert!(
format!("{err}").contains("x-gts-traits-schema") && format!("{err}").contains("retention"),
"abstract child must reject incompatible trait schema without values: {err}"
);
}
#[test]
fn test_op13_abstract_closed_trait_schema_blocks_new_schema_property_without_values() {
let mut store = GtsStore::new();
let base = "gts.x.abst.tr.closed.v1~";
let child = "gts.x.abst.tr.closed.v1~x.abst._.child.v1~";
register_chain_schema(
&mut store,
base,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"additionalProperties": false,
"properties": {"retention": {"type": "string"}}
}
}),
);
register_chain_schema(
&mut store,
child,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {"topicRef": {"type": "string"}}
}
}),
);
let err = store.validate_schema(child).unwrap_err();
assert!(
format!("{err}").contains("topicRef") || format!("{err}").contains("additionalProperties"),
"closed ancestor trait schema must block new descendant property without values: {err}"
);
}
#[test]
fn test_op13_abstract_descendant_closed_trait_schema_orphans_ancestor_property() {
let mut store = GtsStore::new();
let base = "gts.x.abst.tr.orphan.v1~";
let child = "gts.x.abst.tr.orphan.v1~x.abst._.child.v1~";
register_chain_schema(
&mut store,
base,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {"retention": {"type": "string"}}
}
}),
);
register_chain_schema(
&mut store,
child,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"additionalProperties": false,
"properties": {"topicRef": {"type": "string"}}
}
}),
);
let err = store.validate_schema(child).unwrap_err();
assert!(
format!("{err}").contains("retention") && format!("{err}").contains("additionalProperties"),
"closed descendant trait schema must not orphan an ancestor trait: {err}"
);
}
#[test]
fn test_op13_abstract_descendant_closed_trait_schema_restating_ancestor_ok() {
let mut store = GtsStore::new();
let base = "gts.x.abst.tr.restate.v1~";
let child = "gts.x.abst.tr.restate.v1~x.abst._.child.v1~";
register_chain_schema(
&mut store,
base,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {"retention": {"type": "string"}}
}
}),
);
register_chain_schema(
&mut store,
child,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"retention": {"type": "string"},
"topicRef": {"type": "string"}
}
}
}),
);
assert!(
store.validate_schema(child).is_ok(),
"restating the ancestor trait under a closed descendant must pass"
);
}
#[test]
fn test_op13_abstract_descendant_nested_closed_trait_schema_orphans_ancestor_property() {
let mut store = GtsStore::new();
let base = "gts.x.abst.tr.nested_orphan.v1~";
let child = "gts.x.abst.tr.nested_orphan.v1~x.abst._.child.v1~";
register_chain_schema(
&mut store,
base,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {
"routing": {
"type": "object",
"properties": {
"source": {"type": "string"}
}
}
}
}
}),
);
register_chain_schema(
&mut store,
child,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {
"routing": {
"type": "object",
"additionalProperties": false,
"properties": {
"target": {"type": "string"}
}
}
}
}
}),
);
let err = store.validate_schema(child).unwrap_err();
assert!(
format!("{err}").contains("routing.source")
&& format!("{err}").contains("additionalProperties"),
"closed nested descendant trait schema must not orphan an ancestor trait: {err}"
);
}
#[test]
fn test_op13_abstract_descendant_nested_closed_trait_schema_restating_ancestor_ok() {
let mut store = GtsStore::new();
let base = "gts.x.abst.tr.nested_restate.v1~";
let child = "gts.x.abst.tr.nested_restate.v1~x.abst._.child.v1~";
register_chain_schema(
&mut store,
base,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {
"routing": {
"type": "object",
"properties": {
"source": {"type": "string"}
}
}
}
}
}),
);
register_chain_schema(
&mut store,
child,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {
"routing": {
"type": "object",
"additionalProperties": false,
"properties": {
"source": {"type": "string"},
"target": {"type": "string"}
}
}
}
}
}),
);
assert!(
store.validate_schema(child).is_ok(),
"restating the nested ancestor trait under a closed descendant must pass"
);
}
#[test]
fn test_op13_abstract_descendant_valid_narrowing_trait_schema_ok() {
let mut store = GtsStore::new();
let base = "gts.x.abst.tr.narrow.v1~";
let child = "gts.x.abst.tr.narrow.v1~x.abst._.child.v1~";
register_chain_schema(
&mut store,
base,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {"priority": {"type": "string"}}
}
}),
);
register_chain_schema(
&mut store,
child,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {
"priority": {"type": "string", "enum": ["low", "high"]},
"note": {"type": "string"}
}
}
}),
);
assert!(
store.validate_schema(child).is_ok(),
"valid abstract narrowing must pass without values"
);
}
#[test]
fn test_op13_abstract_skips_required_completeness() {
let mut store = GtsStore::new();
let id = "gts.x.abst.tr.req.v1~";
register_chain_schema(
&mut store,
id,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {"topicRef": {"type": "string"}},
"required": ["topicRef"]
}
}),
);
assert!(
store.validate_schema(id).is_ok(),
"abstract type may leave a required trait unresolved for descendants"
);
}
#[test]
fn test_op13_abstract_base_required_enforced_at_concrete_leaf() {
let mut store = GtsStore::new();
let base = "gts.x.abst.tr.base.v1~";
register_chain_schema(
&mut store,
base,
json!({
"x-gts-abstract": true,
"x-gts-traits-schema": {
"type": "object",
"properties": {"topicRef": {"type": "string"}},
"required": ["topicRef"]
}
}),
);
assert!(
store.validate_schema(base).is_ok(),
"abstract base with an unresolved required trait is valid"
);
let good = "gts.x.abst.tr.base.v1~x.abst._.good.v1~";
register_chain_schema(
&mut store,
good,
json!({"x-gts-traits": {"topicRef": "orders"}}),
);
assert!(
store.validate_schema(good).is_ok(),
"concrete leaf that resolves the required trait passes"
);
let bad = "gts.x.abst.tr.base.v1~x.abst._.bad.v1~";
register_chain_schema(&mut store, bad, json!({}));
assert!(
store.validate_schema(bad).is_err(),
"concrete leaf that leaves the required trait unresolved is rejected"
);
}
#[test]
fn test_validate_schema_accepts_gts_ref_with_pointer_fragment() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.vendor.package.namespace.base.v1.0~",
&json!({
"$id": "gts://gts.vendor.package.namespace.base.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"name": {"type": "string"}}
}),
)
.expect("register base");
store
.register_schema(
"gts.vendor.package.namespace.type.v1.0~",
&json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"inner": {
"$ref": "gts://gts.vendor.package.namespace.base.v1.0~#/properties/name"
}
}
}),
)
.expect("register type");
store
.validate_schema_refs("gts.vendor.package.namespace.type.v1.0~")
.expect("fragment $ref must validate");
}
#[test]
fn test_validate_schema_rejects_gts_ref_with_non_pointer_fragment() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.vendor.package.namespace.type.v1.0~",
&json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"inner": {
"$ref": "gts://gts.vendor.package.namespace.base.v1.0~#anchor"
}
}
}),
)
.expect("register type");
assert!(matches!(
store.validate_schema_refs("gts.vendor.package.namespace.type.v1.0~"),
Err(StoreError::InvalidRef(_))
));
}
#[test]
fn test_validate_and_resolve_meta_validates_resolved_schema() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.vendor.package.namespace.dep.v1.0~",
&json!({
"$id": "gts://gts.vendor.package.namespace.dep.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"d": {"type": "string"}}
}),
)
.expect("register dep");
let malformed = json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"ref": {"$ref": "gts://gts.vendor.package.namespace.dep.v1.0~"},
"bad": {"type": 123}
}
});
store
.register_schema("gts.vendor.package.namespace.type.v1.0~", &malformed)
.expect("register type");
store
.validate_schema_refs("gts.vendor.package.namespace.type.v1.0~")
.expect("validate_schema_refs checks ref structure only");
assert!(matches!(
store.validate_schema("gts.vendor.package.namespace.type.v1.0~"),
Err(StoreError::ValidationError(_))
));
}
#[test]
fn test_validate_and_resolve_accepts_well_formed_gts_ref_schema() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.vendor.package.namespace.dep.v1.0~",
&json!({
"$id": "gts://gts.vendor.package.namespace.dep.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"d": {"type": "string"}}
}),
)
.expect("register dep");
store
.register_schema(
"gts.vendor.package.namespace.type.v1.0~",
&json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"ref": {"$ref": "gts://gts.vendor.package.namespace.dep.v1.0~"}
}
}),
)
.expect("register type");
store
.validate_schema("gts.vendor.package.namespace.type.v1.0~")
.expect("well-formed schema must validate and resolve");
}
#[test]
fn test_resolve_schema_refs_wrapper_smoke() {
let store = GtsStore::new();
let err = store
.resolve_schema_refs(&json!({"$ref": "gts://gts.x.core.events.missing.v1~"}))
.expect_err("unresolved external ref must fail checked resolution");
assert!(matches!(
&err,
StoreError::UnresolvedRefs(refs)
if refs == &["gts://gts.x.core.events.missing.v1~".to_owned()]
));
}
#[test]
fn test_resolve_schema_refs_uses_exact_gts_uri_lookup_without_minor_fallback() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.x.core.events.type.v1.0~",
&json!({
"$id": "gts://gts.x.core.events.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"minor": {"const": "v1.0"}}
}),
)
.expect("register target schema");
let schema = json!({"$ref": "gts://gts.x.core.events.type.v1~"});
let err = store
.resolve_schema_refs(&schema)
.expect_err("checked resolution should reject the unresolved v1~ ref");
assert!(matches!(
&err,
StoreError::UnresolvedRefs(refs)
if refs == &["gts://gts.x.core.events.type.v1~".to_owned()]
));
}
#[test]
fn test_validate_instance_resolves_sibling_ref_in_allof() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.vendor.package.namespace.base.v1.0~",
&json!({
"$id": "gts://gts.vendor.package.namespace.base.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {"id": {"type": "string"}}
}),
)
.expect("register base");
store
.register_schema(
"gts.vendor.package.namespace.type.v1.0~",
&json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"allOf": [
{
"$ref": "gts://gts.vendor.package.namespace.base.v1.0~",
"properties": {"name": {"type": "string"}}
}
]
}),
)
.expect("register type");
let cfg = GtsConfig::default();
let entity = GtsEntity::new(
None,
None,
&json!({"id": "gts.vendor.package.namespace.type.v1.0", "name": "test"}),
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("register instance");
assert!(
store
.validate_instance("gts.vendor.package.namespace.type.v1.0")
.is_ok(),
"resolvable sibling $ref should validate"
);
}
#[test]
fn test_validate_instance_reports_unresolvable_ref() {
let mut store = GtsStore::new();
store
.register_schema(
"gts.vendor.package.namespace.type.v1.0~",
&json!({
"$id": "gts://gts.vendor.package.namespace.type.v1.0~",
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"data": {
"$ref": "gts://gts.vendor.package.namespace.nonexistent.v1.0~",
"type": "object"
}
}
}),
)
.expect("register type");
let cfg = GtsConfig::default();
let entity = GtsEntity::new(
None,
None,
&json!({"id": "gts.vendor.package.namespace.type.v1.0", "data": {}}),
Some(&cfg),
None,
false,
String::new(),
None,
Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
);
store.register(entity).expect("register instance");
let err = store
.validate_instance("gts.vendor.package.namespace.type.v1.0")
.expect_err("unresolvable ref must fail validation");
assert!(
err.to_string()
.contains("Unresolved $ref(s): gts://gts.vendor.package.namespace.nonexistent.v1.0~")
);
}