diff --git a/Package.swift b/Package.swift index 25151e5d..8aea3577 100644 --- a/Package.swift +++ b/Package.swift @@ -60,7 +60,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.3.2"), + .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.5.0"), .package(url: "https://github.com/apple/swift-http-types", from: "1.0.2"), ], targets: [ diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/Constants.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/Constants.swift index d1fbedcf..72eed786 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/Constants.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/Constants.swift @@ -319,6 +319,9 @@ enum Constants { /// The form style. static let form = "form" + + /// The deepObject style. + static let deepObject = "deepObject" } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift index e8eb0700..0806f9ae 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift @@ -130,7 +130,12 @@ extension FileTranslator { let location = parameter.location switch location { case .query: - guard case .form = style else { + switch style { + case .form: + break + case .deepObject where explode: + break + default: try diagnostics.emitUnsupported( "Query params of style \(style.rawValue), explode: \(explode)", foundIn: foundIn @@ -243,6 +248,7 @@ extension OpenAPI.Parameter.SchemaContext.Style { var runtimeName: String { switch self { case .form: return Constants.Components.Parameters.Style.form + case .deepObject: return Constants.Components.Parameters.Style.deepObject default: preconditionFailure("Unsupported style") } } diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml b/Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml index ac8a417d..a1c7365b 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml @@ -69,6 +69,18 @@ paths: schema: format: uuid type: string + - name: sort + in: query + required: false + style: deepObject + explode: true + schema: + type: object + properties: + id: + type: string + name: + type: string - $ref: '#/components/parameters/query.born-since' responses: '200': diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift index 75c9bf22..04eaaf6a 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift @@ -87,6 +87,13 @@ public struct Client: APIProtocol { name: "My-Request-UUID", value: input.headers.My_hyphen_Request_hyphen_UUID ) + try converter.setQueryItemAsURI( + in: &request, + style: .deepObject, + explode: true, + name: "sort", + value: input.query.sort + ) try converter.setQueryItemAsURI( in: &request, style: .form, diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift index 80d642b3..d255b183 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift @@ -187,6 +187,13 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { name: "feeds", as: Operations.listPets.Input.Query.feedsPayload.self ), + sort: try converter.getOptionalQueryItemAsURI( + in: request.soar_query, + style: .deepObject, + explode: true, + name: "sort", + as: Operations.listPets.Input.Query.sortPayload.self + ), since: try converter.getOptionalQueryItemAsURI( in: request.soar_query, style: .form, diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift index 47b0390d..db5a5772 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift @@ -1855,6 +1855,31 @@ public enum Operations { public typealias feedsPayload = [Operations.listPets.Input.Query.feedsPayloadPayload] /// - Remark: Generated from `#/paths/pets/GET/query/feeds`. public var feeds: Operations.listPets.Input.Query.feedsPayload? + /// - Remark: Generated from `#/paths/pets/GET/query/sort`. + public struct sortPayload: Codable, Hashable, Sendable { + /// - Remark: Generated from `#/paths/pets/GET/query/sort/id`. + public var id: Swift.String? + /// - Remark: Generated from `#/paths/pets/GET/query/sort/name`. + public var name: Swift.String? + /// Creates a new `sortPayload`. + /// + /// - Parameters: + /// - id: + /// - name: + public init( + id: Swift.String? = nil, + name: Swift.String? = nil + ) { + self.id = id + self.name = name + } + public enum CodingKeys: String, CodingKey { + case id + case name + } + } + /// - Remark: Generated from `#/paths/pets/GET/query/sort`. + public var sort: Operations.listPets.Input.Query.sortPayload? /// Supply this parameter to filter pets born since the provided date. /// /// - Remark: Generated from `#/paths/pets/GET/query/since`. @@ -1865,16 +1890,19 @@ public enum Operations { /// - limit: How many items to return at one time (max 100) /// - habitat: /// - feeds: + /// - sort: /// - since: Supply this parameter to filter pets born since the provided date. 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, since: Components.Parameters.query_period_born_hyphen_since? = nil ) { self.limit = limit self.habitat = habitat self.feeds = feeds + self.sort = sort self.since = since } } diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index ba5e5905..06699fa4 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -2514,6 +2514,18 @@ final class SnippetBasedReferenceTests: XCTestCase { type: array items: type: string + - name: sort + in: query + required: false + style: deepObject + explode: true + schema: + type: object + properties: + option1: + type: string + option2: + type: string responses: default: description: Response @@ -2524,18 +2536,36 @@ final class SnippetBasedReferenceTests: XCTestCase { public var single: Swift.String? public var manyExploded: [Swift.String]? public var manyUnexploded: [Swift.String]? + public struct sortPayload: Codable, Hashable, Sendable { + public var option1: Swift.String? + public var option2: Swift.String? + public init( + option1: Swift.String? = nil, + option2: Swift.String? = nil + ) { + self.option1 = option1 + self.option2 = option2 + } + public enum CodingKeys: String, CodingKey { + case option1 + case option2 + } + } + public var sort: Operations.get_sol_foo.Input.Query.sortPayload public init( single: Swift.String? = nil, manyExploded: [Swift.String]? = nil, - manyUnexploded: [Swift.String]? = nil + manyUnexploded: [Swift.String]? = nil, + sort: Operations.get_sol_foo.Input.Query.sortPayload ) { self.single = single self.manyExploded = manyExploded self.manyUnexploded = manyUnexploded + self.sort = sort } } public var query: Operations.get_sol_foo.Input.Query - public init(query: Operations.get_sol_foo.Input.Query = .init()) { + public init(query: Operations.get_sol_foo.Input.Query) { self.query = query } } @@ -2572,6 +2602,13 @@ final class SnippetBasedReferenceTests: XCTestCase { name: "manyUnexploded", value: input.query.manyUnexploded ) + try converter.setQueryItemAsURI( + in: &request, + style: .deepObject, + explode: true, + name: "sort", + value: input.query.sort + ) return (request, nil) } """, @@ -2598,6 +2635,13 @@ final class SnippetBasedReferenceTests: XCTestCase { explode: false, name: "manyUnexploded", as: [Swift.String].self + ), + sort: try converter.getRequiredQueryItemAsURI( + in: request.soar_query, + style: .deepObject, + explode: true, + name: "sort", + as: Operations.get_sol_foo.Input.Query.sortPayload.self ) ) return Operations.get_sol_foo.Input(query: query) diff --git a/Tests/PetstoreConsumerTests/Test_Client.swift b/Tests/PetstoreConsumerTests/Test_Client.swift index 5b8b81fa..2b79da23 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&since=2023-01-18T10%3A04%3A11Z" + "/pets?limit=24&habitat=water&feeds=herbivore&feeds=carnivore&sort%5Bid%5D=ascending&sort%5Bname%5D=descending&since=2023-01-18T10%3A04%3A11Z" ) XCTAssertEqual(baseURL.absoluteString, "/api") XCTAssertEqual(request.method, .get) @@ -66,7 +66,7 @@ final class Test_Client: XCTestCase { } let response = try await client.listPets( .init( - query: .init(limit: 24, habitat: .water, feeds: [.herbivore, .carnivore], since: .test), + query: .init(limit: 24, habitat: .water, feeds: [.herbivore, .carnivore], sort: .init(id: "ascending", name: "descending"), since: .test), headers: .init(My_hyphen_Request_hyphen_UUID: "abcd-1234") ) ) diff --git a/Tests/PetstoreConsumerTests/Test_Server.swift b/Tests/PetstoreConsumerTests/Test_Server.swift index 5f6be366..c11bd49f 100644 --- a/Tests/PetstoreConsumerTests/Test_Server.swift +++ b/Tests/PetstoreConsumerTests/Test_Server.swift @@ -33,6 +33,7 @@ final class Test_Server: XCTestCase { XCTAssertEqual(input.query.habitat, .water) XCTAssertEqual(input.query.since, .test) XCTAssertEqual(input.query.feeds, [.carnivore, .herbivore]) + XCTAssertEqual(input.query.sort, .init(id: "ascending", name: "descending")) XCTAssertEqual(input.headers.My_hyphen_Request_hyphen_UUID, "abcd-1234") return .ok( .init( @@ -43,7 +44,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&since=\(Date.testString)", + soar_path: "/api/pets?sort%5Bid%5D=ascending&sort%5Bname%5D=descending", method: .get, headerFields: [.init("My-Request-UUID")!: "abcd-1234"] ),