From 3239435d9b72e23d047c31d6e860b544702fa7f8 Mon Sep 17 00:00:00 2001 From: Andrey Konstantinov Date: Tue, 10 Nov 2026 05:34:26 +0000 Subject: [PATCH] chore: prepare 0.25.0 release --- CHANGELOG.md | 18 ++++++++++-------- Cargo.lock | 8 ++++---- reflectapi-cli/Cargo.toml | 4 ++-- reflectapi-demo/clients/python/uv.lock | 2 +- .../tests/rust/enum_compatibility/Cargo.lock | 6 +++--- .../tests/rust/field_presence/Cargo.lock | 6 +++--- reflectapi-derive/Cargo.toml | 4 ++-- reflectapi-python-runtime/pyproject.toml | 2 +- .../src/reflectapi_runtime/__init__.py | 2 +- reflectapi-schema/Cargo.toml | 2 +- reflectapi/Cargo.toml | 6 +++--- 11 files changed, 31 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4802dd1a..5fd5736c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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>`. 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>`. 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` 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` 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 diff --git a/Cargo.lock b/Cargo.lock index f432e296..7e4855d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1653,7 +1653,7 @@ dependencies = [ [[package]] name = "reflectapi" -version = "0.24.0" +version = "0.25.0" dependencies = [ "anyhow", "axum", @@ -1682,7 +1682,7 @@ dependencies = [ [[package]] name = "reflectapi-cli" -version = "0.24.0" +version = "0.25.0" dependencies = [ "anyhow", "clap", @@ -1746,7 +1746,7 @@ dependencies = [ [[package]] name = "reflectapi-derive" -version = "0.24.0" +version = "0.25.0" dependencies = [ "proc-macro-error", "proc-macro2", @@ -1758,7 +1758,7 @@ dependencies = [ [[package]] name = "reflectapi-schema" -version = "0.24.0" +version = "0.25.0" dependencies = [ "glob", "serde", diff --git a/reflectapi-cli/Cargo.toml b/reflectapi-cli/Cargo.toml index 5f874cea..ef9dcdc6 100644 --- a/reflectapi-cli/Cargo.toml +++ b/reflectapi-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reflectapi-cli" -version = "0.24.0" +version = "0.25.0" edition = "2021" default-run = "reflectapi" @@ -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"] } diff --git a/reflectapi-demo/clients/python/uv.lock b/reflectapi-demo/clients/python/uv.lock index 54779146..8d0193db 100644 --- a/reflectapi-demo/clients/python/uv.lock +++ b/reflectapi-demo/clients/python/uv.lock @@ -346,7 +346,7 @@ dev = [ [[package]] name = "reflectapi-runtime" -version = "0.24.0" +version = "0.25.0" source = { editable = "../../../reflectapi-python-runtime" } dependencies = [ { name = "anyio" }, diff --git a/reflectapi-demo/tests/rust/enum_compatibility/Cargo.lock b/reflectapi-demo/tests/rust/enum_compatibility/Cargo.lock index 16f3439f..6bf8a90d 100644 --- a/reflectapi-demo/tests/rust/enum_compatibility/Cargo.lock +++ b/reflectapi-demo/tests/rust/enum_compatibility/Cargo.lock @@ -348,7 +348,7 @@ dependencies = [ [[package]] name = "reflectapi" -version = "0.24.0" +version = "0.25.0" dependencies = [ "anyhow", "bytes", @@ -366,7 +366,7 @@ dependencies = [ [[package]] name = "reflectapi-derive" -version = "0.24.0" +version = "0.25.0" dependencies = [ "proc-macro-error", "proc-macro2", @@ -387,7 +387,7 @@ dependencies = [ [[package]] name = "reflectapi-schema" -version = "0.24.0" +version = "0.25.0" dependencies = [ "glob", "serde", diff --git a/reflectapi-demo/tests/rust/field_presence/Cargo.lock b/reflectapi-demo/tests/rust/field_presence/Cargo.lock index a0fb265d..99856875 100644 --- a/reflectapi-demo/tests/rust/field_presence/Cargo.lock +++ b/reflectapi-demo/tests/rust/field_presence/Cargo.lock @@ -312,7 +312,7 @@ dependencies = [ [[package]] name = "reflectapi" -version = "0.24.0" +version = "0.25.0" dependencies = [ "bytes", "futures-util", @@ -327,7 +327,7 @@ dependencies = [ [[package]] name = "reflectapi-derive" -version = "0.24.0" +version = "0.25.0" dependencies = [ "proc-macro-error", "proc-macro2", @@ -348,7 +348,7 @@ dependencies = [ [[package]] name = "reflectapi-schema" -version = "0.24.0" +version = "0.25.0" dependencies = [ "glob", "serde", diff --git a/reflectapi-derive/Cargo.toml b/reflectapi-derive/Cargo.toml index 27b52761..86c34b78 100644 --- a/reflectapi-derive/Cargo.toml +++ b/reflectapi-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reflectapi-derive" -version = "0.24.0" +version = "0.25.0" edition = "2021" license = "Apache-2.0" @@ -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" diff --git a/reflectapi-python-runtime/pyproject.toml b/reflectapi-python-runtime/pyproject.toml index 2872bc0e..6374ed70 100644 --- a/reflectapi-python-runtime/pyproject.toml +++ b/reflectapi-python-runtime/pyproject.toml @@ -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" diff --git a/reflectapi-python-runtime/src/reflectapi_runtime/__init__.py b/reflectapi-python-runtime/src/reflectapi_runtime/__init__.py index a2b6c999..050489f3 100644 --- a/reflectapi-python-runtime/src/reflectapi_runtime/__init__.py +++ b/reflectapi-python-runtime/src/reflectapi_runtime/__init__.py @@ -58,7 +58,7 @@ ) from .types import BatchResult, ReflectapiEmpty, ReflectapiInfallible -__version__ = "0.24.0" +__version__ = "0.25.0" __all__ = [ # Authentication diff --git a/reflectapi-schema/Cargo.toml b/reflectapi-schema/Cargo.toml index 50245792..48cdc56e 100644 --- a/reflectapi-schema/Cargo.toml +++ b/reflectapi-schema/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reflectapi-schema" -version = "0.24.0" +version = "0.25.0" edition = "2021" license = "Apache-2.0" diff --git a/reflectapi/Cargo.toml b/reflectapi/Cargo.toml index 75503d20..a23047c7 100644 --- a/reflectapi/Cargo.toml +++ b/reflectapi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reflectapi" -version = "0.24.0" +version = "0.25.0" edition = "2021" license = "Apache-2.0" @@ -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"] }