Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 10 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
# Changelog

## Unreleased
## 0.25.0

A route's error can set its response's status and headers through `#[derive(reflectapi::StatusCode)]`, stream handlers can wait before they answer, and OpenAPI describes newtype structs as serde writes them. Stream handlers must now be async; see Breaking.

### Breaking

- **A stream route's handler is async**, as a route's is: `stream_route` takes `Fn(S, I, H) -> impl Future<Output = Result<St, E>>`. A handler can now wait, to authorize a request or set something up, before it returns its error or its stream; before, it had to return one at once. Make each stream handler an `async fn`, or return `std::future::ready(…)`.
- **A stream route's handler is async**, as a route's is: `stream_route` takes `Fn(S, I, H) -> impl Future<Output = Result<St, E>>`. A handler can now wait, to authorize a request or set something up, before it returns its error or its stream; before, it had to return one at once. Make each stream handler an `async fn`, or return `std::future::ready(…)`. ([#228](https://github.com/thepartly/reflectapi/pull/228))

### Added

- **A route's error can set headers on its response.** `StatusCode` gains `response_headers`, none by default, which the response carries beside the error's status, through the dispatcher and axum: a `retry-after`, a `www-authenticate`. A header reflectapi sets itself, `content-type` or `traceparent`, keeps reflectapi's value.
- **`#[derive(reflectapi::StatusCode)]`** for an error enum: each variant gives its status, `#[reflectapi(status = 404)]`, or takes the status and headers of its one field, `#[reflectapi(transparent)]`. It refuses, as it compiles, a status a route's error must not give: `1xx` to `3xx`; `400`, `405` and `415`, reflectapi's own; `408`, `413`, `414` and `431`, the transport's; and every `5xx` but `500`, on a variant named `InternalError` alone, which must be `output_skip`. See [A route's error](https://thepartly.github.io/reflectapi/getting-started/attributes.html#a-routes-error).
- **A `#[serde(flatten)]` field of a request-headers type declares its type's headers**, at any depth, a generic type's arguments filled in, so the handler is given them. Headers several routes read can be one type, flattened into each route's own: `PetsUpdateHeaders { authorization: String, #[serde(flatten)] common: CommonHeaders }` now reads `CommonHeaders`' `x-request-id` too. Before, only the type's own fields were forwarded, and a flattened field was taken for one header named after it. See [Request Headers](https://thepartly.github.io/reflectapi/clients/index.html#request-headers).
- **A route's error can set headers on its response.** `StatusCode` gains `response_headers`, none by default, which the response carries beside the error's status, through the dispatcher and axum: a `retry-after`, a `www-authenticate`. A header reflectapi sets itself, `content-type` or `traceparent`, keeps reflectapi's value. ([#228](https://github.com/thepartly/reflectapi/pull/228))
- **`#[derive(reflectapi::StatusCode)]`** for an error enum: each variant gives its status, `#[reflectapi(status = 404)]`, or takes the status and headers of its one field, `#[reflectapi(transparent)]`. It refuses, as it compiles, a status a route's error must not give: `1xx` to `3xx`; `400`, `405` and `415`, reflectapi's own; `408`, `413`, `414` and `431`, the transport's; and every `5xx` but `500`, on a variant named `InternalError` alone, which must be `output_skip`. See [A route's error](https://thepartly.github.io/reflectapi/getting-started/attributes.html#a-routes-error). ([#228](https://github.com/thepartly/reflectapi/pull/228))
- **A `#[serde(flatten)]` field of a request-headers type declares its type's headers**, at any depth, a generic type's arguments filled in, so the handler is given them. Headers several routes read can be one type, flattened into each route's own: `PetsUpdateHeaders { authorization: String, #[serde(flatten)] common: CommonHeaders }` now reads `CommonHeaders`' `x-request-id` too. Before, only the type's own fields were forwarded, and a flattened field was taken for one header named after it. See [Request Headers](https://thepartly.github.io/reflectapi/clients/index.html#request-headers). ([#228](https://github.com/thepartly/reflectapi/pull/228))

### Fixed

- **OpenAPI describes a newtype struct as the value it wraps**, as serde writes it. `struct UserId(Uuid)` was a one-element array (`"prefixItems": [...]`), so a client generated from the spec sent `["..."]` where the server expects `"..."`; the demo's `pets.create` request body, `PetsCreateRequest(Pet)`, was `[Pet]` instead of `Pet`. Unit structs, which the schema describes as a newtype of `()`, are now `null` instead of `[null]`. Generated Rust, TypeScript and Python clients already treated newtypes this way and are unchanged.
- **Flattening a newtype struct** works in Python and OpenAPI. Serde writes `#[serde(flatten)] w: Wrapper`, where `struct Wrapper(Inner)`, as `Inner`'s keys next to the parent's, and `Option<Wrapper>` as all of them or none. Python codegen failed with "cannot flatten 'Wrapper' into a model", and OpenAPI described the group with the one-element array above, which no object matches.
- **Flattening a newtype or `#[serde(transparent)]` struct that wraps an enum** generates the same Python model as flattening the enum: its tag and fields sit on the parent, as serde writes them. Python decided by the wrapper's own type, so it generated a nested field the wire never has. Schemas built with `fold_transparent_types()` weren't affected for transparent wrappers.
- **OpenAPI describes a newtype struct as the value it wraps**, as serde writes it. `struct UserId(Uuid)` was a one-element array (`"prefixItems": [...]`), so a client generated from the spec sent `["..."]` where the server expects `"..."`; the demo's `pets.create` request body, `PetsCreateRequest(Pet)`, was `[Pet]` instead of `Pet`. Unit structs, which the schema describes as a newtype of `()`, are now `null` instead of `[null]`. Generated Rust, TypeScript and Python clients already treated newtypes this way and are unchanged. ([#217](https://github.com/thepartly/reflectapi/pull/217))
- **Flattening a newtype struct** works in Python and OpenAPI. Serde writes `#[serde(flatten)] w: Wrapper`, where `struct Wrapper(Inner)`, as `Inner`'s keys next to the parent's, and `Option<Wrapper>` as all of them or none. Python codegen failed with "cannot flatten 'Wrapper' into a model", and OpenAPI described the group with the one-element array above, which no object matches. ([#217](https://github.com/thepartly/reflectapi/pull/217))
- **Flattening a newtype or `#[serde(transparent)]` struct that wraps an enum** generates the same Python model as flattening the enum: its tag and fields sit on the parent, as serde writes them. Python decided by the wrapper's own type, so it generated a nested field the wire never has. Schemas built with `fold_transparent_types()` weren't affected for transparent wrappers. ([#217](https://github.com/thepartly/reflectapi/pull/217))
- A single-field tuple variant whose field has a serde rename, such as `A(#[serde(rename = "foo")] u32)`, is a newtype in OpenAPI variants and Python models, as serde writes it; it was described as an object with a `foo` key. This shipped in 0.24.0 without an entry. ([#212](https://github.com/thepartly/reflectapi/pull/212))

## 0.24.0
Expand Down
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions reflectapi-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "reflectapi-cli"
version = "0.24.0"
version = "0.25.0"
edition = "2021"
default-run = "reflectapi"

Expand All @@ -23,7 +23,7 @@ doc = false
workspace = true

[dependencies]
reflectapi = { path = "../reflectapi", version = "0.24.0", features = ["codegen"] }
reflectapi = { path = "../reflectapi", version = "0.25.0", features = ["codegen"] }
rouille = "3"

clap = { version = "4.5.3", features = ["derive"] }
Expand Down
2 changes: 1 addition & 1 deletion reflectapi-demo/clients/python/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions reflectapi-demo/tests/rust/enum_compatibility/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions reflectapi-demo/tests/rust/field_presence/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions reflectapi-derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "reflectapi-derive"
version = "0.24.0"
version = "0.25.0"
edition = "2021"

license = "Apache-2.0"
Expand All @@ -22,7 +22,7 @@ workspace = true
proc-macro = true

[dependencies]
reflectapi-schema = { path = '../reflectapi-schema', version = "0.24.0" }
reflectapi-schema = { path = '../reflectapi-schema', version = "0.25.0" }

proc-macro2 = "1.0"
quote = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion reflectapi-python-runtime/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "reflectapi-runtime"
version = "0.24.0"
version = "0.25.0"
description = "Runtime library for ReflectAPI Python clients"
readme = "README.md"
requires-python = ">=3.12"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
)
from .types import BatchResult, ReflectapiEmpty, ReflectapiInfallible

__version__ = "0.24.0"
__version__ = "0.25.0"

__all__ = [
# Authentication
Expand Down
2 changes: 1 addition & 1 deletion reflectapi-schema/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "reflectapi-schema"
version = "0.24.0"
version = "0.25.0"
edition = "2021"

license = "Apache-2.0"
Expand Down
6 changes: 3 additions & 3 deletions reflectapi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "reflectapi"
version = "0.24.0"
version = "0.25.0"
edition = "2021"

license = "Apache-2.0"
Expand All @@ -20,8 +20,8 @@ workspace = true

[dependencies]
# workspace dependencies
reflectapi-derive = { path = "../reflectapi-derive", version = "0.24.0" }
reflectapi-schema = { path = "../reflectapi-schema", version = "0.24.0", default-features = false }
reflectapi-derive = { path = "../reflectapi-derive", version = "0.25.0" }
reflectapi-schema = { path = "../reflectapi-schema", version = "0.25.0", default-features = false }

# mandatory 3rd party dependencies
serde = { version = "1.0.197", features = ["derive"] }
Expand Down
Loading