Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ jobs:
submodules: recursive

- name: Install Rust toolchain (with llvm-tools)
uses: dtolnay/rust-toolchain@master
uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly
toolchain: stable
components: llvm-tools-preview

- name: Cache Cargo/target
Expand Down Expand Up @@ -196,12 +196,12 @@ jobs:
# Generate lcov.info for upload.
- name: Coverage (lcov)
env:
RUSTFLAGS: "-C debuginfo=0 --cfg coverage_nightly"
RUSTFLAGS: "-C debuginfo=0"
CARGO_LLVM_COV_TARGET_DIR: llvm-cov-target
CARGO_LLVM_COV_BUILD_DIR: llvm-cov-build
run: |
cargo llvm-cov clean --workspace
cargo +nightly llvm-cov --workspace --lcov --output-path lcov.info
cargo llvm-cov --workspace --lcov --output-path lcov.info

# Upload to Codecov; do not fail CI if Codecov is down/misconfigured.
- name: Upload to Codecov
Expand Down
60 changes: 60 additions & 0 deletions Cargo.lock

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

7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["gts", "gts-cli", "gts-macros"]
members = ["gts", "gts-cli", "gts-macros", "gts-macros-cli"]
resolver = "2"

[workspace.lints.rust]
Expand Down Expand Up @@ -142,7 +142,7 @@ serde_json = "1.0"
thiserror = "1.0"
anyhow = "1.0"
regex = "1.10"
uuid = { version = "1.10", features = ["v5"] }
uuid = { version = "1.10", features = ["serde", "v4", "v5"] }

# CLI dependencies
clap = { version = "4.5", features = ["derive"] }
Expand All @@ -160,6 +160,9 @@ chrono = "0.4"
# JSON Schema validation
jsonschema = "0.18"

# JSON Schema generation
schemars = { version = "0.8", features = ["uuid1"] }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

As I remember only schemars = "1.1" supports json schema Draft 2020-12


# File system
walkdir = "2.5"

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Other GTS spec [Reference Implementation](https://github.com/globaltypesystem/gt

Rust-specific features:
- [x] Generate GTS schemas from Rust source code, see [gts-macros/README.md](gts-macros/README.md) and [gts-macros-test/README.md](gts-macros-test/README.md)
- [x] Schema inheritance and composition for nested generic types with automatic `allOf` generation
- [ ] Automatically refer to GTS schemas for referenced objects

Technical Backlog:
Expand Down
27 changes: 9 additions & 18 deletions gts-cli/src/gen_schemas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ fn extract_and_generate_schemas(
) -> Result<Vec<(String, String)>> {
// Regex to find struct_to_gts_schema annotations
let re = Regex::new(
r#"(?s)#\[struct_to_gts_schema\(\s*file_path\s*=\s*"([^"]+)"\s*,\s*schema_id\s*=\s*"([^"]+)"\s*,\s*description\s*=\s*"([^"]+)"\s*,\s*properties\s*=\s*"([^"]+)"\s*\)\]\s*(?:pub\s+)?struct\s+(\w+)\s*\{([^}]+)\}"#,
r#"(?s)#\[struct_to_gts_schema\(\s*dir_path\s*=\s*\"([^\"]+)\"\s*,\s*schema_id\s*=\s*\"([^\"]+)\"\s*,\s*description\s*=\s*\"([^\"]+)\"\s*,\s*properties\s*=\s*\"([^\"]+)\"\s*\)\]\s*(?:pub\s+)?struct\s+(\w+)\s*\{([^}]+)\}"#,
)?;

// Pre-compile field regex outside the loop
Expand All @@ -207,34 +207,25 @@ fn extract_and_generate_schemas(
let mut results = Vec::new();

for cap in re.captures_iter(content) {
let file_path = &cap[1];
let dir_path = &cap[1];
let schema_id = &cap[2];
let description = &cap[3];
let properties_str = &cap[4];
let struct_name = &cap[5];
let struct_body = &cap[6];

// Validate file_path ends with .json
if !std::path::Path::new(file_path)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("json"))
{
bail!(
"Invalid file_path in {}:{} - file_path must end with '.json': {}",
source_file.display(),
struct_name,
file_path
);
}
// Schema file name is always derived from schema_id
// e.g. {dir_path}/{schema_id}.schema.json
let schema_file_rel = format!("{dir_path}/{schema_id}.schema.json");

// Determine output path
let output_path = if let Some(output_dir) = output_override {
// Use CLI-provided output directory
Path::new(output_dir).join(file_path)
Path::new(output_dir).join(&schema_file_rel)
} else {
// Use path from macro (relative to source file's directory)
let source_dir = source_file.parent().unwrap_or(source_root);
source_dir.join(file_path)
source_dir.join(&schema_file_rel)
};

// Security check: ensure output path doesn't escape source repository
Expand All @@ -251,11 +242,11 @@ fn extract_and_generate_schemas(
// Check if output path is within source repository
if !output_canonical.starts_with(source_root) {
bail!(
"Security error in {}:{} - file_path '{}' attempts to write outside source repository. \
"Security error in {}:{} - dir_path '{}' attempts to write outside source repository. \
Resolved to: {}, but must be within: {}",
source_file.display(),
struct_name,
file_path,
dir_path,
output_canonical.display(),
source_root.display()
);
Expand Down
34 changes: 34 additions & 0 deletions gts-macros-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[package]
name = "gts-macros-cli"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
description = "Demo tool for GTS macros schema introspection and inheritance"

[lints]
workspace = true

[[bin]]
name = "gts-macros-cli"
path = "src/main.rs"

[dependencies]
gts = { path = "../gts" }
gts-macros = { path = "../gts-macros" }
serde.workspace = true
serde_json.workspace = true
anyhow.workspace = true
uuid.workspace = true
schemars.workspace = true
clap.workspace = true

# Include test modules for schema discovery
[dev-dependencies]
serde = { workspace = true, features = ["derive"] }

# Build dependencies
[build-dependencies]
serde.workspace = true
serde_json.workspace = true
Loading
Loading