Skip to content

Commit faced11

Browse files
authored
Merge pull request #290 from mattpolzin/bugfix/286/optional-example-values
make value optional for Example Objects
2 parents e805233 + b0ee6cb commit faced11

File tree

10 files changed

+108
-26
lines changed

10 files changed

+108
-26
lines changed

Sources/OpenAPIKit/Content/Content.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ extension OpenAPI.Content {
129129
internal static func firstExample(from exampleDict: OpenAPI.Example.Map) -> AnyCodable? {
130130
return exampleDict
131131
.lazy
132-
.compactMap { $0.value.exampleValue?.value.codableValue }
132+
.compactMap { $0.value.exampleValue?.value?.codableValue }
133133
.first
134134
}
135135

@@ -138,7 +138,7 @@ extension OpenAPI.Content {
138138
internal static func firstExample(from exampleDict: OrderedDictionary<String, OpenAPI.Example>) -> AnyCodable? {
139139
return exampleDict
140140
.lazy
141-
.compactMap { $0.value.value.codableValue }
141+
.compactMap { $0.value.value?.codableValue }
142142
.first
143143
}
144144
}

Sources/OpenAPIKit/Example.swift

+13-7
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ extension OpenAPI {
1515
public struct Example: Equatable, CodableVendorExtendable {
1616
public let summary: String?
1717
public let description: String?
18+
1819
/// Represents the OpenAPI `externalValue` as a URL _or_
1920
/// the OpenAPI `value` as `AnyCodable`.
20-
public let value: Either<URL, AnyCodable>
21+
public let value: Either<URL, AnyCodable>?
2122

2223
/// Dictionary of vendor extensions.
2324
///
@@ -29,7 +30,7 @@ extension OpenAPI {
2930
public init(
3031
summary: String? = nil,
3132
description: String? = nil,
32-
value: Either<URL, AnyCodable>,
33+
value: Either<URL, AnyCodable>? = nil,
3334
vendorExtensions: [String: AnyCodable] = [:]
3435
) {
3536
self.summary = summary
@@ -50,7 +51,7 @@ extension Either where A == OpenAPI.Reference<OpenAPI.Example>, B == OpenAPI.Exa
5051
public static func example(
5152
summary: String? = nil,
5253
description: String? = nil,
53-
value: Either<URL, AnyCodable>,
54+
value: Either<URL, AnyCodable>? = nil,
5455
vendorExtensions: [String: AnyCodable] = [:]
5556
) -> Self {
5657
return .b(
@@ -101,6 +102,8 @@ extension OpenAPI.Example: Encodable {
101102
try container.encode(url.absoluteURL, forKey: .externalValue)
102103
case .b(let example):
103104
try container.encode(example, forKey: .value)
105+
case nil:
106+
break
104107
}
105108

106109
try encodeExtensions(to: &container)
@@ -119,14 +122,17 @@ extension OpenAPI.Example: Decodable {
119122
)
120123
}
121124

122-
let externalValue = try container.decodeURLAsStringIfPresent(forKey: .externalValue)
125+
if let externalValue = try container.decodeURLAsStringIfPresent(forKey: .externalValue) {
126+
value = .a(externalValue)
127+
} else if let internalValue = try container.decodeIfPresent(AnyCodable.self, forKey: .value) {
128+
value = .b(internalValue)
129+
} else {
130+
value = nil
131+
}
123132

124133
summary = try container.decodeIfPresent(String.self, forKey: .summary)
125134
description = try container.decodeIfPresent(String.self, forKey: .description)
126135

127-
value = try externalValue.map(Either.init)
128-
?? .init( container.decode(AnyCodable.self, forKey: .value))
129-
130136
vendorExtensions = try Self.extensions(from: decoder)
131137
}
132138
}

Sources/OpenAPIKit30/Content/Content.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ extension OpenAPI.Content {
129129
internal static func firstExample(from exampleDict: OpenAPI.Example.Map) -> AnyCodable? {
130130
return exampleDict
131131
.lazy
132-
.compactMap { $0.value.exampleValue?.value.codableValue }
132+
.compactMap { $0.value.exampleValue?.value?.codableValue }
133133
.first
134134
}
135135

@@ -138,7 +138,7 @@ extension OpenAPI.Content {
138138
internal static func firstExample(from exampleDict: OrderedDictionary<String, OpenAPI.Example>) -> AnyCodable? {
139139
return exampleDict
140140
.lazy
141-
.compactMap { $0.value.value.codableValue }
141+
.compactMap { $0.value.value?.codableValue }
142142
.first
143143
}
144144
}

Sources/OpenAPIKit30/Example.swift

+13-7
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ extension OpenAPI {
1515
public struct Example: Equatable, CodableVendorExtendable {
1616
public let summary: String?
1717
public let description: String?
18+
1819
/// Represents the OpenAPI `externalValue` as a URL _or_
1920
/// the OpenAPI `value` as `AnyCodable`.
20-
public let value: Either<URL, AnyCodable>
21+
public let value: Either<URL, AnyCodable>?
2122

2223
/// Dictionary of vendor extensions.
2324
///
@@ -29,7 +30,7 @@ extension OpenAPI {
2930
public init(
3031
summary: String? = nil,
3132
description: String? = nil,
32-
value: Either<URL, AnyCodable>,
33+
value: Either<URL, AnyCodable>? = nil,
3334
vendorExtensions: [String: AnyCodable] = [:]
3435
) {
3536
self.summary = summary
@@ -50,7 +51,7 @@ extension Either where A == JSONReference<OpenAPI.Example>, B == OpenAPI.Example
5051
public static func example(
5152
summary: String? = nil,
5253
description: String? = nil,
53-
value: Either<URL, AnyCodable>,
54+
value: Either<URL, AnyCodable>? = nil,
5455
vendorExtensions: [String: AnyCodable] = [:]
5556
) -> Self {
5657
return .b(
@@ -77,6 +78,8 @@ extension OpenAPI.Example: Encodable {
7778
try container.encode(url.absoluteURL, forKey: .externalValue)
7879
case .b(let example):
7980
try container.encode(example, forKey: .value)
81+
case nil:
82+
break
8083
}
8184

8285
try encodeExtensions(to: &container)
@@ -95,14 +98,17 @@ extension OpenAPI.Example: Decodable {
9598
)
9699
}
97100

98-
let externalValue = try container.decodeURLAsStringIfPresent(forKey: .externalValue)
101+
if let externalValue = try container.decodeURLAsStringIfPresent(forKey: .externalValue) {
102+
value = .a(externalValue)
103+
} else if let internalValue = try container.decodeIfPresent(AnyCodable.self, forKey: .value) {
104+
value = .b(internalValue)
105+
} else {
106+
value = nil
107+
}
99108

100109
summary = try container.decodeIfPresent(String.self, forKey: .summary)
101110
description = try container.decodeIfPresent(String.self, forKey: .description)
102111

103-
value = try externalValue.map(Either.init)
104-
?? .init( container.decode(AnyCodable.self, forKey: .value))
105-
106112
vendorExtensions = try Self.extensions(from: decoder)
107113
}
108114
}

Tests/OpenAPIKit30Tests/Content/ContentTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ extension ContentTests {
319319
XCTAssertEqual(content.schema, .init(.object(properties: ["hello": .string])))
320320

321321
XCTAssertEqual(content.example?.value as? [String: String], [ "hello": "world" ])
322-
XCTAssertEqual(content.examples?["hello"]?.exampleValue?.value.codableValue?.value as? [String: String], [ "hello": "world" ])
322+
XCTAssertEqual(content.examples?["hello"]?.exampleValue?.value?.codableValue?.value as? [String: String], [ "hello": "world" ])
323323
}
324324

325325
func test_decodeFailureForBothExampleAndExamples() {

Tests/OpenAPIKit30Tests/ExampleTests.swift

+36-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ final class ExampleTests: XCTestCase {
4040
XCTAssertNil(small.description)
4141
XCTAssertEqual(small.value, .init("hello"))
4242
XCTAssertEqual(small.vendorExtensions, [:])
43+
44+
let noValue = OpenAPI.Example()
45+
XCTAssertNil(noValue.summary)
46+
XCTAssertNil(noValue.description)
47+
XCTAssertNil(noValue.value)
48+
XCTAssertEqual(noValue.vendorExtensions, [:])
4349
}
4450

4551
func test_locallyDereferenceable() throws {
@@ -62,6 +68,9 @@ final class ExampleTests: XCTestCase {
6268

6369
let small = OpenAPI.Example(value: .init("hello"))
6470
XCTAssertEqual(try small.dereferenced(in: .noComponents), small)
71+
72+
let noValue = OpenAPI.Example()
73+
XCTAssertEqual(try noValue.dereferenced(in: .noComponents), noValue)
6574
}
6675
}
6776

@@ -98,7 +107,7 @@ extension ExampleTests {
98107

99108
XCTAssertEqual(example, OpenAPI.Example(summary: "hello",
100109
value: .init(URL(string: "https://google.com")!)))
101-
XCTAssertEqual(example.value.urlValue, URL(string: "https://google.com")!)
110+
XCTAssertEqual(example.value?.urlValue, URL(string: "https://google.com")!)
102111
}
103112

104113
func test_descriptionAndInternalExample_encode() throws {
@@ -219,6 +228,32 @@ extension ExampleTests {
219228
XCTAssertEqual(example, OpenAPI.Example(value: .init(URL(string: "https://google.com")!)))
220229
}
221230

231+
func test_noExample_encode() throws {
232+
let example = OpenAPI.Example()
233+
let encodedExample = try orderUnstableTestStringFromEncoding(of: example)
234+
235+
assertJSONEquivalent(
236+
encodedExample,
237+
"""
238+
{
239+
240+
}
241+
"""
242+
)
243+
}
244+
245+
func test_noExample_decode() throws {
246+
let exampleData =
247+
"""
248+
{
249+
}
250+
""".data(using: .utf8)!
251+
252+
let example = try orderUnstableDecode(OpenAPI.Example.self, from: exampleData)
253+
254+
XCTAssertEqual(example, OpenAPI.Example())
255+
}
256+
222257
func test_failedDecodeForInternalAndExternalExamples() {
223258
let exampleData =
224259
"""

Tests/OpenAPIKit30Tests/Parameter/ParameterSchemaTests.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ final class ParameterSchemaTests: XCTestCase {
112112
XCTAssertNotNil(t7.example)
113113
XCTAssertEqual(t7.example?.value as? String, "hello")
114114
XCTAssertNotNil(t7.examples)
115-
XCTAssertEqual(t7.examples?["two"]?.exampleValue?.value.codableValue?.value as? String, "world")
115+
XCTAssertEqual(t7.examples?["two"]?.exampleValue?.value?.codableValue?.value as? String, "world")
116116

117117
// straight to schema override explode multiple examples
118118
let t8 = Schema(
@@ -132,7 +132,7 @@ final class ParameterSchemaTests: XCTestCase {
132132
XCTAssertNotNil(t8.example)
133133
XCTAssertEqual(t8.example?.value as? String, "hello")
134134
XCTAssertNotNil(t8.examples)
135-
XCTAssertEqual(t8.examples?["two"]?.exampleValue?.value.codableValue?.value as? String, "world")
135+
XCTAssertEqual(t8.examples?["two"]?.exampleValue?.value?.codableValue?.value as? String, "world")
136136

137137
// schema reference multiple examples
138138
let t9 = Schema(

Tests/OpenAPIKitTests/Content/ContentTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ extension ContentTests {
319319
XCTAssertEqual(content.schema, .init(.object(properties: ["hello": .string])))
320320

321321
XCTAssertEqual(content.example?.value as? [String: String], [ "hello": "world" ])
322-
XCTAssertEqual(content.examples?["hello"]?.exampleValue?.value.codableValue?.value as? [String: String], [ "hello": "world" ])
322+
XCTAssertEqual(content.examples?["hello"]?.exampleValue?.value?.codableValue?.value as? [String: String], [ "hello": "world" ])
323323
}
324324

325325
func test_decodeFailureForBothExampleAndExamples() {

Tests/OpenAPIKitTests/ExampleTests.swift

+36-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ final class ExampleTests: XCTestCase {
4040
XCTAssertNil(small.description)
4141
XCTAssertEqual(small.value, .init("hello"))
4242
XCTAssertEqual(small.vendorExtensions, [:])
43+
44+
let noValue = OpenAPI.Example()
45+
XCTAssertNil(noValue.summary)
46+
XCTAssertNil(noValue.description)
47+
XCTAssertNil(noValue.value)
48+
XCTAssertEqual(noValue.vendorExtensions, [:])
4349
}
4450

4551
func test_locallyDereferenceable() throws {
@@ -62,6 +68,9 @@ final class ExampleTests: XCTestCase {
6268

6369
let small = OpenAPI.Example(value: .init("hello"))
6470
XCTAssertEqual(try small.dereferenced(in: .noComponents), small)
71+
72+
let noValue = OpenAPI.Example()
73+
XCTAssertEqual(try noValue.dereferenced(in: .noComponents), noValue)
6574
}
6675
}
6776

@@ -98,7 +107,7 @@ extension ExampleTests {
98107

99108
XCTAssertEqual(example, OpenAPI.Example(summary: "hello",
100109
value: .init(URL(string: "https://google.com")!)))
101-
XCTAssertEqual(example.value.urlValue, URL(string: "https://google.com")!)
110+
XCTAssertEqual(example.value?.urlValue, URL(string: "https://google.com")!)
102111
}
103112

104113
func test_descriptionAndInternalExample_encode() throws {
@@ -219,6 +228,32 @@ extension ExampleTests {
219228
XCTAssertEqual(example, OpenAPI.Example(value: .init(URL(string: "https://google.com")!)))
220229
}
221230

231+
func test_noExample_encode() throws {
232+
let example = OpenAPI.Example()
233+
let encodedExample = try orderUnstableTestStringFromEncoding(of: example)
234+
235+
assertJSONEquivalent(
236+
encodedExample,
237+
"""
238+
{
239+
240+
}
241+
"""
242+
)
243+
}
244+
245+
func test_noExample_decode() throws {
246+
let exampleData =
247+
"""
248+
{
249+
}
250+
""".data(using: .utf8)!
251+
252+
let example = try orderUnstableDecode(OpenAPI.Example.self, from: exampleData)
253+
254+
XCTAssertEqual(example, OpenAPI.Example())
255+
}
256+
222257
func test_failedDecodeForInternalAndExternalExamples() {
223258
let exampleData =
224259
"""

Tests/OpenAPIKitTests/Parameter/ParameterSchemaTests.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ final class ParameterSchemaTests: XCTestCase {
112112
XCTAssertNotNil(t7.example)
113113
XCTAssertEqual(t7.example?.value as? String, "hello")
114114
XCTAssertNotNil(t7.examples)
115-
XCTAssertEqual(t7.examples?["two"]?.exampleValue?.value.codableValue?.value as? String, "world")
115+
XCTAssertEqual(t7.examples?["two"]?.exampleValue?.value?.codableValue?.value as? String, "world")
116116

117117
// straight to schema override explode multiple examples
118118
let t8 = Schema(
@@ -132,7 +132,7 @@ final class ParameterSchemaTests: XCTestCase {
132132
XCTAssertNotNil(t8.example)
133133
XCTAssertEqual(t8.example?.value as? String, "hello")
134134
XCTAssertNotNil(t8.examples)
135-
XCTAssertEqual(t8.examples?["two"]?.exampleValue?.value.codableValue?.value as? String, "world")
135+
XCTAssertEqual(t8.examples?["two"]?.exampleValue?.value?.codableValue?.value as? String, "world")
136136

137137
// schema reference multiple examples
138138
let t9 = Schema(

0 commit comments

Comments
 (0)