Architecture
Overview
ReflectAPI has three layers:
- Rust types and handler functions define the API surface.
- Reflection builds a
Schema, which is the interchange format between the server side and code generators. - Codegen backends transform that schema into language-specific clients or an OpenAPI document.
The workspace is split accordingly:
reflectapi-schema: raw schema types and raw-schema transformsreflectapi-derive:#[derive(Input, Output)]macrosreflectapi: reflection traits, builder, runtime integrations, codegen backends, and the private compiler schema modulereflectapi-cli: CLI wrapper around codegenreflectapi-demo: snapshot and integration testsreflectapi-python-runtime: runtime support for generated Python clients
Reflection Model
Reflection starts from the Input and Output traits in reflectapi/src/traits.rs. Derived implementations and hand-written impls register types into a Typespace and return TypeReferences that point at those definitions.
The top-level Schema in reflectapi-schema/src/lib.rs contains:
functions: endpoint definitionsinput_types: types seen in request positionsoutput_types: types seen in response positions
Input and output types stay separate at schema-construction time so the same Rust name can have different request and response shapes. Some backends later consolidate them into a single naming domain.
Schema and IDs
SymbolId and SymbolKind live in the private
reflectapi/src/codegen/schema/symbol.rs module. They are compiler identifiers,
not part of the serialized JSON contract.
Key points:
- raw
Schema,Function, and type/member definitions do not store symbol IDs build_schema_ids()inreflectapi/src/codegen/schema/ids.rsassigns IDs in a compiler-owned side table- the schema root now uses
SymbolKind::Schema - the schema root path includes the
__schema__sentinel to avoid colliding with a user-defined type of the same name
That keeps reflectapi.json wire-focused while normalization and semantic analysis still get stable identities.
Type Metadata
Every reflected type is one of:
PrimitiveStructEnum
Primitive.fallback lets a backend substitute a simpler representation when it does not natively model the original Rust type. Examples in the current codebase include pointer-like wrappers falling back to T, and ordered collections falling back to unordered equivalents or vectors.
Language-specific metadata is carried by LanguageSpecificTypeCodegenConfig in reflectapi-schema/src/codegen.rs:
- Rust metadata is serialized when present, for example extra derives on generated Rust types.
- Python type mappings are backend-local in
reflectapi/src/codegen/python.rs, not schema annotations.
Response enum policy is shared API metadata: Enum.open retains unfamiliar
tags and JSON in generated responses, while Variant.other preserves an
explicit Serde unit fallback. Both are omitted from JSON when false, and both
are carried into the semantic IR. Reflection records these flags only for
outputs, so request types stay closed.
The derive rejects unsupported or conflicting policies. Rust, TypeScript and
Python codegen also call the shared validate_response_enums() guard before
rendering, including for schemas loaded from JSON. OpenAPI describes the
currently declared wire variants; it does not add the generated unknown case.
Normalization
Normalization lives in reflectapi/src/codegen/schema/normalize.rs.
There are two parts:
- A mutable normalization pipeline over raw
Schema - A
Normalizerthat converts the resulting schema intoSemanticSchema
The configurable pipeline is built with PipelineBuilder. The convenience constructors are:
NormalizationPipeline::standard()Runs type consolidation, naming resolution, and circular dependency resolution.NormalizationPipeline::for_codegen()Skips consolidation and naming, and only runs circular dependency resolution.
After the pipeline runs, Normalizer performs:
- symbol discovery
- type resolution
- dependency analysis
- a semantic validation hook (additional checks are currently a TODO)
- semantic IR construction
SemanticSchema provides resolved, deterministic views of functions and types
and is defined in reflectapi/src/codegen/schema/semantic.rs.
Backend Behavior
Backends do not all consume the schema in the same way.
TypeScript
The TypeScript backend in reflectapi/src/codegen/typescript.rs consolidates raw schema types and renders directly from the raw schema.
Rust
The Rust backend in reflectapi/src/codegen/rust.rs also works primarily from the raw schema after consolidation.
Python
The Python backend in reflectapi/src/codegen/python.rs uses both representations:
- enum-policy validation and hidden-field filtering run before preprocessing
- types are consolidated and flattened generic models are monomorphized
validate_type_references()checks raw referencesNormalizer::normalize_with_pipeline(...)buildsSemanticSchemausing a pipeline that skips consolidation and naming- rendering uses semantic ordering and symbol information, while still consulting raw schema details where the backend needs original field/type shapes
Python-specific type support is driven by backend-local mappings keyed by canonical Rust type name. Those mappings are static codegen knowledge, not part of the shared schema contract.
OpenAPI
The OpenAPI backend in reflectapi/src/codegen/openapi.rs walks the raw schema directly.
Runtime-Specific Types
ReflectAPI includes special API-facing types whose semantics matter to codegen:
reflectapi::Option<T>: three-state optional value for PATCH-like APIsreflectapi::Empty: explicit empty request/response body typereflectapi::Infallible: explicit “no error payload” type
The Python backend treats these as runtime-provided abstractions rather than generated models.
Generated Python clients use real package modules for reflected namespaces; the
flat generated.py file is only a compatibility facade over those modules.
Testing and Validation
reflectapi-demo is the main regression suite.
The combined-type and builder snapshot harnesses in
reflectapi-demo/src/tests/assert.rs generate five artifacts:
- raw schema JSON
- TypeScript client output
- Rust client output
- OpenAPI output
- Python client output
The workspace also contains compile-pass and compile-fail tests driven by trybuild.
reflectapi-demo/tests/enum_compatibility.rs exports a shared enum schema and
Serde-produced fallback samples. Separate Rust, Python and TypeScript suites
decode newer wire values through those generated clients. The suite also
checks consumer-facing TS and Python types, closed request enums, nested and
flattened models, errors, headers where supported, and streaming items.
This architecture chapter is intended to describe the code paths that exist in the repository, not an aspirational future design.