Skip to content

Commit

Permalink
Support NSNull in OpenAPIContainer types (#109)
Browse files Browse the repository at this point in the history
### 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.
  • Loading branch information
czechboy0 authored Jul 17, 2024
1 parent e80046b commit 39fa3ec
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 0 deletions.
17 changes: 17 additions & 0 deletions Sources/OpenAPIRuntime/Base/OpenAPIValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions Tests/OpenAPIRuntimeTests/Base/Test_OpenAPIValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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"])

Expand Down Expand Up @@ -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 {}
Expand Down

0 comments on commit 39fa3ec

Please sign in to comment.