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
1 change: 1 addition & 0 deletions AGENTS.md
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CLAUDE.md
# AI Agent Guidance

Guidance for Claude Code when working in this repository.
Guidance for AI Agents when working in this repository.

## Project Overview

Expand Down
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,12 @@ verbose_file_reads = "deny"
module_name_repetitions = "allow"

[workspace.dependencies]
gts = { version = "0.10.1", path = "gts" }
gts-cli = { version = "0.10.1", path = "gts-cli" }
gts-id = { version = "0.10.1", path = "gts-id" }
gts-macros = { version = "0.10.1", path = "gts-macros" }
gts-macros-cli = { version = "0.10.1", path = "gts-macros-cli" }
gts-validator = { version = "0.10.1", path = "gts-validator" }
gts = { version = "0.11.0", path = "gts" }
gts-cli = { version = "0.11.0", path = "gts-cli" }
gts-id = { version = "0.11.0", path = "gts-id" }
gts-macros = { version = "0.11.0", path = "gts-macros" }
gts-macros-cli = { version = "0.11.0", path = "gts-macros-cli" }
gts-validator = { version = "0.11.0", path = "gts-validator" }

# Core dependencies
serde = { version = "1.0", features = ["derive"] }
Expand Down
38 changes: 25 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,25 @@ Technical Backlog:

## Architecture

The project is organized as a Cargo workspace with two crates:
The project is organized as a Cargo workspace. The three primary crates are:

### `gts-id` (Library Crate)

Standalone GTS identifier crate (no dependency on `gts`) — GTS ID parsing,
validation, and wildcard matching:

- **gts_id.rs** - `GtsId` parsing and validation
- **gts_id_segment.rs** - parsed identifier segments
- **gts_id_pattern.rs** - `GtsIdPattern` pattern matching
- **parse.rs** - the shared identifier parser

See [`gts-id/README.md`](gts-id/README.md) for usage examples.

### `gts` (Library Crate)

Core library providing all GTS functionality:
Core library providing all GTS functionality (re-exports the `gts-id` types):

- **gts.rs** - GTS ID parsing, validation, wildcard matching
- **gts.rs** - typed `GtsTypeId` / `GtsInstanceId` schema-aware wrappers and `gts-id` re-exports
- **entities.rs** - JSON entities, configuration, validation
- **path_resolver.rs** - JSON path resolution
- **schema_cast.rs** - Schema compatibility and casting
Expand Down Expand Up @@ -489,7 +501,7 @@ All operations are available through the `GtsOps` API.
#### Setup

```rust
use gts::{GtsID, GtsOps, GtsConfig, GtsWildcard};
use gts::{GtsId, GtsOps, GtsConfig, GtsIdPattern};
use serde_json::json;

// Initialize GTS operations with data paths
Expand All @@ -513,7 +525,7 @@ assert!(!result.valid);
assert!(!result.error.is_empty());

// Direct validation without ops
let is_valid = GtsID::is_valid("gts.x.core.events.event.v1~");
let is_valid = GtsId::is_valid("gts.x.core.events.event.v1~");
assert!(is_valid);
```

Expand Down Expand Up @@ -565,8 +577,8 @@ let result = ops.parse_id("gts.x.core.events.event.v1~vendor.app._.custom.v2~");
assert_eq!(result.segments.len(), 2);

// Direct parsing
let id = GtsID::new("gts.x.core.events.event.v1~")?;
assert_eq!(id.gts_id_segments.len(), 1);
let id = GtsId::try_new("gts.x.core.events.event.v1~")?;
assert_eq!(id.segments().len(), 1);
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

