domainstack-cli 1.0.1

CLI to generate TypeScript/Zod, JSON Schema, and GraphQL from Rust validation rules. Single source of truth for frontend + backend.
domainstack-cli-1.0.1 is not a library.

domainstack-cli

Blackwell Systems™ Crates.io License: MIT OR Apache-2.0

Code generation CLI for the domainstack full-stack validation ecosystem. Generate TypeScript/Zod schemas, JSON Schema, and OpenAPI specs from your Rust #[validate(...)] attributes.

Overview

domainstack-cli is a command-line tool that transforms Rust types annotated with domainstack validation rules into equivalent schemas for other languages and frameworks. This ensures your validation logic stays synchronized across your entire stack.

# Single source of truth in Rust
#[derive(Validate)]
struct User {
    #[validate(email)]
    #[validate(max_len = 255)]
    email: String,

    #[validate(range(min = 18, max = 120))]
    age: u8,
}

# Generate TypeScript/Zod schemas
domainstack zod --input src --output frontend/schemas.ts

# Generate JSON Schema (Draft 2020-12)
domainstack json-schema --input src --output schemas/types.json

# Generate OpenAPI 3.0/3.1 specification
domainstack openapi --input src --output api/openapi.json

Installation

From crates.io (when published)

cargo install domainstack-cli

From source

# Clone the repository
git clone https://github.com/blackwell-systems/domainstack
cd domainstack/domainstack/domainstack-cli

# Build and install
cargo install --path .

Verify installation

domainstack --version

Quick Start

1. Define your Rust types with validation rules

// src/models.rs
use domainstack::Validate;

#[derive(Validate)]
struct User {
    #[validate(email)]
    #[validate(max_len = 255)]
    email: String,

    #[validate(length(min = 3, max = 50))]
    #[validate(alphanumeric)]
    username: String,

    #[validate(range(min = 18, max = 120))]
    age: u8,

    #[validate(url)]
    profile_url: Option<String>,
}

2. Generate Zod schemas

domainstack zod --input src --output frontend/src/schemas.ts

3. Use the generated schemas in TypeScript

// frontend/src/schemas.ts (auto-generated)
import { z } from "zod";

export const UserSchema = z.object({
  email: z.string().email().max(255),
  username: z.string().min(3).max(50).regex(/^[a-zA-Z0-9]*$/),
  age: z.number().min(18).max(120),
  profile_url: z.string().url().optional(),
});

export type User = z.infer<typeof UserSchema>;

// Use it in your application
const result = UserSchema.safeParse(formData);
if (result.success) {
  // Type-safe validated data
  const user: User = result.data;
}

Commands

domainstack zod

Generate Zod validation schemas from Rust types.

domainstack zod [OPTIONS]

Options:

  • -i, --input <PATH> - Input directory containing Rust source files (default: src)
  • -o, --output <PATH> - Output TypeScript file (required)
  • -w, --watch - Watch for changes and regenerate automatically
  • -v, --verbose - Enable verbose output
  • -h, --help - Print help information

Examples:

# Basic usage
domainstack zod --output schemas.ts

# Specify input directory
domainstack zod --input backend/src --output frontend/schemas.ts

# Verbose output
domainstack zod -i src -o schemas.ts -v

# Watch mode for development
domainstack zod -i src -o schemas.ts --watch

domainstack json-schema

Generate JSON Schema (Draft 2020-12) from Rust types.

domainstack json-schema [OPTIONS]

Options:

  • -i, --input <PATH> - Input directory containing Rust source files (default: src)
  • -o, --output <PATH> - Output JSON file (required)
  • -w, --watch - Watch for changes and regenerate automatically
  • -v, --verbose - Enable verbose output
  • -h, --help - Print help information

Examples:

# Basic usage
domainstack json-schema --output schema.json

# Specify input directory
domainstack json-schema --input backend/src --output api/schemas.json

# Verbose with watch mode
domainstack json-schema -i src -o schema.json -v --watch

Generated output:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$defs": {
    "User": {
      "type": "object",
      "properties": {
        "email": { "type": "string", "format": "email", "maxLength": 255 },
        "age": { "type": "integer", "minimum": 18, "maximum": 120 }
      },
      "required": ["email", "age"],
      "additionalProperties": false
    }
  }
}

