Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,37 @@ jobs:
# host.docker.internal:$PORT. See Makefile.
- name: Run gts-spec tests via docker
run: make gts-spec-tests

dylint:
name: Dylint + Prefix Tests (nightly, Ubuntu)
runs-on: ubuntu-latest
env:
RUSTUP_TOOLCHAIN: nightly-2026-04-16
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false

- name: Install pinned nightly toolchain
uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
toolchain: ${{ env.RUSTUP_TOOLCHAIN }}
components: llvm-tools-preview,rustc-dev

- name: Cache Cargo/target
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
with:
shared-key: dylint

- name: Install cargo-dylint and dylint-link from source
run: cargo install --locked cargo-dylint dylint-link

- name: Run dylint
run: cargo +${{ env.RUSTUP_TOOLCHAIN }} dylint --all

- name: Run dylint UI tests
run: cargo +${{ env.RUSTUP_TOOLCHAIN }} test --manifest-path gts-dylint/Cargo.toml

- name: Run prefix-aware tests
run: make test-gts-id-prefix
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ jobs:
check_version "gts-cli/Cargo.toml"
check_version "gts-macros-cli/Cargo.toml"
check_version "gts-validator/Cargo.toml"
check_version "gts-dylint/Cargo.toml"

# Verify workspace.dependencies versions for all known internal crates.
# Uses explicit key list to avoid false negatives from pattern drift.
Expand Down
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Guidance for AI Agents when working in this repository.

## Project Overview

