Skip to content

GTS macro proposal#78

Open
asmith987 wants to merge 4 commits intoGlobalTypeSystem:mainfrom
asmith987:gts-macro-proposal
Open

GTS macro proposal#78
asmith987 wants to merge 4 commits intoGlobalTypeSystem:mainfrom
asmith987:gts-macro-proposal

Conversation

@asmith987
Copy link
Copy Markdown

@asmith987 asmith987 commented Mar 11, 2026

This PR contains a proposal to evolve #[struct_to_gts_schema] into a derive-based #[derive(GtsSchema)]` macro. The goal is to bring the macro into closer alignment with the GTS spec v0.8.0, particularly around identity field requirements.

The full proposal is at docs/001-macro-proposal.md. Some key changes:

  • Identity fields become optional - the specs defines schemas without GTS identity fields, but the current macro requires them. This change would solve struct_to_gts_schema: gts_type field blocks Deserialize for structs #72 .
  • x-gts-ref: "/$id vs gts.* - the spec distinguishes self-reference from cross-reference. The current macro generates gts.* for all GtsSchemaId fields. The new macro would use field annotations such as #[gts(type_field)] or #[gts(instance_id)] to enable this functionality
  • User control serde - removes the silent derive injection/removals. Nested serde blocking is still the default.
  • properties are auto-derived from struct fields - removes the manual properties = "field1,field2" parameter
  • extends = Parent replaces base = True / base = Parent - Clearer naming and if extends is omitted, the struct is considered root by default
  • Explicit over implicit - All derives are visibile in the struct definition, no hidden trait injections.

All schema output would be structurally identical.

The working implementation is at https://github.com/asmith987/gts-rust/tree/gts-macro-implementation

Relevant docs in the PR:

  • docs/001-macro-proposal.md - Proposal

Summary by CodeRabbit

  • Documentation
    • Added architecture decision record (ADR-001) outlining macro system redesign with new derive-based approach and attribute syntax
    • Added comprehensive implementation plan with phased rollout strategy for macro system modernization
    • Added detailed proposal documentation including migration guidance, validation rules, and code examples
    • Updated submodule reference

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

This PR introduces three architectural documentation files outlining a comprehensive redesign of the gts-rust macro system, transitioning from a monolithic #[struct_to_gts_schema] macro to a modular #[derive(GtsSchema)] approach with field-level attributes. A submodule reference is also updated.

Changes

Cohort / File(s) Summary
Submodule Reference
.gts-spec
Updated submodule commit pointer from e088287 to 48f15e0; metadata-only change with no behavioral impact.
Macro Redesign Documentation
docs/001-macro-proposal.md, docs/001-macro-alignment-adr.md, docs/001-macro-alignment-implementation-plan.md
Three interconnected docs detailing a major macro redesign: shifting to #[derive(GtsSchema)] with #[gts(...)] attributes, replacing properties parameter with auto-derivation, replacing base with extends for inheritance, introducing field-level identity markers (type_field, instance_id), and refining serde handling. Includes phased 6-phase implementation plan with test matrices and migration guidance.

Possibly related PRs

  • chore: release 0.8.0 #62: Updates the same .gts-spec submodule pointer, indicating parallel coordination on spec alignment.

Poem

🐰 A macro reborn, no longer alone—
From monolith's weight to attributes full-grown!
With derives so clear, inheritance true,
The GTS spec alignment hops into view.
Six phases ahead, like burrows so deep,
Composable, modular—promises to keep! 🌿

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'GTS macro proposal' is vague and generic—it doesn't convey the key change (evolving from attribute macro to derive macro) or align with GTS spec. Consider a more specific title like 'Propose derive macro redesign for GTS schema alignment' or 'ADR-001: Align gts-rust macro with GTS spec v0.8.0'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
docs/001-macro-proposal.md (2)

126-174: Clear before/after migration example with helpful summary table.

The side-by-side comparison makes the transition immediately understandable. The table at lines 167-174 provides excellent quick-reference guidance for each change.

