-
Notifications
You must be signed in to change notification settings - Fork 131
Add convenience initializers for primitive-typed multipart parts #775
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
base: main
Are you sure you want to change the base?
Add convenience initializers for primitive-typed multipart parts #775
Conversation
Generate additional `init(value:)` initializers for multipart payload structs when the part is defined with primitive types (integer, number, string, or boolean) in the OpenAPI document. This provides a more ergonomic, typesafe API by allowing direct value initialization instead of requiring manual `HTTPBody` construction with no type validation. The generated initializers automatically convert numeric and boolean values to strings as required by multipart form encoding. Before/After Examples: | Type | Before | After | |---------|---------------------------------|------------------------| | integer | `init(body: HTTPBody("123"))` | `init(value: 123)` | | number | `init(body: HTTPBody("45.67"))` | `init(value: 45.67)` | | string | `init(body: HTTPBody("hello"))` | `init(value: "hello")` | | boolean | `init(body: HTTPBody("true"))` | `init(value: true)` | The new initializers: - For integers: `init(value: Int)` - converts to string internally - For numbers: `init(value: Double)` - converts to string internally - For strings: `init(value: String)` - passes through directly - For booleans: `init(value: Bool)` - converts to string internally These convenience initializers are also generated for multipart parts with custom headers. When headers are present, the generated initializer includes both headers and value parameters: - Without headers: `init(value: String)` - With headers: `init(headers: Headers, value: String)` This partly resolves apple#756. Support for non-trivial types (objects, arrays, etc.) is not included in this change as it would require more complex type handling that I couldn't figure out.
Hi @jpsim, thanks for the PR! For the conversion from primitive types (String, Int, Double, Bool), what's the serialization into bytes being defined by? The OpenAPI specification defines that for primitive types at the top level of the multipart schema are serialized as In addition, how would someone get the values back out in a similar way? Generally we've tried to have all APIs be symmetric - so if we generate serialization for something, we also generate deserialization. So we'd need a related specification that can convert from Content types like |
Ah TIL, this is really unfortunate as it appears to mean that #756 is fundamentally impossible! I did not realize that from our discussion in that issue. It happens that the Swift
Since these are all just convenience initializers, someone could get the values back exactly the same way as they do today. Right now there's no default serialization convention on the sender's side or on the receiver's side. This change introduces a default serialization convention on the sender's side only. A sender can decide if they want to send a single scalar value, that's a safe assumption to make because they're the ones making the request, but a receiver (server) can't safely make the same assumption. Ultimately I understand if you choose to reject this direction because it's making assumptions about the serialization format that aren't enforced by the OpenAPI schema. What if instead, we made this clear in the initializer that it assumes JSON scalar serialization by renaming the parameter from |
Yeah it also only dawned on me that we don't have a spec/RFC to follow here.
Serializing it as JSON would e.g. mean quoting strings, aka converting them to JSON fragments using JSONEncoder. Although I'm worried about creating a side channel of assuming both sides are Swift. We've historically taken the opposite direction, and tried to stick to the spec as much as possible. I'll let @simonjbeaumont chime in as well. |
Generate additional
init(value:)
initializers for multipart payload structs when the part is defined with primitive types (integer, number, string, or boolean) in the OpenAPI document. This provides a more ergonomic, typesafe API by allowing direct value initialization instead of requiring manualHTTPBody
construction with no type validation.The generated initializers automatically convert numeric and boolean values to strings as required by multipart form encoding.
Before/After Examples:
init(body: HTTPBody("123"))
init(value: 123)
init(body: HTTPBody("45.67"))
init(value: 45.67)
init(body: HTTPBody("hello"))
init(value: "hello")
init(body: HTTPBody("true"))
init(value: true)
The new initializers:
init(value: Int)
- converts to string internallyinit(value: Double)
- converts to string internallyinit(value: String)
- passes through directlyinit(value: Bool)
- converts to string internallyThese convenience initializers are also generated for multipart parts with custom headers. When headers are present, the generated initializer includes both headers and value parameters:
init(value: String)
init(headers: Headers, value: String)
This partly resolves #756. Support for non-trivial types (objects, arrays, etc.) is not included in this change as it would require more complex type handling that I couldn't figure out.