β οΈ ALPHA SOFTWARE
This project has core functionality implemented but is still evolving. The CSIL parser and basic code generation work, but the API may change before the first stable release. Suitable for experimentation and early adoption.
A library and CLI tool for implementing CBOR Service Interface Language (CSIL), an aspiring interface definition language that extends beyond what CDDL provides, with some reference generators.
The core is written in Rust, but calls WASM modules with the loaded CSIL datastructures. WASM modules will be given configuration options and return a set of filename/text-string combos that the core will then splat out into the target directory. Modules have no access to filesystem directly.
Flow is similar to protocgen for protobufs.
The core architecture is in place and the CLI is functional end-to-end: parsing, validation, formatting, linting, breaking-change detection, and code generation for Rust, Go, TypeScript, Python, JSON Schema, and OpenAPI all work today. See the detailed Implementation Status below for what's polished vs. what's still on the roadmap.
From a fresh clone, the minimal commands to generate code from CSIL files:
# Clone and build
git clone <repo-url>
cd csilgen
cargo build --workspace --release
# Build and install the WASM generators to ~/.csilgen/generators/
cargo run -p xtask install-wasm
# Generate code from CSIL files
cargo run -p csilgen -- generate --input your-file.csil --target rust --output ./generated/
cargo run -p csilgen -- generate --input your-file.csil --target go --output ./generated/
cargo run -p csilgen -- generate --input your-file.csil --target json --output ./generated/
cargo run -p csilgen -- generate --input your-file.csil --target typescript --output ./generated/
# rust, go, python, and typescript also offer focused -typesonly / -client / -server sub-targets
cargo run -p csilgen -- generate --input your-file.csil --target typescript-client --output ./generated/
cargo run -p csilgen -- generate --input your-file.csil --target python --output ./generated/
cargo run -p csilgen -- generate --input your-file.csil --target openapi --output ./generated/
# Or install and use the CLI directly
cargo install --path crates/csilgen-cli
csilgen generate --input your-file.csil --target rust --output ./generated/See the examples directory for sample CSIL files to experiment with.
All of the RFC 8610 control operators below are parsed, validated, and carried
through every generator. Comparison/size/regex/default constraints are emitted as
runtime checks (code generators) and schema keywords (JSON Schema / OpenAPI);
the encoding/structural operators are represented as encoding hints (e.g.
contentMediaType, allOf, vendor extensions, doc comments) rather than runtime
checks.
.size- Size constraints (exact, range, min, max).regex- Regular expression pattern matching.default- Default value specification.ge/.le/.gt/.lt- Ordered comparisons.eq/.ne- Equality / inequality comparisons.bits- Bit control constraints.and- Type intersection.within- Subset constraints.json- JSON text representation.cbor/.cborseq- CBOR / CBOR-sequence encoding
CSIL also accepts an @-annotation constraint system (@min-length,
@max-length, @min-items, @max-items, @min-value, @max-value) that
composes with the control operators above; both are honored by every generator.
- Base encoding operators (
.b64u,.b64c,.hex, etc.) - Text processing operators (
.printf,.join, etc.)
- Basic types (
int,text,bool,bytes,float, etc.) - Tagged core types:
timestamp(CBOR tag 0, RFC3339, always UTC) anddecimal(CBOR tag 4, exact).decimalmaps to a generated, dependency-freeCsilDecimalby default, or to the language's decimal library viaoptions { decimal_mapping: "library" }. Seedocs/cbor-wire-contract.mdandexamples/tagged-types/. - Arrays with occurrence indicators (
[* type],[+ type],[? type], bounded[*10 type],[2*5 type]) - Fixed-shape arrays: heterogeneous tuples
[text, int, bool]and keyed arrays[tag: text, value: any] - Maps with key-value pairs (
{ key => value }), including nested maps inside records - Groups (
( name: text, age: int )), type choice/, and group choice// - Optional fields (
? fieldname: type) - Comments β CDDL
;line comments (a leading;;is just a;comment) - Type definitions and references
- Range expressions β inclusive
1..100, open-ended1../..100, exclusive1...100, and float ranges (lowered to.ge/.le/.ltconstraints) - Hex byte-string literals (
h'89504E47') - Literal values and enums
;;;documentation comments (attach to the following definition); the linter warns on them as a non-CDDL extension- Service definitions with operations, including push-only ops with no input payload (
op: <- Event) - Field metadata annotations, including boolean
@depends-onconditions (@depends-on(a = "x" & b != "y" | c > 5)) - Import/include statements
- File-level options
- Socket/plug type system
- Breaking change detection
- Multi-file dependency analysis
- Parser: CDDL syntax plus the CSIL extensions (services,
/=///=choices, field metadata,;;;doc comments, options block, imports). - Validator: Constraint checking, dependency analysis, breaking-change detection.
- CLI tools:
validate,generate,format,lint,breaking. - Plugin runtime: Dynamic generator discovery from
~/.csilgen/generators/,./.generators/, andtarget/wasm32-unknown-unknown/release/β first-write-wins precedence. Generators are conformingcsilgen_<target>_generator.wasmcdylibs; no CLI map to edit. - Generators: Rust, Go, TypeScript, Python (each with
-typesonly/-client/-serversub-targets, e.g.rust-client,go-typesonly), plus JSON Schema and OpenAPI. Service directions->,<->,<-all emit consistently (handler + router + outbound encoders). - Constraints & tagged types: the full RFC 8610 control-operator set and the
@-annotation constraints are honored across all six generators; thetimestamp(tag 0) anddecimal(tag 4) core types emit per the CBOR wire contract. - Testing: 605 tests across the workspace.
- Base-encoding and text-processing operators (
.b64u,.hex,.printf,.join, β¦) β not yet parsed; everything else in RFC 8610's control-operator set is supported. - Performance optimizations for very large schemas.
- IDE integrations / language server.
- Schema evolution & versioning tools beyond
breaking. - Live validation / hot-reload during development.
- Schema documentation generation as a first-class generator.
This is a Rust workspace containing multiple crates:
- csilgen-core: CSIL parsing, validation, and AST functionality
- csilgen-cli: Command-line interface (
csilgenbinary) - csilgen-common: Shared types incl. the WASM boundary types
- Runtime (
wasm/):- csilgen-wasm-core: Core types/helpers compiled to wasm
- csilgen-wasm-generators: Discovery + execution runtime
- Generators (
wasm/csilgen-<target>-generator/): each is a singlecdylibcrate that producescsilgen_<target>_generator.wasm. Targets:rust,go,typescript,python(each also accepting-typesonly/-client/-serversub-targets, e.g.rust-client),json,openapi, plus thenooptest fixture. There is no parallel library copy β the wasm crate is the single source of truth for each generator. - Development tools:
tools/xtaskbuild automation.
Generators are discovered dynamically, first-write-wins, scanning in this priority order: target/wasm32-unknown-unknown/release (local dev builds) β ./.generators (project-local pin/override) β ~/.csilgen/generators/ (user-installed baseline). Files matching csilgen_<target>_generator.wasm are registered; everything else is ignored. A project can drop a wasm into .generators/ to override the user's installed copy without touching the homedir. To ship a third-party generator, build a cdylib of the same shape and place it in any of those directories β --target <yourname> resolves automatically.
# Build the entire workspace
cargo build --workspace
# Build WASM generators (using xtask)
cargo run -p xtask build-wasm
# Install WASM generators to ~/.csilgen/generators/ (for system-wide CLI usage)
cargo run -p xtask install-wasm
# Run tests
cargo test --workspace
# Install the CLI tool
cargo install --path crates/csilgen-cli
# Test the CLI
csilgen --help
csilgen validate --input examples/basic-usage/simple-service.csil
csilgen generate --input examples/basic-usage/simple-service.csil --target noop --output ./test-output/csilgen validate --input interface.csil
csilgen generate --input interface.csil --target rust --output ./generated/
csilgen generate --input ./schemas/ --target rust --output ./generated/ # Multi-file with dependency analysis
csilgen breaking --current A.csil --new B.csil
csilgen format path/to/dir/ --dry-run
csilgen lint path/to/dir/ --fixWhen working with multiple CSIL files, csilgen automatically performs dependency analysis to avoid generating duplicate code:
- Single File: Processes normally with import resolution
- Multiple Files:
- Builds a dependency graph of all CSIL files
- Identifies entry points (files not imported by others)
- Generates code only from entry points
- Dependencies are included automatically via import resolution
schemas/
βββ api.csil # Entry point - defines services
βββ admin.csil # Entry point - defines admin services
βββ types/
β βββ user.csil # Dependency - imported by api.csil
β βββ product.csil # Dependency - imported by api.csil
β βββ common.csil # Dependency - imported by user.csil, product.csil
βββ standalone.csil # Entry point - no imports/exports
Command: csilgen generate --input ./schemas/ --target rust --output ./generated/
Result: Generates code from 3 entry points (api.csil, admin.csil, standalone.csil) with all dependencies automatically included.
π Dependency analysis completed:
Entry points: 3 files
Dependencies: 3 files
Generating code from entry points only to avoid duplicates.
π Processing 3 entry points from 6 total files:
π api.csil
π admin.csil
π standalone.csil
(Skipping 3 dependency files to avoid duplicates)Use CSIL_VERBOSE=1 to see detailed dependency trees:
CSIL_VERBOSE=1 csilgen generate --input ./schemas/ --target rust --output ./generated/Shows hierarchical dependency relationships:
Entry Points:
π api.csil
ββπ¦ types/user.csil
ββπ¦ types/common.csil
ββπ¦ types/product.csil
ββπ¦ types/common.csil
Dependency Files:
π¦ types/user.csil (imported by: api.csil)
π¦ types/product.csil (imported by: api.csil)
π¦ types/common.csil (imported by: types/user.csil, types/product.csil)
The system detects and reports circular dependencies:
Error: Circular dependency detected: a.csil β b.csil β a.csil
This creates an infinite loop during import resolution. Please restructure
your CSIL files to remove the circular reference. Consider:
1. Moving shared types to a separate file
2. Consolidating related types into a single file
3. Using forward references instead of direct imports-
Organize by Purpose:
- Entry Points: Files that define services or main interfaces (e.g.,
user-api.csil,admin-api.csil) - Dependencies: Files with shared types and common definitions (e.g.,
types/user.csil,common/errors.csil)
- Entry Points: Files that define services or main interfaces (e.g.,
-
Clear File Naming:
- Use descriptive names that indicate the file's role
- Consider prefixes like
api-,types-,shared-for clarity - Group related files in subdirectories
-
Dependency Flow:
- Structure imports to flow in one direction (avoid circular dependencies)
- Place shared types in dedicated files at the bottom of the dependency tree
- Keep service definitions at the top level as entry points
-
Testing Your Structure:
# Verify dependency analysis matches expectations CSIL_VERBOSE=1 csilgen generate --input ./schemas/ --target noop --output /tmp/test # Should show clear entry points vs dependencies # Entry points should be service definitions # Dependencies should be shared types
-
Migration from Single Files:
- Existing single-file workflows continue to work unchanged
- Gradually extract shared types into separate files
- Use dependency analysis to verify no duplicates are generated
See examples/multi-file/ for concrete examples of these patterns.
csilgen supports custom code generators via WASM modules. Every generator β including the built-in ones β is a cdylib crate that follows exactly one naming convention; nothing in the CLI is hardcoded.
At startup the runtime scans, first-write-wins, in priority order:
target/wasm32-unknown-unknown/release/β local dev build output (csilgen workspace only)./.generators/β project-local override of the user-installed baseline~/.csilgen/generators/β user-installed generators
Files that match csilgen_<target>_generator.wasm are registered as generators serving --target <target>. The target name is the substring between csilgen_ and _generator.wasm. Anything that does not match the pattern is silently ignored, so a search directory can hold unrelated files without affecting discovery. A project pins or replaces a generator by dropping a wasm into .generators/; the homedir copy is shadowed without being touched. There is no map to edit β drop a conforming file in, run --target <name>, done.
-
Create the crate. Place it under
wasm/if upstreaming, or anywhere if external.cd wasm cargo new --lib csilgen-mylang-generator -
Configure as a cdylib in
Cargo.toml:[package] name = "csilgen-mylang-generator" edition = "2024" [lib] crate-type = ["cdylib"] [dependencies] csilgen-common = { path = "../../crates/csilgen-common" } serde = { version = "1", features = ["derive"] } serde_json = "1"
-
Implement the four exports. Use
csilgen-common'sWasmGeneratorInput/WasmGeneratorOutputfor I/O andwasm_interface::*for error codes /MAX_INPUT_SIZE. See any existingwasm/csilgen-*-generator/for the boilerplate. -
Wire into the workspace. Add the crate path to
Cargo.tomlmembers, and the package name totools/xtask/src/main.rsbuild_wasm's--packagelist. Thencargo run -p xtask install-wasmbuilds and installs it. -
Use it.
csilgen generate --input file.csil --target mylang --output ./out/. No CLI changes required at any point.
- One crate per generator. Do not create a parallel
libversion undercrates/; thecdylibis the single source of truth. The codebase has been bitten by this twice before. - Filename is the contract.
csilgen_<target>_generator.wasm. The CLI derives the target from it; if you rename, the target name changes. - No async, no filesystem access β generators run in a sandbox and return everything via the WASM result.
- Don't enumerate targets in the CLI. If you find yourself patching a
match targetsomewhere, stop β that's the anti-pattern the dynamic discovery exists to prevent.
See any of wasm/csilgen-{rust,go,typescript,json,python,openapi}-generator/ for working examples.