-
Notifications
You must be signed in to change notification settings - Fork 129
Support nullable values #82
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
Comments
Hi @m4p, thanks for taking the time to file the issue. Could you attach a short snippet of OpenAPI (ideally YAML) that shows more of the context of how you use nullable values? My understanding is that object properties not being required, and a schema being nullable, are similar in the sense that we want an optional value in Swift. (@mattpolzin can add more background here). With the OpenAPI snippet, I'm also curious how you think the generated Swift code should change, to help us understand how and whether we need to treat non-required object properties and nullable schemas differently. Thanks! 🙏 |
Sure thing. I'm working with this third-party spec. I can try making the types non-required and see if that has the desired effects. |
Yeah, removing nullable values from required, results in the code working as expected, so it may be an issue with the quality of the spec. |
I believe it's legal to have a required property marked as nullable, it's just a question of how to represent that well in Swift, where struct Foo: Decodable {
var foo: String?
} I suspect this is a genuine thing we should handle in Swift OpenAPI Generator, by not just checking whether a property schema is required, but also whether it's nullable. Just to confirm my suspicion, the code that emits the error you had in the issue description, is that property generated as |
Yeah, required nullable (and all 3 other permutations) are all valid. In my own API development I've even used these permutations to drive home the semantics of the API even though a client might handle "null" in the same way as the given property being omitted. For this reason, I've always preferred to distinguish between a property being there or not and a property being null or not in server code that I write even though that isn't particularly natural with Swift when encoding or decoding. |
Thanks for the confirmation, @mattpolzin. So this is a task on Swift OpenAPI Generator. Presumably, we should create a utility method on an object schema that can tell us whether a property should be generated as an optional type, and it'll return |
@czechboy0 It generates |
@m4p Great, that matches my expectations. So if anyone would like to contribute a fix, this comment outlines the rough steps: #82 (comment) |
I'm experience the same problem. Consider the following input
The current output is the following public struct groupsPayloadPayload: Codable, Equatable, Hashable, Sendable {
/// - Remark: Generated from `#/paths/site.json/GET/json/groupsPayload/id`.
public var id: Swift.Int
/// - Remark: Generated from `#/paths/site.json/GET/json/groupsPayload/flair_url`.
public var flair_url: Swift.String
/// Creates a new `groupsPayloadPayload`.
///
/// - Parameters:
/// - id:
/// - flair_url:
public init(
id: Swift.Int,
flair_url: Swift.String
) {
self.id = id
self.flair_url = flair_url
}
public enum CodingKeys: String, CodingKey {
case id
case flair_url
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Swift.Int.self, forKey: .id)
flair_url = try container.decode(
Swift.String.self,
forKey: .flair_url
)
try decoder.ensureNoAdditionalProperties(knownKeys: [
"id", "flair_url", "flair_bg_color", "flair_color",
])
}
} The expected output is public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Swift.Int.self, forKey: .id)
// ❌ since flair_url is marked as required
flair_url = try? container.decode(Swift.String.self, forKey: .flair_url)
// ✅ Only String or nil is accepted
flair_url = try container.decode(Swift.String?.self, forKey: .flair_url)
try decoder.ensureNoAdditionalProperties(knownKeys: [
"id", "flair_url", "flair_bg_color", "flair_color",
])
} Here we treat null as a special case. But what about the following input? (We have random types combined)
|
We need to diff requirement and nullability. A non-required type can infer nullability, but nullability can't infer requirement info. Consider the following define and input
And I think it was not a good idea to use |
A more detailed hint for anyone who'd like to submit a fix. The fix will probably involve the following code Line 142 in 6b11135
And you may need to add a /// A string representation of the fully qualified Swift type name, with
/// optional wrapping added.
///
/// For example: `Swift.Int?`.
var fullyQualifiedOptionalSwiftName: String {
withOptional(true).fullyQualifiedSwiftName
} |
I think I've hit a related problem - this spec (which I don't control), specifies enums like: "refund_behaviour": {
"type": "string",
"nullable": true,
"description": "How the category's refunds or deductions should be reported on.",
"enum": [
"debits_are_deductions",
"credits_are_refunds",
null
],
"example": "credits_are_refunds"
}, Which then results in errors like:
(For the time being I've just vendored and edited the spec, as this is a hobby project just to poke at OpenAPI via Swift.) |
We did, but this is subtly different. With #59, we now support: type: string
enum:
- foo
- "" But the above is: type: string
nullable: true
enum:
- foo
- null We carefully have to design support for Some additional reading:
We'll need a proposal for this, for sure, and possibly even more than one. I imagine we could first propose to support nullable types as object properties, then as enum values, etc, as they might be handled differently. The main question I'd ask anyone considering taking this on is: what should the generated code look and behave like? Once we agree on that, we can help guide the implementation. |
Nullable enums with an empty string fail to get generated ### Motivation Fixes #118. The issue is that when a `type: string, enum: ...` schema is marked as `nullable: true`, _empty cases are parsed as Void_ instead of _nil_. See the issue for the investigation and identifying the root cause, but the TL;DR is that we need a workaround in the generator in the short term, and it's uncertain if the issue will ever be addressed in our dependency Yams (might be unlikely, as it'd probably be a breaking change for them at this point). ### Modifications This PR adds code that catches this case and converts the `Void` into an empty string, which is what the OpenAPI document author actually put into their document in the first place. ### Result Now `nullable: true` string enums with an empty case can be generated correctly. One example of such use is the [GitHub API](https://github.com/github/rest-api-description). Note that this PR isn't really adding support for nullable schemas in general, that's tracked by #82. We're just working around one specific scenario in which a relatively common input trips up the generator. ### Test Plan Added a unit test for each of: non-nullable and nullable variants. Reviewed by: gjcairo, simonjbeaumont Builds: ✔︎ pull request validation (5.8) - Build finished. ✔︎ pull request validation (5.9) - Build finished. ✔︎ pull request validation (docc test) - Build finished. ✔︎ pull request validation (integration test) - Build finished. ✔︎ pull request validation (nightly) - Build finished. ✔︎ pull request validation (soundness) - Build finished. #176
### Motivation Fixes #82. More background: In JSON Schema, there are two fields that affect what we represent as optionality in Swift. 1. In object schemas, not including a property in the `required` array. 2. In all schemas, marking the schema as nullable. In OpenAPI 3.0, that used to be done using a dedicated `nullable` field, in OpenAPI 3.1, that field is removed and instead the same result is achieved by adding the `null` type in the `type` array. So for a non-nullable string, you'd use `type: string`, and for a nullable string, you'd use `type: [string, null]`. Up until now, we've only supported (1) in how we decide which types are generated as optional, and that seems to be how most adopters control nullability. However, (2) is also supported in JSON Schema, so we should also support it, and as evidenced by the comments in #82, a few folks have hit the lack of nullability support in Swift OpenAPI Generator. ### Modifications This PR takes nullability on the schema into account when deciding whether to generate a type as optional or not. Since this is a source-breaking change, it's introduced behind the feature flag `nullableSchemas`, and it'll become default behavior in 0.3.0. Most of this change is just propagating the feature flag to the right place. ### Result Marking a schema as nullable will result in an optional type being generated. ### Test Plan Added a snippet test, both with and without the feature flag, which shows the change.
Tested with the following and still failed to decode. i18n_name:
type:
- string
- 'null' Since there is some unsolved case for the issue, maybe we should consider reopen it? |
@Kyle-Ye are you including the |
No. After I enable it, it did solve the problem. Thanks for the clarification. |
Commit hash: f530c90
Context:
While testing my application that uses with swift-openapi-generator, I noticed that
nullable
is not yet supported on Schema Objects values. This leads to a Decoder error for API calls that returnnull
for nullable values.Steps to reproduce:
DecodingError: valueNotFound String - Expected String value but found null instead.
$ swift --version
swift-driver version: 1.75.2 Apple Swift version 5.8.1 (swiftlang-5.8.0.124.5 clang-1403.0.22.11.100)
Target: arm64-apple-macosx13.0
$ uname -a
Darwin hayai.lan 22.5.0 Darwin Kernel Version 22.5.0: Mon Apr 24 20:52:24 PDT 2023; root:xnu-8796.121.2~5/RELEASE_ARM64_T6000 arm64
My system has IPv6 enabled.
The text was updated successfully, but these errors were encountered: