Skip to content

feat(gts): improve API with single-pass validation and reusable trait helpers#102

Closed
aviator5 wants to merge 4 commits into
GlobalTypeSystem:mainfrom
aviator5:traits-merge-support
Closed

feat(gts): improve API with single-pass validation and reusable trait helpers#102
aviator5 wants to merge 4 commits into
GlobalTypeSystem:mainfrom
aviator5:traits-merge-support

Conversation

@aviator5

@aviator5 aviator5 commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

This PR refactors GTS trait validation and schema resolution: it adds a new schema_refs.rs module for extracting external GTS type references, consolidates trait materialization behind an EffectiveTraits struct (const+default precedence, JSON Pointer inlining), reworks the GtsStore constructor API (new() + with_reader()), adds single-pass validation methods plus CircularRef/UnresolvedRefs error variants, and introduces check_entity_traits() (OP#13) entity-level validation. It also tightens x-gts-ref wildcard handling via GtsIdPattern and derives entity type IDs purely from the $id chain.

Summary by CodeRabbit

Summary

  • New Features

    • Added public support to extract external schema $ref dependencies.
    • Exposed resolved type schema details (including effective traits) via the store API.
  • Bug Fixes

    • Improved strict (“checked”) $ref resolution: now errors on circular and unresolved external gts:// references.
    • Tightened entity and trait validation rules, including const/default materialization precedence and allOf additionalProperties closedness merging.
  • Tests

    • Updated and expanded coverage for schema ID handling, $ref extraction/resolution, and trait/schema validation edge cases.

- Derive Type Schema parent IDs from chained  values instead of  fallbacks.
- Validate x-gts-ref literals and matches through GtsIdPattern for malformed wildcard rejection and segment-aware version matching.

Signed-off-by: Aviator 5 <ai.agent.tor@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors GtsStore construction to separate new() (empty) from with_reader(reader), introduces typed StoreError variants for cycle and unresolved-ref detection in checked $ref resolution, adds a new schema_refs module with extract_gts_refs, rewrites schema trait validation around a new EffectiveTraits struct and materialize_traits (replacing apply_defaults), migrates x-gts-ref matching to GtsIdPattern, reworks entity $id extraction, introduces ResolvedTypeSchema, and adds OP#13 entity-level traits completeness enforcement in GtsOps.

Changes

GTS Store Refactor and Traits Enforcement

Layer / File(s) Summary
GtsStore construction API: new() / with_reader()
gts/src/store.rs, gts/src/ops.rs, gts/src/schema_modifiers.rs, gts-macros/tests/inheritance_tests.rs, gts-macros/tests/integration_tests.rs
Splits GtsStore::new(Option<reader>) into new() (empty store) and with_reader(reader) (reader-backed store); all call sites in production code and tests are migrated accordingly.
Typed $ref resolution: StoreError variants, checked mode, and strict validation
gts/src/store.rs
Introduces StoreError::CircularRef and UnresolvedRefs(Vec<String>), makes resolve_schema_refs_checked public with typed Result<Value, StoreError>, extends resolve_schema_refs_inner to track unresolved refs through recursion and DFS cycle detection, tightens validate_schema_refs fragment handling, and makes validate_instance fail on unresolved external refs.
New schema_refs module: extract_gts_refs
gts/src/schema_refs.rs, gts/src/lib.rs
Adds public extract_gts_refs function that recursively scans JSON schemas for external $ref GTS type ids, normalizes (gts:// prefix stripping, fragment removal), validates via GtsTypeId::try_new, deduplicates into BTreeSet, and fails fast on invalid refs or depth limits; includes comprehensive unit tests.
schema_compat: closedness-preserving additionalProperties merging
gts/src/schema_compat.rs
Introduces merge_additional_properties_constraint helper that preserves closed (false) over open (true) constraints when schemas compose via allOf; updates extract_effective_schema and validate_schema_compatibility to use lattice-based merging instead of overwriting.
EffectiveTraits struct, materialize_traits, and inline_local_pointers
gts/src/schema_traits.rs
Introduces EffectiveTraits crate-visible struct with validate() method; adds build_effective_traits / build_effective_traits_schema; replaces apply_defaults with materialize_traits (constdefault precedence, recursive); adds inline_local_pointers; updates required-trait completeness to accept const or default; adds XGtsRefValidator pass in validate_trait_values.
x_gts_ref: GtsIdPattern-based pattern matching
gts/src/x_gts_ref.rs
Migrates x-gts-ref validation to GtsIdPattern for both schema-level pattern validation and instance value matching; introduces validate_value_matches_gts_pattern; reworks validate_ref_pattern to accept resolved wildcard patterns; updates all related tests.
Entity $id extraction and instance_id derivation
gts/src/entities.rs
Reworks extract_type_ids to derive gts_id, type_id, and instance_id exclusively from JSON Schema $id (gts:// URI), removing prior $schema-based type_id fallback; standalone single-segment schemas now have type_id=None.
ResolvedTypeSchema output type and store trait integration
gts/src/store.rs
Introduces public ResolvedTypeSchema struct carrying resolved schema, effective traits, effective traits schema, and abstract flag; refactors trait validation with content_is_abstract helper and effective_traits(...) flow; updates validate_and_resolve_type_schema to meta-validate inlined schemas with trait completeness gating only for non-abstract types.
OP#13 entity-level traits enforcement: check_entity_traits
gts/src/ops.rs
Adds private check_entity_traits helper: exempts abstract types, errors when trait schemas exist without merged trait values, and requires additionalProperties: false on resolved trait schemas; wires into validate_entity, replacing prior store.validate_entity_traits call; adds four unit tests.
Test migration and comprehensive coverage
gts/src/store_test.rs, gts-macros/tests/integration_tests.rs, gts-id/src/gts_id.rs
Migrates all construction sites to GtsStore::new() / with_reader(); strengthens unresolvable $ref assertions to specific error messages; adds extensive OP#12 chain and OP#13 trait-resolution test blocks including StoreError variant assertions, circular-ref detection, and effective-traits tests; removes test_gts_id_does_not_accept_uri_prefix; extends gts_id.rs rejection tests.

Sequence Diagram(s)

sequenceDiagram
  participant GtsOps
  participant GtsStore
  participant resolve_schema_refs_checked
  participant EffectiveTraits
  participant check_entity_traits

  GtsOps->>GtsStore: with_reader(GtsFileReader)
  GtsOps->>GtsOps: validate_entity(gts_id)
  GtsOps->>GtsStore: validate_schema_refs, validate_schema_traits
  GtsStore->>resolve_schema_refs_checked: schema
  resolve_schema_refs_checked-->>GtsStore: Ok(inlined) or Err(CircularRef / UnresolvedRefs)
  GtsStore->>EffectiveTraits: build_effective_traits(schemas, merged, dialect)
  EffectiveTraits-->>GtsStore: validate(check_unresolved)
  GtsOps->>check_entity_traits: gts_id
  check_entity_traits->>GtsStore: effective_traits, content_is_abstract
  check_entity_traits-->>GtsOps: Ok or Err(traits violation)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • GlobalTypeSystem/gts-rust#90: Directly overlaps on resolve_schema_refs_checked DFS cycle detection and required-only trait resolution semantics that this PR extends into the typed StoreError / EffectiveTraits pipeline.
  • GlobalTypeSystem/gts-rust#57: Implements and extends the same OP#13 x-gts-traits-schema / x-gts-traits validation behavior that this PR further refactors into EffectiveTraits and check_entity_traits.
  • GlobalTypeSystem/gts-rust#87: Overlaps at schema validation logic in gts/src/schema_modifiers.rs by expanding instance-modifier validation to reject schema-only x-gts-traits constraints in instance documents.

Suggested reviewers

  • Artifizer

🐇 Hoppity-hop through schemas and refs,
new() needs no None—cleaner, no frets!
Cycles caught, unresolved tracked with care,
EffectiveTraits bounds the trait-value snare.
const beats default, the rabbit declares! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main objectives of the PR: API improvements through constructor refactoring (single-pass validation) and trait helper consolidation (reusable trait helpers).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter

codecov-commenter commented Jun 18, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 98.32736% with 28 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
gts/src/store.rs 93.64% 19 Missing ⚠️
gts/src/schema_traits.rs 98.91% 3 Missing ⚠️
gts/src/schema_compat.rs 92.85% 2 Missing ⚠️
gts/src/store_test.rs 99.71% 2 Missing ⚠️
gts/src/ops.rs 99.26% 1 Missing ⚠️
gts/src/x_gts_ref.rs 98.85% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

… helpers

- Split GtsStore::new(Option<reader>) into new() and with_reader() for clarity
- Export ResolvedTypeSchema and ResolveSchemaRefsError for composable validation
- Refactor trait validation into single-pass: validate_and_resolve_type_schema,
  validate_payload, validate_traits. Each builds effective traits schema once.
- Extract inline_local_pointers to resolve JSON Pointer refs against host doc
- Split validate_effective_traits into validate_prebuilt_effective (reusable
  after schema is pre-built) and effective_traits_schema_and_values (builder)
- Add extract_gts_refs for discovering schema dependencies before resolution
- resolve_schema_refs_checked now returns ResolveSchemaRefsError with CircularRef
  and UnresolvedRefs variants; preserve unresolved refs in lenient output
- Add Default impl for GtsStore
- Update all tests to use GtsStore::new() instead of GtsStore::new(None)

Signed-off-by: Aviator 5 <ai.agent.tor@gmail.com>
@aviator5 aviator5 changed the title Traits merge support feat(gts): improve API with single-pass validation and reusable trait helpers Jun 18, 2026
@aviator5 aviator5 force-pushed the traits-merge-support branch from e758186 to 09a04bd Compare June 18, 2026 17:59
@aviator5 aviator5 marked this pull request as ready for review June 18, 2026 21:14
- Accept gts:// $ref values with supported JSON Pointer fragments while rejecting unsupported fragments.
- Compile fully resolved schemas in validate_and_resolve_type_schema to catch malformed bodies deferred by raw GTS refs.
- Validate relative x-gts-ref targets with GtsIdPattern so wildcard patterns resolve consistently.

Signed-off-by: Aviator 5 <ai.agent.tor@gmail.com>
@aviator5 aviator5 force-pushed the traits-merge-support branch 2 times, most recently from 94e95eb to 18bc635 Compare June 19, 2026 08:42
- Surface checked resolution failures through StoreError for unresolved and circular refs.
- Preserve allOf closedness from referenced schemas and keep non-object ref targets intact.
- Reject over-deep ref scans and add regression coverage for chained IDs, URI validation, local pointer overlay, and referenced trait schemas.

Signed-off-by: Aviator 5 <ai.agent.tor@gmail.com>
@aviator5 aviator5 force-pushed the traits-merge-support branch from 18bc635 to 8afe001 Compare June 19, 2026 14:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
gts/src/store.rs (1)

527-614: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unresolved allOf items are silently dropped when other items resolve.

When allOf contains a mix of resolvable and unresolvable items, the resolved_all_of vector accumulates the unresolved items (line 549) but is never included in the returned merged_schema (lines 582-614). This silently drops unresolved $refs from the lenient resolution output, contradicting the documented behavior that "unresolved refs [are] left intact."

Example: {"allOf": [{"$ref": "gts://missing~"}, {"properties": {"a": {}}}]} would flatten to {"properties": {"a": {}}}, dropping the unresolved ref entirely.

For resolve_schema_refs_checked, this is masked because unresolved_refs tracking correctly raises an error. But for the lenient resolve_schema_refs, schemas are silently weakened.

Proposed fix: include unresolved items in the returned schema
                     // If we have merged properties, create a single schema instead of allOf
                     if !merged_properties.is_empty() {
                         let mut merged_schema = serde_json::Map::new();
 
                         // Copy all properties except allOf
                         for (k, v) in map {
                             if k != "allOf" {
                                 merged_schema.insert(k.clone(), v.clone());
                             }
                         }
 
                         // Add merged properties and required fields
                         merged_schema
                             .insert("properties".to_owned(), Value::Object(merged_properties));
                         if let Some(ap) = merged_schema.get("additionalProperties").cloned() {
                             merge_additional_properties_constraint(
                                 &mut merged_additional_properties,
                                 &ap,
                             );
                         }
                         if let Some(ap) = merged_additional_properties {
                             merged_schema.insert("additionalProperties".to_owned(), ap);
                         }
                         if !merged_required.is_empty() {
                             merged_schema.insert(
                                 "required".to_owned(),
                                 Value::Array(
                                     merged_required.into_iter().map(Value::String).collect(),
                                 ),
                             );
                         }
+
+                        // Preserve unresolved allOf items to avoid silently weakening the schema
+                        if !resolved_all_of.is_empty() {
+                            merged_schema.insert("allOf".to_owned(), Value::Array(resolved_all_of));
+                        }
 
                         return Value::Object(merged_schema);
                     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gts/src/store.rs` around lines 527 - 614, When merging allOf array items in
the resolve_schema_refs_inner function, the unresolved items that still contain
$ref are correctly accumulated in the resolved_all_of vector, but they are never
included in the returned merged_schema. After creating the merged_schema and
populating it with merged properties, required fields, and additionalProperties
constraints, add logic to include the resolved_all_of items back into the
merged_schema by inserting them as an allOf field if the vector is not empty.
This ensures that unresolved refs are preserved in the output instead of being
silently dropped from the schema.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@gts/src/store.rs`:
- Around line 527-614: When merging allOf array items in the
resolve_schema_refs_inner function, the unresolved items that still contain $ref
are correctly accumulated in the resolved_all_of vector, but they are never
included in the returned merged_schema. After creating the merged_schema and
populating it with merged properties, required fields, and additionalProperties
constraints, add logic to include the resolved_all_of items back into the
merged_schema by inserting them as an allOf field if the vector is not empty.
This ensures that unresolved refs are preserved in the output instead of being
silently dropped from the schema.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b92e65dc-e29d-4431-a736-04fee66f321b

📥 Commits

Reviewing files that changed from the base of the PR and between 18bc635 and 8afe001.

📒 Files selected for processing (9)
  • gts-id/src/gts_id.rs
  • gts/src/entities.rs
  • gts/src/lib.rs
  • gts/src/ops.rs
  • gts/src/schema_compat.rs
  • gts/src/schema_refs.rs
  • gts/src/schema_traits.rs
  • gts/src/store.rs
  • gts/src/store_test.rs
🚧 Files skipped from review as they are similar to previous changes (7)
  • gts/src/lib.rs
  • gts/src/schema_compat.rs
  • gts/src/entities.rs
  • gts/src/schema_refs.rs
  • gts/src/ops.rs
  • gts/src/schema_traits.rs
  • gts/src/store_test.rs

@aviator5 aviator5 closed this Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants