Skip to content

Commit 00a1bde

Browse files
[Generator] Add support of deepObject style in query params (#538)
### Motivation The generator changes for: [apple/swift-openapi-generator](#259) Depends on apple/swift-openapi-runtime#100 landing first and getting released, and the version dependency being bumped here. ### Modifications Added `deepObject` style to serializer & parser in order to support nested keys on query parameters. ### Result Support nested keys on query parameters. ### Test Plan Adapted snippet tests (SnippetBasedReferenceTests) --------- Co-authored-by: Honza Dvorsky <[email protected]>
1 parent 3f881d6 commit 00a1bde

File tree

9 files changed

+228
-11
lines changed

9 files changed

+228
-11
lines changed

Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/Constants.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,8 @@ enum Constants {
319319

320320
/// The form style.
321321
static let form = "form"
322+
/// The deepObject style.
323+
static let deepObject = "deepObject"
322324
}
323325
}
324326

Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ extension FileTranslator {
130130
let location = parameter.location
131131
switch location {
132132
case .query:
133-
guard case .form = style else {
133+
switch style {
134+
case .form: break
135+
case .deepObject where explode: break
136+
default:
134137
try diagnostics.emitUnsupported(
135138
"Query params of style \(style.rawValue), explode: \(explode)",
136139
foundIn: foundIn
@@ -243,6 +246,7 @@ extension OpenAPI.Parameter.SchemaContext.Style {
243246
var runtimeName: String {
244247
switch self {
245248
case .form: return Constants.Components.Parameters.Style.form
249+
case .deepObject: return Constants.Components.Parameters.Style.deepObject
246250
default: preconditionFailure("Unsupported style")
247251
}
248252
}

Tests/OpenAPIGeneratorReferenceTests/Resources/Docs/petstore.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,34 @@ paths:
6969
schema:
7070
format: uuid
7171
type: string
72+
- name: sort
73+
in: query
74+
required: false
75+
style: deepObject
76+
explode: true
77+
schema:
78+
type: object
79+
required:
80+
- id
81+
properties:
82+
id:
83+
type: string
84+
name:
85+
type: string
86+
- name: filter
87+
in: query
88+
required: true
89+
style: deepObject
90+
explode: true
91+
schema:
92+
type: object
93+
required:
94+
- name
95+
properties:
96+
name:
97+
type: string
98+
state:
99+
type: string
72100
- $ref: '#/components/parameters/query.born-since'
73101
responses:
74102
'200':

Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ public struct Client: APIProtocol {
8787
name: "My-Request-UUID",
8888
value: input.headers.myRequestUUID
8989
)
90+
try converter.setQueryItemAsURI(
91+
in: &request,
92+
style: .deepObject,
93+
explode: true,
94+
name: "sort",
95+
value: input.query.sort
96+
)
97+
try converter.setQueryItemAsURI(
98+
in: &request,
99+
style: .deepObject,
100+
explode: true,
101+
name: "filter",
102+
value: input.query.filter
103+
)
90104
try converter.setQueryItemAsURI(
91105
in: &request,
92106
style: .form,

Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,20 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol {
187187
name: "feeds",
188188
as: Operations.ListPets.Input.Query.FeedsPayload.self
189189
),
190+
sort: try converter.getOptionalQueryItemAsURI(
191+
in: request.soar_query,
192+
style: .deepObject,
193+
explode: true,
194+
name: "sort",
195+
as: Operations.ListPets.Input.Query.SortPayload.self
196+
),
197+
filter: try converter.getRequiredQueryItemAsURI(
198+
in: request.soar_query,
199+
style: .deepObject,
200+
explode: true,
201+
name: "filter",
202+
as: Operations.ListPets.Input.Query.FilterPayload.self
203+
),
190204
since: try converter.getOptionalQueryItemAsURI(
191205
in: request.soar_query,
192206
style: .form,

Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ extension APIProtocol {
6666
/// - Remark: HTTP `GET /pets`.
6767
/// - Remark: Generated from `#/paths//pets/get(listPets)`.
6868
public func listPets(
69-
query: Operations.ListPets.Input.Query = .init(),
69+
query: Operations.ListPets.Input.Query,
7070
headers: Operations.ListPets.Input.Headers = .init()
7171
) async throws -> Operations.ListPets.Output {
7272
try await listPets(Operations.ListPets.Input(
@@ -1895,6 +1895,56 @@ public enum Operations {
18951895
public typealias FeedsPayload = [Operations.ListPets.Input.Query.FeedsPayloadPayload]
18961896
/// - Remark: Generated from `#/paths/pets/GET/query/feeds`.
18971897
public var feeds: Operations.ListPets.Input.Query.FeedsPayload?
1898+
/// - Remark: Generated from `#/paths/pets/GET/query/sort`.
1899+
public struct SortPayload: Codable, Hashable, Sendable {
1900+
/// - Remark: Generated from `#/paths/pets/GET/query/sort/id`.
1901+
public var id: Swift.String
1902+
/// - Remark: Generated from `#/paths/pets/GET/query/sort/name`.
1903+
public var name: Swift.String?
1904+
/// Creates a new `SortPayload`.
1905+
///
1906+
/// - Parameters:
1907+
/// - id:
1908+
/// - name:
1909+
public init(
1910+
id: Swift.String,
1911+
name: Swift.String? = nil
1912+
) {
1913+
self.id = id
1914+
self.name = name
1915+
}
1916+
public enum CodingKeys: String, CodingKey {
1917+
case id
1918+
case name
1919+
}
1920+
}
1921+
/// - Remark: Generated from `#/paths/pets/GET/query/sort`.
1922+
public var sort: Operations.ListPets.Input.Query.SortPayload?
1923+
/// - Remark: Generated from `#/paths/pets/GET/query/filter`.
1924+
public struct FilterPayload: Codable, Hashable, Sendable {
1925+
/// - Remark: Generated from `#/paths/pets/GET/query/filter/name`.
1926+
public var name: Swift.String
1927+
/// - Remark: Generated from `#/paths/pets/GET/query/filter/state`.
1928+
public var state: Swift.String?
1929+
/// Creates a new `FilterPayload`.
1930+
///
1931+
/// - Parameters:
1932+
/// - name:
1933+
/// - state:
1934+
public init(
1935+
name: Swift.String,
1936+
state: Swift.String? = nil
1937+
) {
1938+
self.name = name
1939+
self.state = state
1940+
}
1941+
public enum CodingKeys: String, CodingKey {
1942+
case name
1943+
case state
1944+
}
1945+
}
1946+
/// - Remark: Generated from `#/paths/pets/GET/query/filter`.
1947+
public var filter: Operations.ListPets.Input.Query.FilterPayload
18981948
/// Supply this parameter to filter pets born since the provided date.
18991949
///
19001950
/// - Remark: Generated from `#/paths/pets/GET/query/since`.
@@ -1905,16 +1955,22 @@ public enum Operations {
19051955
/// - limit: How many items to return at one time (max 100)
19061956
/// - habitat:
19071957
/// - feeds:
1958+
/// - sort:
1959+
/// - filter:
19081960
/// - since: Supply this parameter to filter pets born since the provided date.
19091961
public init(
19101962
limit: Swift.Int32? = nil,
19111963
habitat: Operations.ListPets.Input.Query.HabitatPayload? = nil,
19121964
feeds: Operations.ListPets.Input.Query.FeedsPayload? = nil,
1965+
sort: Operations.ListPets.Input.Query.SortPayload? = nil,
1966+
filter: Operations.ListPets.Input.Query.FilterPayload,
19131967
since: Components.Parameters.Query_bornSince? = nil
19141968
) {
19151969
self.limit = limit
19161970
self.habitat = habitat
19171971
self.feeds = feeds
1972+
self.sort = sort
1973+
self.filter = filter
19181974
self.since = since
19191975
}
19201976
}
@@ -1946,7 +2002,7 @@ public enum Operations {
19462002
/// - query:
19472003
/// - headers:
19482004
public init(
1949-
query: Operations.ListPets.Input.Query = .init(),
2005+
query: Operations.ListPets.Input.Query,
19502006
headers: Operations.ListPets.Input.Headers = .init()
19512007
) {
19522008
self.query = query

Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2600,6 +2600,32 @@ final class SnippetBasedReferenceTests: XCTestCase {
26002600
type: array
26012601
items:
26022602
type: string
2603+
- name: sort
2604+
in: query
2605+
required: false
2606+
style: deepObject
2607+
explode: true
2608+
schema:
2609+
type: object
2610+
properties:
2611+
option1:
2612+
type: string
2613+
option2:
2614+
type: string
2615+
- name: filter
2616+
in: query
2617+
required: true
2618+
style: deepObject
2619+
explode: true
2620+
schema:
2621+
type: object
2622+
required:
2623+
- option3
2624+
properties:
2625+
option3:
2626+
type: string
2627+
option4:
2628+
type: string
26032629
responses:
26042630
default:
26052631
description: Response
@@ -2610,18 +2636,54 @@ final class SnippetBasedReferenceTests: XCTestCase {
26102636
public var single: Swift.String?
26112637
public var manyExploded: [Swift.String]?
26122638
public var manyUnexploded: [Swift.String]?
2639+
public struct sortPayload: Codable, Hashable, Sendable {
2640+
public var option1: Swift.String?
2641+
public var option2: Swift.String?
2642+
public init(
2643+
option1: Swift.String? = nil,
2644+
option2: Swift.String? = nil
2645+
) {
2646+
self.option1 = option1
2647+
self.option2 = option2
2648+
}
2649+
public enum CodingKeys: String, CodingKey {
2650+
case option1
2651+
case option2
2652+
}
2653+
}
2654+
public var sort: Operations.get_sol_foo.Input.Query.sortPayload?
2655+
public struct filterPayload: Codable, Hashable, Sendable {
2656+
public var option3: Swift.String
2657+
public var option4: Swift.String?
2658+
public init(
2659+
option3: Swift.String,
2660+
option4: Swift.String? = nil
2661+
) {
2662+
self.option3 = option3
2663+
self.option4 = option4
2664+
}
2665+
public enum CodingKeys: String, CodingKey {
2666+
case option3
2667+
case option4
2668+
}
2669+
}
2670+
public var filter: Operations.get_sol_foo.Input.Query.filterPayload
26132671
public init(
26142672
single: Swift.String? = nil,
26152673
manyExploded: [Swift.String]? = nil,
2616-
manyUnexploded: [Swift.String]? = nil
2674+
manyUnexploded: [Swift.String]? = nil,
2675+
sort: Operations.get_sol_foo.Input.Query.sortPayload? = nil,
2676+
filter: Operations.get_sol_foo.Input.Query.filterPayload
26172677
) {
26182678
self.single = single
26192679
self.manyExploded = manyExploded
26202680
self.manyUnexploded = manyUnexploded
2681+
self.sort = sort
2682+
self.filter = filter
26212683
}
26222684
}
26232685
public var query: Operations.get_sol_foo.Input.Query
2624-
public init(query: Operations.get_sol_foo.Input.Query = .init()) {
2686+
public init(query: Operations.get_sol_foo.Input.Query) {
26252687
self.query = query
26262688
}
26272689
}
@@ -2658,6 +2720,20 @@ final class SnippetBasedReferenceTests: XCTestCase {
26582720
name: "manyUnexploded",
26592721
value: input.query.manyUnexploded
26602722
)
2723+
try converter.setQueryItemAsURI(
2724+
in: &request,
2725+
style: .deepObject,
2726+
explode: true,
2727+
name: "sort",
2728+
value: input.query.sort
2729+
)
2730+
try converter.setQueryItemAsURI(
2731+
in: &request,
2732+
style: .deepObject,
2733+
explode: true,
2734+
name: "filter",
2735+
value: input.query.filter
2736+
)
26612737
return (request, nil)
26622738
}
26632739
""",
@@ -2684,6 +2760,20 @@ final class SnippetBasedReferenceTests: XCTestCase {
26842760
explode: false,
26852761
name: "manyUnexploded",
26862762
as: [Swift.String].self
2763+
),
2764+
sort: try converter.getOptionalQueryItemAsURI(
2765+
in: request.soar_query,
2766+
style: .deepObject,
2767+
explode: true,
2768+
name: "sort",
2769+
as: Operations.get_sol_foo.Input.Query.sortPayload.self
2770+
),
2771+
filter: try converter.getRequiredQueryItemAsURI(
2772+
in: request.soar_query,
2773+
style: .deepObject,
2774+
explode: true,
2775+
name: "filter",
2776+
as: Operations.get_sol_foo.Input.Query.filterPayload.self
26872777
)
26882778
)
26892779
return Operations.get_sol_foo.Input(query: query)

Tests/PetstoreConsumerTests/Test_Client.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ final class Test_Client: XCTestCase {
4040
XCTAssertEqual(operationID, "listPets")
4141
XCTAssertEqual(
4242
request.path,
43-
"/pets?limit=24&habitat=water&feeds=herbivore&feeds=carnivore&since=2023-01-18T10%3A04%3A11Z"
43+
"/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"
4444
)
4545
XCTAssertEqual(baseURL.absoluteString, "/api")
4646
XCTAssertEqual(request.method, .get)
@@ -66,7 +66,14 @@ final class Test_Client: XCTestCase {
6666
}
6767
let response = try await client.listPets(
6868
.init(
69-
query: .init(limit: 24, habitat: .water, feeds: [.herbivore, .carnivore], since: .test),
69+
query: .init(
70+
limit: 24,
71+
habitat: .water,
72+
feeds: [.herbivore, .carnivore],
73+
sort: .init(id: "ascending", name: "descending"),
74+
filter: .init(name: "whale"),
75+
since: .test
76+
),
7077
headers: .init(myRequestUUID: "abcd-1234")
7178
)
7279
)
@@ -84,7 +91,7 @@ final class Test_Client: XCTestCase {
8491
func testListPets_default() async throws {
8592
transport = .init { request, body, baseURL, operationID in
8693
XCTAssertEqual(operationID, "listPets")
87-
XCTAssertEqual(request.path, "/pets?limit=24")
94+
XCTAssertEqual(request.path, "/pets?limit=24&filter%5Bname%5D=whale")
8895
XCTAssertEqual(baseURL.absoluteString, "/api")
8996
XCTAssertEqual(request.method, .get)
9097
XCTAssertEqual(request.headerFields, [.accept: "application/json"])
@@ -100,7 +107,7 @@ final class Test_Client: XCTestCase {
100107
"""#
101108
)
102109
}
103-
let response = try await client.listPets(.init(query: .init(limit: 24)))
110+
let response = try await client.listPets(.init(query: .init(limit: 24, filter: .init(name: "whale"))))
104111
guard case let .default(statusCode, value) = response else {
105112
XCTFail("Unexpected response: \(response)")
106113
return

0 commit comments

Comments
 (0)