Skip to content

Commit 1a2c840

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

File tree

8 files changed

+530
-25
lines changed

8 files changed

+530
-25
lines changed

Sources/OpenAPIKit/Content/Content.swift

Lines changed: 194 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,31 @@ 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+
///
28+
/// If the OpenAPI Document specifies the 'encoding' key (a map)
29+
/// then this property will be set to its first case. If the OpenAPI
30+
/// Document specifies either or both of the 'prefixEncoding' and
31+
/// 'itemEncoding' keys, this property will be set to its second case.
32+
///
33+
/// You can access the encoding map (OAS 'encoding' property) as the `Content`
34+
/// type's `encodingMap` as well.
35+
///
36+
/// You can access the positional encoding (OAS 'prefixEncoding' and
37+
/// `itemEncoding` properties) as the `Content` type's `prefixEncoding`
38+
/// and `itemEncoding` properties.
39+
public var encoding: Either<OrderedDictionary<String, Encoding>, PositionalEncoding>?
1940

2041
/// Dictionary of vendor extensions.
2142
///
@@ -24,18 +45,87 @@ extension OpenAPI {
2445
/// where the values are anything codable.
2546
public var vendorExtensions: [String: AnyCodable]
2647

48+
/// The encoding of this `Content` (Media Type Object) if it is a map
49+
/// from property names to encoding information.
50+
///
51+
/// This property gets modified as part of the `encoding` property.
52+
///
53+
/// See also the `prefixEncoding` and `itemEncoding` properties.
54+
public var encodingMap: OrderedDictionary<String, Encoding>? { encoding?.a }
55+
56+
/// The positional prefix-encoding for this `Content` (Media Type
57+
/// Object) if set.
58+
///
59+
/// This property gets modified as part of the `encoding` property.
60+
///
61+
/// See also the `itemEncoding` and `encodingMap` properties.
62+
public var prefixEncoding: [Encoding]? { encoding?.b?.prefixEncoding }
63+
64+
/// The positional item-encoding for this `Content` (Media Type
65+
/// Object) if set.
66+
///
67+
/// This property gets modified as part of the `encoding` property.
68+
///
69+
/// See also the `prefixEncoding` and `encodingMap` properties.
70+
public var itemEncoding: Encoding? { encoding?.b?.itemEncoding }
71+
2772
/// Create `Content` with a schema, a reference to a schema, or no
2873
/// schema at all and optionally provide a single example.
2974
public init(
3075
schema: JSONSchema?,
76+
itemSchema: JSONSchema? = nil,
3177
example: AnyCodable? = nil,
3278
encoding: OrderedDictionary<String, Encoding>? = nil,
3379
vendorExtensions: [String: AnyCodable] = [:]
3480
) {
3581
self.schema = schema
82+
self.itemSchema = itemSchema
83+
self.example = example
84+
self.examples = nil
85+
self.encoding = encoding.map(Either.a)
86+
self.vendorExtensions = vendorExtensions
87+
}
88+
89+
/// Create `Content` with a schema, a reference to a schema, or no
90+
/// schema at all and optionally provide a single example.
91+
public init(
92+
schema: JSONSchema?,
93+
itemSchema: JSONSchema? = nil,
94+
example: AnyCodable? = nil,
95+
prefixEncoding: [Encoding] = [],
96+
itemEncoding: Encoding? = nil,
97+
vendorExtensions: [String: AnyCodable] = [:]
98+
) {
99+
self.schema = schema
100+
self.itemSchema = itemSchema
101+
self.example = example
102+
self.examples = nil
103+
if itemEncoding != nil || prefixEncoding != [] {
104+
self.encoding = .b(.init(prefixEncoding: prefixEncoding, itemEncoding: itemEncoding))
105+
} else {
106+
self.encoding = nil
107+
}
108+
self.vendorExtensions = vendorExtensions
109+
}
110+
111+
/// Create `Content` with a schema, a reference to a schema, or no
112+
/// schema at all and optionally provide a single example.
113+
public init(
114+
itemSchema: JSONSchema?,
115+
example: AnyCodable? = nil,
116+
prefixEncoding: [Encoding] = [],
117+
itemEncoding: Encoding? = nil,
118+
vendorExtensions: [String: AnyCodable] = [:]
119+
) {
120+
self.schema = nil
121+
self.itemSchema = itemSchema
36122
self.example = example
37123
self.examples = nil
38-
self.encoding = encoding
124+
if itemEncoding != nil || prefixEncoding != [] {
125+
self.encoding = .b(.init(prefixEncoding: prefixEncoding, itemEncoding: itemEncoding))
126+
} else {
127+
self.encoding = nil
128+
}
39129
self.vendorExtensions = vendorExtensions
40130
}
41131

@@ -50,22 +140,24 @@ extension OpenAPI {
50140
self.schema = .reference(schemaReference.jsonReference, description: schemaReference.description)
51141
self.example = example
52142
self.examples = nil
53-
self.encoding = encoding
143+
self.encoding = encoding.map(Either.a)
54144
self.vendorExtensions = vendorExtensions
55145
}
56146

57147
/// Create `Content` with a schema and optionally provide a single
58148
/// example.
59149
public init(
60150
schema: JSONSchema,
151+
itemSchema: JSONSchema? = nil,
61152
example: AnyCodable? = nil,
62153
encoding: OrderedDictionary<String, Encoding>? = nil,
63154
vendorExtensions: [String: AnyCodable] = [:]
64155
) {
65156
self.schema = schema
157+
self.itemSchema = itemSchema
66158
self.example = example
67159
self.examples = nil
68-
self.encoding = encoding
160+
self.encoding = encoding.map(Either.a)
69161
self.vendorExtensions = vendorExtensions
70162
}
71163

@@ -89,7 +181,7 @@ extension OpenAPI {
89181
}
90182
self.examples = examples
91183
self.example = examples.flatMap(Self.firstExample(from:))
92-
self.encoding = encoding
184+
self.encoding = encoding.map(Either.a)
93185
self.vendorExtensions = vendorExtensions
94186
}
95187

