mizan-fastapi e2e — example app + Playwright harness, 14/14 green
Demonstration milestone. The substrate work earlier in the session established that mizan-fastapi can dispatch RPC, bundle context fetches, and emit invalidation envelopes via TestClient (in-process ASGI). This commit closes the demonstration gap: a real FastAPI server on port 8001 + a real React harness on port 5175 + Playwright in real Chromium, exercising generated hooks. What ships: backends/mizan-fastapi/src/mizan_fastapi/cli.py — schema-export CLI: - `python -m mizan_fastapi.cli <module>` imports the named module (triggering @client decorations + register() side effects), then prints the OpenAPI schema to stdout. Mirrors mizan-django's `manage.py export_mizan_schema` so the codegen consumes either backend the same subprocess way. backends/mizan-django/generate/generator/lib/fetch.mjs — codegen now dispatches on source.django vs source.fastapi. Refactored the subprocess plumbing into a shared runSubprocess helper. The codegen package is still named "mizan-django" by historical accident — it's the framework-agnostic CLI now (a rename for later). backends/mizan-fastapi/src/mizan_fastapi/executor.py — bug fix: mizan_core's @client decorator normalizes auth=True to meta['auth']='required'. The executor's match was only handling True, not 'required', so any auth-required endpoint failed with INTERNAL_ERROR. Now matches both. Caught when wiring up the FastAPI example backend's whoami fixture; would have surfaced first time any real FastAPI app used auth=True. backends/mizan-fastapi/tests/test_dispatch.py — added AuthTests covering the auth=True path so the bug fix has unit coverage. Suite now 12/12. examples/fastapi-react-site/ — parallel to examples/django-react-site/: - backend/main.py: FastAPI app with 11 @client fixtures matching the harness surface (echo, add, multiply, whoami, staff/superuser/ verified-only, notImplementedFn, buggyFn, permissionCheckFn, current_user context). Drops Django-only stuff (forms, channels, ws-whoami, session-bound JWT). - harness/: vite proxy → FastAPI on 8001; generated api/ produced by the codegen against fastapi.config.mjs. - mizan.spec.ts: Playwright suite, 14 tests covering the same axes as Django minus channel-chat. - ContextCurrentUser fixture renders 'loading' until data arrives rather than emitting <pre>null</pre> — fixes a race the Django harness has too (just doesn't trip in practice). Verified: - mizan-fastapi unit: 12/12 (incl. new auth=True coverage) - mizan-fastapi e2e: 14/14 (Playwright via real Chromium) - mizan-core unit: 15/15 - mizan-django unit: 348 pass, 21 skip - AFI conformance: 3/3 - mizan-django e2e: 14/15 (1 skip — channels, deferred) What remains for FastAPI side: - Dockerfile.test + docker-compose.test.yml so CI can run the e2e in the same containerized way as the Django example. - Makefile test-integration target for symmetry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanFetch } from '@mizan/base'
|
||||
|
||||
import type { currentUserOutput } from '../types'
|
||||
|
||||
export interface GlobalContextData {
|
||||
current_user: currentUserOutput
|
||||
}
|
||||
|
||||
export type GlobalContextParams = Record<string, never>
|
||||
|
||||
export function fetchGlobalContext(params: GlobalContextParams): Promise<GlobalContextData> {
|
||||
return mizanFetch('global', params)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { addInput, addOutput } from '../types'
|
||||
|
||||
export function callAdd(args: addInput): Promise<addOutput> {
|
||||
return mizanCall('add', args)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { buggyFnOutput } from '../types'
|
||||
|
||||
export function callBuggyFn(): Promise<buggyFnOutput> {
|
||||
return mizanCall('buggy_fn', {})
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { echoInput, echoOutput } from '../types'
|
||||
|
||||
export function callEcho(args: echoInput): Promise<echoOutput> {
|
||||
return mizanCall('echo', args)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { multiplyInput, multiplyOutput } from '../types'
|
||||
|
||||
export function callMultiply(args: multiplyInput): Promise<multiplyOutput> {
|
||||
return mizanCall('multiply', args)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { notImplementedFnOutput } from '../types'
|
||||
|
||||
export function callNotImplementedFn(): Promise<notImplementedFnOutput> {
|
||||
return mizanCall('not_implemented_fn', {})
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { permissionCheckFnInput, permissionCheckFnOutput } from '../types'
|
||||
|
||||
export function callPermissionCheckFn(args: permissionCheckFnInput): Promise<permissionCheckFnOutput> {
|
||||
return mizanCall('permission_check_fn', args)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { staffOnlyOutput } from '../types'
|
||||
|
||||
export function callStaffOnly(): Promise<staffOnlyOutput> {
|
||||
return mizanCall('staff_only', {})
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { superuserOnlyOutput } from '../types'
|
||||
|
||||
export function callSuperuserOnly(): Promise<superuserOnlyOutput> {
|
||||
return mizanCall('superuser_only', {})
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { verifiedOnlyOutput } from '../types'
|
||||
|
||||
export function callVerifiedOnly(): Promise<verifiedOnlyOutput> {
|
||||
return mizanCall('verified_only', {})
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import { mizanCall } from '@mizan/base'
|
||||
|
||||
import type { whoamiOutput } from '../types'
|
||||
|
||||
export function callWhoami(): Promise<whoamiOutput> {
|
||||
return mizanCall('whoami', {})
|
||||
}
|
||||
19
examples/fastapi-react-site/harness/src/api/index.ts
Normal file
19
examples/fastapi-react-site/harness/src/api/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
export * from './types'
|
||||
|
||||
export { fetchGlobalContext, type GlobalContextData, type GlobalContextParams } from './contexts/global'
|
||||
|
||||
export { callEcho } from './functions/echo'
|
||||
export { callAdd } from './functions/add'
|
||||
export { callMultiply } from './functions/multiply'
|
||||
export { callWhoami } from './functions/whoami'
|
||||
export { callStaffOnly } from './functions/staffOnly'
|
||||
export { callSuperuserOnly } from './functions/superuserOnly'
|
||||
export { callVerifiedOnly } from './functions/verifiedOnly'
|
||||
export { callNotImplementedFn } from './functions/notImplementedFn'
|
||||
export { callBuggyFn } from './functions/buggyFn'
|
||||
export { callPermissionCheckFn } from './functions/permissionCheckFn'
|
||||
|
||||
// Stage 2 framework adapter
|
||||
export * from './react'
|
||||
169
examples/fastapi-react-site/harness/src/api/react.tsx
Normal file
169
examples/fastapi-react-site/harness/src/api/react.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
'use client'
|
||||
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
useSyncExternalStore,
|
||||
type ReactNode,
|
||||
} from 'react'
|
||||
import {
|
||||
configure,
|
||||
initSession,
|
||||
mizanCall,
|
||||
mizanFetch,
|
||||
MizanError,
|
||||
registerContext,
|
||||
type ContextState,
|
||||
} from '@mizan/base'
|
||||
|
||||
import { fetchGlobalContext, type GlobalContextData, type GlobalContextParams, callEcho, callAdd, callMultiply, callWhoami, callStaffOnly, callSuperuserOnly, callVerifiedOnly, callNotImplementedFn, callBuggyFn, callPermissionCheckFn } from './index'
|
||||
|
||||
// Internal — runs inside a Provider, registers with the kernel exactly once.
|
||||
function useContextSubscription<T>(
|
||||
name: string,
|
||||
params: Record<string, any>,
|
||||
fetchFn: () => Promise<T>,
|
||||
initialData?: T,
|
||||
): ContextState<T> {
|
||||
const ref = useRef<ReturnType<typeof registerContext> | null>(null)
|
||||
if (!ref.current) {
|
||||
ref.current = registerContext(name, params, fetchFn, initialData)
|
||||
}
|
||||
const handle = ref.current
|
||||
|
||||
useEffect(() => {
|
||||
if (handle.getState().status === 'idle') handle.refetch()
|
||||
return () => handle.unregister()
|
||||
}, [handle])
|
||||
|
||||
return useSyncExternalStore(handle.subscribe, handle.getState, handle.getState)
|
||||
}
|
||||
|
||||
// Internal — wraps an imperative call() with isPending / error state.
|
||||
interface MutationHook<TArgs, TResult> {
|
||||
mutate: (args: TArgs) => Promise<TResult>
|
||||
isPending: boolean
|
||||
error: Error | null
|
||||
}
|
||||
|
||||
function useMutation<TArgs, TResult>(
|
||||
callFn: (args: TArgs) => Promise<TResult>,
|
||||
): MutationHook<TArgs, TResult> {
|
||||
const [isPending, setIsPending] = useState(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
const mutate = useCallback(async (args: TArgs) => {
|
||||
setIsPending(true)
|
||||
setError(null)
|
||||
try {
|
||||
return await callFn(args)
|
||||
} catch (e) {
|
||||
setError(e as Error)
|
||||
throw e
|
||||
} finally {
|
||||
setIsPending(false)
|
||||
}
|
||||
}, [callFn])
|
||||
|
||||
return { mutate, isPending, error }
|
||||
}
|
||||
|
||||
// ── Global Context ──
|
||||
|
||||
const GlobalCtx = createContext<ContextState<GlobalContextData> | null>(null)
|
||||
|
||||
export function GlobalContextProvider({ children }: { children: ReactNode }) {
|
||||
const ssrData = typeof window !== 'undefined' ? (window as any).__MIZAN_SSR_DATA__ : undefined
|
||||
const state = useContextSubscription('global', {}, () => fetchGlobalContext({} as any), ssrData)
|
||||
return <GlobalCtx.Provider value={state}>{children}</GlobalCtx.Provider>
|
||||
}
|
||||
|
||||
export function useGlobalContext(): ContextState<GlobalContextData> {
|
||||
const ctx = useContext(GlobalCtx)
|
||||
if (!ctx) throw new Error('useGlobalContext requires <MizanContext> or <GlobalContextProvider>')
|
||||
return ctx
|
||||
}
|
||||
|
||||
export function useCurrentUser(): currentUserOutput | null {
|
||||
return useGlobalContext().data?.current_user ?? null
|
||||
}
|
||||
|
||||
export function useEcho() {
|
||||
return useMutation<Parameters<typeof callEcho>[0], Awaited<ReturnType<typeof callEcho>>>(callEcho)
|
||||
}
|
||||
|
||||
export function useAdd() {
|
||||
return useMutation<Parameters<typeof callAdd>[0], Awaited<ReturnType<typeof callAdd>>>(callAdd)
|
||||
}
|
||||
|
||||
export function useMultiply() {
|
||||
return useMutation<Parameters<typeof callMultiply>[0], Awaited<ReturnType<typeof callMultiply>>>(callMultiply)
|
||||
}
|
||||
|
||||
export function useWhoami() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callWhoami>>>(() => callWhoami() as any)
|
||||
}
|
||||
|
||||
export function useStaffOnly() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callStaffOnly>>>(() => callStaffOnly() as any)
|
||||
}
|
||||
|
||||
export function useSuperuserOnly() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callSuperuserOnly>>>(() => callSuperuserOnly() as any)
|
||||
}
|
||||
|
||||
export function useVerifiedOnly() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callVerifiedOnly>>>(() => callVerifiedOnly() as any)
|
||||
}
|
||||
|
||||
export function useNotImplementedFn() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callNotImplementedFn>>>(() => callNotImplementedFn() as any)
|
||||
}
|
||||
|
||||
export function useBuggyFn() {
|
||||
return useMutation<void, Awaited<ReturnType<typeof callBuggyFn>>>(() => callBuggyFn() as any)
|
||||
}
|
||||
|
||||
export function usePermissionCheckFn() {
|
||||
return useMutation<Parameters<typeof callPermissionCheckFn>[0], Awaited<ReturnType<typeof callPermissionCheckFn>>>(callPermissionCheckFn)
|
||||
}
|
||||
|
||||
// ── MizanContext root provider ──
|
||||
|
||||
export interface MizanContextProps {
|
||||
/** Base URL for protocol endpoints. Defaults to "/api/mizan". */
|
||||
baseUrl?: string
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Root provider — calls configure() once and mounts the global context (if defined).
|
||||
* Must wrap any component using Mizan-generated hooks.
|
||||
*/
|
||||
export function MizanContext({ baseUrl, children }: MizanContextProps) {
|
||||
const configured = useRef(false)
|
||||
if (!configured.current) {
|
||||
if (baseUrl) configure({ baseUrl })
|
||||
configured.current = true
|
||||
}
|
||||
return <GlobalContextProvider>{children}</GlobalContextProvider>
|
||||
}
|
||||
|
||||
// ── Imperative escape hatch ──
|
||||
|
||||
/**
|
||||
* Returns the imperative kernel API. For test harnesses or rare cases where
|
||||
* a typed generated hook does not fit. Most app code should use the typed hooks.
|
||||
*/
|
||||
export function useMizan() {
|
||||
return { call: mizanCall, fetch: mizanFetch }
|
||||
}
|
||||
|
||||
export type { ContextState } from '@mizan/base'
|
||||
export { configure, initSession, MizanError } from '@mizan/base'
|
||||
756
examples/fastapi-react-site/harness/src/api/schema.json
Normal file
756
examples/fastapi-react-site/harness/src/api/schema.json
Normal file
@@ -0,0 +1,756 @@
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "mizan Server Functions",
|
||||
"description": "Auto-generated schema for mizan server functions",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"paths": {
|
||||
"/mizan/echo": {
|
||||
"post": {
|
||||
"summary": "Echoes the input text.",
|
||||
"operationId": "echo",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/echoInput"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/echoOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-mizan": {
|
||||
"transport": "http",
|
||||
"isContext": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"/mizan/add": {
|
||||
"post": {
|
||||
"summary": "Returns a + b.",
|
||||
"operationId": "add",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/addInput"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/addOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-mizan": {
|
||||
"transport": "http",
|
||||
"isContext": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"/mizan/multiply": {
|
||||
"post": {
|
||||
"summary": "Returns x * y.",
|
||||
"operationId": "multiply",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/multiplyInput"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/multiplyOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-mizan": {
|
||||
"transport": "http",
|
||||
"isContext": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"/mizan/whoami": {
|
||||
"post": {
|
||||
"summary": "Returns the authenticated user's identity. Anonymous → UNAUTHORIZED.",
|
||||
"operationId": "whoami",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/whoamiOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-mizan": {
|
||||
"transport": "http",
|
||||
"isContext": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"/mizan/staff_only": {
|
||||
"post": {
|
||||
"summary": "Staff-only endpoint.",
|
||||
"operationId": "staffOnly",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/staffOnlyOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-mizan": {
|
||||
"transport": "http",
|
||||
"isContext": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"/mizan/superuser_only": {
|
||||
"post": {
|
||||
"summary": "Superuser-only endpoint.",
|
||||
"operationId": "superuserOnly",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/superuserOnlyOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-mizan": {
|
||||
"transport": "http",
|
||||
"isContext": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"/mizan/verified_only": {
|
||||
"post": {
|
||||
"summary": "Verified-users-only endpoint. Anonymous → FORBIDDEN.",
|
||||
"operationId": "verifiedOnly",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/verifiedOnlyOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-mizan": {
|
||||
"transport": "http",
|
||||
"isContext": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"/mizan/not_implemented_fn": {
|
||||
"post": {
|
||||
"summary": "Always raises NotImplementedError → NOT_IMPLEMENTED.",
|
||||
"operationId": "notImplementedFn",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/notImplementedFnOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-mizan": {
|
||||
"transport": "http",
|
||||
"isContext": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"/mizan/buggy_fn": {
|
||||
"post": {
|
||||
"summary": "Always raises a generic exception → INTERNAL_ERROR.",
|
||||
"operationId": "buggyFn",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/buggyFnOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-mizan": {
|
||||
"transport": "http",
|
||||
"isContext": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"/mizan/permission_check_fn": {
|
||||
"post": {
|
||||
"summary": "Wrong secret → FORBIDDEN; correct secret → success.",
|
||||
"operationId": "permissionCheckFn",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/permissionCheckFnInput"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/permissionCheckFnOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-mizan": {
|
||||
"transport": "http",
|
||||
"isContext": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"/mizan/current_user": {
|
||||
"post": {
|
||||
"summary": "The global context — auto-mounted at the React root.",
|
||||
"operationId": "currentUser",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/currentUserOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-mizan": {
|
||||
"transport": "http",
|
||||
"isContext": "global"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"HTTPValidationError": {
|
||||
"properties": {
|
||||
"detail": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ValidationError"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Detail"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "HTTPValidationError"
|
||||
},
|
||||
"ValidationError": {
|
||||
"properties": {
|
||||
"loc": {
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Location"
|
||||
},
|
||||
"msg": {
|
||||
"type": "string",
|
||||
"title": "Message"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"title": "Error Type"
|
||||
},
|
||||
"input": {
|
||||
"title": "Input"
|
||||
},
|
||||
"ctx": {
|
||||
"type": "object",
|
||||
"title": "Context"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"loc",
|
||||
"msg",
|
||||
"type"
|
||||
],
|
||||
"title": "ValidationError"
|
||||
},
|
||||
"addInput": {
|
||||
"properties": {
|
||||
"a": {
|
||||
"type": "integer",
|
||||
"title": "A"
|
||||
},
|
||||
"b": {
|
||||
"type": "integer",
|
||||
"title": "B"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"a",
|
||||
"b"
|
||||
],
|
||||
"title": "addInput"
|
||||
},
|
||||
"addOutput": {
|
||||
"properties": {
|
||||
"result": {
|
||||
"type": "integer",
|
||||
"title": "Result"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"result"
|
||||
],
|
||||
"title": "addOutput"
|
||||
},
|
||||
"buggyFnOutput": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"title": "Message"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"title": "buggyFnOutput"
|
||||
},
|
||||
"currentUserOutput": {
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"title": "Email"
|
||||
},
|
||||
"authenticated": {
|
||||
"type": "boolean",
|
||||
"title": "Authenticated"
|
||||
},
|
||||
"is_staff": {
|
||||
"type": "boolean",
|
||||
"title": "Is Staff",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"email",
|
||||
"authenticated"
|
||||
],
|
||||
"title": "currentUserOutput"
|
||||
},
|
||||
"echoInput": {
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"title": "Text"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"text"
|
||||
],
|
||||
"title": "echoInput"
|
||||
},
|
||||
"echoOutput": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"title": "Message"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"title": "echoOutput"
|
||||
},
|
||||
"multiplyInput": {
|
||||
"properties": {
|
||||
"x": {
|
||||
"type": "integer",
|
||||
"title": "X"
|
||||
},
|
||||
"y": {
|
||||
"type": "integer",
|
||||
"title": "Y"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"x",
|
||||
"y"
|
||||
],
|
||||
"title": "multiplyInput"
|
||||
},
|
||||
"multiplyOutput": {
|
||||
"properties": {
|
||||
"product": {
|
||||
"type": "integer",
|
||||
"title": "Product"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"product"
|
||||
],
|
||||
"title": "multiplyOutput"
|
||||
},
|
||||
"notImplementedFnOutput": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"title": "Message"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"title": "notImplementedFnOutput"
|
||||
},
|
||||
"permissionCheckFnInput": {
|
||||
"properties": {
|
||||
"secret": {
|
||||
"type": "string",
|
||||
"title": "Secret"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"secret"
|
||||
],
|
||||
"title": "permissionCheckFnInput"
|
||||
},
|
||||
"permissionCheckFnOutput": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"title": "Message"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"title": "permissionCheckFnOutput"
|
||||
},
|
||||
"staffOnlyOutput": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"title": "Message"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"title": "staffOnlyOutput"
|
||||
},
|
||||
"superuserOnlyOutput": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"title": "Message"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"title": "superuserOnlyOutput"
|
||||
},
|
||||
"verifiedOnlyOutput": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"title": "Message"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"title": "verifiedOnlyOutput"
|
||||
},
|
||||
"whoamiOutput": {
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"title": "Email"
|
||||
},
|
||||
"authenticated": {
|
||||
"type": "boolean",
|
||||
"title": "Authenticated"
|
||||
},
|
||||
"is_staff": {
|
||||
"type": "boolean",
|
||||
"title": "Is Staff",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"email",
|
||||
"authenticated"
|
||||
],
|
||||
"title": "whoamiOutput"
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-mizan-functions": [
|
||||
{
|
||||
"name": "echo",
|
||||
"camelName": "echo",
|
||||
"hasInput": true,
|
||||
"inputType": "echoInput",
|
||||
"outputType": "echoOutput",
|
||||
"transport": "http",
|
||||
"isContext": false,
|
||||
"isForm": false,
|
||||
"formName": null,
|
||||
"formRole": null
|
||||
},
|
||||
{
|
||||
"name": "add",
|
||||
"camelName": "add",
|
||||
"hasInput": true,
|
||||
"inputType": "addInput",
|
||||
"outputType": "addOutput",
|
||||
"transport": "http",
|
||||
"isContext": false,
|
||||
"isForm": false,
|
||||
"formName": null,
|
||||
"formRole": null
|
||||
},
|
||||
{
|
||||
"name": "multiply",
|
||||
"camelName": "multiply",
|
||||
"hasInput": true,
|
||||
"inputType": "multiplyInput",
|
||||
"outputType": "multiplyOutput",
|
||||
"transport": "http",
|
||||
"isContext": false,
|
||||
"isForm": false,
|
||||
"formName": null,
|
||||
"formRole": null
|
||||
},
|
||||
{
|
||||
"name": "whoami",
|
||||
"camelName": "whoami",
|
||||
"hasInput": false,
|
||||
"inputType": null,
|
||||
"outputType": "whoamiOutput",
|
||||
"transport": "http",
|
||||
"isContext": false,
|
||||
"isForm": false,
|
||||
"formName": null,
|
||||
"formRole": null
|
||||
},
|
||||
{
|
||||
"name": "staff_only",
|
||||
"camelName": "staffOnly",
|
||||
"hasInput": false,
|
||||
"inputType": null,
|
||||
"outputType": "staffOnlyOutput",
|
||||
"transport": "http",
|
||||
"isContext": false,
|
||||
"isForm": false,
|
||||
"formName": null,
|
||||
"formRole": null
|
||||
},
|
||||
{
|
||||
"name": "superuser_only",
|
||||
"camelName": "superuserOnly",
|
||||
"hasInput": false,
|
||||
"inputType": null,
|
||||
"outputType": "superuserOnlyOutput",
|
||||
"transport": "http",
|
||||
"isContext": false,
|
||||
"isForm": false,
|
||||
"formName": null,
|
||||
"formRole": null
|
||||
},
|
||||
{
|
||||
"name": "verified_only",
|
||||
"camelName": "verifiedOnly",
|
||||
"hasInput": false,
|
||||
"inputType": null,
|
||||
"outputType": "verifiedOnlyOutput",
|
||||
"transport": "http",
|
||||
"isContext": false,
|
||||
"isForm": false,
|
||||
"formName": null,
|
||||
"formRole": null
|
||||
},
|
||||
{
|
||||
"name": "not_implemented_fn",
|
||||
"camelName": "notImplementedFn",
|
||||
"hasInput": false,
|
||||
"inputType": null,
|
||||
"outputType": "notImplementedFnOutput",
|
||||
"transport": "http",
|
||||
"isContext": false,
|
||||
"isForm": false,
|
||||
"formName": null,
|
||||
"formRole": null
|
||||
},
|
||||
{
|
||||
"name": "buggy_fn",
|
||||
"camelName": "buggyFn",
|
||||
"hasInput": false,
|
||||
"inputType": null,
|
||||
"outputType": "buggyFnOutput",
|
||||
"transport": "http",
|
||||
"isContext": false,
|
||||
"isForm": false,
|
||||
"formName": null,
|
||||
"formRole": null
|
||||
},
|
||||
{
|
||||
"name": "permission_check_fn",
|
||||
"camelName": "permissionCheckFn",
|
||||
"hasInput": true,
|
||||
"inputType": "permissionCheckFnInput",
|
||||
"outputType": "permissionCheckFnOutput",
|
||||
"transport": "http",
|
||||
"isContext": false,
|
||||
"isForm": false,
|
||||
"formName": null,
|
||||
"formRole": null
|
||||
},
|
||||
{
|
||||
"name": "current_user",
|
||||
"camelName": "currentUser",
|
||||
"hasInput": false,
|
||||
"inputType": null,
|
||||
"outputType": "currentUserOutput",
|
||||
"transport": "http",
|
||||
"isContext": "global",
|
||||
"isForm": false,
|
||||
"formName": null,
|
||||
"formRole": null
|
||||
}
|
||||
],
|
||||
"x-mizan-contexts": {
|
||||
"global": {
|
||||
"functions": [
|
||||
"current_user"
|
||||
],
|
||||
"params": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
607
examples/fastapi-react-site/harness/src/api/types.ts
Normal file
607
examples/fastapi-react-site/harness/src/api/types.ts
Normal file
@@ -0,0 +1,607 @@
|
||||
// AUTO-GENERATED by mizan — do not edit
|
||||
|
||||
export interface paths {
|
||||
"/mizan/echo": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Echoes the input text. */
|
||||
post: operations["echo"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/mizan/add": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Returns a + b. */
|
||||
post: operations["add"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/mizan/multiply": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Returns x * y. */
|
||||
post: operations["multiply"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/mizan/whoami": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Returns the authenticated user's identity. Anonymous → UNAUTHORIZED. */
|
||||
post: operations["whoami"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/mizan/staff_only": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Staff-only endpoint. */
|
||||
post: operations["staffOnly"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/mizan/superuser_only": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Superuser-only endpoint. */
|
||||
post: operations["superuserOnly"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/mizan/verified_only": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Verified-users-only endpoint. Anonymous → FORBIDDEN. */
|
||||
post: operations["verifiedOnly"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/mizan/not_implemented_fn": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Always raises NotImplementedError → NOT_IMPLEMENTED. */
|
||||
post: operations["notImplementedFn"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/mizan/buggy_fn": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Always raises a generic exception → INTERNAL_ERROR. */
|
||||
post: operations["buggyFn"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/mizan/permission_check_fn": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Wrong secret → FORBIDDEN; correct secret → success. */
|
||||
post: operations["permissionCheckFn"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/mizan/current_user": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** The global context — auto-mounted at the React root. */
|
||||
post: operations["currentUser"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
}
|
||||
export type webhooks = Record<string, never>;
|
||||
export interface components {
|
||||
schemas: {
|
||||
/** HTTPValidationError */
|
||||
HTTPValidationError: {
|
||||
/** Detail */
|
||||
detail?: components["schemas"]["ValidationError"][];
|
||||
};
|
||||
/** ValidationError */
|
||||
ValidationError: {
|
||||
/** Location */
|
||||
loc: (string | number)[];
|
||||
/** Message */
|
||||
msg: string;
|
||||
/** Error Type */
|
||||
type: string;
|
||||
/** Input */
|
||||
input?: unknown;
|
||||
/** Context */
|
||||
ctx?: Record<string, never>;
|
||||
};
|
||||
/** addInput */
|
||||
addInput: {
|
||||
/** A */
|
||||
a: number;
|
||||
/** B */
|
||||
b: number;
|
||||
};
|
||||
/** addOutput */
|
||||
addOutput: {
|
||||
/** Result */
|
||||
result: number;
|
||||
};
|
||||
/** buggyFnOutput */
|
||||
buggyFnOutput: {
|
||||
/** Message */
|
||||
message: string;
|
||||
};
|
||||
/** currentUserOutput */
|
||||
currentUserOutput: {
|
||||
/** Email */
|
||||
email: string;
|
||||
/** Authenticated */
|
||||
authenticated: boolean;
|
||||
/**
|
||||
* Is Staff
|
||||
* @default false
|
||||
*/
|
||||
is_staff: boolean;
|
||||
};
|
||||
/** echoInput */
|
||||
echoInput: {
|
||||
/** Text */
|
||||
text: string;
|
||||
};
|
||||
/** echoOutput */
|
||||
echoOutput: {
|
||||
/** Message */
|
||||
message: string;
|
||||
};
|
||||
/** multiplyInput */
|
||||
multiplyInput: {
|
||||
/** X */
|
||||
x: number;
|
||||
/** Y */
|
||||
y: number;
|
||||
};
|
||||
/** multiplyOutput */
|
||||
multiplyOutput: {
|
||||
/** Product */
|
||||
product: number;
|
||||
};
|
||||
/** notImplementedFnOutput */
|
||||
notImplementedFnOutput: {
|
||||
/** Message */
|
||||
message: string;
|
||||
};
|
||||
/** permissionCheckFnInput */
|
||||
permissionCheckFnInput: {
|
||||
/** Secret */
|
||||
secret: string;
|
||||
};
|
||||
/** permissionCheckFnOutput */
|
||||
permissionCheckFnOutput: {
|
||||
/** Message */
|
||||
message: string;
|
||||
};
|
||||
/** staffOnlyOutput */
|
||||
staffOnlyOutput: {
|
||||
/** Message */
|
||||
message: string;
|
||||
};
|
||||
/** superuserOnlyOutput */
|
||||
superuserOnlyOutput: {
|
||||
/** Message */
|
||||
message: string;
|
||||
};
|
||||
/** verifiedOnlyOutput */
|
||||
verifiedOnlyOutput: {
|
||||
/** Message */
|
||||
message: string;
|
||||
};
|
||||
/** whoamiOutput */
|
||||
whoamiOutput: {
|
||||
/** Email */
|
||||
email: string;
|
||||
/** Authenticated */
|
||||
authenticated: boolean;
|
||||
/**
|
||||
* Is Staff
|
||||
* @default false
|
||||
*/
|
||||
is_staff: boolean;
|
||||
};
|
||||
};
|
||||
responses: never;
|
||||
parameters: never;
|
||||
requestBodies: never;
|
||||
headers: never;
|
||||
pathItems: never;
|
||||
}
|
||||
export type $defs = Record<string, never>;
|
||||
export interface operations {
|
||||
echo: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["echoInput"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["echoOutput"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
add: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["addInput"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["addOutput"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
multiply: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["multiplyInput"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["multiplyOutput"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
whoami: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["whoamiOutput"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
staffOnly: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["staffOnlyOutput"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
superuserOnly: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["superuserOnlyOutput"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
verifiedOnly: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["verifiedOnlyOutput"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
notImplementedFn: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["notImplementedFnOutput"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
buggyFn: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["buggyFnOutput"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
permissionCheckFn: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["permissionCheckFnInput"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["permissionCheckFnOutput"];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
currentUser: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["currentUserOutput"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Convenience type exports
|
||||
export type HTTPValidationError = components["schemas"]["HTTPValidationError"]
|
||||
export type ValidationError = components["schemas"]["ValidationError"]
|
||||
export type addInput = components["schemas"]["addInput"]
|
||||
export type addOutput = components["schemas"]["addOutput"]
|
||||
export type buggyFnOutput = components["schemas"]["buggyFnOutput"]
|
||||
export type currentUserOutput = components["schemas"]["currentUserOutput"]
|
||||
export type echoInput = components["schemas"]["echoInput"]
|
||||
export type echoOutput = components["schemas"]["echoOutput"]
|
||||
export type multiplyInput = components["schemas"]["multiplyInput"]
|
||||
export type multiplyOutput = components["schemas"]["multiplyOutput"]
|
||||
export type notImplementedFnOutput = components["schemas"]["notImplementedFnOutput"]
|
||||
export type permissionCheckFnInput = components["schemas"]["permissionCheckFnInput"]
|
||||
export type permissionCheckFnOutput = components["schemas"]["permissionCheckFnOutput"]
|
||||
export type staffOnlyOutput = components["schemas"]["staffOnlyOutput"]
|
||||
export type superuserOnlyOutput = components["schemas"]["superuserOnlyOutput"]
|
||||
export type verifiedOnlyOutput = components["schemas"]["verifiedOnlyOutput"]
|
||||
export type whoamiOutput = components["schemas"]["whoamiOutput"]
|
||||
132
examples/fastapi-react-site/harness/src/fixtures.tsx
Normal file
132
examples/fastapi-react-site/harness/src/fixtures.tsx
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* E2E Test Fixtures — FastAPI flavor.
|
||||
*
|
||||
* Mirrors examples/django-react-site/harness/src/fixtures.tsx minus the
|
||||
* Django-only surfaces (channels, forms). Each fixture uses GENERATED
|
||||
* mizan hooks (not raw call()). Playwright reads the DOM to verify
|
||||
* behavior. URL hash selects the fixture: #echo, #add, etc.
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
import {
|
||||
MizanContext,
|
||||
useEcho,
|
||||
useAdd,
|
||||
useMultiply,
|
||||
useWhoami,
|
||||
useStaffOnly,
|
||||
useSuperuserOnly,
|
||||
useVerifiedOnly,
|
||||
useNotImplementedFn,
|
||||
useBuggyFn,
|
||||
usePermissionCheckFn,
|
||||
useCurrentUser,
|
||||
MizanError,
|
||||
useMizan,
|
||||
} from './api'
|
||||
|
||||
|
||||
export function Fixtures() {
|
||||
const [hash, setHash] = useState(window.location.hash.slice(1))
|
||||
|
||||
useEffect(() => {
|
||||
const onHash = () => setHash(window.location.hash.slice(1))
|
||||
window.addEventListener('hashchange', onHash)
|
||||
return () => window.removeEventListener('hashchange', onHash)
|
||||
}, [])
|
||||
|
||||
switch (hash) {
|
||||
case 'echo': return <Echo />
|
||||
case 'add': return <Add />
|
||||
case 'multiply': return <Multiply />
|
||||
case 'not-found': return <NotFound />
|
||||
case 'validation-error': return <ValidationError />
|
||||
case 'auth-required': return <AuthRequired />
|
||||
case 'staff-only': return <StaffOnly />
|
||||
case 'superuser-only': return <SuperuserOnly />
|
||||
case 'verified-only': return <VerifiedOnly />
|
||||
case 'not-implemented': return <NotImplemented />
|
||||
case 'internal-error': return <InternalError />
|
||||
case 'permission-error': return <PermissionError_ />
|
||||
case 'permission-success': return <PermissionSuccess />
|
||||
case 'context-current-user': return <ContextCurrentUser />
|
||||
default: return <div data-testid="ready">Harness ready. Set #hash.</div>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Result({ data, error }: { data?: unknown; error?: unknown }) {
|
||||
return (
|
||||
<>
|
||||
{data !== undefined && (
|
||||
<pre data-testid="result">{JSON.stringify(data)}</pre>
|
||||
)}
|
||||
{error !== undefined && error !== null && (
|
||||
<>
|
||||
<div data-testid="error-type">
|
||||
{error instanceof MizanError ? 'MizanError' : 'Error'}
|
||||
</div>
|
||||
<div data-testid="error-code">
|
||||
{error instanceof MizanError ? error.code : ''}
|
||||
</div>
|
||||
<pre data-testid="error-message">
|
||||
{error instanceof Error ? error.message : String(error)}
|
||||
</pre>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function useRun<T>(hook: () => { mutate: (input?: any) => Promise<T> }, input?: any) {
|
||||
const { mutate } = hook()
|
||||
const [data, setData] = useState<T>()
|
||||
const [error, setError] = useState<unknown>()
|
||||
|
||||
useEffect(() => {
|
||||
mutate(input).then(setData).catch(setError)
|
||||
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return { data, error }
|
||||
}
|
||||
|
||||
|
||||
function Echo() { const r = useRun(useEcho, { text: 'e2e-test' }); return <Result {...r} /> }
|
||||
function Add() { const r = useRun(useAdd, { a: 17, b: 25 }); return <Result {...r} /> }
|
||||
function Multiply() { const r = useRun(useMultiply, { x: 6, y: 7 }); return <Result {...r} /> }
|
||||
|
||||
function NotFound() {
|
||||
const { call } = useMizan()
|
||||
const [error, setError] = useState<unknown>()
|
||||
useEffect(() => { call('does_not_exist').catch(setError) }, [call])
|
||||
return <Result error={error} />
|
||||
}
|
||||
|
||||
function ValidationError() {
|
||||
const { mutate } = useAdd()
|
||||
const [error, setError] = useState<unknown>()
|
||||
useEffect(() => { (mutate as any)({ a: 'not_a_number', b: 'also_not' }).catch(setError) }, [mutate])
|
||||
return <Result error={error} />
|
||||
}
|
||||
|
||||
function AuthRequired() { const r = useRun(useWhoami); return <Result {...r} /> }
|
||||
function StaffOnly() { const r = useRun(useStaffOnly); return <Result {...r} /> }
|
||||
function SuperuserOnly() { const r = useRun(useSuperuserOnly); return <Result {...r} /> }
|
||||
function VerifiedOnly() { const r = useRun(useVerifiedOnly); return <Result {...r} /> }
|
||||
function NotImplemented() { const r = useRun(useNotImplementedFn); return <Result {...r} /> }
|
||||
function InternalError() { const r = useRun(useBuggyFn); return <Result {...r} /> }
|
||||
function PermissionError_() { const r = useRun(usePermissionCheckFn, { secret: 'wrong' }); return <Result {...r} /> }
|
||||
function PermissionSuccess() { const r = useRun(usePermissionCheckFn, { secret: 'open-sesame' }); return <Result {...r} /> }
|
||||
|
||||
|
||||
function ContextCurrentUser() {
|
||||
try {
|
||||
const user = useCurrentUser()
|
||||
if (user === null) return <div>loading context...</div>
|
||||
return <pre data-testid="result">{JSON.stringify(user)}</pre>
|
||||
} catch {
|
||||
return <div>loading context...</div>
|
||||
}
|
||||
}
|
||||
13
examples/fastapi-react-site/harness/src/main.tsx
Normal file
13
examples/fastapi-react-site/harness/src/main.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { MizanContext } from './api'
|
||||
import { Fixtures } from './fixtures'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<MizanContext baseUrl="/api/mizan">
|
||||
<Fixtures />
|
||||
</MizanContext>
|
||||
)
|
||||
}
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<App />)
|
||||
Reference in New Issue
Block a user