| Crates.io | paramdef |
| lib.rs | paramdef |
| version | 0.2.0 |
| created_at | 2026-01-03 02:49:28.015361+00 |
| updated_at | 2026-01-03 02:49:28.015361+00 |
| description | Type-safe parameter definition system |
| homepage | |
| repository | https://github.com/vanyastaff/paramdef |
| max_upload_size | |
| id | 2019503 |
| size | 1,606,531 |
Universal Form Schema System for Rust โ Define once, use everywhere
Like Zod + React Hook Form for TypeScript, but for Rust with compile-time safety. Inspired by Blender RNA, Unreal UPROPERTY, and Qt Property System.
The missing link between backend schemas and frontend forms in Rust.
paramdef is a form schema definition system that works across your entire stack:
Not just validation โ Rich metadata, layout hints, and semantic types built-in.
use paramdef::prelude::*;
// Define parameter schema
let schema = Schema::builder()
.parameter(Text::builder("username")
.label("Username")
.required()
.build())
.parameter(Number::builder("age")
.label("Age")
.default(18.0)
.build())
.parameter(Boolean::builder("active")
.label("Active")
.default(true)
.build())
.build();
// Create runtime context
let mut ctx = Context::new(Arc::new(schema));
// Set and get values
ctx.set("username", Value::text("alice"));
ctx.set("age", Value::Float(25.0));
assert_eq!(ctx.get("username").and_then(|v| v.as_text()), Some("alice"));
// Define once
let user_form = Object::builder("user")
.field("email", Text::email("email").required())
.field("age", Number::integer("age"))
.build();
// Use in Axum backend
async fn create_user(Json(data): Json<Value>) -> Result<(), Error> {
user_form.validate(&data)?; // โ Backend validation
// ...
}
// Render in Leptos frontend
#[component]
fn UserForm() -> impl IntoView {
let form = user_form.clone(); // โ Same schema!
view! { <DynamicForm schema={form} /> }
}
// Interactive CLI prompt
fn main() {
let values = user_form.prompt()?; // โ CLI wizard
// ...
}
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Schema Layer (Immutable) โ โ Shared definitions (Arc)
โ - Metadata, flags, validators โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Runtime Layer (Mutable) โ โ Per-instance state
โ - Current values, dirty flags โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Value Layer โ โ Runtime representation
โ - Unified Value enum โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
| Category | Own Value | Children | Types |
|---|---|---|---|
| Group | โ | โ | 2 - Root aggregators |
| Decoration | โ | โ | 8 - Display elements |
| Container | โ | โ | 7 - Structured data |
| Leaf | โ | โ | 6 - Terminal values |
Leaf Types: Text, Number, Boolean, Vector, Select, File Containers: Object, List, Mode, Matrix, Routing, Expirable, Reference Decorations: Notice, Separator, Link, Code, Image, Html, Video, Progress Group: Group, Panel
Compile-time constraints for specialized parameters:
use paramdef::types::leaf::{Text, Number, Vector};
use paramdef::subtype::{Email, Port, Percentage};
// Email validation (compile-time enforced)
let email: Text<Email> = Text::email("contact");
// Port numbers (integer-only)
let port: Number<Port> = Number::port("http_port")
.default(8080.0)
.build();
// Percentage (float-only, 0-100 range)
let opacity: Number<Percentage> = Number::percentage("alpha")
.default(100.0)
.build();
// Fixed-size vectors (compile-time size)
let position = Vector::builder::<f64, 3>("pos")
.default([0.0, 0.0, 0.0])
.build();
Separate semantic meaning from measurement system:
use paramdef::subtype::NumberUnit;
// Subtype = WHAT it is (semantic)
// Unit = HOW to measure (system)
let distance = Number::builder("length")
.unit(NumberUnit::Meters)
.default(10.0)
.build();
// 60 subtypes ร 17 unit categories = powerful combinations!
Excellent performance characteristics:
Optimizations:
SmartString for stack-allocated short strings (<23 bytes)Arc for cheap cloning of immutable data[dependencies]
paramdef = { version = "0.2", features = ["serde", "validation"] }
| Feature | Description |
|---|---|
serde |
Serialization/deserialization support |
validation |
Validation system with custom validators |
visibility |
Visibility conditions and expressions |
events |
Event system with tokio channels |
i18n |
Internationalization with Fluent |
chrono |
Chrono type conversions |
full |
Enable all features |
Core library has zero UI dependencies - works headless (servers, CLI).
use paramdef::types::container::Object;
use paramdef::types::leaf::{Text, Number, Boolean};
let address = Object::builder("address")
.field("street", Text::builder("street").required().build())
.field("city", Text::builder("city").required().build())
.field("zip", Text::builder("zip").build())
.build()
.unwrap();
let user = Object::builder("user")
.field("name", Text::builder("name").required().build())
.field("email", Text::email("email"))
.field("age", Number::builder("age").build())
.field("address", address)
.build()
.unwrap();
use paramdef::types::container::Mode;
// Output can be file, database, or API
let output = Mode::builder("output")
.variant("file", file_params)
.variant("database", db_params)
.variant("api", api_params)
.build()
.unwrap();
// Runtime value: {"mode": "database", "value": {...}}
use paramdef::core::Flags;
let password = Text::builder("password")
.flags(Flags::REQUIRED | Flags::SENSITIVE)
.build();
assert!(password.flags().contains(Flags::REQUIRED));
assert!(password.flags().contains(Flags::SENSITIVE));
use paramdef::types::container::Object;
use paramdef::types::leaf::{Number, Select};
use paramdef::subtype::NumberUnit;
// Image resize node with rich metadata
let resize_node = Object::builder("resize")
.field("width",
Number::integer("width")
.label("Width")
.description("Output image width")
.unit(NumberUnit::Pixels)
.default(1920.0)
.required()
.build())
.field("height",
Number::integer("height")
.label("Height")
.unit(NumberUnit::Pixels)
.default(1080.0)
.build())
.field("method",
Select::single("method")
.label("Resize Method")
.options(vec![
SelectOption::simple("nearest"),
SelectOption::simple("bilinear"),
SelectOption::simple("bicubic"),
])
.default_single("bilinear")
.build())
.build()
.unwrap();
// โ
Backend validates incoming JSON
// โ
Frontend renders form with labels, units, tooltips
// โ
CLI creates interactive wizard
use paramdef::subtype::NumberUnit;
// Physics simulation parameters
let simulation = Object::builder("simulation")
.field("duration",
Number::builder("duration")
.label("Simulation Duration")
.unit(NumberUnit::Seconds)
.default(60.0)
.build())
.field("temperature",
Number::builder("temp")
.label("Initial Temperature")
.unit(NumberUnit::Celsius)
.default(20.0)
.build())
.field("mass",
Number::builder("mass")
.label("Object Mass")
.unit(NumberUnit::Kilograms)
.default(1.0)
.build())
.build()
.unwrap();
// Units displayed in UI: "60 s", "20 ยฐC", "1 kg"
// Single schema definition works everywhere!
let product_form = Object::builder("product")
.field("name", Text::builder("name")
.label("Product Name")
.required()
.build())
.field("sku", Text::builder("sku")
.label("SKU")
.description("Stock Keeping Unit")
.required()
.build())
.field("price", Number::builder("price")
.label("Price")
.unit(NumberUnit::Currency)
.default(0.0)
.build())
.field("active", Boolean::builder("active")
.label("Active")
.description("Is product visible in store?")
.default(true)
.build())
.build()
.unwrap();
// โ
Axum/Actix: Validate POST /api/products
// โ
Leptos/Yew: Render create/edit forms
// โ
OpenAPI: Generate spec automatically
Group (2 types)
ValueAccess at runtimeDecoration (8 types)
Container (7 types)
ValueAccess at runtimeLeaf (6 types)
Version 0.2.0 - Production-Ready Core
โ Complete:
๐ง Coming Soon (v0.3):
dialoguer integration๐ฎ Roadmap (v0.4+):
๐ Documentation:
docs/Add to your Cargo.toml:
[dependencies]
paramdef = "0.2"
paramdef is designed to be a universal foundation for parameter systems across different ecosystems:
// Each node in your workflow has a paramdef schema
struct ResizeImageNode {
schema: Arc<Object>, // paramdef schema
}
impl WorkflowNode for ResizeImageNode {
fn schema(&self) -> &Object {
&self.schema // โ Rich metadata for UI
}
fn execute(&self, inputs: Value) -> Result<Value> {
self.schema.validate(&inputs)?; // โ Backend validation
// ... execute node logic
}
}
// โ
Visual editor renders form from schema
// โ
Runtime validates with same schema
// โ
Export to JSON for sharing
use bevy::prelude::*;
use paramdef::prelude::*;
// Alternative to Bevy's Reflect for properties
#[derive(Component)]
struct Transform {
schema: Arc<Object>, // paramdef schema
values: Context, // runtime values
}
impl Transform {
fn new() -> Self {
let schema = Object::builder("transform")
.field("position", Vector::builder::<f32, 3>("pos")
.label("Position")
.default([0.0, 0.0, 0.0])
.build())
.field("rotation", Vector::builder::<f32, 3>("rot")
.label("Rotation")
.build())
.build()
.unwrap();
Self {
schema: Arc::new(schema),
values: Context::new(Arc::clone(&schema)),
}
}
}
// โ
Inspector UI auto-generated from schema
// โ
Serialization built-in
// โ
Undo/redo support (coming in v0.4)
use egui::{Ui, Widget};
// Auto-generate egui widgets from paramdef schemas
struct ParamDefWidget<'a> {
schema: &'a Object,
context: &'a mut Context,
}
impl<'a> Widget for ParamDefWidget<'a> {
fn ui(self, ui: &mut Ui) -> Response {
// Iterate schema fields, render appropriate widgets
for field in self.schema.fields() {
match field.kind() {
NodeKind::Leaf => {
// Text input, number slider, checkbox, etc.
}
NodeKind::Container => {
// Nested group with collapsible
}
// ...
}
}
}
}
// โ
No manual UI code - schema drives everything
// โ
Consistent forms across your app
// Shared types crate
mod shared {
pub fn user_schema() -> Object {
Object::builder("user")
.field("email", Text::email("email").required())
.field("age", Number::integer("age"))
.build()
.unwrap()
}
}
// Backend (Axum)
async fn create_user(Json(data): Json<Value>) -> Result<Json<User>> {
let schema = shared::user_schema();
schema.validate(&data)?; // โ Same schema!
// ...
}
// Frontend (Leptos)
#[component]
fn UserForm() -> impl IntoView {
let schema = shared::user_schema(); // โ Same schema!
view! { <DynamicForm schema={schema} /> }
}
// โ
Single source of truth
// โ
Type-safe across the stack
// โ
No JSON Schema duplication
// Settings panel auto-generated from schema
let app_settings = Object::builder("settings")
.field("theme", Select::single("theme")
.options(vec![
SelectOption::simple("light"),
SelectOption::simple("dark"),
SelectOption::simple("auto"),
]))
.field("language", Select::single("lang")
.options(vec![
SelectOption::new("en", "English"),
SelectOption::new("ru", "ะ ัััะบะธะน"),
]))
.build()
.unwrap();
// โ
Settings UI rendered from schema
// โ
Persistence via serde
// โ
Validation built-in
// Plugins register their parameters via paramdef
trait Plugin {
fn name(&self) -> &str;
fn schema(&self) -> Arc<Object>; // โ paramdef schema
fn execute(&self, params: &Context) -> Result<()>;
}
// Host app can:
// โ
Discover plugin parameters automatically
// โ
Generate UI for any plugin
// โ
Validate plugin configs
// โ
Serialize plugin state
Community Integrations Welcome!
Building a paramdef integration for your framework? Let us know - we'd love to feature it here!
Minimum Supported Rust Version: 1.85
Uses Rust 2024 Edition.
Contributions are welcome! Please open an issue or pull request on GitHub.
Licensed under either of:
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.