@@ -104,22 +196,61 @@ extension OpenAPI {
104196
self.schema = .reference(schemaReference.jsonReference)
105197
self.examples = examples
106198
self.example = examples.flatMap(Self.firstExample(from:))
107-
self.encoding = encoding
199+
self.encoding = encoding.map(Either.a)
108200
self.vendorExtensions = vendorExtensions
109201
}
110202

111203
/// Create `Content` with a schema and optionally provide a map
112204
/// of examples.
113205
public init(
114206
schema: JSONSchema,
207+
itemSchema: JSONSchema? = nil,
115208
examples: Example.Map?,
116209
encoding: OrderedDictionary<String, Encoding>? = nil,
117210
vendorExtensions: [String: AnyCodable] = [:]
118211
) {
119212
self.schema = schema
213+
self.itemSchema = itemSchema
214+
self.examples = examples
215+
self.example = examples.flatMap(Self.firstExample(from:))
216+
self.encoding = encoding.map(Either.a)
217+
self.vendorExtensions = vendorExtensions
218+
}
219+
220+
/// Create `Content` with a schema and optionally provide a map
221+
/// of examples.
222+
public init(
223+
itemSchema: JSONSchema?,
224+
examples: Example.Map?,
225+
encoding: OrderedDictionary<String, Encoding>? = nil,
226+
vendorExtensions: [String: AnyCodable] = [:]
227+
) {
228+
self.schema = nil
229+
self.itemSchema = itemSchema
230+
self.examples = examples
231+
self.example = examples.flatMap(Self.firstExample(from:))
232+
self.encoding = encoding.map(Either.a)
233+
self.vendorExtensions = vendorExtensions
234+
}
235+
236+
/// Create `Content` with a schema and optionally provide a map
237+
/// of examples.
238+
public init(
239+
itemSchema: JSONSchema? = nil,
240+
examples: Example.Map?,
241+
prefixEncoding: [Encoding] = [],
242+
itemEncoding: Encoding? = nil,
243+
vendorExtensions: [String: AnyCodable] = [:]
244+
) {
245+
self.schema = nil
246+
self.itemSchema = itemSchema
120247
self.examples = examples
121248
self.example = examples.flatMap(Self.firstExample(from:))
122-
self.encoding = encoding
249+
if itemEncoding != nil || prefixEncoding != [] {
250+
self.encoding = .b(.init(prefixEncoding: prefixEncoding, itemEncoding: itemEncoding))
251+
} else {
252+
self.encoding = nil
253+
}
123254
self.vendorExtensions = vendorExtensions
124255
}
125256
}
@@ -159,6 +290,7 @@ extension OpenAPI.Content: Encodable {
159290
var container = encoder.container(keyedBy: CodingKeys.self)
160291

161292
try container.encodeIfPresent(schema, forKey: .schema)
293+
try container.encodeIfPresent(itemSchema, forKey: .itemSchema)
162294

163295
// only encode `examples` if non-nil,
164296
// otherwise encode `example` if non-nil
@@ -168,7 +300,18 @@ extension OpenAPI.Content: Encodable {
168300
try container.encode(example, forKey: .example)
169301
}
170302

171-
try container.encodeIfPresent(encoding, forKey: .encoding)
303+
if let encoding {
304+
switch encoding {
305+
case .a(let encoding):
306+
try container.encode(encoding, forKey: .encoding)
307+
308+
case .b(let positionalEncoding):
309+
if !positionalEncoding.prefixEncoding.isEmpty {
310+
try container.encode(positionalEncoding.prefixEncoding, forKey: .prefixEncoding)
311+
}
312+
try container.encodeIfPresent(positionalEncoding.itemEncoding, forKey: .itemEncoding)
313+
}
314+
}
172315

173316
if VendorExtensionsConfiguration.isEnabled(for: encoder) {
174317
try encodeExtensions(to: &container)
@@ -188,9 +331,27 @@ extension OpenAPI.Content: Decodable {
188331
)
189332
}
190333

334+
guard !(container.contains(.encoding) && (container.contains(.prefixEncoding) || container.contains(.itemEncoding))) else {
335+
throw GenericError(
336+
subjectName: "Encoding and Positional Encoding",
337+
details: "If `prefixEncoding` or `itemEncoding` are specified then `encoding` is not allowed in the Media Type Object (`OpenAPI.Content`).",
338+
codingPath: container.codingPath
339+
)
340+
}
341+
191342
schema = try container.decodeIfPresent(JSONSchema.self, forKey: .schema)
343+
itemSchema = try container.decodeIfPresent(JSONSchema.self, forKey: .itemSchema)
344+
345+
if container.contains(.encoding) {
346+
encoding = .a(try container.decode(OrderedDictionary<String, Encoding>.self, forKey: .encoding))
347+
} else if container.contains(.prefixEncoding) || container.contains(.itemEncoding) {
348+
let prefixEncoding = try container.decodeIfPresent([Encoding].self, forKey: .prefixEncoding) ?? []
349+
let itemEncoding = try container.decodeIfPresent(Encoding.self, forKey: .itemEncoding)
192350

193-
encoding = try container.decodeIfPresent(OrderedDictionary<String, Encoding>.self, forKey: .encoding)
351+
encoding = .b(.init(prefixEncoding: prefixEncoding, itemEncoding: itemEncoding))
352+
} else {
353+
encoding = nil
354+
}
194355

195356
if container.contains(.example) {
196357
example = try container.decode(AnyCodable.self, forKey: .example)
@@ -208,13 +369,24 @@ extension OpenAPI.Content: Decodable {
208369
extension OpenAPI.Content {
209370
internal enum CodingKeys: ExtendableCodingKey {
210371
case schema
372+
case itemSchema
211373
case example // `example` and `examples` are mutually exclusive
212374
case examples // `example` and `examples` are mutually exclusive
213375
case encoding
376+
case itemEncoding
377+
case prefixEncoding
214378
case extended(String)
215379

216380
static var allBuiltinKeys: [CodingKeys] {
217-
return [.schema, .example, .examples, .encoding]
381+
return [
382+
.schema,
383+
.itemSchema,
384+
.example,
385+
.examples,
386+
.encoding,
387+
.itemEncoding,
388+
.prefixEncoding
389+
]
218390
}
219391

220392
static func extendedKey(for value: String) -> CodingKeys {
@@ -225,12 +397,18 @@ extension OpenAPI.Content {
225397
switch stringValue {
226398
case "schema":
227399
self = .schema
400+
case "itemSchema":
401+
self = .itemSchema
228402
case "example":
229403
self = .example
230404
case "examples":
231405
self = .examples
232406
case "encoding":
233407
self = .encoding
408+
case "itemEncoding":
409+
self = .itemEncoding
410+
case "prefixEncoding":
411+
self = .prefixEncoding
234412
default:
235413
self = .extendedKey(for: stringValue)
236414
}
@@ -240,12 +418,18 @@ extension OpenAPI.Content {
240418
switch self {
241419
case .schema:
242420
return "schema"
421+
case .itemSchema:
422+
return "itemSchema"
243423
case .example:
244424
return "example"
245425
case .examples:
246426
return "examples"
247427
case .encoding:
248428
return "encoding"
429+
case .itemEncoding:
430+
return "itemEncoding"
431+
case .prefixEncoding:
432+
return "prefixEncoding"
249433
case .extended(let key):
250434
return key
251435
}
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)