ADR 0004: Code Generation Architecture¶
Status: Accepted Date: 2024-12-28 (Updated)
Context¶
Spikard generates type-safe, idiomatic code from supported schema families across Python, TypeScript, Rust, Ruby, PHP, and Elixir. The current CLI surface covers OpenAPI, AsyncAPI, OpenRPC/JSON-RPC, GraphQL, and Protobuf/gRPC across all six bindings. Generated code is designed to integrate with Spikard's runtime, and the repository includes validation utilities for checking generated output with language-native tooling.
Decision¶
Architecture¶
CLI Entry Point: spikard-cli (crates/spikard-cli/src/cli.rs)
spikard generate <protocol>- Generate handlers/types from schemaspikard init- Scaffold new projects with idiomatic structure
Generator Organization: crates/spikard-cli/src/codegen/
codegen/
├── common/ # Shared utilities (case conversion, escaping, sanitization)
├── formatters/ # Language-specific code formatting
├── quality/ # Quality validation framework
├── graphql/generators/ # GraphQL: Python, TypeScript, Ruby, PHP, Rust
├── openapi/ # OpenAPI generators
├── asyncapi/ # AsyncAPI generators
└── openrpc/ # OpenRPC generators
Core Principles:
- DRY: Extract common logic into
codegen/common/- no duplication across generators - Quality-First: All generated code must pass language-specific quality tools
- Idiomatic Output: Follow ecosystem conventions (uv for Python, pnpm for TS, bundler for Ruby, etc.)
- Zero Runtime Dependencies: Generated code uses only Spikard runtime APIs
Shared Utilities¶
Case Conversion (common/case_conversion.rs):
to_snake_case,to_camel_case,to_pascal_case,to_kebab_case- Handles acronyms, preserves underscores, consistent across all generators
Escaping (common/escaping.rs):
- Context-aware string escaping for Python, JavaScript, Ruby, PHP, Rust
escape_for_docstring,escape_quotes,escape_template_literal
Identifier Sanitization (common/identifier_sanitization.rs):
- Language-specific keyword avoidance
- Invalid character handling per language rules
Formatters (formatters/{python,typescript,ruby,php,rust}.rs):
- Language-specific headers (shebang, strict mode, frozen_string_literal)
- Import organization and ordering
- Docstring/comment formatting
Quality Validation¶
QualityValidator (quality/validator.rs):
- Runs language-specific quality tools on generated code
- Python:
mypy --strict,ruff check - TypeScript:
tsc --noEmit,biome check - Ruby:
ruby -c,steep check - PHP:
php -l,phpstan --level=max - Rust:
cargo check,cargo clippy
Test Coverage: Fixture-driven quality tests for each protocol × language combination
Protocol Generators¶
GraphQL (codegen/graphql/):
- SDL builder for schema reconstruction
- Type mapper for cross-language type mapping
- Generators produce resolvers, types, and schema bindings
OpenAPI/AsyncAPI/OpenRPC:
- Follow same shared utility patterns
- Protocol-specific parsers in respective modules
- Consistent output quality across all protocols
Project Scaffolding¶
Init Command (crates/spikard-cli/src/init/):
- Trait-based scaffolder system
- Language-specific implementations:
PythonScaffolder,TypeScriptScaffolder, etc. - Generates idiomatic project structure with:
- Package manager config (pyproject.toml, package.json, Gemfile, composer.json, Cargo.toml)
- Testing framework setup (pytest, vitest, rspec, phpunit, cargo test)
- Quality tools (mypy, biome, steep, phpstan, clippy)
- Example health endpoint
Schema Integration: init creates the starter project first; schema-aware generation then happens through the explicit spikard generate ... commands (or via MCP init_project + generation tools).
Consequences¶
Benefits:
- Single source of truth for common operations (1,670 lines of duplication eliminated)
- Consistent quality across all generated code
- Easy to add new protocols (reuse shared utilities)
- Generated code passes strict type checkers out of the box
Trade-offs:
- Breaking changes to generator internals when updating shared utilities
- Must maintain quality tool compatibility as they evolve
- Pre-1.0: Breaking changes to generated code structure are expected
Maintenance:
- Update shared utilities when adding language-specific edge cases
- Keep quality validators in sync with tool versions
- Test fixtures must cover all protocol × language combinations
References¶
- CLI:
crates/spikard-cli/src/cli.rs - Generators:
crates/spikard-cli/src/codegen/ - Init scaffolders:
crates/spikard-cli/src/init/ - Test fixtures:
testing_data/{openapi,asyncapi,openrpc,graphql}_schemas/ - Quality tests:
crates/spikard-cli/tests/*_quality.rs