Skip to content

Commit 8eb8fb2

Browse files
committed
WIP move parameter schema or content into parameter context
1 parent 71912da commit 8eb8fb2

File tree

6 files changed

+193
-212
lines changed

6 files changed

+193
-212
lines changed

Sources/OpenAPIKit/Parameter/DereferencedParameter.swift

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -90,26 +90,59 @@ extension OpenAPI.Parameter: ExternallyDereferenceable {
9090
// next line:
9191
// let (newSchemaOrContent, components) = try await schemaOrContent.externallyDereferenced(with: loader)
9292

93-
let newSchemaOrContent: Either<SchemaContext, OpenAPI.Content.Map>
93+
let newContext: OpenAPI.Parameter.Context
9494
let newComponents: OpenAPI.Components
9595
let newMessages: [Loader.Message]
9696

97-
switch schemaOrContent {
98-
case .a(let schemaContext):
99-
let (context, components, messages) = try await schemaContext.externallyDereferenced(with: loader)
100-
newSchemaOrContent = .a(context)
101-
newComponents = components
102-
newMessages = messages
103-
case .b(let contentMap):
104-
let (map, components, messages) = try await contentMap.externallyDereferenced(with: loader)
105-
newSchemaOrContent = .b(map)
106-
newComponents = components
107-
newMessages = messages
97+
switch context {
98+
case .query(required: let required, allowEmptyValue: let allowEmptyValue, schemaOrContent: let schemaOrContent):
99+
let newSchemaOrContent: Either<OpenAPI.Parameter.SchemaContext, OpenAPI.Content.Map>
100+
(newSchemaOrContent, newComponents, newMessages) = try await externallyDereference(schemaOrContent: schemaOrContent, with: Loader.self)
101+
102+
newContext = .query(required: required, allowEmptyValue: allowEmptyValue, schemaOrContent: newSchemaOrContent)
103+
104+
case .header(required: let required, schemaOrContent: let schemaOrContent):
105+
let newSchemaOrContent: Either<OpenAPI.Parameter.SchemaContext, OpenAPI.Content.Map>
106+
(newSchemaOrContent, newComponents, newMessages) = try await externallyDereference(schemaOrContent: schemaOrContent, with: Loader.self)
107+
108+
newContext = .header(required: required, schemaOrContent: newSchemaOrContent)
109+
110+
case .path(schemaOrContent: let schemaOrContent):
111+
let newSchemaOrContent: Either<OpenAPI.Parameter.SchemaContext, OpenAPI.Content.Map>
112+
(newSchemaOrContent, newComponents, newMessages) = try await externallyDereference(schemaOrContent: schemaOrContent, with: Loader.self)
113+
114+
newContext = .path(schemaOrContent: newSchemaOrContent)
115+
116+
case .cookie(required: let required, schemaOrContent: let schemaOrContent):
117+
let newSchemaOrContent: Either<OpenAPI.Parameter.SchemaContext, OpenAPI.Content.Map>
118+
(newSchemaOrContent, newComponents, newMessages) = try await externallyDereference(schemaOrContent: schemaOrContent, with: Loader.self)
119+
120+
newContext = .cookie(required: required, schemaOrContent: newSchemaOrContent)
121+
122+
case .querystring(required: let required, content: let content):
123+
let newContent: OpenAPI.Content.Map
124+
(newContent, newComponents, newMessages) = try await content.externallyDereferenced(with: Loader.self)
125+
126+
newContext = .querystring(required: required, content: newContent)
108127
}
109128

110129
var newParameter = self
111-
newParameter.schemaOrContent = newSchemaOrContent
130+
newParameter.context = newContext
112131

113132
return (newParameter, newComponents, newMessages)
114133
}
115134
}
135+
136+
fileprivate func externallyDereference<Loader: ExternalLoader>(
137+
schemaOrContent: Either<OpenAPI.Parameter.SchemaContext, OpenAPI.Content.Map>,
138+
with loader: Loader.Type
139+
) async throws -> (Either<OpenAPI.Parameter.SchemaContext, OpenAPI.Content.Map>, OpenAPI.Components, [Loader.Message]) {
140+
switch schemaOrContent {
141+
case .a(let schemaContext):
142+
let (context, components, messages) = try await schemaContext.externallyDereferenced(with: loader)
143+
return (.a(context), components, messages)
144+
case .b(let contentMap):
145+
let (map, components, messages) = try await contentMap.externallyDereferenced(with: loader)
146+
return (.b(map), components, messages)
147+
}
148+
}

