Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
- [Crates.io](https://crates.io/crates/reflectapi) - Package information and versions
- [API Documentation](https://docs.rs/reflectapi) - Complete API reference
- [User Guide](docs/src/SUMMARY.md) - Tutorials and examples (build locally with `mdbook serve` in `docs/`)
- [Architecture](docs/architecture.md) - System design and internals
- [Architecture](docs/src/architecture.md) - System design and internals

## Development notes

Expand Down
533 changes: 0 additions & 533 deletions docs/architecture.md

This file was deleted.

4 changes: 2 additions & 2 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Summary

[Introduction](./introduction.md)
- [Introduction](./introduction.md)
- [Architecture](./architecture.md)

# Getting Started

- [Quick Start](./getting-started/quick-start.md)
- [Installation](./getting-started/installation.md)
- [Client Libraries](./clients/README.md)

137 changes: 137 additions & 0 deletions docs/src/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# Architecture

## Overview

ReflectAPI has three layers:

1. Rust types and handler functions define the API surface.
2. Reflection builds a `Schema`, which is the interchange format between the server side and code generators.
3. Codegen backends transform that schema into language-specific clients or an OpenAPI document.

The workspace is split accordingly:

- `reflectapi-schema`: schema types, symbol IDs, normalization pipeline, semantic IR
- `reflectapi-derive`: `#[derive(Input, Output)]` macros
- `reflectapi`: reflection traits, builder, runtime integrations, codegen backends
- `reflectapi-cli`: CLI wrapper around codegen
- `reflectapi-demo`: snapshot and integration tests
- `reflectapi-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 `TypeReference`s that point at those definitions.

The top-level `Schema` in `reflectapi-schema/src/lib.rs` contains:

- `functions`: endpoint definitions
- `input_types`: types seen in request positions
- `output_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 `reflectapi-schema/src/symbol.rs`. They are internal compiler identifiers, not part of the stable JSON contract.

Key points:

- `Schema.id`, `Function.id`, and member/type IDs are marked `skip_serializing`
- `ensure_symbol_ids()` in `reflectapi-schema/src/ids.rs` assigns IDs after deserialization
- 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 means `reflectapi.json` stays wire-focused, while normalization and semantic analysis still get stable identities.

## Type Metadata

Every reflected type is one of:

- `Primitive`
- `Struct`
- `Enum`

`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 metadata is attached in memory during schema construction and consumed by the Python backend. It is intentionally not serialized today, so CLI codegen from `reflectapi.json` still uses shared default mappings as a compatibility fallback.

## Normalization

Normalization lives in `reflectapi-schema/src/normalize.rs`.

There are two parts:

1. A mutable normalization pipeline over raw `Schema`
2. A `Normalizer` that converts the resulting schema into `SemanticSchema`

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:

1. symbol discovery
2. type resolution
3. dependency analysis
4. semantic validation
5. semantic IR construction

`SemanticSchema` provides resolved, deterministic views of functions and types and is defined in `reflectapi-schema/src/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:

1. `schema.consolidate_types()` runs first
2. `validate_type_references()` checks raw references
3. `Normalizer::normalize_with_pipeline(...)` builds `SemanticSchema` using a pipeline that skips consolidation and naming
4. 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 first by per-type metadata attached during reflection. When that metadata is absent, the backend falls back to shared default mappings by canonical Rust type name so serialized schemas still work.

### 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 APIs
- `reflectapi::Empty`: explicit empty request/response body type
- `reflectapi::Infallible`: explicit “no error payload” type

The Python backend treats these as runtime-provided abstractions rather than generated models.

## Testing and Validation

`reflectapi-demo` is the main regression suite.

The snapshot harness in `reflectapi-demo/src/tests/assert.rs` generates five artifacts per test:

- 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`.

This architecture chapter is intended to describe the code paths that exist in the repository, not an aspirational future design.
123 changes: 74 additions & 49 deletions docs/src/clients/README.md
Original file line number Diff line number Diff line change
@@ -1,70 +1,95 @@
# Client Generation

`reflectapi` automatically generates type-safe client libraries for multiple programming languages from your Rust API server. This section covers how to generate and use clients in different languages.
`reflectapi` can generate client code from a reflected schema JSON file.

## Supported Languages
## Supported Outputs

| Language | Status |
|------------|----------|
| TypeScript | Stable |
| Rust | ✅ Stable |
| Python | ✅ Experiemental |
| Output | Status | Notes |
|--------|--------|-------|
| TypeScript | Stable | Single generated file |
| Rust | Stable | Single generated file |
| Python | Experimental | Package-style output with `__init__.py` and `generated.py` |

## Code Generation Workflow
OpenAPI generation is also supported by the CLI, but it is documented separately as an API description format rather than a client library.

See demo project setup [https://github.com/thepartly/reflectapi/tree/main/reflectapi-demo](https://github.com/thepartly/reflectapi/tree/main/reflectapi-demo)
## Workflow

1. **Define your API server** using `reflectapi` traits and builder
2. **Generate schema** as JSON from your Rust application
3. **Run the CLI** to generate client libraries
4. **Use the clients** in your applications with full type safety
1. Define your API server using `reflectapi` derives and the builder API.
2. Write the schema JSON from your Rust application.
3. Run `reflectapi codegen` for the target language.
4. Commit or consume the generated client code from your application.

```bash
# Generate schema (from your Rust app)
cargo run # Your app should save reflectapi-schema.json
The CLI defaults to `reflectapi.json` if `--schema` is omitted. The demo project uses that filename. If your application writes a different filename such as `reflectapi-schema.json`, pass that path explicitly.

# Generate clients
cargo run reflectapi codegen --language typescript --schema reflectapi-schema.json --output clients/typescript
cargo run reflectapi codegen --language python --schema reflectapi-schema.json --output clients/python
cargo run reflectapi codegen --language rust --schema reflectapi-schema.json --output clients/rust
```bash
# Create output directories first. TypeScript and Rust write a single file
# unless the output path already exists as a directory or ends with a slash.
mkdir -p clients/typescript clients/python clients/rust

# Generate TypeScript client -> clients/typescript/generated.ts
cargo run --bin reflectapi -- codegen \
--language typescript \
--schema reflectapi.json \
--output clients/typescript/

# Generate Python client -> clients/python/__init__.py and generated.py
cargo run --bin reflectapi -- codegen \
--language python \
--schema reflectapi.json \
--output clients/python/ \
--python-sync

# Generate Rust client -> clients/rust/generated.rs
cargo run --bin reflectapi -- codegen \
--language rust \
--schema reflectapi.json \
--output clients/rust/
```

## Common Features
If you installed the CLI separately, replace `cargo run --bin reflectapi --` with `reflectapi`.

## Output Shape

The generators do not all emit the same file layout:

| Output | Files written by the generator |
|--------|--------------------------------|
| TypeScript | `generated.ts` |
| Rust | `generated.rs` |
| Python | `__init__.py`, `generated.py` |

The demo repository includes extra project scaffolding around some generated clients, but that scaffolding is not produced by `reflectapi codegen` itself.

## Language Behavior

All generated clients share these characteristics:
### TypeScript

### Type Safety
- Native type definitions for each language
- Compile-time or runtime type checking
- IDE support with autocompletion
- Uses generated TypeScript types and function wrappers.
- Uses a `fetch`-based default client implementation.
- Parses JSON responses, but does not generate runtime schema validators today.
- Supports custom client implementations via the generated client interface.

### Extensibility
- Default base client implementation is provided
- Which can be replaced or extended with features, such opentelemetry instrumentation or playwrite tracing
### Python

### Error Handling
- Structured error types
- Network error handling
- Generates Pydantic-based models and client code.
- Generates an async client by default.
- Adds a sync client only when `--python-sync` is passed.
- Uses `reflectapi_runtime` for client base classes and runtime helpers.

### Async Support
- Modern async/await patterns
### Rust

### Documentation
- Generated from your Rust documentation
- Type information in IDE tooltips
- Generates typed async client methods.
- Integrates with `reflectapi::rt::Client`.
- Supports optional tracing instrumentation through `--instrument`.
- Generates serde-compatible types and request helpers for JSON-based transport.

### HTTP Client Libraries
## Shared Characteristics

| Language | HTTP Library | Features |
|----------|--------------|----------|
| TypeScript | fetch API | Native browser/Node.js support |
| Python | httpx | Async/sync, HTTP/2, connection pooling |
| Rust | reqwest | Async, HTTP/2, TLS, middleware |
The generated clients all aim to provide:

### Serialization
- Types derived from the Rust-reflected schema
- Function wrappers with generated documentation
- Structured handling of application errors versus transport/protocol failures
- Good IDE support through generated type information

| Language | Serialization | Validation |
|----------|---------------|------------|
| TypeScript | JSON.parse/stringify | Runtime type checking |
| Python | Pydantic | Schema validation, type coercion |
| Rust | JSON or MessagePack (serde) | Compile-time, zero-cost |
They do not currently all provide the same runtime validation guarantees or the same runtime transport abstractions, so those details should be considered language-specific.
4 changes: 3 additions & 1 deletion docs/src/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ cargo add reflectapi --features builder,axum,json,chrono
Install the CLI tool to generate client libraries:

```bash
cargo install reflectapi-cli --help
cargo install reflectapi-cli
```

This installs the `reflectapi` binary.

## Next Steps

- **New users**: Follow the [Quick Start](./quick-start.md) guide
Loading
Loading