domainstack openapi

Generate OpenAPI 3.0/3.1 specification from Rust types.

domainstack openapi [OPTIONS]

Options:

  • -i, --input <PATH> - Input directory containing Rust source files (default: src)
  • -o, --output <PATH> - Output JSON file (required)
  • --openapi-31 - Use OpenAPI 3.1 (default is 3.0)
  • -w, --watch - Watch for changes and regenerate automatically
  • -v, --verbose - Enable verbose output
  • -h, --help - Print help information

Examples:

# Generate OpenAPI 3.0 spec
domainstack openapi --output openapi.json

# Generate OpenAPI 3.1 spec
domainstack openapi --output openapi.json --openapi-31

# Watch mode
domainstack openapi -i src -o openapi.json --watch

Generated output:

{
  "openapi": "3.0.3",
  "info": { "title": "Generated API Schema", "version": "1.0.0" },
  "paths": {},
  "components": {
    "schemas": {
      "User": {
        "type": "object",
        "properties": {
          "email": { "type": "string", "format": "email", "maxLength": 255 },
          "age": { "type": "integer", "minimum": 18, "maximum": 120 }
        },
        "required": ["email", "age"],
        "additionalProperties": false
      }
    }
  }
}

Supported Validation Rules

String Validations

Rust Attribute Zod Output Description
#[validate(email)] .email() Valid email address
#[validate(url)] .url() Valid URL
#[validate(min_len = N)] .min(N) Minimum string length
#[validate(max_len = N)] .max(N) Maximum string length
#[validate(length(min = N, max = M))] .min(N).max(M) String length range
#[validate(non_empty)] .min(1) Non-empty string
#[validate(non_blank)] .trim().min(1) Non-blank string (after trim)
#[validate(alphanumeric)] .regex(/^[a-zA-Z0-9]*$/) Alphanumeric only
#[validate(alpha_only)] .regex(/^[a-zA-Z]*$/) Letters only
#[validate(numeric_string)] .regex(/^[0-9]*$/) Digits only
#[validate(ascii)] .regex(/^[\x00-\x7F]*$/) ASCII characters only
#[validate(starts_with = "prefix")] .startsWith("prefix") Must start with prefix
#[validate(ends_with = "suffix")] .endsWith("suffix") Must end with suffix
#[validate(contains = "substring")] .includes("substring") Must contain substring
#[validate(matches_regex = "pattern")] .regex(/pattern/) Custom regex pattern
#[validate(no_whitespace)] .regex(/^\S*$/) No whitespace allowed

Numeric Validations

Rust Attribute Zod Output Description
#[validate(range(min = N, max = M))] .min(N).max(M) Numeric range
#[validate(min = N)] .min(N) Minimum value
#[validate(max = N)] .max(N) Maximum value
#[validate(positive)] .positive() Must be positive (> 0)
#[validate(negative)] .negative() Must be negative (< 0)
#[validate(non_zero)] .refine(n => n !== 0, ...) Cannot be zero
#[validate(multiple_of = N)] .multipleOf(N) Must be multiple of N
#[validate(finite)] .finite() Must be finite (not NaN/Infinity)

Type Mappings

Rust Type Zod Type Notes
String z.string()
bool z.boolean()
u8, u16, u32, i8, i16, i32, f32, f64 z.number()
u64, u128, i64, i128 z.number() With precision warning comment
Option<T> T.optional() Validations applied to inner type
Vec<T> z.array(T)
Custom types CustomTypeSchema References generated schema

Examples

Multiple Validation Rules

You can apply multiple validation rules to a single field:

#[derive(Validate)]
struct Account {
    #[validate(email)]
    #[validate(max_len = 255)]
    #[validate(non_empty)]
    email: String,

    #[validate(min_len = 8)]
    #[validate(max_len = 128)]
    #[validate(matches_regex = "^(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])")]
    password: String,
}

Generates:

export const AccountSchema = z.object({
  email: z.string().email().max(255).min(1),
  password: z.string().min(8).max(128).regex(/(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])/),
});

