-
Notifications
You must be signed in to change notification settings - Fork 125
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
Swift OpenAPI Generator Fails to Decode anyOf Structure for nullable Field #286
Comments
Just to be safe, can you try with the latest 0.2.x version and the feature flag |
Updated to version 0.2.X, but still seeing the same behavior. updated original description |
@brandonmaul one more question, is your document using OpenAPI 3.0 or 3.1? |
Ah! |
Gotcha, thanks. @mattpolzin what's your read on how that should be represented in OpenAPIKit? I haven't checked yet what we get today. @brandonmaul our of curiosity, why not spell this as a nullable value instead of the anyof? |
@czechboy0 Honestly, that's a great question, and I'm unsure of why it's generated like that. The response model is generated from the definition below. We're using FastAPI with Pydantic for this.
It's my understanding that when Pydantic serialises Python Optional types to OpenAPI, it uses the |
Ah, it's not a hand-written document, I see. I'll try to repro locally and it'll likely be on us to fix. Thanks for reporting! |
Just to speak for OpenAPIKit, I try not to implement this particular kind of heuristic (turning the above into a nullable string instead of leaving it as an anyOf) because I think the sheer number of ways anyOf might be used would make that difficult to do generally and not sustainable if tackled one specific case at a time. The fact that JSON schemas can often represent simpler scenarios as more complex schemas was the motivation behind the OpenAPIKit code that "simplifies" schemas; this code only currently simplifies allOf schemas but I always wanted it to handle anyOf and others as well. I realize I've walked a line here in the past since I do handle e.g. |
I didn't even really answer the question. In OpenAPIKit this is represented as an anyOf with two schemas within it, no funny business. |
Would it be helpful if I applied a change that propagated nullable such that "if any schema in an anyOf is nullable then the parent anyOf is nullable?" That I could do. I do that with allOf. |
Yeah, and remove the null case from the child schemas. I don't think it should stop being an anyof, but it'd be easier if this was parsed as a nullable anyof of a single child schema. Since Swift doesn't have the equivalent of NSNull, I don't think it makes sense to treat null as a separate child schema of the anyof, as I'm not sure what its associated value would even be. |
How about this compromise: I’ll propagate nullable to the root of an anyOf and then swift-openapi-generator can choose to ignore |
Yup that works, it's trivial to filter out the null for us. |
@czechboy0 I'd love to help out in solving this if I can. I've built my career on Swift, but I've not much experience with Open Source software development, especially on a project this big. Any pointers on the what to do to get started? (in this project or OpenAPIKit) |
The necessary change to OpenAPIKit was made with the newest release candidate. This project's package manifest has already been updated to use that version, so the remaining work will be to filter out That may not fully answer the question but I'm not as familiar with the internals of the generator so I'll quit while I'm ahead. 🙂 |
Hi @brandonmaul - a good start is to update the reference tests and create a failing unit test that shows an example of where this doesn't work. Then work backwards. I haven't looked into this yet, so feel free to ask more questions as you investigate this. But Matt is basically right, I think filtering out |
As I was attempting to build the test case (and determine exactly what it is I want to see generated) I ran into a few questions.
this was the generated response:
Im noticing the Another question I have is, why are the null options in the For example,
I don't think I fully understand what the |
Correct, it's a free-form JSON value. Why it's being used for null here? I don't know, that isn't intentional, so warrants further investigation. And I think you're right, if null is one of the types, we should not generate the verify call at the end of the initializer. Seems you're on the right track! In the generated types for the properties, I'd expect only one property, not two. That's where the filtering out of null should come in, but we need to also keep track of null being one of the options so that we skip generating the verify call. |
Awesome! I'll keep investigating. Thanks for the info! |
@czechboy0 what are your thoughts on what the "correct" output should be for the translation?
Basically, ignoring the "null" value entirely, and then later removing the check to make sure at least 1 isn't null. I mulled over the idea of using CFNull or NSNull as the type for "value2", but this seems cleaner. Upon more investigation, I noticed that actually the "null" schema in the anyOf is being translated into the OpenAPIKit JSONSchema.fragment type. Is this correct? I see there's a JSONSchema.null type that I would believe makes more sense. |
Yup that's what I was thinking as well. Filter it out, just keep track of the fact so that you also omit the verify call at the end of the decoding initializer. |
Yeah, that looks like not what I would expect. In fact, it seems contradictory to a test case in OpenAPIKit where I assert that Can you confirm for me that your local copy of OpenAPIKit resolved for swift-package-generator is |
RE keeping track of having filtered it out, this should be one of two viable options, the other one being checking the |
@mattpolzin Yep confirmed, the screenshot of the Xcode window shows that version under the package dependencies on the left |
@brandonmaul ah yes, the screenshot does have that info. thanks. is it possible you are inspecting another part of the same OpenAPI Document in the given screenshot? I notice the schemas under the Assuming this is something like |
I don't think so (although I very well may be wrong). The test case the breakpoint was hit on is located on a test case I added: https://github.com/brandonmaul/swift-openapi-generator/blob/81b0cc0dee366e8a50255f925a2563c87ba7c8a9/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift#L431C2-L431C2 and in the debugger screenshot I think I confirmed it's looking at the "errorCode" property for ResponseSchema. |
@brandonmaul very strange. you've got me stumped; l was able to reproduce your experience by running locally myself and using Xcode to set breakpoints and inspect values. However, I have to conclude that this is a bug with Xcode and/or the Swift language debugging tools, because the following added to the top of your new test case will produce the correct and expected results: let t = """
{
"properties": {
"status": { "type": "string", "title": "Status" },
"timestampUTC": { "type": "string", "title": "Timestamputc" },
"errorCode": {
"anyOf": [{ "type": "integer" }, { "type": "null" }],
"title": "Errorcode"
},
"errorMessage": {
"anyOf": [{ "type": "string" }, { "type": "null" }],
"title": "Errormessage"
},
"data": { "anyOf": [{}, { "type": "null" }], "title": "Data" }
},
"type": "object",
"required": [
"status",
"timestampUTC",
"errorCode",
"errorMessage",
"data"
],
"title": "ResponseSchema"
}
""".data(using: .utf8)!
let u = try JSONDecoder().decode(OpenAPIKit.JSONSchema.self, from: t)
print(String(describing: u)) Specifically, this appears in the String description:
That's a mess to look at, no doubt, but I see an |
Well thats confusing! Okay... I'll proceed assuming the JSONSchemas being evaluated are correct. Thanks so much for taking the time to confirm @mattpolzin! |
Hey there again. So, although Matt proved yesterday that a "normal" decoding of the test case to JSONSchema should provide an core object context with I went to where I think the JSONSchema is originally being decoded. Here's a screenshot showing this: And here's the full output of
Do I need to provide any feature flags or anything when testing? I noticed that the "nullableSchemas" feature flag I'd normally put the generator config isn't a FeatureFlag enum option. There's only "empty". The reason I'm fixated on this OpenAPIKit.JSONSchema.CoreContext nullable property is because I'm almost certain I'll need to use it when the translation is actually performed. Any guidance on how to proceed? |
This is a pretty twisty rabbit hole. I was wrong when I blamed the Swift tooling or Xcode before; the difference was that I was using the Unfortunately, this bug comes from the decoder, not OpenAPIKit. Specifically, this bug is tracked here: jpsim/Yams#301. In that bug ticket's description, it mentions that Yams will parse the string value |
Thanks for the writeup, @mattpolzin - what do you propose we do here? Should OpenAPIKit check for this special case and translate it to |
I'm very conflicted on this one. Unfortunately, the interpretation that Yams gives can also be found in a document that is not valid OpenAPI ( Without downplaying the massive amount of work that went into Yams and how incredibly grateful I am that it exists, I find myself wishing for a good way for consumers of my work (and more directly this generator and other downstream projects) to use a fixed fork of Yams or even a different project. I don't think that desire is very pragmatic, though, unless this generator project would like to spearhead a movement to a particular fork of that project or even depend on a fork of it temporarily (which may perhaps end up being more indefinite if the bug in question is destined to remain open on the upstream project). The bug has purportedly been fixed by someone else already, but I have not independently verified the fix referenced on that GitHub issue. |
Maybe add your take to jpsim/Yams#351? Not sure if there's another way to get attention there. |
I commented on the open PR in hopes that JP notices and has time to reply. I didn't suggest the possibility of a fork right-off because that's a proposed solution under only 1 of three possible scenarios and I wanted to ask which scenario we are looking at here ( (a) the open PR could be merged if conflicts were fixed, (b) JP would like a different solution to the problem, or (c) JP does not intend to merge any PRs solving the associated bug now or in the future). |
JP has merged the fix into Yams and plans to cut a release! Best case outcome. Once that release is cut that should unblock work on this ticket, although both OpenAPIKit and this project will need to accept the next major version of Yams first. |
Great! Marking as a blocker for 1.0. |
Thanks for investigating this deeper guys! Sounds like we have a plan for solving this. Really appreciate it! 😊 |
Marking as blocked by Yams 6. |
Not blocked on us, moving to post 1.0 |
@czechboy0 I have the opposite error with
Generated code tries to decoding in the same order as defined in yaml:
and there are more non-nullable fields in refferenced |
@erikhric You'll need to add the I'd rewrite the above as follows, which will work fine with Swift OpenAPI Generator: VideoPost:
type: object
properties:
text:
type: string
description: The text content of the post
video:
type: object
properties:
data:
$ref: '#/components/schemas/ProcessedVideo'
required:
- data
required:
- video |
When using the Swift OpenAPI Generator and runtime to decode a JSON payload from an API endpoint, a decoding error occurs for a field defined with the anyOf keyword in the OpenAPI spec. The error message is "The anyOf structure did not decode into any child schema."
Here is a simplified snippet of the OpenAPI spec that defines the response schema:
Here is a snippet of the JSON payload received from the API endpoint:
Error Message:
DecodingError: valueNotFound errorCodePayload - at CodingKeys(stringValue: "errorCode", intValue: nil): The anyOf structure did not decode into any child schema. (underlying error: <nil>)
Steps to Reproduce:
Generate Swift code using the provided OpenAPI spec.
Make an API request to receive the JSON payload.
Attempt to decode the JSON payload using the generated Swift code.
Expected Behavior:
The JSON payload should be successfully decoded into the corresponding Swift model.
Actual Behavior:
Decoding fails with the error message mentioned above.
Environment:
Swift OpenAPI Generator version: 0.2.2
Swift version: 5.9
Runtime: 0.2.3
OpenAPI Spec version: 3.1.0
OpenAPI Generator config:
The text was updated successfully, but these errors were encountered: