Skip to content

Fix OpenAPIValueContainer serialization of nested values #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Sources/OpenAPIRuntime/Base/OpenAPIValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ public struct OpenAPIValueContainer: Codable, Equatable, Hashable, Sendable {
try container.encode(value)
case let value as String:
try container.encode(value)
case let value as [OpenAPIValueContainer?]:
case let value as [Sendable?]:
try container.encode(value.map(OpenAPIValueContainer.init(validatedValue:)))
case let value as [String: OpenAPIValueContainer?]:
case let value as [String: Sendable?]:
try container.encode(value.mapValues(OpenAPIValueContainer.init(validatedValue:)))
default:
throw EncodingError.invalidValue(
Expand Down Expand Up @@ -211,11 +211,11 @@ public struct OpenAPIValueContainer: Codable, Equatable, Hashable, Sendable {
hasher.combine(value)
case let value as String:
hasher.combine(value)
case let value as [Sendable]:
case let value as [Sendable?]:
for item in value {
hasher.combine(OpenAPIValueContainer(validatedValue: item))
}
case let value as [String: Sendable]:
case let value as [String: Sendable?]:
for (key, itemValue) in value {
hasher.combine(key)
hasher.combine(OpenAPIValueContainer(validatedValue: itemValue))
Expand Down
194 changes: 194 additions & 0 deletions Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIValue.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import XCTest
@_spi(Generated) import OpenAPIRuntime

final class Test_OpenAPIValue: Test_Runtime {

func testValidationOnCreation() throws {
_ = OpenAPIValueContainer("hello")
_ = OpenAPIValueContainer(true)
_ = OpenAPIValueContainer(1)
_ = OpenAPIValueContainer(4.5)

_ = try OpenAPIValueContainer(unvalidatedValue: ["hello"])
_ = try OpenAPIValueContainer(unvalidatedValue: ["hello": "world"])

_ = try OpenAPIObjectContainer(unvalidatedValue: ["hello": "world"])
_ = try OpenAPIObjectContainer(unvalidatedValue: ["hello": ["nested": "world", "nested2": 2]])

_ = try OpenAPIArrayContainer(unvalidatedValue: ["hello"])
_ = try OpenAPIArrayContainer(unvalidatedValue: ["hello", ["nestedHello", 2]])
}

func testEncoding_container_success() throws {
let values: [Any?] = [
nil,
"Hello",
[
"key": "value",
"anotherKey": [
1,
"two",
],
],
1 as Int,
2.5 as Double,
[true],
]
let container = try OpenAPIValueContainer(unvalidatedValue: values)
let expectedString = #"""
[
null,
"Hello",
{
"anotherKey" : [
1,
"two"
],
"key" : "value"
},
1,
2.5,
[
true
]
]
"""#
try _testPrettyEncoded(container, expectedJSON: expectedString)
}

func testEncoding_container_failure() throws {
struct Foobar: Equatable {}
XCTAssertThrowsError(try OpenAPIValueContainer(unvalidatedValue: Foobar())) { error in
let err = try! XCTUnwrap(error as? EncodingError)
guard case let .invalidValue(value, context) = err else {
XCTFail("Unexpected error")
return
}
let typedValue = try! XCTUnwrap(value as? Foobar)
XCTAssertEqual(typedValue, Foobar())
XCTAssert(context.codingPath.isEmpty)
XCTAssertNil(context.underlyingError)
XCTAssertEqual(context.debugDescription, "Type 'Foobar' is not a supported OpenAPI value.")
}
}

func testDecoding_container_success() throws {
let json = #"""
[
null,
"Hello",
{
"anotherKey" : [
1,
"two"
],
"key" : "value"
},
1,
2.5,
[
true
]
]
"""#
let container: OpenAPIValueContainer = try _getDecoded(json: json)
let value = try XCTUnwrap(container.value)
let array = try XCTUnwrap(value as? [Sendable?])
XCTAssertEqual(array.count, 6)
XCTAssertNil(array[0])
XCTAssertEqual(array[1] as? String, "Hello")
let dict = try XCTUnwrap(array[2] as? [String: Sendable?])
XCTAssertEqual(dict.count, 2)
let nestedArray = try XCTUnwrap(dict["anotherKey"] as? [Sendable?])
XCTAssertEqual(nestedArray.count, 2)
XCTAssertEqual(nestedArray[0] as? Int, 1)
XCTAssertEqual(nestedArray[1] as? String, "two")
XCTAssertEqual(dict["key"] as? String, "value")
XCTAssertEqual(array[3] as? Int, 1)
XCTAssertEqual(array[4] as? Double, 2.5)
let boolArray = try XCTUnwrap(array[5] as? [Sendable?])
XCTAssertEqual(boolArray.count, 1)
XCTAssertEqual(boolArray[0] as? Bool, true)
}

func testEncoding_object_success() throws {
let values: [String: Any?] = [
"key": "value",
"keyMore": [
true
],
]
let container = try OpenAPIObjectContainer(unvalidatedValue: values)
let expectedString = #"""
{
"key" : "value",
"keyMore" : [
true
]
}
"""#
try _testPrettyEncoded(container, expectedJSON: expectedString)
}

func testDecoding_object_success() throws {
let json = #"""
{
"key" : "value",
"keyMore" : [
true
]
}
"""#
let container: OpenAPIObjectContainer = try _getDecoded(json: json)
let value = container.value
XCTAssertEqual(value.count, 2)
XCTAssertEqual(value["key"] as? String, "value")
XCTAssertEqual(value["keyMore"] as? [Bool], [true])
}

func testEncoding_array_success() throws {
let values: [Any?] = [
"one",
["two": 2],
]
let container = try OpenAPIArrayContainer(unvalidatedValue: values)
let expectedString = #"""
[
"one",
{
"two" : 2
}
]
"""#
try _testPrettyEncoded(container, expectedJSON: expectedString)
}

func testDecoding_array_success() throws {
let json = #"""
[
"one",
{
"two" : 2
}
]
"""#
let container: OpenAPIArrayContainer = try _getDecoded(json: json)
let value = container.value
XCTAssertEqual(value.count, 2)
XCTAssertEqual(value[0] as? String, "one")
XCTAssertEqual(value[1] as? [String: Int], ["two": 2])
}
}
13 changes: 13 additions & 0 deletions Tests/OpenAPIRuntimeTests/Test_Runtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,19 @@ class Test_Runtime: XCTestCase {
var testStructPrettyData: Data {
Data(testStructPrettyString.utf8)
}

func _testPrettyEncoded<Value: Encodable>(_ value: Value, expectedJSON: String) throws {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let data = try encoder.encode(value)
XCTAssertEqual(String(data: data, encoding: .utf8)!, expectedJSON)
}

func _getDecoded<Value: Decodable>(json: String) throws -> Value {
let inputData = json.data(using: .utf8)!
let decoder = JSONDecoder()
return try decoder.decode(Value.self, from: inputData)
}
}

public func XCTAssertEqualURLString(_ lhs: URL?, _ rhs: String, file: StaticString = #file, line: UInt = #line) {
Expand Down