From 39fa3ec57879bdeef185854bdc2ce356e1cb66b5 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 17 Jul 2024 16:43:40 +0200 Subject: [PATCH] Support NSNull in OpenAPIContainer types (#109) ### Motivation When receiving containers from adopter code, there might be NSNull values, which represent a nil value. Previously, this value would not be treated as nil, instead it'd throw an error as an unrecognized type. ### Modifications Handle NSNull and treat it as nil. ### Result You can provide a container with an NSNull nested value and it'll get encoded correctly. ### Test Plan Added a unit test. --- .../OpenAPIRuntime/Base/OpenAPIValue.swift | 17 +++++++++++++++ .../Base/Test_OpenAPIValue.swift | 21 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/Sources/OpenAPIRuntime/Base/OpenAPIValue.swift b/Sources/OpenAPIRuntime/Base/OpenAPIValue.swift index 0d39a6c4..2bb98f4c 100644 --- a/Sources/OpenAPIRuntime/Base/OpenAPIValue.swift +++ b/Sources/OpenAPIRuntime/Base/OpenAPIValue.swift @@ -12,6 +12,14 @@ // //===----------------------------------------------------------------------===// +#if canImport(Foundation) +#if canImport(Darwin) +import class Foundation.NSNull +#else +@preconcurrency import class Foundation.NSNull +#endif +#endif + /// A container for a value represented by JSON Schema. /// /// Contains an untyped JSON value. In some cases, the structure of the data @@ -62,6 +70,9 @@ public struct OpenAPIValueContainer: Codable, Hashable, Sendable { /// - Throws: When the value is not supported. static func tryCast(_ value: (any Sendable)?) throws -> (any Sendable)? { guard let value = value else { return nil } + #if canImport(Foundation) + if value is NSNull { return value } + #endif if let array = value as? [(any Sendable)?] { return try array.map(tryCast(_:)) } if let dictionary = value as? [String: (any Sendable)?] { return try dictionary.mapValues(tryCast(_:)) } if let value = tryCastPrimitiveType(value) { return value } @@ -123,6 +134,12 @@ public struct OpenAPIValueContainer: Codable, Hashable, Sendable { try container.encodeNil() return } + #if canImport(Foundation) + if value is NSNull { + try container.encodeNil() + return + } + #endif switch value { case let value as Bool: try container.encode(value) case let value as Int: try container.encode(value) diff --git a/Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIValue.swift b/Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIValue.swift index d95ee8c4..89a4a5a8 100644 --- a/Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIValue.swift +++ b/Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIValue.swift @@ -12,6 +12,13 @@ // //===----------------------------------------------------------------------===// import XCTest +#if canImport(Foundation) +#if canImport(Darwin) +import class Foundation.NSNull +#else +@preconcurrency import class Foundation.NSNull +#endif +#endif @_spi(Generated) @testable import OpenAPIRuntime final class Test_OpenAPIValue: Test_Runtime { @@ -22,6 +29,10 @@ final class Test_OpenAPIValue: Test_Runtime { _ = OpenAPIValueContainer(1) _ = OpenAPIValueContainer(4.5) + #if canImport(Foundation) + XCTAssertEqual(try OpenAPIValueContainer(unvalidatedValue: NSNull()).value as? NSNull, NSNull()) + #endif + _ = try OpenAPIValueContainer(unvalidatedValue: ["hello"]) _ = try OpenAPIValueContainer(unvalidatedValue: ["hello": "world"]) @@ -60,6 +71,16 @@ final class Test_OpenAPIValue: Test_Runtime { """# try _testPrettyEncoded(container, expectedJSON: expectedString) } + #if canImport(Foundation) + func testEncodingNSNull() throws { + let value = NSNull() + let container = try OpenAPIValueContainer(unvalidatedValue: value) + let expectedString = #""" + null + """# + try _testPrettyEncoded(container, expectedJSON: expectedString) + } + #endif func testEncoding_container_failure() throws { struct Foobar: Equatable {}