mizan-tauri + Pydantic-aware codegen: Tauri-as-Mizan-backend substrate
Tauri now joins FastAPI/Django/axum as a first-class Mizan backend. The
React frontend calls Mizan-registered functions through Tauri's IPC
with the same {result, invalidate, merge} envelope the HTTP path uses;
the schema flows Pydantic → decoru → Rust → KDL → TS in one
mizan-generate invocation.
New packages:
* backends/mizan-tauri — Tauri plugin exposing a single `mizan_invoke`
command that routes through mizan-core's FUNCTIONS / CONTEXTS
registries. No per-function tauri::command; the linkme slice IS the
dispatch table.
* frontends/mizan-tauri-transport — TS package exporting
tauriTransport() that wraps invoke('plugin:mizan|mizan_invoke', ...)
and re-shapes errors into MizanError. Pairs with mizan-tauri.
@mizan/base — pluggable transport:
* Adds MizanTransport interface + transport config field.
* Existing fetch-based body factored into httpTransport() (default).
* mizanCall/mizanFetch delegate to config.transport; merge/invalidate
side-effects stay in the kernel (transport-agnostic).
* Consumers swap via configure({ transport: tauriTransport() }).
mizan-codegen — Rust source + Pydantic pre-step:
* [source.rust] runs a Cargo bin (cargo run --bin <name>) and parses
KDL from stdout. The bin uses mizan_core::build_ir() after
force-linking the consumer's #[derive(Mizan)] / #[mizan::client]
registrations.
* [source.rust.pydantic] is an optional pre-step that pipes an
embedded Python bridge (scripts/run_decoru.py) to python and writes
decoru-emitted Rust types into the consumer crate. The bridge
auto-discovers BaseModel subclasses AND Enum subclasses
(last-variant-is-default convention so decoru's impl Default keeps
compiling against enum-typed fields without explicit Pydantic
defaults).
* Pure-Rust usage stays intact — omit pydantic block and write Rust
types by hand.
mizan-macros:
* #[mizan::client] now supports Result<T, MizanError> returns. The
dispatch wrapper `?`-unwraps the user fn so server-side errors
surface as the protocol's standard {code, message, details?}
envelope; T-returning functions stay unchanged.
* #[derive(Mizan)] strips the r# raw-identifier prefix and honors
field-level #[serde(rename = "...")] when emitting IR field names.
Matches serde's wire shape — fixes IR-vs-JSON drift for Rust-keyword
fields (e.g. `r#type` → `type`).
react.tsx template:
* Conditionally emits context-related imports / useContextSubscription
helper based on has_global || !named_contexts.is_empty(). Consumers
without contexts (mutation/RPC-only apps like claude-manage) no
longer get dead imports that trip noUnusedLocals.
Verified end-to-end: cargo build clean across mizan-tauri,
mizan-codegen, AFI rust_app; AFI three-way KDL parity tests pass;
claude-manage migration drives the full stack (Pydantic schema →
generated TS api → Tauri-IPC transport → mizan-core dispatch).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,13 @@ pub struct SourceConfig {
|
||||
|
||||
#[serde(default)]
|
||||
pub django: Option<DjangoSource>,
|
||||
|
||||
/// Canonical "Pydantic + Rust" DX path. The Rust crate is the IR
|
||||
/// authority; an optional `pydantic` sub-block invokes decoru as a
|
||||
/// pre-step to author Rust types from Pydantic models. Pure-Rust
|
||||
/// usage (no Pydantic) just omits the sub-block.
|
||||
#[serde(default)]
|
||||
pub rust: Option<RustSource>,
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +105,107 @@ pub struct DjangoSource {
|
||||
}
|
||||
|
||||
|
||||
/// `[source.rust]` — spawn a Cargo binary that emits the Mizan IR (KDL)
|
||||
/// to stdout. The binary uses `mizan_core::build_ir()` after force-linking
|
||||
/// the consumer crate's `#[derive(Mizan)]` types and `#[mizan::client]`
|
||||
/// functions.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RustSource {
|
||||
/// Path to the consumer's Cargo.toml, relative to the codegen config
|
||||
/// directory. Defaults to `Cargo.toml` (i.e. config_dir/Cargo.toml).
|
||||
#[serde(default)]
|
||||
pub manifest_path: Option<PathBuf>,
|
||||
|
||||
/// Name of the binary under `[[bin]]` that exports the IR. Defaults
|
||||
/// to `emit-mizan-ir` — the convention this substrate documents.
|
||||
#[serde(default = "default_rust_bin")]
|
||||
pub bin: String,
|
||||
|
||||
/// Cargo features to enable when building the bin.
|
||||
#[serde(default)]
|
||||
pub features: Vec<String>,
|
||||
|
||||
/// Build in release mode. Defaults to false (dev mode is faster for
|
||||
/// codegen, and the binary is throwaway).
|
||||
#[serde(default)]
|
||||
pub release: bool,
|
||||
|
||||
/// Environment overrides for the cargo subprocess.
|
||||
#[serde(default)]
|
||||
pub env: BTreeMap<String, String>,
|
||||
|
||||
/// Optional pre-step — invoke decoru on a Pydantic source module
|
||||
/// before running the Cargo bin. When present, the pipeline becomes:
|
||||
/// 1. python + decoru → write Rust types to `pydantic.output`
|
||||
/// 2. cargo run --bin <bin> → emit IR to stdout
|
||||
/// Omit for pure-Rust usage (hand-authored or otherwise-generated
|
||||
/// Rust types with `#[derive(Mizan)]`).
|
||||
#[serde(default)]
|
||||
pub pydantic: Option<PydanticPreStep>,
|
||||
}
|
||||
|
||||
|
||||
fn default_rust_bin() -> String {
|
||||
"emit-mizan-ir".to_string()
|
||||
}
|
||||
|
||||
|
||||
/// Pydantic → Rust pre-step. Runs an embedded Python helper that walks
|
||||
/// the named module for `BaseModel` subclasses and invokes decoru's
|
||||
/// `walk_pydantic_model` + `emit_rust_struct` to produce a Rust file.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PydanticPreStep {
|
||||
/// Python module name to import (e.g. `claude_manage.schema`).
|
||||
pub module: String,
|
||||
|
||||
/// Path to write the generated Rust file, relative to the codegen
|
||||
/// config directory.
|
||||
pub output: PathBuf,
|
||||
|
||||
/// Working directory for the python subprocess, relative to the
|
||||
/// codegen config directory. Defaults to the config directory itself.
|
||||
/// The script prepends this to `sys.path` so the module imports.
|
||||
#[serde(default)]
|
||||
pub cwd: Option<PathBuf>,
|
||||
|
||||
/// Python executable. Defaults to `python`.
|
||||
#[serde(default)]
|
||||
pub python: Option<String>,
|
||||
|
||||
/// Full command override (e.g. `["uv", "run", "python"]`). Wins over
|
||||
/// `python` when present.
|
||||
#[serde(default)]
|
||||
pub command: Option<Vec<String>>,
|
||||
|
||||
/// Derive macros to apply to every generated struct. The default
|
||||
/// matches the Mizan-canonical set used in `cores/rust/blazr/session`
|
||||
/// — serde + mizan_core::Mizan for end-to-end RPC participation.
|
||||
#[serde(default = "default_pydantic_derives")]
|
||||
pub derives: Vec<String>,
|
||||
|
||||
/// Optional prelude inserted at the top of the generated file
|
||||
/// (typically a "// AUTO-GENERATED" warning + `use` statements for
|
||||
/// referenced types not produced by decoru itself).
|
||||
#[serde(default)]
|
||||
pub header: Option<String>,
|
||||
|
||||
/// Environment overrides for the python subprocess.
|
||||
#[serde(default)]
|
||||
pub env: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
|
||||
fn default_pydantic_derives() -> Vec<String> {
|
||||
vec![
|
||||
"Debug".to_string(),
|
||||
"Clone".to_string(),
|
||||
"::serde::Serialize".to_string(),
|
||||
"::serde::Deserialize".to_string(),
|
||||
"::mizan_core::Mizan".to_string(),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum RustKernelSpec {
|
||||
|
||||
Reference in New Issue
Block a user