| Crates.io | domainstack-schema |
| lib.rs | domainstack-schema |
| version | 1.0.0 |
| created_at | 2026-01-06 13:51:08.024695+00 |
| updated_at | 2026-01-06 13:51:08.024695+00 |
| description | OpenAPI schema generation for domainstack validation types |
| homepage | |
| repository | https://github.com/blackwell-systems/domainstack |
| max_upload_size | |
| id | 2025897 |
| size | 154,268 |
OpenAPI and JSON Schema generation for the domainstack full-stack validation ecosystem.
domainstack-schema provides tools to generate OpenAPI 3.0 schemas and JSON Schema (Draft 2020-12) from your domainstack domain types. This enables automatic API documentation and validation schemas that stay in sync with your validation rules.
| Approach | OpenAPI | JSON Schema |
|---|---|---|
| Trait | ToSchema |
ToJsonSchema |
| CLI | domainstack openapi |
domainstack json-schema |
Use the trait approach for programmatic generation and type-safe composition. Use the CLI approach for build-time codegen from source files.
[dependencies]
domainstack-schema = "1.0.0"
use domainstack_schema::{OpenApiBuilder, Schema, ToSchema};
struct User {
email: String,
age: u8,
}
impl ToSchema for User {
fn schema_name() -> &'static str {
"User"
}
fn schema() -> Schema {
Schema::object()
.property("email", Schema::string().format("email"))
.property("age", Schema::integer().minimum(18).maximum(120))
.required(&["email", "age"])
}
}
fn main() {
let spec = OpenApiBuilder::new("My API", "1.0.0")
.description("User management API")
.register::<User>()
.build();
let json = spec.to_json().expect("Failed to serialize");
println!("{}", json);
}
use domainstack_schema::{JsonSchema, JsonSchemaBuilder, ToJsonSchema};
struct User {
email: String,
age: u8,
}
impl ToJsonSchema for User {
fn schema_name() -> &'static str {
"User"
}
fn json_schema() -> JsonSchema {
JsonSchema::object()
.property("email", JsonSchema::string().format("email"))
.property("age", JsonSchema::integer().minimum(0).maximum(150))
.required(&["email", "age"])
}
}
fn main() {
let doc = JsonSchemaBuilder::new()
.title("My Schema")
.register::<User>()
.build();
let json = serde_json::to_string_pretty(&doc).expect("Failed to serialize");
println!("{}", json);
}
Supports all field-level validation rules that have direct OpenAPI Schema constraint mappings:
| Validation Rule | OpenAPI Constraint | Example |
|---|---|---|
length(min, max) |
minLength, maxLength |
.min_length(3).max_length(50) |
range(min, max) |
minimum, maximum |
.minimum(0).maximum(100) |
email() |
format: "email" |
.format("email") |
one_of(...) |
enum |
.enum_values(&["a", "b"]) |
numeric_string() |
pattern: "^[0-9]+$" |
.pattern("^[0-9]+$") |
min_items(n) |
minItems |
.min_items(1) |
max_items(n) |
maxItems |
.max_items(10) |
Note: Cross-field validations, conditional rules, and business logic validations (e.g., database uniqueness) don't have direct OpenAPI equivalents. For these, use vendor extensions (see below).
Combine schemas using anyOf, allOf, or oneOf:
// Union type (anyOf): string OR integer
let flexible = Schema::any_of(vec![
Schema::string(),
Schema::integer(),
]);
// Composition (allOf): extends base schema
let admin_user = Schema::all_of(vec![
Schema::reference("User"),
Schema::object().property("admin", Schema::boolean()),
]);
// Discriminated union (oneOf): exactly one match
let payment = Schema::one_of(vec![
Schema::object().property("type", Schema::string().enum_values(&["card"])),
Schema::object().property("type", Schema::string().enum_values(&["cash"])),
]);
Add defaults, examples, and documentation:
let theme = Schema::string()
.enum_values(&["light", "dark", "auto"])
.default(json!("auto")) // Default value
.example(json!("dark")) // Single example
.examples(vec![ // Multiple examples
json!("light"),
json!("dark"),
])
.description("UI theme preference");
Mark fields as read-only, write-only, or deprecated:
let user = Schema::object()
.property("id",
Schema::string()
.read_only(true) // Response only
.description("Auto-generated ID")
)
.property("password",
Schema::string()
.format("password")
.write_only(true) // Request only
.min_length(8)
)
.property("old_field",
Schema::string()
.deprecated(true) // Mark as deprecated
.description("Use 'new_field' instead")
);
Build schemas using the fluent API:
use domainstack_schema::Schema;
// String with constraints
let name = Schema::string()
.min_length(1)
.max_length(100)
.description("User's full name");
// Integer with range
let age = Schema::integer()
.minimum(0)
.maximum(150);
// Enum
let status = Schema::string()
.enum_values(&["active", "pending", "inactive"]);
// Array
let tags = Schema::array(Schema::string())
.min_items(1)
.max_items(10);
// Object with properties
let user = Schema::object()
.property("name", name)
.property("age", age)
.required(&["name", "age"]);
// Reference to another schema
let team_member = Schema::reference("User");
use domainstack_schema::OpenApiBuilder;
let spec = OpenApiBuilder::new("User API", "1.0.0")
.description("API for managing users")
.register::<User>()
.register::<Address>()
.register::<Team>()
.build();
// Export as JSON
let json = spec.to_json()?;
println!("{}", json);
ToSchema or ToJsonSchema traitsSee examples/user_api.rs for a complete example demonstrating:
cargo run --example user_api
See examples/v08_features.rs for advanced features:
cargo run --example v08_features
What this crate does:
What this crate does NOT do:
Positioning: domainstack-schema focuses on schema generation for domain types. Full OpenAPI spec generation (paths, operations, security) is intentionally out of scope and may be addressed in a separate crate.
Some validation rules don't map cleanly to OpenAPI Schema constraints:
// Cross-field validation - no OpenAPI equivalent
#[validate(check = "self.end_date > self.start_date")]
// Conditional validation - no OpenAPI equivalent
#[validate(when = "self.requires_card", rule = "...")]
// Business logic - no OpenAPI equivalent
async fn validate_email_unique(&self, db: &Database) -> Result<()>
Solution: Use vendor extensions to preserve validation metadata:
Schema::object()
.property("end_date", Schema::string().format("date"))
.extension("x-domainstack-validations", json!({
"cross_field": ["end_date > start_date"]
}))
This maintains a single source of truth while acknowledging OpenAPI's expressiveness limits.
| Crate | Purpose |
|---|---|
domainstack |
Core validation library |
domainstack-derive |
#[derive(Validate, ToSchema)] macros |
domainstack-cli |
CLI for Zod, JSON Schema, OpenAPI generation |
For build-time codegen from source files (without implementing traits), use domainstack-cli:
# Generate JSON Schema from Rust source files
domainstack json-schema --input src --output schemas/types.json
# Generate OpenAPI spec from Rust source files
domainstack openapi --input src --output api/openapi.json
# Generate Zod schemas for TypeScript
domainstack zod --input src --output frontend/schemas.ts
Licensed under MIT OR Apache-2.0.