One minor observation: The "After" example (line 149) explicitly includes JsonSchema in the derives, but according to the ADR (lines 235-236 in the ADR file), JsonSchema is auto-added if not present. Consider adding a note clarifying that JsonSchema can be omitted and will be added automatically.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/001-macro-proposal.md` around lines 126 - 174, Add a brief clarifying
sentence to the "After" example explaining that JsonSchema in the derive list is
optional because the macro will auto-add JsonSchema when omitted per the ADR;
reference the example struct BaseEventV1 and the derive attributes (Serialize,
Deserialize, JsonSchema, GtsSchema) and mention the ADR lines about auto-adding
JsonSchema so readers know they can omit JsonSchema and rely on the macro to
inject it.

380-418: Well-planned modular architecture for extensibility.

The proposed module structure (lines 380-390) is a significant improvement over the current ~1,843-line single file. The two concrete examples (lines 392-417) demonstrate how the architecture accommodates future spec features:

  1. Adding a new field-level attribute requires changes to only 4 focused modules
  2. Schema traits (§9.7) can be added via the same parse-validate-generate pipeline

However, the code blocks at lines 400 and 418 are missing language specifiers. While these are pseudo-structure examples rather than actual code, adding language hints would improve rendering.

Minor: Add language specifiers to code blocks
-```
+```text
 gts-macros/src/
   lib.rs                  Entry points (current + updated macro)
   gts_schema_derive.rs    #[derive(GtsSchema)] orchestration

and

-```
+```json
 "x-gts-traits-schema": {
   "properties": {
     "topicRef": { "x-gts-ref": "gts.x.core.events.topic.v1~" },

As per coding guidelines, this addresses the markdownlint warnings for missing language specifiers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/001-macro-proposal.md` around lines 380 - 418, Add missing Markdown
language specifiers to the two fenced code blocks showing the directory layout
and the JSON trait example: change the first fence to use "text" (for the
gts-macros/src/ listing) and the second fence to use "json" (for the
"x-gts-traits-schema"/"x-gts-traits" example). Locate the blocks that contain
the "gts-macros/src/" directory listing and the JSON snippet starting with
"x-gts-traits-schema" and update their opening triple-backtick fences to ```text
and ```json respectively so markdownlint warnings are resolved and syntax
rendering improves.
docs/001-macro-alignment-implementation-plan.md (1)

60-82: Comprehensive compile-fail test coverage.

The 15 compile-fail tests cover all error cases for the new macro. Each test has a clear validation purpose and spec reference. However, the code block at line 78 is missing a language specifier.

Minor: Add language specifier
-| `unknown_gts_attr` | `#[gts(nonexistent)]` | Fail-fast on typos |
+| `unknown_gts_attr` | `#[gts(nonexistent)]` (markdown table, no code block) | Fail-fast on typos |

Actually, reviewing the context, this appears to be within a markdown table, not a code block. The static analysis warning at line 78 may be a false positive or referring to a different issue. No change needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/001-macro-alignment-implementation-plan.md` around lines 60 - 82, The
reviewer flagged a missing code-block language specifier around the table entry
near `nested_direct_serialize_cfg_attr`; inspect the markdown row containing
`nested_direct_serialize` / `nested_direct_serialize_cfg_attr` to confirm
whether it is truly a fenced code block or just table text; if it's just table
content, leave it unchanged and mark the static-analysis warning as a false
positive (or add a comment in the PR), but if there is an accidental fenced code
block, add the appropriate language specifier (e.g., ```rust) to that fence to
silence the warning.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/001-macro-alignment-implementation-plan.md`:
- Around line 60-82: The reviewer flagged a missing code-block language
specifier around the table entry near `nested_direct_serialize_cfg_attr`;
inspect the markdown row containing `nested_direct_serialize` /
`nested_direct_serialize_cfg_attr` to confirm whether it is truly a fenced code
block or just table text; if it's just table content, leave it unchanged and
mark the static-analysis warning as a false positive (or add a comment in the
PR), but if there is an accidental fenced code block, add the appropriate
language specifier (e.g., ```rust) to that fence to silence the warning.

In `@docs/001-macro-proposal.md`:
- Around line 126-174: Add a brief clarifying sentence to the "After" example
explaining that JsonSchema in the derive list is optional because the macro will
auto-add JsonSchema when omitted per the ADR; reference the example struct
BaseEventV1 and the derive attributes (Serialize, Deserialize, JsonSchema,
GtsSchema) and mention the ADR lines about auto-adding JsonSchema so readers
know they can omit JsonSchema and rely on the macro to inject it.
- Around line 380-418: Add missing Markdown language specifiers to the two
fenced code blocks showing the directory layout and the JSON trait example:
change the first fence to use "text" (for the gts-macros/src/ listing) and the
second fence to use "json" (for the "x-gts-traits-schema"/"x-gts-traits"
example). Locate the blocks that contain the "gts-macros/src/" directory listing
and the JSON snippet starting with "x-gts-traits-schema" and update their
opening triple-backtick fences to ```text and ```json respectively so
markdownlint warnings are resolved and syntax rendering improves.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f4d4cf41-1e27-4748-96ae-3af4dd6c9af5

📥 Commits

Reviewing files that changed from the base of the PR and between 3c3a5db and 5376ad5.

📒 Files selected for processing (4)
  • .gts-spec
  • docs/001-macro-alignment-adr.md
  • docs/001-macro-alignment-implementation-plan.md
  • docs/001-macro-proposal.md


Categories 4 and 5 require GTS identity fields. But the spec examples include schemas that produce instances with **no GTS identity field at all**:

- `gts.x.commerce.orders.order.v1.0~` — the Order schema has `id: uuid` (a plain business identifier, not a `GtsInstanceId`) and no `type` field
Copy link
Copy Markdown
Contributor

@Artifizer Artifizer Apr 15, 2026

