apple / swift-openapi-generator

Generate Swift client and server code from an OpenAPI document.
https://swiftpackageindex.com/apple/swift-openapi-generator/documentation
Apache License 2.0
1.21k stars 87 forks source link

Empty Dictionary fails to encode #525

Closed grahamburgsma closed 1 month ago

grahamburgsma commented 3 months ago

Description

Returning an empty dictionary as the response body fails to encode.

Error thrown: EncodingError: invalidValue jsonPayload(additionalProperties: [:]) - at : Top-level jsonPayload did not encode any values.

Reproduction

openapi: '3.1.0'

paths:
  /test:
    get:
      operationId: testEmpty
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: array
                  items:
                    type: string
func testEmpty(_ input: Operations.testEmpty.Input) async throws -> Operations.testEmpty.Output {
    return .ok(.init(body: .json(.init(additionalProperties: [:]))))
}

Package version(s)

OpenAPIKit 3.1.2 swift-openapi-generator 1.2.0 swift-openapi-runtime 1.3.2 swift-openapi-vapor 1.0.0 swift-openapi-context 1.0.0

Expected behavior

The response should be encoded as:

{}

Environment

swift-driver version: 1.87.3 Apple Swift version 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5)
Target: arm64-apple-macosx14.0
czechboy0 commented 3 months ago

Seems to be coming from the underlying Foundation.JSONEncoder, so might need to be filed on Foundation unless we identify that the generated code is doing something wrong.

grahamburgsma commented 3 months ago

@czechboy0 I don't believe so, the following works fine:

let empty = [String: [String]]()
let data = try! JSONEncoder().encode(empty)
print(String(decoding: data, as: UTF8.self)) // prints {}

Could be related to something going on here?

grahamburgsma commented 3 months ago

Actually I wonder if I've done something wrong in my spec definition as instead of transforming into a dictionary ([String: [String]]) it generates a struct with an additionalProperties property on it which can't be right.

What is the correct way to have a Swift Dictionary type generated?

czechboy0 commented 3 months ago

A freeform JSON-based object? That'd be just:

type: object

If you'd like a dictionary of a specific type, for example integers, then:

type: object
additionalProperties:
  type: integer
grahamburgsma commented 3 months ago

@czechboy0 That doesn't generate a dictionary property though, it gets put inside a wrapper struct which would make working with those types very clumsy. I must be doing something wrong, but this is what I'm seeing.

For example this:

Test:
  type: object
  properties:
    dict:
      type: object
      additionalProperties:
        type: integer

Gets generated into this (simplified):

struct Test {
    struct dictPayload {
        var additionalProperties: [String: Swift.Int]
    }

    var dict: Components.Schemas.Test.dictPayload?
}

Rather than what I would expect (and what all other OpenAPI generators seem to do):

struct Test {
    var dict: [String: Swift.Int]?
}
czechboy0 commented 3 months ago

This is expected behavior. If you defined a property in addition to the additionalProperties in the schema for dict, we need a type to define it on. This is to ensure that adding a property doesn't fundamentally change the generated type (from a dictionary to a struct), so it's always a nested type here.

czechboy0 commented 1 month ago

@grahamburgsma Does this address your issue or do you believe that there is something we need to change in the generator?

grahamburgsma commented 1 month ago

@czechboy0 I understand why it generates the code as it does now, thanks for explaining that.

The original issue as described still exists though. An empty dictionary response fails to encode.

czechboy0 commented 1 month ago

Gotcha, here's a fix, @grahamburgsma: https://github.com/apple/swift-openapi-runtime/pull/103