Mizan-Rust backend adapter: server-side substrate + three-way parity
Adds first-class Rust-backed Mizan to sit alongside mizan-django and
mizan-fastapi. A Rust dev writes:
#[derive(Mizan, Serialize, Deserialize)]
pub struct ProfileOutput { pub user_id: i64, pub name: String }
#[mizan::context("user")]
pub struct UserCtx;
#[mizan::client(context = UserCtx)]
pub async fn user_profile(_req: &RequestHandle<'_>, user_id: i64) -> ProfileOutput { ... }
…and gets byte-identical KDL to the Python emitters, served over the
same wire protocol the React / Rust / Vue / Svelte kernels speak.
New crates:
- cores/mizan-rust/ (Cargo: mizan-core) — IR types, KDL emitter, traits, registry,
runtime (compute_invalidation / compute_merges
ported from mizan-fastapi), graph_check with
structural type-matching
- cores/mizan-rust-macros/ (Cargo: mizan-macros) — #[derive(Mizan)], #[mizan::context],
#[mizan::client] proc macros
- backends/mizan-rust-axum/ (Cargo: mizan-axum) — axum HTTP adapter: /session/, /call/, /ctx/:name/
- tests/afi/rust_app/ — AFI fixture port + server / export-ir binaries
Substrate-shape moves required by cross-language equivalence:
- IR canonicalization: functions / contexts / context-members / shared-by
now sort alphabetically in both Python and Rust emitters. The IR is a
contract; linkme doesn't preserve declaration order, so canonical sort
is the only stable mapping. afi_ir.kdl + per-target baselines regenerated.
- MizanType::TYPE_NAME is a const (with a default type_name() reader) so
it's usable in linkme TypeEntry static initializers.
- Tree-shaken type registry: #[derive(Mizan)] only emits the trait impl;
the #[mizan::client] macro registers canonical-named entries from
fn signatures, including Vec<T> element types for ref resolution.
- Merge resolution is structural (NamedType shape comparison) rather than
by name — matches the Python types_match_for_merge semantics.
Three-way forcing functions:
- tests/afi/test_codegen_parity.py — Django ≡ FastAPI ≡ Rust on KDL bytes (3 pass)
- tests/rust/run_wire_parity.py — 12/12 probes against FastAPI + Rust (EXIT=0)
Incidental fixes surfaced by the new tests:
- Stale `from .registry import validate_registry` import removed from
mizan-django/setup/discovery.py (referenced a function that no longer
exists; was masking codegen-parity).
- BASE_DIR added to tests/afi/django_app/project/settings.py.
- /session/ endpoint added to mizan-fastapi for protocol-shaped readiness
probe parity (wire-parity harness now polls /api/mizan/session/ on both
backends rather than FastAPI's /openapi.json).
- Root .gitignore picks up Rust target/ across the tree so new crates
don't need per-crate gitignore.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -36,14 +36,6 @@ class MizanClient:
|
||||
raw = self._inner.call("echo", args.model_dump())
|
||||
return EchoOutput(**raw)
|
||||
|
||||
def call_whoami(self) -> WhoamiOutput:
|
||||
raw = self._inner.call("whoami", {})
|
||||
return WhoamiOutput(**raw)
|
||||
|
||||
def call_update_profile(self, args: UpdateProfileInput) -> UpdateProfileOutput:
|
||||
raw = self._inner.call("update_profile", args.model_dump())
|
||||
return UpdateProfileOutput(**raw)
|
||||
|
||||
def call_find_user(self, args: FindUserInput) -> FindUserOutput | None:
|
||||
raw = self._inner.call("find_user", args.model_dump())
|
||||
return FindUserOutput(**raw) if raw is not None else None
|
||||
@@ -52,6 +44,14 @@ class MizanClient:
|
||||
raw = self._inner.call("rename_user", args.model_dump())
|
||||
return RenameUserOutput(**raw)
|
||||
|
||||
def call_update_profile(self, args: UpdateProfileInput) -> UpdateProfileOutput:
|
||||
raw = self._inner.call("update_profile", args.model_dump())
|
||||
return UpdateProfileOutput(**raw)
|
||||
|
||||
def call_whoami(self) -> WhoamiOutput:
|
||||
raw = self._inner.call("whoami", {})
|
||||
return WhoamiOutput(**raw)
|
||||
|
||||
def invalidate(self, context: str) -> None:
|
||||
self._inner.invalidate(context)
|
||||
|
||||
@@ -63,5 +63,5 @@ class MizanClient:
|
||||
|
||||
class UserContextData(BaseModel):
|
||||
"""Bundled return of fetch_user_context."""
|
||||
user_profile: UserProfileOutput
|
||||
user_orders: UserOrdersOutput
|
||||
user_profile: UserProfileOutput
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import { mizanFetch } from '@mizan/base'
|
||||
|
||||
import type { userProfileOutput, userOrdersOutput } from '../types'
|
||||
import type { userOrdersOutput, userProfileOutput } from '../types'
|
||||
|
||||
export interface UserContextData {
|
||||
user_profile: userProfileOutput
|
||||
user_orders: userOrdersOutput
|
||||
user_profile: userProfileOutput
|
||||
}
|
||||
|
||||
export interface UserContextParams {
|
||||
|
||||
@@ -5,10 +5,10 @@ export * from './types'
|
||||
export { fetchUserContext, type UserContextData, type UserContextParams } from './contexts/user'
|
||||
|
||||
export { callEcho } from './functions/echo'
|
||||
export { callWhoami } from './functions/whoami'
|
||||
export { callUpdateProfile } from './mutations/updateProfile'
|
||||
export { callFindUser } from './functions/findUser'
|
||||
export { callRenameUser } from './functions/renameUser'
|
||||
export { callUpdateProfile } from './mutations/updateProfile'
|
||||
export { callWhoami } from './functions/whoami'
|
||||
|
||||
// Stage 2 framework adapter
|
||||
export * from './react'
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
type ContextState,
|
||||
} from '@mizan/base'
|
||||
|
||||
import { fetchUserContext, type UserContextData, type UserContextParams, callUpdateProfile, callEcho, callWhoami, callFindUser, callRenameUser, type userProfileOutput, type userOrdersOutput } from './index'
|
||||
import { fetchUserContext, type UserContextData, type UserContextParams, callUpdateProfile, callEcho, callFindUser, callRenameUser, callWhoami, type userOrdersOutput, type userProfileOutput } from './index'
|
||||
|
||||
// Internal — runs inside a Provider, registers with the kernel exactly once.
|
||||
function useContextSubscription<T>(
|
||||
@@ -89,14 +89,14 @@ export function useUserContext(): ContextState<UserContextData> {
|
||||
return ctx
|
||||
}
|
||||
|
||||
export function useUserProfile(): userProfileOutput | null {
|
||||
return useUserContext().data?.user_profile ?? null
|
||||
}
|
||||
|
||||
export function useUserOrders(): userOrdersOutput | null {
|
||||
return useUserContext().data?.user_orders ?? null
|
||||
}
|
||||
|
||||
export function useUserProfile(): userProfileOutput | null {
|
||||
return useUserContext().data?.user_profile ?? null
|
||||
}
|
||||
|
||||
export function useUpdateProfile() {
|
||||
return useMutation<Parameters<typeof callUpdateProfile>[0], Awaited<ReturnType<typeof callUpdateProfile>>>(callUpdateProfile)
|
||||
}
|
||||
@@ -105,10 +105,6 @@ export function useEcho() {
|
||||
return useMutation<Parameters<typeof callEcho>[0], Awaited<ReturnType<typeof callEcho>>>(callEcho)
|
||||
}
|
||||
|
||||
export function useWhoami() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callWhoami>>>(() => callWhoami() as any)
|
||||
}
|
||||
|
||||
export function useFindUser() {
|
||||
return useMutation<Parameters<typeof callFindUser>[0], Awaited<ReturnType<typeof callFindUser>>>(callFindUser)
|
||||
}
|
||||
@@ -117,6 +113,10 @@ export function useRenameUser() {
|
||||
return useMutation<Parameters<typeof callRenameUser>[0], Awaited<ReturnType<typeof callRenameUser>>>(callRenameUser)
|
||||
}
|
||||
|
||||
export function useWhoami() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callWhoami>>>(() => callWhoami() as any)
|
||||
}
|
||||
|
||||
// ── MizanContext root provider ──
|
||||
|
||||
export interface MizanContextProps {
|
||||
|
||||
@@ -5,12 +5,12 @@ use serde_json::Value;
|
||||
|
||||
use mizan_rust::{MizanClient, MizanError};
|
||||
|
||||
use crate::types::{UserProfileOutput, UserOrdersOutput};
|
||||
use crate::types::{UserOrdersOutput, UserProfileOutput};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserContextData {
|
||||
pub user_profile: UserProfileOutput,
|
||||
pub user_orders: UserOrdersOutput,
|
||||
pub user_profile: UserProfileOutput,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import { mizanFetch } from '@mizan/base'
|
||||
|
||||
import type { userProfileOutput, userOrdersOutput } from '../types'
|
||||
import type { userOrdersOutput, userProfileOutput } from '../types'
|
||||
|
||||
export interface UserContextData {
|
||||
user_profile: userProfileOutput
|
||||
user_orders: userOrdersOutput
|
||||
user_profile: userProfileOutput
|
||||
}
|
||||
|
||||
export interface UserContextParams {
|
||||
|
||||
@@ -5,7 +5,7 @@ export * from './types'
|
||||
export { fetchUserContext, type UserContextData, type UserContextParams } from './contexts/user'
|
||||
|
||||
export { callEcho } from './functions/echo'
|
||||
export { callWhoami } from './functions/whoami'
|
||||
export { callUpdateProfile } from './mutations/updateProfile'
|
||||
export { callFindUser } from './functions/findUser'
|
||||
export { callRenameUser } from './functions/renameUser'
|
||||
export { callUpdateProfile } from './mutations/updateProfile'
|
||||
export { callWhoami } from './functions/whoami'
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import { mizanFetch } from '@mizan/base'
|
||||
|
||||
import type { userProfileOutput, userOrdersOutput } from '../types'
|
||||
import type { userOrdersOutput, userProfileOutput } from '../types'
|
||||
|
||||
export interface UserContextData {
|
||||
user_profile: userProfileOutput
|
||||
user_orders: userOrdersOutput
|
||||
user_profile: userProfileOutput
|
||||
}
|
||||
|
||||
export interface UserContextParams {
|
||||
|
||||
@@ -5,10 +5,10 @@ export * from './types'
|
||||
export { fetchUserContext, type UserContextData, type UserContextParams } from './contexts/user'
|
||||
|
||||
export { callEcho } from './functions/echo'
|
||||
export { callWhoami } from './functions/whoami'
|
||||
export { callUpdateProfile } from './mutations/updateProfile'
|
||||
export { callFindUser } from './functions/findUser'
|
||||
export { callRenameUser } from './functions/renameUser'
|
||||
export { callUpdateProfile } from './mutations/updateProfile'
|
||||
export { callWhoami } from './functions/whoami'
|
||||
|
||||
// Stage 2 framework adapter
|
||||
export * from './svelte'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { readable, type Readable } from 'svelte/store'
|
||||
import { registerContext, type ContextState } from '@mizan/base'
|
||||
|
||||
import { fetchUserContext, type UserContextData, type UserContextParams, callUpdateProfile, callEcho, callWhoami, callFindUser, callRenameUser } from '../index'
|
||||
import { fetchUserContext, type UserContextData, type UserContextParams, callUpdateProfile, callEcho, callFindUser, callRenameUser, callWhoami } from '../index'
|
||||
|
||||
export function createUserContext(params: UserContextParams) {
|
||||
const store = readable<ContextState<UserContextData>>(
|
||||
@@ -21,9 +21,9 @@ export function createUserContext(params: UserContextParams) {
|
||||
|
||||
export { callUpdateProfile } from '../index'
|
||||
export { callEcho } from '../index'
|
||||
export { callWhoami } from '../index'
|
||||
export { callFindUser } from '../index'
|
||||
export { callRenameUser } from '../index'
|
||||
export { callWhoami } from '../index'
|
||||
|
||||
export type { ContextState } from '@mizan/base'
|
||||
export { configure, initSession, MizanError } from '@mizan/base'
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import { mizanFetch } from '@mizan/base'
|
||||
|
||||
import type { userProfileOutput, userOrdersOutput } from '../types'
|
||||
import type { userOrdersOutput, userProfileOutput } from '../types'
|
||||
|
||||
export interface UserContextData {
|
||||
user_profile: userProfileOutput
|
||||
user_orders: userOrdersOutput
|
||||
user_profile: userProfileOutput
|
||||
}
|
||||
|
||||
export interface UserContextParams {
|
||||
|
||||
@@ -5,10 +5,10 @@ export * from './types'
|
||||
export { fetchUserContext, type UserContextData, type UserContextParams } from './contexts/user'
|
||||
|
||||
export { callEcho } from './functions/echo'
|
||||
export { callWhoami } from './functions/whoami'
|
||||
export { callUpdateProfile } from './mutations/updateProfile'
|
||||
export { callFindUser } from './functions/findUser'
|
||||
export { callRenameUser } from './functions/renameUser'
|
||||
export { callUpdateProfile } from './mutations/updateProfile'
|
||||
export { callWhoami } from './functions/whoami'
|
||||
|
||||
// Stage 2 framework adapter
|
||||
export * from './vue'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { ref, computed, onMounted, onUnmounted, onServerPrefetch, type ComputedRef } from 'vue'
|
||||
import { registerContext, type ContextState } from '@mizan/base'
|
||||
|
||||
import { fetchUserContext, type UserContextData, type UserContextParams, callUpdateProfile, callEcho, callWhoami, callFindUser, callRenameUser } from '../index'
|
||||
import { fetchUserContext, type UserContextData, type UserContextParams, callUpdateProfile, callEcho, callFindUser, callRenameUser, callWhoami } from '../index'
|
||||
|
||||
export function useUserContext(params: UserContextParams) {
|
||||
const state = ref<ContextState<UserContextData>>({ data: null, status: 'idle', error: null })
|
||||
@@ -25,8 +25,8 @@ export function useUserContext(params: UserContextParams) {
|
||||
|
||||
return {
|
||||
state,
|
||||
userProfile: computed(() => state.value.data?.user_profile ?? null) as ComputedRef<userProfileOutput | null>,
|
||||
userOrders: computed(() => state.value.data?.user_orders ?? null) as ComputedRef<userOrdersOutput | null>,
|
||||
userProfile: computed(() => state.value.data?.user_profile ?? null) as ComputedRef<userProfileOutput | null>,
|
||||
loading: computed(() => state.value.status === 'loading'),
|
||||
error: computed(() => state.value.error),
|
||||
}
|
||||
@@ -56,18 +56,6 @@ export function useEcho() {
|
||||
return { mutate, isPending, error }
|
||||
}
|
||||
|
||||
export function useWhoami() {
|
||||
const isPending = ref(false)
|
||||
const error = ref<Error | null>(null)
|
||||
async function mutate() {
|
||||
isPending.value = true; error.value = null
|
||||
try { return await callWhoami() }
|
||||
catch (e) { error.value = e as Error; throw e }
|
||||
finally { isPending.value = false }
|
||||
}
|
||||
return { mutate, isPending, error }
|
||||
}
|
||||
|
||||
export function useFindUser() {
|
||||
const isPending = ref(false)
|
||||
const error = ref<Error | null>(null)
|
||||
@@ -92,5 +80,17 @@ export function useRenameUser() {
|
||||
return { mutate, isPending, error }
|
||||
}
|
||||
|
||||
export function useWhoami() {
|
||||
const isPending = ref(false)
|
||||
const error = ref<Error | null>(null)
|
||||
async function mutate() {
|
||||
isPending.value = true; error.value = null
|
||||
try { return await callWhoami() }
|
||||
catch (e) { error.value = e as Error; throw e }
|
||||
finally { isPending.value = false }
|
||||
}
|
||||
return { mutate, isPending, error }
|
||||
}
|
||||
|
||||
export type { ContextState } from '@mizan/base'
|
||||
export { configure, initSession, MizanError } from '@mizan/base'
|
||||
|
||||
Reference in New Issue
Block a user