-
-
Notifications
You must be signed in to change notification settings - Fork 7.4k
[Rust-Axum] Support AnyOf, AllOf #21948
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Kindly ping @wing328 to review. |
|
Please ignore current error: python-lazyImports samples have not been updated yet. |
|
Thanks for working on this so quickly @linxGnu - I tested it with my use-case. Looks like I'm no longer getting However, I can see a new issue - I'm looking at generating a server against the OpenAI API spec (https://github.com/openai/openai-openapi), and running the following command in order to generate it against the binaries built from this branch. java -jar ../openapi-generator/modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -i openai.yml -g rust-axum -o ./generated --additional-properties=packageName=generated --openapi-normalizer FILTER="operationId:listModels|retrieveModel|createChatCompletion" --skip-validate-specOne concern I had was that the stable release of openapi-generator-cli is on my $PATH - could that cause the problem below? You'll note that the output of this command generates models for createChatCompletion, one of which contains: I believe the confounding factor here is that the CreateChatCompletionRequest object has only one parameter - an allOf that comprises
|
|
cc @frol (2017/07) @farcaller (2017/08) @richardwhiuk (2019/07) @paladinzh (2020/05) @jacob-pro (2022/10) @dsteeley (2025/07) |
|
@jacob-mink-1996 |
|
@linxGnu at first glance, the generated code looks model-wise correct! Types have been splatted in appropriately. However, I'm seeing lots of errors related to unresolved names: impl TranscriptTextDeltaEvent {
fn _name_for_r#type() -> String {
String::from("TranscriptTextDeltaEvent")
}
fn _serialize_r#type<S>(_: &String, s: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
s.serialize_str(&Self::_name_for_r#type())
}
}Edit: a little further research - looks like r#IDENTIFIER is legitimate, learn something new every day. However, it's showing up in places where it probably should not, e.g. Additionally, I found some models and structs like: #[derive(Default)]
#[allow(dead_code)]
struct IntermediateRep {
pub r#type: Vec<String>,
pub text: Vec<String>,
pub logprobs: Vec<Vec<models::TranscriptTextDeltaEventLogprobsInner>>,
pub usage: Vec<models::TranscriptTextUsageTokens>,
pub r#type: Vec<String>,
}Here, TypeA And type B is much the same. The duplicate r#type field is breaking things here. I'm curious how other languages would have dealt with this. |
|
@jacob-mink-1996 please checkout my latest commit bb922e4 I have just patched However, there is one compilation error left: validate(nested). I will try fixing it tomorrow. |
|
Generated code for https://github.com/openai/openai-openapi works well for me. Please check out latest commit fa93897 and kindly let me know if that works for you as well. |
|
Getting it built now @linxGnu! Thanks for your support on this, really appreciate how fast you're working. I'll see if the compilation error is something I can manually handle in my generated code and get back to you. |
This comment was marked as duplicate.
This comment was marked as duplicate.
|
@linxGnu
TypeA:
anyOf:
- $ref: AnotherTypeFor now, I'll fix this by adding another item in the spec under this anyOf to get by. TypeA:
anyOf:
- $ref: AnotherType
- type: string
title: Never
description: A field I added to trick the generatorExpected behavior is that TypeA would be an enum like pub enum TypeA {
AnotherType(models::AnotherType)
}
ChatCompletionRequestMessage:
anyOf:
- $ref: '#/components/schemas/ChatCompletionRequestDeveloperMessage'
- $ref: '#/components/schemas/ChatCompletionRequestSystemMessage'
- $ref: '#/components/schemas/ChatCompletionRequestUserMessage'
- $ref: '#/components/schemas/ChatCompletionRequestAssistantMessage'
- $ref: '#/components/schemas/ChatCompletionRequestToolMessage'
- $ref: '#/components/schemas/ChatCompletionRequestFunctionMessage'I would expect that the discriminator used by #[derive(Debug, Clone, PartialEq, serde::Deserialize)]
#[serde(tag = "role")]
#[allow(non_camel_case_types)]
pub enum ChatCompletionRequestMessage {
ChatCompletionRequestDeveloperMessage(models::ChatCompletionRequestDeveloperMessage),
ChatCompletionRequestSystemMessage(models::ChatCompletionRequestSystemMessage),
ChatCompletionRequestUserMessage(models::ChatCompletionRequestUserMessage),
ChatCompletionRequestAssistantMessage(models::ChatCompletionRequestAssistantMessage),
ChatCompletionRequestToolMessage(models::ChatCompletionRequestToolMessage),
ChatCompletionRequestFunctionMessage(models::ChatCompletionRequestFunctionMessage),
}However, that creates a problem for the user of the library, who actually expects this field to be a SPECIFIC enum value. role:
type: string
enum:
- developer
description: The role of the messages author, in this case `developer`.
x-stainless-const: trueI think if the serde tag is a specific field and that field is an enum with one value in each option, then the generated code should accept that enum value. i.e. right now my code from python to the server looks like messages=[
{ 'role': 'ChatCompletionRequestSystemMessage', 'content': 'You are a pirate who always responds in pirate-speak. You should mention your love of the sea and treasure whenever possible.'},
{ 'role': 'ChatCompletionRequestUserMessage', 'content': 'Hello, how are you?'}
]I would like the e.g. #[derive(Debug, Clone, PartialEq, serde::Deserialize)]
#[serde(tag = "role")]
#[allow(non_camel_case_types)]
pub enum ChatCompletionRequestMessage {
#[serde(rename = "developer")]
ChatCompletionRequestDeveloperMessage(models::ChatCompletionRequestDeveloperMessage),
#[serde(rename = "system")]
ChatCompletionRequestSystemMessage(models::ChatCompletionRequestSystemMessage),
#[serde(rename = "user")]
ChatCompletionRequestUserMessage(models::ChatCompletionRequestUserMessage),
#[serde(rename = "assistant")]
ChatCompletionRequestAssistantMessage(models::ChatCompletionRequestAssistantMessage),
#[serde(rename = "tool")]
ChatCompletionRequestToolMessage(models::ChatCompletionRequestToolMessage),
#[serde(rename = "function")]
ChatCompletionRequestFunctionMessage(models::ChatCompletionRequestFunctionMessage),
} |
|
Concern 1:
Yes, you are right. I can reproduce at my side schemas:
Hello:
type: object
properties:
g:
$ref: "#/components/schemas/Gum"
Gum:
anyOf:
- $ref: "#/components/schemas/Bubble"
Bubble:
type: object
properties:
pop:
type: stringGenerated Code: pub struct Gum { // Blank }
pub struct Bubble {
pub pop: Option<String>,
}
pub struct Hello {
pub g: Option<models::Bubble>,
}@wing328 Please correct me if I am wrong, seem like there is a hidden optimization inside DefaultCodegen when In my example, |
it's done in openapi normalizer with the rule |
|
That's awesome. Thank you for your insight @wing328 @jacob-mink-1996 as shared by @wing328 above:
|
|
For your question 2, I don't see any problem. Specs: ChatCompletionRequestMessage:
oneOf:
- $ref: "#/components/schemas/ChatCompletionRequestDeveloperMessage"
- $ref: "#/components/schemas/ChatCompletionRequestSystemMessage"
...Generated code: #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
#[allow(non_camel_case_types)]
pub enum ChatCompletionRequestMessage {
ChatCompletionRequestDeveloperMessage(models::ChatCompletionRequestDeveloperMessage),
ChatCompletionRequestSystemMessage(models::ChatCompletionRequestSystemMessage),
...
}Let's analyze:
However, thank to comprehensive features of OpenAPI Generator, here is the solution, specially for your case:
ChatCompletionRequestDeveloperMessage:
properties:
role:
type: string
enum:
- developer
description: The role of the messages author, in this case `developer`.
pub struct ChatCompletionRequestDeveloperMessage {
#[serde(rename = "role")]
#[validate(nested)]
pub role: models::ChatCompletionRequestDeveloperMessageRole,
...
}
pub enum ChatCompletionRequestDeveloperMessageRole {
#[serde(rename = "developer")]
Developer,
} |
|
Thank you @linxGnu - I believe in your latest, the |
|
@jacob-mink-1996 could you please share your build error and ensure that you are building openapi codegen using my latest commit 5d96de6. Noted:
Command: |
|
@linxGnu thanks for the info. Looks like my environment was dirty - all is well. Not that I'm a maintainer, but I'm signing off at least for my use case :) Thanks again for your hard work here. Another question that may not be related (I can make a new issue if need be). For an endpoint that has more than one response type specified, shouldn't the oneOf mechanism be applied to those when generating the api response types, as well? e.g. post:
operationId: createChatCompletion
tags:
- Chat
summary: Create chat completion
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateChatCompletionRequest'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/CreateChatCompletionResponse'
text/event-stream:
schema:
$ref: '#/components/schemas/CreateChatCompletionStreamResponse'would yield an enum like // in api
pub enum CreateChatCompletionResponse {
Status200_OK(models::NewEnumNotGenerated)
}
// in models
pub enum NewEnumNotGenerated {
ApplicationJson(models::CreateChatCompletionResponse),
EventStream(models::CreateChatCompletionStreamResponse),
}If that's a different concept than the one you are working on in this PR, I'll make a new ticket. |
|
@jacob-mink-1996 yes, it's another matter, please create a new ticket for it. Just fyi, only |
|
@wing328 I think it's ready to review & merge. Please ignore CI Error due to Rust Servers* (not related to Rust Axum) |
|
@linxGnu Tried with your very latest. The enum for ChatCompletionRequestMessage is not working on the latest - it is still generating #[derive(Debug, Clone, PartialEq, serde::Deserialize)]
#[serde(tag = "role")]
#[allow(non_camel_case_types, clippy::large_enum_variant)]
pub enum ChatCompletionRequestMessage {
ChatCompletionRequestDeveloperMessage(models::ChatCompletionRequestDeveloperMessage),
ChatCompletionRequestSystemMessage(models::ChatCompletionRequestSystemMessage),
ChatCompletionRequestUserMessage(models::ChatCompletionRequestUserMessage),
ChatCompletionRequestAssistantMessage(models::ChatCompletionRequestAssistantMessage),
ChatCompletionRequestToolMessage(models::ChatCompletionRequestToolMessage),
ChatCompletionRequestFunctionMessage(models::ChatCompletionRequestFunctionMessage),
}and not placing any FYR the command I was trying to use is java -jar ../openapi-generator/modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -i ../spec/openai.yml -g rust-axum -o server_generated --additional-properties=packageName=server_generated --openapi-normalizer FILTER="operationId:listModels|retrieveModel|createChatCompletion",SIMPLIFY_ONEOF_ANYOF=false --enable-post-process-file --inline-schema-options RESOLVE_INLINE_ENUMS=trueNOTE: after manual modification, the only way I could get the enum to deserialize correctly was by making it |
|
There is another issue
What was fixed
TL;DR
|
|
@jacob-mink-1996 Do you have any other concerns |
|
I'll merge it before the release this weekend. Thanks for the PR Have a nice weekend |
|
Thank you @wing328 |
* Support AnyOf, AllOf * Update * Fix * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update


PR checklist
Commit all changed files.
This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
These must match the expectations made by your contribution.
You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example
./bin/generate-samples.sh bin/configs/java*.IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
master(upcoming7.x.0minor release - breaking changes with fallbacks),8.0.x(breaking changes without fallbacks)"fixes #123"present in the PR description)