Open
Description
Question
Hello,
I'm writing a client with an openapi.yml that frequently uses nullable ref properties, as below:
// Technique 1
my_field:
nullable: true
allof:
- $ref: '#/components/schemas/MyField'
An alternative technique, AFAIK, is:
// Technique 2
my_field:
oneOf:
- $ref: '#/components/schemas/MyField'
- type: 'null'
With both techniques, the generated code is not quite easy to deal with, both when decoding responses or encoding requests. The generated payload is a struct that has a non-semantic value1
property, or an enum that does not expose any accessor for extracting its non-null value.
// Technique 1
public struct my_fieldPayload: Codable, Hashable, Sendable {
public var value1: Components.Schemas.MyField
public init(value1: Components.Schemas.MyField) {
self.value1 = value1
}
}
// Technique 2
@frozen public enum my_fieldPayload: Codable, Hashable, Sendable {
case MyField(Components.Schemas.MyField)
case case2(OpenAPIRuntime.OpenAPIValueContainer)
}
We're far from the expected Swift optionals. The struct generated by the technique 1 is surprisingly shallow.
Did any member in the community meet the same problem? Is there any workaround? Or a better YAML technique for specifying a nullable ref, while preserving good Swift ergonomics? Maybe I'm just holding it wrong?
Metadata
Metadata
Assignees
Type
Projects
Relationships
Development
No branches or pull requests
Activity
czechboy0 commentedon Jan 24, 2024
This looks a bit related to a question from just yesterday: #512
What is the use case? What are the HTTP semantics you're trying to express?
Note that
nullable
is only OpenAPI 3.0 syntax, and adding anull
to types is the equivalent in OpenAPI 3.1. So which you use depends on the version of the OpenAPI specification.groue commentedon Jan 26, 2024
Yes: in both issues the OpenAPI spec describes optionality with a pattern, and the generated code creates the desire to share some remarks.
Two such patterns are described in this issue, and another one in #512. I guess there are others.
The JSON api uses some objects in requests and responses. Those objects are defined in the
components/schemas
section of the spec, for reusability. Some properties of those objects are defined with$ref
to#/components/schemas/Whatever
. Some of those properties can be null in the JSON payload. Some of those properties are required, some are not. Nullability and requirement are orthogonal concerns, because requirement describes the presence of the key, and nullability describes the validity of anull
value. (I know you know all of this).Let's consider a first spec that tries to use all four combinations of requirement/nullability:
OpenAPIGenerator 1.2.0 generates a
Parent
struct wherechild1
(required nullable) is not a Swift optional, andchild2
can not distinguish between missing and present and null.Another attempt, with ONE of the patterns for nullable refs:
This generates:
This API is not easy to use because of
value1
.It's still not possible to express the difference between a
child2
key that is missing, or present with the value null.Another attempt, with another patterns for nullable refs:
This API is not easy to use again.
And it's still not possible to express the difference between a
child2
key that is missing, or present with the value null.In the end:
Ideally, I'd have one of:
groue commentedon Jan 26, 2024
I'm now discovering that changing
openapi: 3.0.0
toopenapi: 3.1.0
introduces some breaking changes in the generated code with thenullable: true, allOf: - $ref: '...'
pattern:3.0.0
3.1.0
I need to strengthen my knowledge of OpenAPI :-/
groue commentedon Jan 26, 2024
My suggestions are such a breaking change that I can not reasonably hope anything like that.
But the ergonomics of the generated code would be enhanced if the generator would recognize frequent patterns, and have the payload types adopt protocols that enhance their ergonomics. This could be done in a non-breaking way. The generator could even be enhanced over time by recognizing more and more patterns, decorating the generated more payload types with more protocols.
czechboy0 commentedon Jan 26, 2024
Thanks @groue for the detail, it really helps me understand what you're trying to do.
Unfortunately, I have some bad news:
In Swift OpenAPI Generator, we do not make the distinction between a missing value and a null value, both are represented as an optional value. What's more, if a field is both nullable and not required, we condense the two optionals into one, so you never get
String??
, you only ever getString?
.The main reason we don't have that distinction is because we use the platform JSONDecoder/JSONEncoder and Codable, which don't make that distinction. If we wanted to provide that distinction, it'd basically mean reimplementing all those from scratch in this project, which we decided against.
Note that the nullability syntax changed between OpenAPI 3.0 and 3.1.
OpenAPI 3.0:
OpenAPI 3.1:
With this in mind, I think you can get some of the distinction with an explicit oneOf? But I haven't tested it myself, just a potential direction of exploration.
brandonbloom commentedon Mar 30, 2024
I too am surprised to discovered that the following does not produce an Optional:
type: 'null'
asOpenAPIValueContainer
#557Support {oneOf: [{type: 'null'}, ...]} as optional
Support {oneOf: [{type: 'null'}, ...]} as optional
brandonbloom commentedon Apr 2, 2024
PR #558 contains a proposed improvement for "technique 2" discussed in this thread. If it's accepted, can look into technique 1 as well.