Optional Fields with Validations

Optional fields have .optional() applied AFTER all validations:

#[derive(Validate)]
struct Profile {
    #[validate(url)]
    website: Option<String>,

    #[validate(length(min = 10, max = 500))]
    bio: Option<String>,
}

Generates:

export const ProfileSchema = z.object({
  website: z.string().url().optional(),  // Correct order
  bio: z.string().min(10).max(500).optional(),
});

Arrays and Collections

#[derive(Validate)]
struct Post {
    tags: Vec<String>,

    #[validate(min = 1)]
    #[validate(max = 100)]
    scores: Vec<u8>,
}

Generates:

export const PostSchema = z.object({
  tags: z.array(z.string()),
  scores: z.array(z.number()).min(1).max(100),
});

Architecture

Unified CLI Design

domainstack-cli is designed as a unified code generation tool with a single binary and multiple subcommands:

domainstack
├── zod          ✅ Generate Zod schemas
├── json-schema  ✅ Generate JSON Schema (Draft 2020-12)
├── openapi      ✅ Generate OpenAPI 3.0/3.1 specification
├── yup          📋 Generate Yup schemas (planned)
├── graphql      📋 Generate GraphQL schemas (planned)
└── prisma       📋 Generate Prisma schemas (planned)

Benefits:

  • Single installation, multiple generators
  • Shared parsing infrastructure (efficient, consistent)
  • Consistent CLI interface across all generators
  • Easy to add new generators

Internal Structure

domainstack-cli/
├── src/
│   ├── main.rs              # CLI entry point with clap
│   ├── commands/            # Subcommand implementations
│   │   ├── zod.rs
│   │   ├── json_schema.rs
│   │   └── openapi.rs
│   ├── parser/              # Shared parsing infrastructure
│   │   ├── mod.rs           # Directory walking
│   │   ├── ast.rs           # Rust AST parsing
│   │   └── validation.rs    # Validation rule extraction
│   └── generators/          # Language-specific generators
│       ├── zod.rs
│       ├── json_schema.rs
│       └── openapi.rs

The parser module (parser/) is shared across all generators, ensuring consistent interpretation of Rust validation rules. Each generator (generators/) contains language-specific transformation logic.

Future Generators

The roadmap includes support for additional generators:

Yup (TypeScript validation)

domainstack yup --input src --output schemas.ts

GraphQL Schema Definition Language

domainstack graphql --input src --output schema.graphql

Prisma Schema

domainstack prisma --input src --output schema.prisma

Documentation

Contributing

Adding a New Generator

To add a new generator (e.g., Yup):

  1. Create generator file: src/generators/yup.rs
  2. Implement generation logic using shared parser types
  3. Create command file: src/commands/yup.rs
  4. Add subcommand to src/main.rs

The shared parser infrastructure (parser::ParsedType, parser::ValidationRule) makes adding new generators straightforward - focus only on the output format transformation.

Development Setup

# Clone repository
git clone https://github.com/blackwell-systems/domainstack
cd domainstack

# Build CLI
cargo build -p domainstack-cli

# Run tests
cargo test -p domainstack-cli

# Install locally
cargo install --path domainstack/domainstack-cli

Troubleshooting

"No types found with validation rules"

Make sure your Rust types:

  1. Have #[derive(Validate)] attribute
  2. Contain at least one #[validate(...)] attribute
  3. Are in .rs files within the input directory

Generated schemas don't match expectations

Run with --verbose flag to see parsing details:

domainstack zod --input src --output schemas.ts --verbose

Large integer precision warnings

JavaScript numbers cannot safely represent integers larger than Number.MAX_SAFE_INTEGER (2^53 - 1). Types like u64, i64, u128, i128 will generate schemas with inline warning comments:

big_number: z.number() /* Warning: Large integers may lose precision in JavaScript */

Consider using strings for large integers in your TypeScript schemas if precision is critical.

License

This project is part of the domainstack workspace. See the root LICENSE file for details.

Related Projects

  • domainstack - Core validation library
  • zod - TypeScript-first schema validation
  • ts-rs - TypeScript type generation (no validation)
  • typeshare - Cross-language type sharing