Skip to content

Commit 8e67619

Browse files
test(ci): align compatibility fixtures with generated models
1 parent ee2c108 commit 8e67619

36 files changed

Lines changed: 2637 additions & 551 deletions

File tree

examples/server-anthropic-messages/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ fn messages_unary() -> MessagesPostResponse {
5858
content: vec![gen::ContentBlock::TextBlock(gen::ResponseTextBlock {
5959
citations: None,
6060
text: "hello (unary)".into(),
61+
r#type: gen::ResponseTextBlockType::Text,
6162
})],
6263
id: "msg_demo".into(),
6364
model: gen::Model::Custom("claude-demo".into()),

examples/server-openai-responses/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Example: host a perfect-replica of OpenAI's `POST /v1/responses`.
22
//!
33
//! Exercises both branches of the typed response enum:
4-
//! - `body.stream == Some(true)` → `OkStream(Sse<...>)`
4+
//! - `body.stream.flatten().unwrap_or(false)` → `OkStream(Sse<...>)`
55
//! - otherwise → `Ok(Response)` (single JSON body)
66
//!
77
//! Run:
@@ -35,7 +35,7 @@ struct AppState;
3535
#[async_trait::async_trait]
3636
impl ResponsesApi for AppState {
3737
async fn create_response(&self, body: CreateResponse) -> CreateResponseResponse {
38-
if body.stream == Some(true) {
38+
if body.stream.flatten().unwrap_or(false) {
3939
create_response_streaming()
4040
} else {
4141
create_response_unary(body)

src/snapshots/openapi_to_rust__test_helpers__array_item_enum_disambiguation.snap

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,61 +16,61 @@ pub struct Outer {
1616
#[serde(skip_serializing_if = "Option::is_none")]
1717
pub nested: Option<OuterNested>,
1818
#[serde(skip_serializing_if = "Option::is_none")]
19-
pub tags: Option<Vec<OuterTagsItemRED>>,
19+
pub tags: Option<Vec<OuterTagsItem>>,
2020
}
2121
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
22-
pub enum OuterTagsItemRED {
22+
pub enum OuterTagsItem {
2323
#[default]
2424
#[serde(rename = "RED")]
2525
Red,
2626
#[serde(rename = "BLUE")]
2727
Blue,
2828
}
29-
impl OuterTagsItemRED {
29+
impl OuterTagsItem {
3030
pub fn as_str(&self) -> &'static str {
3131
match self {
3232
Self::Red => "RED",
3333
Self::Blue => "BLUE",
3434
}
3535
}
3636
}
37-
impl ::std::fmt::Display for OuterTagsItemRED {
37+
impl ::std::fmt::Display for OuterTagsItem {
3838
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
3939
f.write_str(self.as_str())
4040
}
4141
}
42-
impl AsRef<str> for OuterTagsItemRED {
42+
impl AsRef<str> for OuterTagsItem {
4343
fn as_ref(&self) -> &str {
4444
self.as_str()
4545
}
4646
}
4747
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
4848
pub struct OuterNested {
4949
#[serde(skip_serializing_if = "Option::is_none")]
50-
pub tags: Option<Vec<OuterTagsItem>>,
50+
pub tags: Option<Vec<OuterNestedTagsItem>>,
5151
}
5252
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Default)]
53-
pub enum OuterTagsItem {
53+
pub enum OuterNestedTagsItem {
5454
#[default]
5555
#[serde(rename = "HOT")]
5656
Hot,
5757
#[serde(rename = "COLD")]
5858
Cold,
5959
}
60-
impl OuterTagsItem {
60+
impl OuterNestedTagsItem {
6161
pub fn as_str(&self) -> &'static str {
6262
match self {
6363
Self::Hot => "HOT",
6464
Self::Cold => "COLD",
6565
}
6666
}
6767
}
68-
impl ::std::fmt::Display for OuterTagsItem {
68+
impl ::std::fmt::Display for OuterNestedTagsItem {
6969
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
7070
f.write_str(self.as_str())
7171
}
7272
}
73-
impl AsRef<str> for OuterTagsItem {
73+
impl AsRef<str> for OuterNestedTagsItem {
7474
fn as_ref(&self) -> &str {
7575
self.as_str()
7676
}

src/snapshots/openapi_to_rust__test_helpers__array_union_items.snap

Lines changed: 153 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,38 @@ impl serde::Serialize for ToolsRequestToolsItemUnion {
3939
),
4040
)
4141
})?;
42-
object
43-
.insert(
44-
"type".to_string(),
45-
serde_json::Value::String("text".to_string()),
46-
);
42+
match object.get("type") {
43+
Some(
44+
serde_json::Value::String(tag),
45+
) if matches!(tag.as_str(), "text") => {}
46+
Some(serde_json::Value::String(tag)) => {
47+
return Err(
48+
serde::ser::Error::custom(
49+
format!(
50+
"discriminator `{}` value `{tag}` is not valid for variant `{}`",
51+
"type", stringify!(TextTool),
52+
),
53+
),
54+
);
55+
}
56+
Some(_) => {
57+
return Err(
58+
serde::ser::Error::custom(
59+
concat!(
60+
"discriminator `", "type",
61+
"` did not serialize as a string",
62+
),
63+
),
64+
);
65+
}
66+
None => {
67+
object
68+
.insert(
69+
"type".to_string(),
70+
serde_json::Value::String("text".to_string()),
71+
);
72+
}
73+
}
4774
value.serialize(serializer)
4875
}
4976
Self::CodeTool(payload) => {
@@ -59,11 +86,38 @@ impl serde::Serialize for ToolsRequestToolsItemUnion {
5986
),
6087
)
6188
})?;
62-
object
63-
.insert(
64-
"type".to_string(),
65-
serde_json::Value::String("code".to_string()),
66-
);
89+
match object.get("type") {
90+
Some(
91+
serde_json::Value::String(tag),
92+
) if matches!(tag.as_str(), "code") => {}
93+
Some(serde_json::Value::String(tag)) => {
94+
return Err(
95+
serde::ser::Error::custom(
96+
format!(
97+
"discriminator `{}` value `{tag}` is not valid for variant `{}`",
98+
"type", stringify!(CodeTool),
99+
),
100+
),
101+
);
102+
}
103+
Some(_) => {
104+
return Err(
105+
serde::ser::Error::custom(
106+
concat!(
107+
"discriminator `", "type",
108+
"` did not serialize as a string",
109+
),
110+
),
111+
);
112+
}
113+
None => {
114+
object
115+
.insert(
116+
"type".to_string(),
117+
serde_json::Value::String("code".to_string()),
118+
);
119+
}
120+
}
67121
value.serialize(serializer)
68122
}
69123
}
@@ -75,30 +129,99 @@ impl<'de> serde::Deserialize<'de> for ToolsRequestToolsItemUnion {
75129
D: serde::Deserializer<'de>,
76130
{
77131
let value = serde_json::Value::deserialize(deserializer)?;
78-
let discriminator = value
79-
.get("type")
80-
.and_then(serde_json::Value::as_str)
81-
.ok_or_else(|| serde::de::Error::custom(
82-
concat!("missing string discriminator `", "type", "`",),
83-
))?
84-
.to_string();
85-
match discriminator.as_str() {
86-
"text" => {
87-
serde_json::from_value::<TextTool>(value)
88-
.map(Self::TextTool)
89-
.map_err(serde::de::Error::custom)
132+
let discriminator = match value.get("type") {
133+
Some(serde_json::Value::String(discriminator)) => {
134+
Some(discriminator.as_str())
90135
}
91-
"code" => {
92-
serde_json::from_value::<CodeTool>(value)
93-
.map(Self::CodeTool)
94-
.map_err(serde::de::Error::custom)
136+
Some(_) => {
137+
return Err(
138+
serde::de::Error::custom(
139+
concat!("non-string discriminator `", "type", "`",),
140+
),
141+
);
142+
}
143+
None => None,
144+
};
145+
match discriminator {
146+
Some(discriminator) => {
147+
match discriminator {
148+
"text" => {
149+
let primary_error = match serde_json::from_value::<
150+
TextTool,
151+
>(value.clone()) {
152+
Ok(payload) => return Ok(Self::TextTool(payload)),
153+
Err(error) => error,
154+
};
155+
let mut structural_match: Option<(Self, &'static str)> = None;
156+
if let Ok(payload) = serde_json::from_value::<
157+
CodeTool,
158+
>(value.clone()) {
159+
if let Some((_, first_name)) = &structural_match {
160+
return Err(
161+
serde::de::Error::custom(
162+
format!(
163+
"discriminator `{}` value `{}` did not fit its mapped branch and structurally matched both `{}` and `{}`",
164+
"type", "text", first_name, stringify!(CodeTool),
165+
),
166+
),
167+
);
168+
}
169+
structural_match = Some((
170+
Self::CodeTool(payload),
171+
stringify!(CodeTool),
172+
));
173+
}
174+
match structural_match {
175+
Some((payload, _)) => Ok(payload),
176+
None => Err(serde::de::Error::custom(primary_error)),
177+
}
178+
}
179+
"code" => {
180+
let primary_error = match serde_json::from_value::<
181+
CodeTool,
182+
>(value.clone()) {
183+
Ok(payload) => return Ok(Self::CodeTool(payload)),
184+
Err(error) => error,
185+
};
186+
let mut structural_match: Option<(Self, &'static str)> = None;
187+
if let Ok(payload) = serde_json::from_value::<
188+
TextTool,
189+
>(value.clone()) {
190+
if let Some((_, first_name)) = &structural_match {
191+
return Err(
192+
serde::de::Error::custom(
193+
format!(
194+
"discriminator `{}` value `{}` did not fit its mapped branch and structurally matched both `{}` and `{}`",
195+
"type", "code", first_name, stringify!(TextTool),
196+
),
197+
),
198+
);
199+
}
200+
structural_match = Some((
201+
Self::TextTool(payload),
202+
stringify!(TextTool),
203+
));
204+
}
205+
match structural_match {
206+
Some((payload, _)) => Ok(payload),
207+
None => Err(serde::de::Error::custom(primary_error)),
208+
}
209+
}
210+
other => {
211+
Err(
212+
serde::de::Error::custom(
213+
format!(
214+
"unknown discriminator value `{other}` for `{}`", "type",
215+
),
216+
),
217+
)
218+
}
219+
}
95220
}
96-
other => {
221+
None => {
97222
Err(
98223
serde::de::Error::custom(
99-
format!(
100-
"unknown discriminator value `{other}` for `{}`", "type",
101-
),
224+
concat!("missing string discriminator `", "type", "`",),
102225
),
103226
)
104227
}

0 commit comments

Comments
 (0)