Choose a reason for hiding this comment

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

Well, the intention was that all the GTS instances have GTS type encoded into the object field, either as well-known ID, or type field. So I'd say it's rather a bug in the the gts.x.commerce.orders.order.v1.0~ example... Because otherwise, if a want to change my REST API to become, say, RPC (or MCP) API and my service has 2 flavors of orders with 2 schemas, how would I know what schema to be used?

I'd enforce GTS type to be always "deducible" from the objects.

Categories 4 and 5 require GTS identity fields. But the spec examples include schemas that produce instances with **no GTS identity field at all**:

- `gts.x.commerce.orders.order.v1.0~` — the Order schema has `id: uuid` (a plain business identifier, not a `GtsInstanceId`) and no `type` field
- `gts.x.core.idp.contact.v1.0~` — the Contact schema has `id: uuid` and no `type` field
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The same problem here

};
```

**Conclusion**: The `type` field is a runtime value set by the user — it depends on which concrete child type the instance represents. It is not something the macro can or should auto-populate from the struct's own `SCHEMA_ID`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not sure that conclusion reflects original intention. The intention was to generate the type automatically when we create an instance, or at least to minimize error surface not to allow humans to create an instance and put random/irrelevant type there. For example using macros, dylint, and such

| `orders.order.v1.0~` | UUID | (none) | Plain data entity |
| `idp.contact.v1.0~` | UUID | (none) | Plain data entity |

**Conclusion**: There is no single "correct" identity pattern. The choice is domain-specific and implementation-defined ([spec §11.1](https://github.com/GlobalTypeSystem/gts-spec#111-global-rules-schema-vs-instance-normalization-and-document-categories)). The macro should support these patterns, not mandate one.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Correct. The id can be:

  • uuid or whatever other string
    or
  • GTS ID (for well-known instances)

1. **Validates the schema ID format** against GTS identifier rules
2. **Validates version consistency** between the struct name suffix (e.g., `V1`) and the schema ID version (e.g., `v1~`)
3. **Validates parent-child inheritance** — segment count, parent schema ID match
4. **Validates the `properties` list** — every listed property must exist as a struct field
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The properties list was added intentionally to be able to serialize a subset of the fields only, I don't mind if we can use per-field annotation instead

- Implement the `GtsSchema` trait (`SCHEMA_ID`, `GENERIC_FIELD`, schema composition methods)
- Generate `gts_schema_id()`, `gts_base_schema_id()`, `gts_make_instance_id()`
- Generate `gts_schema_with_refs_as_string()` and similar convenience methods
- Store `dir_path` and `description` as associated constants for CLI schema generation
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I believe we also need this:

  1. validate id/type fields — base structs must have either an id: GtsInstanceId or a type field (type/gts_type/etc.) of type GtsSchemaId, but not both

- Excludes the field from the generated JSON Schema properties
- Does not affect serde behavior (use `#[serde(skip)]` for that)

These attributes are **all optional**. A struct without any `#[gts(type_field)]` or `#[gts(instance_id)]` annotation is valid — it represents a data entity like `order.v1.0~` or `contact.v1.0~` from the spec.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

let's make at least one mandatory


```rust
#[derive(Debug, Clone, GtsSchema)]
#[gts(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is going to happen with the 'dir_path' ?

}
```

**Migration**: The `properties` parameter is removed. Fields previously omitted from `properties` should add `#[gts(skip)]`. The `dir_path` parameter is retained as it controls file output location for the CLI.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I can't see it in examples above. Any changes here?

#[gts(
schema_id = "gts.x.core.events.type.v1~x.core.audit.event.v1~",
description = "...",
extends = BaseEventV1,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The base = true was used to quickly find all the base types in IDE, PR, repo and such
Can we support extends = None or so?


### 4.4 Let users control serde

The current macro silently adds `Serialize`/`Deserialize` derives for base structs and silently removes them for nested structs. This is surprising behavior that fights against Rust conventions.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's not surprising, it's a key thing.

  1. We do not want to allow to serialize only nested type, otherwise humans will make mistakes.
    We want to serialize only audit event schema as whole, so it means we need to serialize the base type with respect of all the nested objects serializer...

  2. We do not want to have unserializable GTS entities, because we need to register them all in Type Registry

#[gts(
schema_id = "...",
extends = BaseEventV1,
allow_direct_serde, // opt-out: allow Serialize/Deserialize
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would prohibit it because people will make mistakes, we never (?) need nested objects as separate json


## 11. Open Questions

1. **CLI impact**: The CLI currently scans source files for `#[struct_to_gts_schema]` annotations to extract metadata (schema ID, properties, description). The new `#[derive(GtsSchema)]` + `#[gts(...)]` pattern requires updating the CLI parser. The removal of `properties` means the CLI must parse struct fields directly (respecting `#[gts(skip)]` and `#[serde(skip)]` attributes). This is deferred for a separate design discussion.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, the CLI must be updated respectively

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