`gts-rust` is the Rust reference implementation of [GTS](https://github.com/GlobalTypeSystem/gts-spec) — library (`gts/`), CLI and HTTP server (`gts-cli/`, binary name `gts`), plus supporting crates (`gts-id`, `gts-macros`, `gts-macros-cli`, `gts-validator`). The server answers the REST API exercised by the shared gts-spec conformance suite.
`gts-rust` is the Rust reference implementation of [GTS](https://github.com/GlobalTypeSystem/gts-spec) — library (`gts/`), CLI and HTTP server (`gts-cli/`, binary name `gts`), plus supporting crates (`gts-id`, `gts-macros`, `gts-macros-cli`, `gts-validator`, `gts-dylint`). The server answers the REST API exercised by the shared gts-spec conformance suite.

The conformance suite is shipped as a Docker image — `ghcr.io/globaltypesystem/gts-spec-tests` — and the spec version this implementation targets is pinned in `.gts-spec-version` (the file's contents are used verbatim as the image tag, format `vMAJOR.MINOR.PATCH`). The pin is immutable on purpose: every commit reproduces the same test run, and rolling forward requires a deliberate bump.

Expand Down Expand Up @@ -51,4 +51,5 @@ The server holds state in memory with no reset endpoint — restart it between f

- `.gts-spec-version` is the canonical pin (`vMAJOR.MINOR.PATCH`). Bump it (commit + push) to roll the spec forward — both CI and `make gts-spec-tests` pick it up. Local cache survives across runs; `docker rmi $(GTS_SPEC_REF)` if you ever need to force a refetch.
- Handlers in `gts-cli/src/server.rs` stay thin — logic goes in `gts/` where it is unit-testable. New REST behavior usually already has coverage in the gts-spec suite; run the relevant file (`make gts-spec-tests TEST=...`) before and after to confirm.
- `make check` is the full local gate: fmt + clippy + test + gts-spec-tests.
- `make check` is the full local gate: fmt + clippy + test + test-gts-id-prefix + dylint + gts-spec-tests.
- `gts-dylint` is a Dylint lint (requires nightly) that flags hard-coded `"gts."` string literals in production code. Run it with `make dylint`. Use `#[allow(unknown_lints, gts_id_hardcoded_prefix)]` to suppress in specific locations. Prefixes can be customized via `GTS_DYLINT_PREFIXES` env var. Tests for the lint itself live in `gts-dylint/ui/` and run with `cargo +nightly test` inside the crate.
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ members = [
"gts-macros-cli",
"gts-validator",
]
exclude = ["gts-dylint"]
resolver = "2"

[workspace.metadata.dylint]
libraries = [
{ path = "gts-dylint" },
]

[workspace.lints.rust]
deprecated = "deny"
non_ascii_idents = "forbid"
Expand Down
22 changes: 20 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ CI := 1
# Default target - show help
.DEFAULT_GOAL := help

.PHONY: help build dev-fmt dev-clippy all check fmt clippy test deny security generate-schemas coverage
.PHONY: help build dev-fmt dev-clippy all check fmt clippy test test-gts-id-prefix dylint dylint-tests deny security generate-schemas coverage

# Show this help message
help:
Expand Down Expand Up @@ -41,6 +41,24 @@ clippy:
test:
cargo test --workspace

# Re-run gts-id unit tests with a non-default GTS_ID_PREFIX to catch
# hard-coded "gts." literals that should use the GTS_ID_PREFIX constant.
# The prefix is read at compile time (option_env!), so this is a clean
# rebuild + test cycle. Currently scoped to gts-id (whose tests are
# prefix-aware); expand to more crates as their test data is cleaned up.
test-gts-id-prefix:
GTS_ID_PREFIX=acme. cargo test -p gts-id
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# Run dylint lints (requires nightly toolchain + cargo-dylint)
# Detects hard-coded "gts." / "gts://" string literals in production code
dylint:
@command -v cargo-dylint >/dev/null || (echo "Installing cargo-dylint..." && cargo install cargo-dylint)
cargo +nightly-2026-04-16 dylint --all

# Run dylint UI/example tests (requires nightly toolchain)
dylint-tests:
cargo +nightly-2026-04-16 test --manifest-path gts-dylint/Cargo.toml

# Check licenses and dependencies
deny:
@command -v cargo-deny >/dev/null || (echo "Installing cargo-deny..." && cargo install cargo-deny)
Expand All @@ -56,7 +74,7 @@ coverage:
cargo llvm-cov report

# Run all quality checks
check: fmt clippy test gts-spec-tests
check: fmt clippy test test-gts-id-prefix dylint dylint-tests gts-spec-tests


# ==============================================================================
Expand Down
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ Command-line tool and HTTP server:
- **server.rs** - Axum-based HTTP server
- **main.rs** - Entry point

### `gts-dylint` (Dylint Library Crate)

A [Dylint](https://github.com/trailofbits/dylint) lint that flags hard-coded GTS ID prefixes in production code, encouraging use of the configurable `GTS_ID_PREFIX` constant or the `gts_id!` macro instead. Prefixes can be customized via `GTS_DYLINT_PREFIXES`, the active `GTS_ID_PREFIX` is included automatically, and trusted wrapper macros can be registered with `GTS_DYLINT_ALLOWED_MACROS`. Requires nightly Rust. See [`gts-dylint/README.md`](gts-dylint/README.md) for details.

## Installation

### From Source
Expand Down Expand Up @@ -930,7 +934,9 @@ GTS identifiers follow this format:
gts.<vendor>.<package>.<namespace>.<type>.v<MAJOR>[.<MINOR>][~]
```

- **Prefix**: Always starts with `gts.`
- **Prefix**: Always starts with `gts.` (configurable at compile time via the
`GTS_ID_PREFIX` environment variable — see
[gts-id/README.md](gts-id/README.md#configurable-identifier-prefix))
- **Vendor**: Organization or vendor code
- **Package**: Module or application name
- **Namespace**: Category within the package
Expand Down Expand Up @@ -1031,6 +1037,24 @@ cargo fmt
cargo clippy
```

### Dylint (custom lints)

Run the `gts-dylint` lint to detect hard-coded GTS prefixes (requires nightly):

```bash
make dylint
```

See [`gts-dylint/README.md`](gts-dylint/README.md) for setup and usage details.

### Prefix-aware tests

Re-run `gts-id` tests with a non-default `GTS_ID_PREFIX` to catch hard-coded prefixes:

```bash
make test-gts-id-prefix
```

## License

Apache-2.0
Expand Down
10 changes: 5 additions & 5 deletions gts-cli/src/gen_schemas.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::{Result, bail};
use gts::{GtsInstanceId, GtsTypeId};
use gts::{GTS_ID_URI_PREFIX, GtsInstanceId, GtsTypeId};
use regex::Regex;
use std::collections::HashMap;
use std::fs;
Expand Down Expand Up @@ -415,7 +415,7 @@ fn build_json_schema(
BaseAttr::IsBase => {
// Base type - simple flat schema
let mut s = json!({
"$id": format!("gts://{type_id}"),
"$id": format!("{GTS_ID_URI_PREFIX}{type_id}"),
"$schema": gts::JSON_SCHEMA_DRAFT_07,
"title": struct_name,
"type": "object",
Expand Down Expand Up @@ -447,12 +447,12 @@ fn build_json_schema(
}

let mut s = json!({
"$id": format!("gts://{type_id}"),
"$id": format!("{GTS_ID_URI_PREFIX}{type_id}"),
"$schema": gts::JSON_SCHEMA_DRAFT_07,
"title": format!("{struct_name} (extends {parent_name})"),
"type": "object",
"allOf": [
{ "$ref": format!("gts://{parent_type_id}") },
{ "$ref": format!("{GTS_ID_URI_PREFIX}{parent_type_id}") },
own_properties
]
});
Expand Down Expand Up @@ -682,7 +682,7 @@ mod tests {
assert!(req);
assert_eq!(schema["type"], "string");
assert_eq!(schema["format"], "gts-instance-id");
assert_eq!(schema["x-gts-ref"], "gts.*");
assert_eq!(schema["x-gts-ref"], format!("{}*", gts::GTS_ID_PREFIX));

// Generic type parameter
let (req, schema) = rust_type_to_json_schema("P");
Expand Down
11 changes: 11 additions & 0 deletions gts-dylint/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[target.aarch64-apple-darwin]
linker = "dylint-link"

[target.x86_64-apple-darwin]
linker = "dylint-link"

[target.x86_64-unknown-linux-gnu]
linker = "dylint-link"

[target.aarch64-unknown-linux-gnu]
linker = "dylint-link"
Loading
Loading