#### OP#4 - ID Pattern Matching
Expand All @@ -587,9 +599,9 @@ let result = ops.match_id_pattern(
assert!(!result.is_match);

// Direct wildcard matching
let pattern = GtsWildcard::new("gts.x.*.events.*")?;
let id = GtsID::new("gts.x.core.events.event.v1~")?;
assert!(pattern.matches(&id));
let pattern = GtsIdPattern::try_new("gts.x.core.events.*")?;
let id = GtsId::try_new("gts.x.core.events.event.v1~")?;
assert!(id.matches_pattern(&pattern));
```

#### OP#5 - ID to UUID Mapping
Expand All @@ -603,13 +615,13 @@ assert!(!result.uuid.is_empty());
let result = ops.uuid("gts.x.core.events.event.v1.0", "minor");

// Direct UUID generation
let id = GtsID::new("gts.x.core.events.event.v1~")?;
let id = GtsId::try_new("gts.x.core.events.event.v1~")?;
let uuid = id.to_uuid();
println!("UUID: {}", uuid);

// Same major version produces same UUID
let id1 = GtsID::new("gts.x.core.events.event.v1.0")?;
let id2 = GtsID::new("gts.x.core.events.event.v1.5")?;
let id1 = GtsId::try_new("gts.x.core.events.event.v1.0")?;
let id2 = GtsId::try_new("gts.x.core.events.event.v1.5")?;
assert_eq!(id1.to_uuid(), id2.to_uuid());
```

Expand Down
2 changes: 1 addition & 1 deletion gts-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gts-cli"
version = "0.10.1"
version = "0.11.0"
edition.workspace = true
authors.workspace = true
license.workspace = true
Expand Down
9 changes: 8 additions & 1 deletion gts-id/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "gts-id"
version = "0.10.1"
version = "0.11.0"
edition.workspace = true
authors.workspace = true
license.workspace = true
Expand All @@ -15,5 +15,12 @@ publish = true
[lints]
workspace = true

[features]
# Opt-in deterministic UUID v5 derivation (`GtsID::to_uuid`). Off by default so
# parsing-only consumers — notably the `gts-macros` proc-macro crate — don't pull
# `uuid` into their build graph or couple their semver to it.
uuid = ["dep:uuid"]

[dependencies]
thiserror.workspace = true
uuid = { workspace = true, optional = true }
160 changes: 160 additions & 0 deletions gts-id/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# gts-id

Validation and parsing primitives for [GTS](https://github.com/GlobalTypeSystem/gts-spec) (Global Type System) identifiers.

This crate is the single source of truth for GTS identifier parsing in [`gts-rust`](https://github.com/GlobalTypeSystem/gts-rust): it is shared by the `gts` runtime library and the `gts-macros` proc-macro crate. It has no runtime dependencies beyond `thiserror` (and optionally `uuid`).

## Identifier shape

A GTS identifier is a `~`-chained sequence of segments under the `gts.` prefix:

```text
gts.<vendor>.<package>.<namespace>.<type>.v<MAJOR>[.<MINOR>]
```

* A **type** identifier ends with a `~` marker: `gts.x.core.events.topic.v1~`
* An **instance** identifier does not: `gts.x.core.events.topic.v1~acme.shop.orders.order.v1.0`
* A **combined anonymous instance** ends with a UUID tail: `gts.x.core.events.topic.v1~7a1d2f34-5678-49ab-9012-abcdef123456`

## Types

| Type | Purpose |
|------|---------|
| `GtsId` | A validated, concrete identifier. |
| `GtsIdPattern` | A match pattern — an identifier that may end in a single trailing `*`, or be fully concrete. |
| `GtsIdSegment` | One concrete segment of a `GtsId` (`Concrete` or `UuidTail`). |
| `GtsIdPatternSegment` | One segment of a pattern (`Segment` or `Wildcard`). |
| `GtsUuidTail` | An anonymous-instance UUID tail (guaranteed well-formed). |
| `GtsIdError` | The error returned by all parsing entry points. |

Every value is produced only by the validating constructors, so a parsed value is always well-formed and its invariants cannot be forged.

## Examples

The snippets below use `?` for brevity; assume they run inside a function that returns `Result<_, gts_id::GtsIdError>`.

### Validate and parse a concrete identifier

```rust
use gts_id::GtsId;

// Fallible constructor.
let id = GtsId::try_new("gts.x.core.events.topic.v1~")?;
assert_eq!(id.id(), "gts.x.core.events.topic.v1~");
assert!(id.is_type()); // ends with '~'

// `FromStr` is also available.
let id: GtsId = "gts.x.core.events.topic.v1~".parse()?;

// Cheap validity check that doesn't keep the parsed value.
assert!(GtsId::is_valid("gts.x.core.events.topic.v1~"));
assert!(!GtsId::is_valid("gts.x.Core.events.topic.v1~")); // uppercase rejected
```

### Inspect segments

```rust
use gts_id::GtsId;

let id = GtsId::try_new("gts.x.core.events.topic.v1.2~")?;
let seg = &id.segments()[0];
assert_eq!(seg.vendor(), "x");
assert_eq!(seg.package(), "core");
assert_eq!(seg.namespace(), "events");
assert_eq!(seg.type_name(), "topic");
assert_eq!(seg.ver_major(), 1);
assert_eq!(seg.ver_minor(), Some(2));
assert!(seg.is_type());
```

### Type vs. instance, and the parent type of a chain

```rust
use gts_id::GtsId;

let instance = GtsId::try_new("gts.x.core.events.topic.v1~acme.shop.orders.order.v1.0")?;
assert!(!instance.is_type());

// The parent type id (every segment but the last).
assert_eq!(
instance.get_type_id().as_deref(),
Some("gts.x.core.events.topic.v1~"),
);
```

### Wildcard patterns and matching

A `GtsIdPattern` may contain a single trailing `*` (e.g. `gts.x.core.*` or `gts.x.core.events.topic.v1~*`), or be fully concrete. Concrete identifiers are validated with `GtsId`, which rejects wildcards.

```rust
use gts_id::{GtsId, GtsIdPattern};

let pattern = GtsIdPattern::try_new("gts.x.core.*")?;

let id = GtsId::try_new("gts.x.core.events.topic.v1~")?;
assert!(id.matches_pattern(&pattern));

let other = GtsId::try_new("gts.y.core.events.topic.v1~")?;
assert!(!other.matches_pattern(&pattern));

// A concrete `GtsId` never accepts a wildcard:
assert!(GtsId::try_new("gts.x.core.*").is_err());
```

### Pattern coverage

`covers` answers whether one pattern is broader than another — every identifier matched by `other` is also matched by `self`:

```rust
use gts_id::GtsIdPattern;

let broad = GtsIdPattern::try_new("gts.x.core.events.topic.v1~*")?;
let narrow = GtsIdPattern::try_new("gts.x.core.events.topic.v1~acme.*")?;
assert!(broad.covers(&narrow));
assert!(!narrow.covers(&broad));
```

### Anonymous instances (UUID tail)

```rust
use gts_id::{GtsId, GtsIdSegment};

let id = GtsId::try_new("gts.x.core.events.topic.v1~7a1d2f34-5678-49ab-9012-abcdef123456")?;

// The last segment is a UUID tail; match on it to read the UUID.
if let Some(GtsIdSegment::UuidTail(tail)) = id.segments().last() {
assert_eq!(tail.as_str(), "7a1d2f34-5678-49ab-9012-abcdef123456");
}
```

### Error handling

```rust
use gts_id::GtsId;

let err = GtsId::try_new("gts.x.core.events.topic").unwrap_err();
// `GtsIdError` implements `Display` with a human-readable cause.
println!("{err}");
```

## Feature flags

* **`uuid`** — enables `GtsId::to_uuid()`, a deterministic UUID v5 derivation (the same identifier always maps to the same UUID). Off by default so parsing-only consumers don't pull in the `uuid` crate.

```toml
[dependencies]
gts-id = { version = "0.11", features = ["uuid"] }
```

```rust
// with the `uuid` feature enabled:
use gts_id::GtsId;

let id = GtsId::try_new("gts.x.core.events.topic.v1~")?;
let uuid = id.to_uuid();
assert_eq!(id.to_uuid(), uuid); // deterministic
```

## License

Apache-2.0
Loading
Loading