Sources/OpenAPIKit/Parameter/Parameter.swift

Lines changed: 65 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,6 @@ extension OpenAPI {
2525
/// if unspecified and only gets encoded if true.
2626
public var deprecated: Bool // default is false
2727

28-
/// OpenAPI Spec "content" or "schema" properties.
29-
///
30-
/// You can access the schema context (if it is in use for
31-
/// this parameter) with `schemaOrContent.schemaContextValue`.
32-
/// The schema context contains lots of information detailed in the
33-
/// OpenAPI specification under the **Parameter Object** section.
34-
///
35-
/// You can directly access the underlying `JSONSchema` with
36-
/// `schemaOrContent.schemaValue`. If the schema is a reference
37-
/// instead of an inline value, `schemaOrContent.schemaReference`
38-
/// will get you the reference.
39-
///
40-
/// You can access the content map (if it is in use for
41-
/// this parameter) with `schemaOrContent.contentValue`.
42-
public var schemaOrContent: Either<SchemaContext, OpenAPI.Content.Map>
43-
4428
/// Dictionary of vendor extensions.
4529
///
4630
/// These should be of the form:
@@ -58,88 +42,45 @@ extension OpenAPI {
5842
/// parameter.
5943
public var location: Context.Location { return context.location }
6044

61-
/// Create a parameter with an `Either<SchemaContext, OpenAPI.Content.Map>`.
62-
public init(
63-
name: String,
64-
context: Context,
65-
schemaOrContent: Either<SchemaContext, OpenAPI.Content.Map>,
66-
description: String? = nil,
67-
deprecated: Bool = false,
68-
vendorExtensions: [String: AnyCodable] = [:]
69-
) {
70-
self.name = name
71-
self.context = context
72-
self.schemaOrContent = schemaOrContent
73-
self.description = description
74-
self.deprecated = deprecated
75-
self.vendorExtensions = vendorExtensions
76-
}
77-
78-
/// Create a parameter with a `SchemaContext`.
79-
public init(
80-
name: String,
81-
context: Context,
82-
schema: SchemaContext,
83-
description: String? = nil,
84-
deprecated: Bool = false,
85-
vendorExtensions: [String: AnyCodable] = [:]
86-
) {
87-
self.name = name
88-
self.context = context
89-
self.schemaOrContent = .init(schema)
90-
self.description = description
91-
self.deprecated = deprecated
92-
self.vendorExtensions = vendorExtensions
93-
}
94-
95-
/// Create a parameter with a `JSONSchema` and the default
96-
/// `style` for the given `Context`.
97-
public init(
98-
name: String,
99-
context: Context,
100-
schema: JSONSchema,
101-
description: String? = nil,
102-
deprecated: Bool = false,
103-
vendorExtensions: [String: AnyCodable] = [:]
104-
) {
105-
self.name = name
106-
self.context = context
107-
self.schemaOrContent = .init(SchemaContext(schema, style: .default(for: context)))
108-
self.description = description
109-
self.deprecated = deprecated
110-
self.vendorExtensions = vendorExtensions
111-
}
112-
113-
/// Create a parameter with a reference to a `JSONSchema`
114-
/// and the default `style` for the given `Context`.
115-
public init(
116-
name: String,
117-
context: Context,
118-
schemaReference: OpenAPI.Reference<JSONSchema>,
119-
description: String? = nil,
120-
deprecated: Bool = false,
121-
vendorExtensions: [String: AnyCodable] = [:]
122-
) {
123-
self.name = name
124-
self.context = context
125-
self.schemaOrContent = .init(SchemaContext(schemaReference: schemaReference, style: .default(for: context)))
126-
self.description = description
127-
self.deprecated = deprecated
128-
self.vendorExtensions = vendorExtensions
45+
/// OpenAPI Spec "content" or "schema" properties.
46+
///
47+
/// You can access the schema context (if it is in use for
48+
/// this parameter) with `schemaOrContent.schemaContextValue`.
49+
/// The schema context contains lots of information detailed in the
50+
/// OpenAPI specification under the **Parameter Object** section.
51+
///
52+
/// You can directly access the underlying `JSONSchema` with
53+
/// `schemaOrContent.schemaValue`. If the schema is a reference
54+
/// instead of an inline value, `schemaOrContent.schemaReference`
55+
/// will get you the reference.
56+
///
57+
/// You can access the content map (if it is in use for
58+
/// this parameter) with `schemaOrContent.contentValue`.
59+
public var schemaOrContent: Either<SchemaContext, OpenAPI.Content.Map> {
60+
switch context {
61+
case .query(required: _, allowEmptyValue: _, schemaOrContent: let schemaOrContent):
62+
return schemaOrContent
63+
case .header(required: _, schemaOrContent: let schemaOrContent):
64+
return schemaOrContent
65+
case .path(schemaOrContent: let schemaOrContent):
66+
return schemaOrContent
67+
case .cookie(required: _, schemaOrContent: let schemaOrContent):
68+
return schemaOrContent
69+
case .querystring(required: _, content: let content):
70+
return .content(content)
71+
}
12972
}
13073

131-
/// Create a parameter with a `Content.Map`.
74+
/// Create a parameter.
13275
public init(
13376
name: String,
13477
context: Context,
135-
content: OpenAPI.Content.Map,
13678
description: String? = nil,
13779
deprecated: Bool = false,
13880
vendorExtensions: [String: AnyCodable] = [:]
13981
) {
14082
self.name = name
14183
self.context = context
142-
self.schemaOrContent = .init(content)
14384
self.description = description
14485
self.deprecated = deprecated
14586
self.vendorExtensions = vendorExtensions
@@ -173,32 +114,10 @@ extension OpenAPI.Parameter {
173114
// OpenAPI.PathItem.Array.Element =>
174115
extension Either where A == OpenAPI.Reference<OpenAPI.Parameter>, B == OpenAPI.Parameter {
175116

176-
/// Construct a parameter using a `JSONSchema`.
177-
public static func parameter(
178-
name: String,
179-
context: OpenAPI.Parameter.Context,
180-
schema: JSONSchema,
181-
description: String? = nil,
182-
deprecated: Bool = false,
183-
vendorExtensions: [String: AnyCodable] = [:]
184-
) -> Self {
185-
return .b(
186-
.init(
187-
name: name,
188-
context: context,
189-
schema: schema,
190-
description: description,
191-
deprecated: deprecated,
192-
vendorExtensions: vendorExtensions
193-
)
194-
)
195-
}
196-
197-
/// Construct a parameter using a `Content.Map`.
117+
/// Construct a parameter.
198118
public static func parameter(
199119
name: String,
200120
context: OpenAPI.Parameter.Context,
201-
content: OpenAPI.Content.Map,
202121
description: String? = nil,
203122
deprecated: Bool = false,
204123
vendorExtensions: [String: AnyCodable] = [:]
@@ -207,7 +126,6 @@ extension Either where A == OpenAPI.Reference<OpenAPI.Parameter>, B == OpenAPI.P
207126
.init(
208127
name: name,
209128
context: context,
210-
content: content,
211129
description: description,
212130
deprecated: deprecated,
213131
vendorExtensions: vendorExtensions
@@ -238,23 +156,23 @@ extension OpenAPI.Parameter: Encodable {
238156
let required: Bool
239157
let location: Context.Location
240158
switch context {
241-
case .query(required: let req, allowEmptyValue: let allowEmptyValue):
159+
case .query(required: let req, allowEmptyValue: let allowEmptyValue, schemaOrContent: _):
242160
required = req
243161
location = .query
244162

245163
if allowEmptyValue {
246164
try container.encode(allowEmptyValue, forKey: .allowEmptyValue)
247165
}
248-
case .header(required: let req):
166+
case .header(required: let req, schemaOrContent: _):
249167
required = req
250168
location = .header
251-
case .path:
169+
case .path(schemaOrContent: _):
252170
required = true
253171
location = .path
254-
case .cookie(required: let req):
172+
case .cookie(required: let req, schemaOrContent: _):
255173
required = req
256174
location = .cookie
257-
case .querystring(required: let req):
175+
case .querystring(required: let req, content: _):
258176
required = req
259177
location = .querystring
260178
}
@@ -266,7 +184,7 @@ extension OpenAPI.Parameter: Encodable {
266184

267185
switch schemaOrContent {
268186
case .a(let schema):
269-
try schema.encode(to: encoder, for: context)
187+
try schema.encode(to: encoder, for: location)
270188
case .b(let contentMap):
271189
try container.encode(contentMap, forKey: .content)
272190
}
@@ -293,44 +211,16 @@ extension OpenAPI.Parameter: Decodable {
293211
let required = try container.decodeIfPresent(Bool.self, forKey: .required) ?? false
294212
let location = try container.decode(Context.Location.self, forKey: .parameterLocation)
295213

296-
switch location {
297-
case .query:
298-
let allowEmptyValue = try container.decodeIfPresent(Bool.self, forKey: .allowEmptyValue) ?? false
299-
context = .query(required: required, allowEmptyValue: allowEmptyValue)
300-
case .header:
301-
context = .header(required: required)
302-
case .path:
303-
if !required {
304-
throw GenericError(
305-
subjectName: name,
306-
details: "positional path parameters must be explicitly set to required",
307-
codingPath: decoder.codingPath
308-
)
309-
}
310-
context = .path
311-
case .cookie:
312-
context = .cookie(required: required)
313-
case .querystring:
314-
context = .querystring(required: required)
315-
}
316-
317214
let maybeContent = try container.decodeIfPresent(OpenAPI.Content.Map.self, forKey: .content)
318215

319216
let maybeSchema: SchemaContext?
320217
if container.contains(.schema) {
321-
maybeSchema = try SchemaContext(from: decoder, for: context)
218+
maybeSchema = try SchemaContext(from: decoder, for: location)
322219
} else {
323220
maybeSchema = nil
324221
}
325222

326-
if location == .querystring && maybeSchema != nil {
327-
throw GenericError(
328-
subjectName: name,
329-
details: "`schema` and `style` are disallowed for `querystring` parameters",
330-
codingPath: decoder.codingPath
331-
)
332-
}
333-
223+
let schemaOrContent: Either<SchemaContext, OpenAPI.Content.Map>
334224
switch (maybeContent, maybeSchema) {
335225
case (let content?, nil):
336226
schemaOrContent = .init(content)
@@ -350,6 +240,34 @@ extension OpenAPI.Parameter: Decodable {
350240
)
351241
}
352242

243+
switch location {
244+
case .query:
245+
let allowEmptyValue = try container.decodeIfPresent(Bool.self, forKey: .allowEmptyValue) ?? false
246+
context = .query(required: required, allowEmptyValue: allowEmptyValue, schemaOrContent: schemaOrContent)
247+
case .header:
248+
context = .header(required: required, schemaOrContent: schemaOrContent)
249+
case .path:
250+
if !required {
251+
throw GenericError(
252+
subjectName: name,
253+
details: "positional path parameters must be explicitly set to required",
254+
codingPath: decoder.codingPath
255+
)
256+
}
257+
context = .path(schemaOrContent: schemaOrContent)
258+
case .cookie:
259+
context = .cookie(required: required, schemaOrContent: schemaOrContent)
260+
case .querystring:
261+
guard case .b(let content) = schemaOrContent else {
262+
throw GenericError(
263+
subjectName: name,
264+
details: "`schema` and `style` are disallowed for `querystring` parameters",
265+
codingPath: decoder.codingPath
266+
)
267+
}
268+
context = .querystring(required: required, content: content)
269+
}
270+
353271
description = try container.decodeIfPresent(String.self, forKey: .description)
354272

355273
deprecated = try container.decodeIfPresent(Bool.self, forKey: .deprecated) ?? false

0 commit comments

Comments
 (0)