From 7aae29ba756c4f2a71c68d614c03807b7ae12d90 Mon Sep 17 00:00:00 2001 From: ayush-that Date: Sun, 27 Sep 2026 01:38:14 +0530 Subject: [PATCH 1/3] Cover required JSON query parameter generation --- ...Converting-between-data-and-Swift-types.md | 5 +- .../SnippetBasedReferenceTests.swift | 69 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md b/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md index 608522f45..88812574c 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md +++ b/Sources/swift-openapi-generator/Documentation.docc/Development/Converting-between-data-and-Swift-types.md @@ -25,7 +25,7 @@ At the time of writing, the list of coders used is as follows. | Format | Encoder | Decoder | Supported in | | ------ | ------- | ------- | ----- | -| JSON | `Foundation.JSONEncoder` | `Foundation.JSONDecoder` | Bodies, headers | +| JSON | `Foundation.JSONEncoder` | `Foundation.JSONDecoder` | Bodies, headers, query items | | URI (†) | `OpenAPIRuntime.URIEncoder` | `OpenAPIRuntime.URIDecoder` | Path, query, headers | | Plain text | `OpenAPIRuntime.StringEncoder` | `OpenAPIRuntime.StringDecoder` | Bodies | @@ -91,6 +91,7 @@ method parameters: value or type of value | common | get | header field | JSON | required | getRequiredHeaderFieldAsJSON | | client | set | request path | URI | required | renderedPath | | client | set | request query | URI | both | setQueryItemAsURI | +| client | set | request query | JSON | both | setQueryItemAsJSON | | client | set | request body | JSON | optional | setOptionalRequestBodyAsJSON | | client | set | request body | JSON | required | setRequiredRequestBodyAsJSON | | client | set | request body | binary | optional | setOptionalRequestBodyAsBinary | @@ -104,6 +105,8 @@ method parameters: value or type of value | server | get | request path | URI | required | getPathParameterAsURI | | server | get | request query | URI | optional | getOptionalQueryItemAsURI | | server | get | request query | URI | required | getRequiredQueryItemAsURI | +| server | get | request query | JSON | optional | getOptionalQueryItemAsJSON | +| server | get | request query | JSON | required | getRequiredQueryItemAsJSON | | server | get | request body | JSON | optional | getOptionalRequestBodyAsJSON | | server | get | request body | JSON | required | getRequiredRequestBodyAsJSON | | server | get | request body | binary | optional | getOptionalRequestBodyAsBinary | diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index 22ace244b..ed13e44cd 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -3016,6 +3016,75 @@ final class SnippetBasedReferenceTests: XCTestCase { ) } + func testRequestWithRequiredJSONQueryItem() throws { + try self.assertRequestInTypesClientServerTranslation( + """ + /foo: + get: + parameters: + - name: filters + in: query + required: true + content: + application/json: + schema: + type: array + items: + type: string + responses: + default: + description: Response + """, + input: """ + public struct Input: Sendable, Hashable { + public struct Query: Sendable, Hashable { + public var filters: [Swift.String] + public init(filters: [Swift.String]) { + self.filters = filters + } + } + public var query: Operations.get_sol_foo.Input.Query + public init(query: Operations.get_sol_foo.Input.Query) { + self.query = query + } + } + """, + client: """ + { input in + let path = try converter.renderedPath( + template: "/foo", + parameters: [] + ) + var request: HTTPTypes.HTTPRequest = .init( + soar_path: path, + method: .get + ) + suppressMutabilityWarning(&request) + try converter.setQueryItemAsJSON( + in: &request, + style: .form, + explode: true, + name: "filters", + value: input.query.filters + ) + return (request, nil) + } + """, + server: """ + { request, requestBody, metadata in + let query: Operations.get_sol_foo.Input.Query = .init(filters: try converter.getRequiredQueryItemAsJSON( + in: request.soar_query, + style: .form, + explode: true, + name: "filters", + as: [Swift.String].self + )) + return Operations.get_sol_foo.Input(query: query) + } + """ + ) + } + func testRequestWithPathParams() throws { try self.assertRequestInTypesClientServerTranslation( """ From 5a3d106f5f47b265f98a1910cc1d122e7491e437 Mon Sep 17 00:00:00 2001 From: ayush-that Date: Sun, 27 Sep 2026 01:38:29 +0530 Subject: [PATCH 2/3] Exercise referenced JSON query parameters in Petstore --- .../Resources/Docs/petstore.yaml | 20 ++++++++++++++++ .../ReferenceSources/Petstore/Client.swift | 7 ++++++ .../ReferenceSources/Petstore/Server.swift | 7 ++++++ .../Types+Components+Parameters.swift | 4 ++++ .../Petstore/Types+Components+Schemas.swift | 23 +++++++++++++++++++ .../Petstore/Types+Operations.swift | 9 +++++++- Tests/PetstoreConsumerTests/Test_Client.swift | 5 ++-- Tests/PetstoreConsumerTests/Test_Server.swift | 6 +++-- 8 files changed, 76 insertions(+), 5 deletions(-) diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml b/Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml index 052cc8eb8..7ec289483 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml @@ -98,6 +98,7 @@ paths: state: type: string - $ref: '#/components/parameters/query.born-since' + - $ref: '#/components/parameters/query.json-filters' responses: '200': description: A paged array of pets @@ -296,6 +297,16 @@ components: name: since schema: $ref: '#/components/schemas/DOB' + query.json-filters: + description: Filter pets with structured JSON criteria. + in: query + name: jsonFilters + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PetFilter' path.petId: name: petId in: path @@ -305,6 +316,15 @@ components: type: integer format: int64 schemas: + PetFilter: + type: object + required: + - name + properties: + name: + type: string + state: + type: string Pet: type: object title: "Pet title" diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift index fbcde001b..c58e676ef 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift @@ -108,6 +108,13 @@ public struct Client: APIProtocol { name: "since", value: input.query.since ) + try converter.setQueryItemAsJSON( + in: &request, + style: .form, + explode: true, + name: "jsonFilters", + value: input.query.jsonFilters + ) converter.setAcceptHeader( in: &request.headerFields, contentTypes: input.headers.accept diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift index 31e0375ed..96bbc6e9a 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift @@ -207,6 +207,13 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { explode: true, name: "since", as: Components.Parameters.Query_bornSince.self + ), + jsonFilters: try converter.getOptionalQueryItemAsJSON( + in: request.soar_query, + style: .form, + explode: true, + name: "jsonFilters", + as: Components.Parameters.Query_jsonFilters.self ) ) let headers: Operations.ListPets.Input.Headers = .init( diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types+Components+Parameters.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types+Components+Parameters.swift index 0c0e94fb1..e3022ee25 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types+Components+Parameters.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types+Components+Parameters.swift @@ -16,6 +16,10 @@ extension Components { /// /// - Remark: Generated from `#/components/parameters/query.born-since`. public typealias Query_bornSince = Components.Schemas.Dob + /// Filter pets with structured JSON criteria. + /// + /// - Remark: Generated from `#/components/parameters/query.json-filters`. + public typealias Query_jsonFilters = [Components.Schemas.PetFilter] /// The id of the pet to retrieve /// /// - Remark: Generated from `#/components/parameters/path.petId`. diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types+Components+Schemas.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types+Components+Schemas.swift index 76d270e58..1da586538 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types+Components+Schemas.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types+Components+Schemas.swift @@ -12,6 +12,29 @@ public import struct Foundation.Date extension Components { /// Types generated from the `#/components/schemas` section of the OpenAPI document. public enum Schemas { + /// - Remark: Generated from `#/components/schemas/PetFilter`. + public struct PetFilter: Codable, Hashable, Sendable { + /// - Remark: Generated from `#/components/schemas/PetFilter/name`. + public var name: Swift.String + /// - Remark: Generated from `#/components/schemas/PetFilter/state`. + public var state: Swift.String? + /// Creates a new `PetFilter`. + /// + /// - Parameters: + /// - name: + /// - state: + public init( + name: Swift.String, + state: Swift.String? = nil + ) { + self.name = name + self.state = state + } + public enum CodingKeys: String, CodingKey { + case name + case state + } + } /// Pet metadata /// /// - Remark: Generated from `#/components/schemas/Pet`. diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types+Operations.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types+Operations.swift index 770ea20d7..c43157839 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types+Operations.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types+Operations.swift @@ -100,6 +100,10 @@ public enum Operations { /// /// - Remark: Generated from `#/paths/pets/GET/query/since`. public var since: Components.Parameters.Query_bornSince? + /// Filter pets with structured JSON criteria. + /// + /// - Remark: Generated from `#/paths/pets/GET/query/jsonFilters`. + public var jsonFilters: Components.Parameters.Query_jsonFilters? /// Creates a new `Query`. /// /// - Parameters: @@ -109,13 +113,15 @@ public enum Operations { /// - sort: /// - filter: /// - since: Supply this parameter to filter pets born since the provided date. + /// - jsonFilters: Filter pets with structured JSON criteria. public init( limit: Swift.Int32? = nil, habitat: Operations.ListPets.Input.Query.HabitatPayload? = nil, feeds: Operations.ListPets.Input.Query.FeedsPayload? = nil, sort: Operations.ListPets.Input.Query.SortPayload? = nil, filter: Operations.ListPets.Input.Query.FilterPayload, - since: Components.Parameters.Query_bornSince? = nil + since: Components.Parameters.Query_bornSince? = nil, + jsonFilters: Components.Parameters.Query_jsonFilters? = nil ) { self.limit = limit self.habitat = habitat @@ -123,6 +129,7 @@ public enum Operations { self.sort = sort self.filter = filter self.since = since + self.jsonFilters = jsonFilters } } public var query: Operations.ListPets.Input.Query diff --git a/Tests/PetstoreConsumerTests/Test_Client.swift b/Tests/PetstoreConsumerTests/Test_Client.swift index 6b44ae4f1..2a424b21a 100644 --- a/Tests/PetstoreConsumerTests/Test_Client.swift +++ b/Tests/PetstoreConsumerTests/Test_Client.swift @@ -40,7 +40,7 @@ final class Test_Client: XCTestCase { XCTAssertEqual(operationID, "listPets") XCTAssertEqual( request.path, - "/pets?limit=24&habitat=water&feeds=herbivore&feeds=carnivore&sort%5Bid%5D=ascending&sort%5Bname%5D=descending&filter%5Bname%5D=whale&since=2023-01-18T10%3A04%3A11Z" + "/pets?limit=24&habitat=water&feeds=herbivore&feeds=carnivore&sort%5Bid%5D=ascending&sort%5Bname%5D=descending&filter%5Bname%5D=whale&since=2023-01-18T10%3A04%3A11Z&jsonFilters=%5B%7B%22name%22%3A%22whale%22%2C%22state%22%3A%22water%22%7D%2C%7B%22name%22%3A%22dog%22%7D%5D" ) XCTAssertEqual(baseURL.absoluteString, "/api") XCTAssertEqual(request.method, .get) @@ -72,7 +72,8 @@ final class Test_Client: XCTestCase { feeds: [.herbivore, .carnivore], sort: .init(id: "ascending", name: "descending"), filter: .init(name: "whale"), - since: .test + since: .test, + jsonFilters: [.init(name: "whale", state: "water"), .init(name: "dog")] ), headers: .init(myRequestUUID: "abcd-1234") ) diff --git a/Tests/PetstoreConsumerTests/Test_Server.swift b/Tests/PetstoreConsumerTests/Test_Server.swift index 05a7fb7a8..85c40424a 100644 --- a/Tests/PetstoreConsumerTests/Test_Server.swift +++ b/Tests/PetstoreConsumerTests/Test_Server.swift @@ -34,6 +34,7 @@ final class Test_Server: XCTestCase { XCTAssertEqual(input.query.since, .test) XCTAssertEqual(input.query.feeds, [.carnivore, .herbivore]) XCTAssertEqual(input.query.sort, .init(id: "ascending", name: "descending")) + XCTAssertEqual(input.query.jsonFilters, [.init(name: "whale", state: "water"), .init(name: "dog")]) XCTAssertEqual(input.headers.myRequestUUID, "abcd-1234") return .ok( .init( @@ -45,7 +46,7 @@ final class Test_Server: XCTestCase { let (response, responseBody) = try await server.listPets( .init( soar_path: - "/api/pets?limit=24&habitat=water&feeds=carnivore&feeds=herbivore&sort%5Bid%5D=ascending&sort%5Bname%5D=descending&filter%5Bname%5D=whale&since=\(Date.testString)", + "/api/pets?limit=24&habitat=water&feeds=carnivore&feeds=herbivore&sort%5Bid%5D=ascending&sort%5Bname%5D=descending&filter%5Bname%5D=whale&since=\(Date.testString)&jsonFilters=%5B%7B%22name%22%3A%22whale%22%2C%22state%22%3A%22water%22%7D%2C%7B%22name%22%3A%22dog%22%7D%5D", method: .get, headerFields: [.init("My-Request-UUID")!: "abcd-1234"] ), @@ -81,7 +82,8 @@ final class Test_Server: XCTestCase { func testListPets_default() async throws { client = .init(listPetsBlock: { input in - .default(statusCode: 400, .init(body: .json(.init(code: 1, me_dollar_sage: "Oh no!")))) + XCTAssertNil(input.query.jsonFilters) + return .default(statusCode: 400, .init(body: .json(.init(code: 1, me_dollar_sage: "Oh no!")))) }) let (response, responseBody) = try await server.listPets( .init(soar_path: "/api/pets?filter%5Bname%5D=whale", method: .get), From 32388b5b16caa3455bcbe024146d8bc753a87316 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 6 Oct 2026 10:43:46 +0200 Subject: [PATCH 3/3] Bump runtime dependency --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index d93164980..85e36482a 100644 --- a/Package.swift +++ b/Package.swift @@ -46,7 +46,7 @@ let package = Package( // Tests-only: Runtime library linked by generated code, and also // helps keep the runtime library new enough to work with the generated // code. - .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.12.2", traits: []), + .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.13.0", traits: []), .package(url: "https://github.com/apple/swift-http-types", from: "1.7.0"), ], targets: [