diff --git a/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPITypes+OpenAPI.swift b/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPITypes+OpenAPI.swift index cfdef4b..f9c411a 100644 --- a/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPITypes+OpenAPI.swift +++ b/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPITypes+OpenAPI.swift @@ -53,6 +53,31 @@ extension Attribute: WrappedRawOpenAPIType where RawValue: RawOpenAPINodeType { } } +extension Attribute: GenericOpenAPINodeType where RawValue: GenericOpenAPINodeType { + public static func genericOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode { + // If the RawValue is not required, we actually consider it + // nullable. To be not required is for the Attribute itself + // to be optional. + if try !RawValue.genericOpenAPINode(using: encoder).required { + return try RawValue.genericOpenAPINode(using: encoder).requiredNode().nullableNode() + } + return try RawValue.genericOpenAPINode(using: encoder) + } +} + +extension Attribute: DateOpenAPINodeType where RawValue: DateOpenAPINodeType { + public static func dateOpenAPINodeGuess(using encoder: JSONEncoder) -> JSONNode? { + // If the RawValue is not required, we actually consider it + // nullable. To be not required is for the Attribute itself + // to be optional. + if + !(RawValue.dateOpenAPINodeGuess(using: encoder)?.required ?? true) { + return RawValue.dateOpenAPINodeGuess(using: encoder)?.requiredNode().nullableNode() + } + return RawValue.dateOpenAPINodeGuess(using: encoder) + } +} + extension Attribute: AnyJSONCaseIterable where RawValue: CaseIterable, RawValue: Codable { public static func allCases(using encoder: JSONEncoder) -> [AnyCodable] { return (try? allCases(from: Array(RawValue.allCases), using: encoder)) ?? [] @@ -65,18 +90,6 @@ extension Attribute: AnyWrappedJSONCaseIterable where RawValue: AnyJSONCaseItera } } -extension Attribute: GenericOpenAPINodeType where RawValue: GenericOpenAPINodeType { - public static func genericOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode { - return try RawValue.genericOpenAPINode(using: encoder) - } -} - -extension Attribute: DateOpenAPINodeType where RawValue: DateOpenAPINodeType { - public static func dateOpenAPINodeGuess(using encoder: JSONEncoder) -> JSONNode? { - return RawValue.dateOpenAPINodeGuess(using: encoder) - } -} - extension TransformedAttribute: OpenAPINodeType where RawValue: OpenAPINodeType { static public func openAPINode() throws -> JSONNode { // If the RawValue is not required, we actually consider it diff --git a/Sources/JSONAPIOpenAPI/OpenAPI/SwiftPrimitiveTypes+OpenAPI.swift b/Sources/JSONAPIOpenAPI/OpenAPI/SwiftPrimitiveTypes+OpenAPI.swift index 19ee4da..235cb82 100644 --- a/Sources/JSONAPIOpenAPI/OpenAPI/SwiftPrimitiveTypes+OpenAPI.swift +++ b/Sources/JSONAPIOpenAPI/OpenAPI/SwiftPrimitiveTypes+OpenAPI.swift @@ -62,6 +62,12 @@ extension Optional: AnyJSONCaseIterable where Wrapped: CaseIterable, Wrapped: Co } } +extension Optional: DateOpenAPINodeType where Wrapped: DateOpenAPINodeType { + static public func dateOpenAPINodeGuess(using encoder: JSONEncoder) -> JSONNode? { + return Wrapped.dateOpenAPINodeGuess(using: encoder)?.optionalNode() + } +} + extension String: OpenAPINodeType { static public func openAPINode() throws -> JSONNode { return .string(.init(format: .generic, diff --git a/Tests/JSONAPIOpenAPITests/JSONAPIAttributeOpenAPITests.swift b/Tests/JSONAPIOpenAPITests/JSONAPIAttributeOpenAPITests.swift index 0acab15..1cee8f8 100644 --- a/Tests/JSONAPIOpenAPITests/JSONAPIAttributeOpenAPITests.swift +++ b/Tests/JSONAPIOpenAPITests/JSONAPIAttributeOpenAPITests.swift @@ -802,62 +802,80 @@ extension JSONAPIAttributeOpenAPITests { XCTAssertEqual(numberContext, .init()) } -// func test_NullableEnumAttribute() { -// let node = try! Attribute.wrappedOpenAPINode() -// -// XCTAssertTrue(node.required) -// XCTAssertEqual(node.jsonTypeFormat, .string(.generic)) -// -// guard case .string(let contextA, let stringContext) = node else { -// XCTFail("Expected string Node") -// return -// } -// -// XCTAssertEqual(contextA, .init(format: .generic, -// required: true, -// nullable: true, -// allowedValues: nil)) -// -// XCTAssertEqual(stringContext, .init()) -// } -// -// func test_OptionalEnumAttribute() { -// let node = try! Attribute?.wrappedOpenAPINode() -// -// XCTAssertFalse(node.required) -// XCTAssertEqual(node.jsonTypeFormat, .string(.generic)) -// -// guard case .string(let contextA, let stringContext) = node else { -// XCTFail("Expected string Node") -// return -// } -// -// XCTAssertEqual(contextA, .init(format: .generic, -// required: false, -// nullable: false, -// allowedValues: nil)) -// -// XCTAssertEqual(stringContext, .init()) -// } -// -// func test_OptionalNullableEnumAttribute() { -// let node = try! Attribute?.wrappedOpenAPINode() -// -// XCTAssertFalse(node.required) -// XCTAssertEqual(node.jsonTypeFormat, .string(.generic)) -// -// guard case .string(let contextA, let stringContext) = node else { -// XCTFail("Expected string Node") -// return -// } -// -// XCTAssertEqual(contextA, .init(format: .generic, -// required: false, -// nullable: true, -// allowedValues: nil)) -// -// XCTAssertEqual(stringContext, .init()) -// } + func test_NullableDateAttribute() { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + encoder.dateEncodingStrategy = .secondsSince1970 + + let node = Attribute.dateOpenAPINodeGuess(using: encoder) + + XCTAssertNotNil(node) + + XCTAssertTrue(node?.required ?? false) + XCTAssertEqual(node?.jsonTypeFormat, .number(.double)) + + guard case .number(let contextA, let numberContext)? = node else { + XCTFail("Expected string Node") + return + } + + XCTAssertEqual(contextA, .init(format: .double, + required: true, + nullable: true, + allowedValues: nil)) + + XCTAssertEqual(numberContext, .init()) + } + + func test_OptionalDateAttribute() { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + encoder.dateEncodingStrategy = .secondsSince1970 + + let node = Attribute?.dateOpenAPINodeGuess(using: encoder) + + XCTAssertNotNil(node) + + XCTAssertFalse(node?.required ?? true) + XCTAssertEqual(node?.jsonTypeFormat, .number(.double)) + + guard case .number(let contextA, let numberContext)? = node else { + XCTFail("Expected string Node") + return + } + + XCTAssertEqual(contextA, .init(format: .double, + required: false, + nullable: false, + allowedValues: nil)) + + XCTAssertEqual(numberContext, .init()) + } + + func test_OptionalNullableDateAttribute() { + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + encoder.dateEncodingStrategy = .secondsSince1970 + + let node = Attribute?.dateOpenAPINodeGuess(using: encoder) + + XCTAssertNotNil(node) + + XCTAssertFalse(node?.required ?? true) + XCTAssertEqual(node?.jsonTypeFormat, .number(.double)) + + guard case .number(let contextA, let numberContext)? = node else { + XCTFail("Expected string Node") + return + } + + XCTAssertEqual(contextA, .init(format: .double, + required: false, + nullable: true, + allowedValues: nil)) + + XCTAssertEqual(numberContext, .init()) + } } // MARK: - Test Types