Skip to content

Commit 18d2163

Browse files
committed
WIP addition of positional schema, encoding for media type object
1 parent eb8de23 commit 18d2163

File tree

6 files changed

+471
-21
lines changed

6 files changed

+471
-21
lines changed

Sources/OpenAPIKit/Content/Content.swift

Lines changed: 144 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,19 @@ extension OpenAPI {
1212
///
1313
/// See [OpenAPI Media Type Object](https://spec.openapis.org/oas/v3.1.1.html#media-type-object).
1414
public struct Content: Equatable, CodableVendorExtendable, Sendable {
15+
/// A schema describing the complete content of the request, response,
16+
/// parameter, or header.
1517
public var schema: JSONSchema?
18+
19+
/// A schema describing each item within a sequential media type.
20+
public var itemSchema: JSONSchema?
21+
1622
public var example: AnyCodable?
1723
public var examples: Example.Map?
18-
public var encoding: OrderedDictionary<String, Encoding>?
24+
25+
/// Provide either a map of encodings or some combination of prefix-
26+
/// and item- positional encodings.
27+
public var encoding: Either<OrderedDictionary<String, Encoding>, PositionalEncoding>?
1928

2029
/// Dictionary of vendor extensions.
2130
///
@@ -28,14 +37,51 @@ extension OpenAPI {
2837
/// schema at all and optionally provide a single example.
2938
public init(
3039
schema: JSONSchema?,
40+
itemSchema: JSONSchema? = nil,
3141
example: AnyCodable? = nil,
3242
encoding: OrderedDictionary<String, Encoding>? = nil,
3343
vendorExtensions: [String: AnyCodable] = [:]
3444
) {
3545
self.schema = schema
46+
self.itemSchema = itemSchema
47+
self.example = example
48+
self.examples = nil
49+
self.encoding = encoding.map(Either.a)
50+
self.vendorExtensions = vendorExtensions
51+
}
52+
53+
/// Create `Content` with a schema, a reference to a schema, or no
54+
/// schema at all and optionally provide a single example.
55+
public init(
56+
schema: JSONSchema?,
57+
itemSchema: JSONSchema? = nil,
58+
example: AnyCodable? = nil,
59+
prefixEncoding: [Encoding] = [],
60+
itemEncoding: Encoding? = nil,
61+
vendorExtensions: [String: AnyCodable] = [:]
62+
) {
63+
self.schema = schema
64+
self.itemSchema = itemSchema
3665
self.example = example
3766
self.examples = nil
38-
self.encoding = encoding
67+
self.encoding = .b(.init(prefixEncoding: prefixEncoding, itemEncoding: itemEncoding))
68+
self.vendorExtensions = vendorExtensions
69+
}
70+
71+
/// Create `Content` with a schema, a reference to a schema, or no
72+
/// schema at all and optionally provide a single example.
73+
public init(
74+
itemSchema: JSONSchema?,
75+
example: AnyCodable? = nil,
76+
prefixEncoding: [Encoding] = [],
77+
itemEncoding: Encoding? = nil,
78+
vendorExtensions: [String: AnyCodable] = [:]
79+
) {
80+
self.schema = nil
81+
self.itemSchema = itemSchema
82+
self.example = example
83+
self.examples = nil
84+
self.encoding = .b(.init(prefixEncoding: prefixEncoding, itemEncoding: itemEncoding))
3985
self.vendorExtensions = vendorExtensions
4086
}
4187

@@ -50,22 +96,24 @@ extension OpenAPI {
5096
self.schema = .reference(schemaReference.jsonReference, description: schemaReference.description)
5197
self.example = example
5298
self.examples = nil
53-
self.encoding = encoding
99+
self.encoding = encoding.map(Either.a)
54100
self.vendorExtensions = vendorExtensions
55101
}
56102

57103
/// Create `Content` with a schema and optionally provide a single
58104
/// example.
59105
public init(
60106
schema: JSONSchema,
107+
itemSchema: JSONSchema? = nil,
61108
example: AnyCodable? = nil,
62109
encoding: OrderedDictionary<String, Encoding>? = nil,
63110
vendorExtensions: [String: AnyCodable] = [:]
64111
) {
65112
self.schema = schema
113+
self.itemSchema = itemSchema
66114
self.example = example
67115
self.examples = nil
68-
self.encoding = encoding
116+
self.encoding = encoding.map(Either.a)
69117
self.vendorExtensions = vendorExtensions
70118
}
71119

@@ -89,7 +137,7 @@ extension OpenAPI {
89137
}
90138
self.examples = examples
91139
self.example = examples.flatMap(Self.firstExample(from:))
92-
self.encoding = encoding
140+
self.encoding = encoding.map(Either.a)
93141
self.vendorExtensions = vendorExtensions
94142
}
95143

@@ -104,22 +152,57 @@ extension OpenAPI {
104152
self.schema = .reference(schemaReference.jsonReference)
105153
self.examples = examples
106154
self.example = examples.flatMap(Self.firstExample(from:))
107-
self.encoding = encoding
155+
self.encoding = encoding.map(Either.a)
108156
self.vendorExtensions = vendorExtensions
109157
}
110158

111159
/// Create `Content` with a schema and optionally provide a map
112160
/// of examples.
113161
public init(
114162
schema: JSONSchema,
163+
itemSchema: JSONSchema? = nil,
115164
examples: Example.Map?,
116165
encoding: OrderedDictionary<String, Encoding>? = nil,
117166
vendorExtensions: [String: AnyCodable] = [:]
118167
) {
119168
self.schema = schema
169+
self.itemSchema = itemSchema
120170
self.examples = examples
121171
self.example = examples.flatMap(Self.firstExample(from:))
122-
self.encoding = encoding
172+
self.encoding = encoding.map(Either.a)
173+
self.vendorExtensions = vendorExtensions
174+
}
175+
176+
/// Create `Content` with a schema and optionally provide a map
177+
/// of examples.
178+
public init(
179+
itemSchema: JSONSchema?,
180+
examples: Example.Map?,
181+
encoding: OrderedDictionary<String, Encoding>? = nil,
182+
vendorExtensions: [String: AnyCodable] = [:]
183+
) {
184+
self.schema = nil
185+
self.itemSchema = itemSchema
186+
self.examples = examples
187+
self.example = examples.flatMap(Self.firstExample(from:))
188+
self.encoding = encoding.map(Either.a)
189+
self.vendorExtensions = vendorExtensions
190+
}
191+
192+
/// Create `Content` with a schema and optionally provide a map
193+
/// of examples.
194+
public init(
195+
itemSchema: JSONSchema? = nil,
196+
examples: Example.Map?,
197+
prefixEncoding: [Encoding] = [],
198+
itemEncoding: Encoding? = nil,
199+
vendorExtensions: [String: AnyCodable] = [:]
200+
) {
201+
self.schema = nil
202+
self.itemSchema = itemSchema
203+
self.examples = examples
204+
self.example = examples.flatMap(Self.firstExample(from:))
205+
self.encoding = .b(.init(prefixEncoding: prefixEncoding, itemEncoding: itemEncoding))
123206
self.vendorExtensions = vendorExtensions
124207
}
125208
}
@@ -159,6 +242,7 @@ extension OpenAPI.Content: Encodable {
159242
var container = encoder.container(keyedBy: CodingKeys.self)
160243

161244
try container.encodeIfPresent(schema, forKey: .schema)
245+
try container.encodeIfPresent(itemSchema, forKey: .itemSchema)
162246

163247
// only encode `examples` if non-nil,
164248
// otherwise encode `example` if non-nil
@@ -168,7 +252,18 @@ extension OpenAPI.Content: Encodable {
168252
try container.encode(example, forKey: .example)
169253
}
170254

171-
try container.encodeIfPresent(encoding, forKey: .encoding)
255+
if let encoding {
256+
switch encoding {
257+
case .a(let encoding):
258+
try container.encode(encoding, forKey: .encoding)
259+
260+
case .b(let positionalEncoding):
261+
if !positionalEncoding.prefixEncoding.isEmpty {
262+
try container.encode(positionalEncoding.prefixEncoding, forKey: .prefixEncoding)
263+
}
264+
try container.encodeIfPresent(positionalEncoding.itemEncoding, forKey: .itemEncoding)
265+
}
266+
}
172267

173268
if VendorExtensionsConfiguration.isEnabled(for: encoder) {
174269
try encodeExtensions(to: &container)
@@ -188,9 +283,25 @@ extension OpenAPI.Content: Decodable {
188283
)
189284
}
190285

286+
guard !(container.contains(.encoding) && (container.contains(.prefixEncoding) || container.contains(.itemEncoding))) else {
287+
throw GenericError(
288+
subjectName: "Encoding and Positional Encoding",
289+
details: "If `prefixEncoding` or `itemEncoding` are specified then `encoding` is not allowed in the Media Type Object (`OpenAPI.Content`).",
290+
codingPath: container.codingPath
291+
)
292+
}
293+
191294
schema = try container.decodeIfPresent(JSONSchema.self, forKey: .schema)
295+
itemSchema = try container.decodeIfPresent(JSONSchema.self, forKey: .itemSchema)
192296

193-
encoding = try container.decodeIfPresent(OrderedDictionary<String, Encoding>.self, forKey: .encoding)
297+
if container.contains(.encoding) {
298+
encoding = .a(try container.decode(OrderedDictionary<String, Encoding>.self, forKey: .encoding))
299+
} else {
300+
let prefixEncoding = try container.decodeIfPresent([Encoding].self, forKey: .prefixEncoding) ?? []
301+
let itemEncoding = try container.decodeIfPresent(Encoding.self, forKey: .itemEncoding)
302+
303+
encoding = .b(.init(prefixEncoding: prefixEncoding, itemEncoding: itemEncoding))
304+
}
194305

195306
if container.contains(.example) {
196307
example = try container.decode(AnyCodable.self, forKey: .example)
@@ -208,13 +319,24 @@ extension OpenAPI.Content: Decodable {
208319
extension OpenAPI.Content {
209320
internal enum CodingKeys: ExtendableCodingKey {
210321
case schema
322+
case itemSchema
211323
case example // `example` and `examples` are mutually exclusive
212324
case examples // `example` and `examples` are mutually exclusive
213325
case encoding
326+
case itemEncoding
327+
case prefixEncoding
214328
case extended(String)
215329

216330
static var allBuiltinKeys: [CodingKeys] {
217-
return [.schema, .example, .examples, .encoding]
331+
return [
332+
.schema,
333+
.itemSchema,
334+
.example,
335+
.examples,
336+
.encoding,
337+
.itemEncoding,
338+
.prefixEncoding
339+
]
218340
}
219341

220342
static func extendedKey(for value: String) -> CodingKeys {
@@ -225,12 +347,18 @@ extension OpenAPI.Content {
225347
switch stringValue {
226348
case "schema":
227349
self = .schema
350+
case "itemSchema":
351+
self = .itemSchema
228352
case "example":
229353
self = .example
230354
case "examples":
231355
self = .examples
232356
case "encoding":
233357
self = .encoding
358+
case "itemEncoding":
359+
self = .itemEncoding
360+
case "prefixEncoding":
361+
self = .prefixEncoding
234362
default:
235363
self = .extendedKey(for: stringValue)
236364
}
@@ -240,12 +368,18 @@ extension OpenAPI.Content {
240368
switch self {
241369
case .schema:
242370
return "schema"
371+
case .itemSchema:
372+
return "itemSchema"
243373
case .example:
244374
return "example"
245375
case .examples:
246376
return "examples"
247377
case .encoding:
248378
return "encoding"
379+
case .itemEncoding:
380+
return "itemEncoding"
381+
case .prefixEncoding:
382+
return "prefixEncoding"
249383
case .extended(let key):
250384
return key
251385
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// ContentPositionalEncoding.swift
3+
//
4+
//
5+
// Created by Mathew Polzin on 12/29/19.
6+
//
7+
8+
import OpenAPIKitCore
9+
10+
extension OpenAPI.Content {
11+
/// OpenAPI Spec `itemEncoding` and `prefixEncoding` on the "Media Type Object"
12+
///
13+
/// See [OpenAPI Media Type Object](https://spec.openapis.org/oas/v3.2.0.html#media-type-object).
14+
public struct PositionalEncoding: Equatable, Sendable {
15+
16+
/// An array of positional encoding information, as defined under
17+
/// [Encoding By Position](https://spec.openapis.org/oas/v3.2.0.html#encoding-by-position).
18+
/// The `prefixEncoding` field **SHALL** only apply when the media type is
19+
/// `multipart`. If no Encoding Object is provided for a property, the
20+
/// behavior is determined by the default values documented for the
21+
/// Encoding Object.
22+
public var prefixEncoding: [OpenAPI.Content.Encoding]
23+
24+
/// A single Encoding Object that provides encoding information for
25+
/// multiple array items, as defined under [Encoding By Position](https://spec.openapis.org/oas/v3.2.0.html#encoding-by-position).
26+
/// The `itemEncoding` field **SHALL** only apply when the media type
27+
/// is multipart. If no Encoding Object is provided for a property, the
28+
/// behavior is determined by the default values documented for the
29+
/// Encoding Object.
30+
public var itemEncoding: OpenAPI.Content.Encoding?
31+
32+
public init(
33+
prefixEncoding: [OpenAPI.Content.Encoding] = [],
34+
itemEncoding: OpenAPI.Content.Encoding? = nil
35+
) {
36+
self.prefixEncoding = prefixEncoding
37+
self.itemEncoding = itemEncoding
38+
}
39+
}
40+
}
41+

0 commit comments

Comments
 (0)