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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## Unreleased

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

Generated clients keep decoding responses after the server removes a deprecated field, and Python clients reject a `None` the server can't read before sending it. TypeScript and Rust code that reads a deprecated response field must now handle its absence; see Breaking.
Expand Down
22 changes: 3 additions & 19 deletions reflectapi-demo/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/myapi.proto.UnauthorizedError"
"$ref": "#/components/schemas/std.tuple.Tuple0"
}
}
}
Expand Down Expand Up @@ -83,7 +83,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/myapi.proto.PetsCreateRequest"
"$ref": "#/components/schemas/myapi.model.input.Pet"
}
}
},
Expand Down Expand Up @@ -292,7 +292,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/myapi.proto.UnauthorizedError"
"$ref": "#/components/schemas/std.tuple.Tuple0"
}
}
}
Expand Down Expand Up @@ -816,14 +816,6 @@
}
]
},
"myapi.proto.PetsCreateRequest": {
"type": "array",
"prefixItems": [
{
"$ref": "#/components/schemas/myapi.model.input.Pet"
}
]
},
"myapi.proto.PetsListError": {
"oneOf": [
{
Expand Down Expand Up @@ -998,14 +990,6 @@
}
}
},
"myapi.proto.UnauthorizedError": {
"type": "array",
"prefixItems": [
{
"$ref": "#/components/schemas/std.tuple.Tuple0"
}
]
},
"myapi.proto.ValidationA": {
"type": "object",
"title": "myapi.proto.ValidationA",
Expand Down
29 changes: 29 additions & 0 deletions reflectapi-demo/src/tests/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1832,3 +1832,32 @@ struct TestWrapperWithNestedTypevarArg<I> {
fn test_generic_flatten_typevar_nested_in_generic_arg() {
assert_snapshot!(TestWrapperWithNestedTypevarArg<TestFlattenInner>);
}

#[test]
fn test_flatten_newtype_struct() {
#[derive(reflectapi::Input, reflectapi::Output, serde::Deserialize, serde::Serialize)]
struct Inner {
a: u32,
b: String,
}
#[derive(reflectapi::Input, reflectapi::Output, serde::Deserialize, serde::Serialize)]
struct Wrapper(Inner);
#[derive(reflectapi::Input, reflectapi::Output, serde::Deserialize, serde::Serialize)]
struct OptionalInner {
c: u32,
}
#[derive(reflectapi::Input, reflectapi::Output, serde::Deserialize, serde::Serialize)]
struct OptionalWrapper(OptionalInner);
// Serde writes a flattened newtype struct as the keys of the struct it
// wraps: `{"x": 1, "a": 2, "b": "s"}`, with `"c"` only when `o` is `Some`.
#[derive(reflectapi::Input, reflectapi::Output, serde::Deserialize, serde::Serialize)]
struct TestFlattenNewtypeStruct {
x: u32,
#[serde(flatten)]
w: Wrapper,
#[serde(flatten)]
o: Option<OptionalWrapper>,
}

assert_snapshot!(TestFlattenNewtypeStruct);
}

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

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

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

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

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

Loading
Loading