Skip to content

Commit

Permalink
Merge pull request #57 from mattpolzin/bugfix/yams-whole-number-floats
Browse files Browse the repository at this point in the history
support whole number floats decoded as integers
  • Loading branch information
mattpolzin authored Apr 23, 2020
2 parents abbfc11 + a32cfe6 commit 3c7e9f4
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 14 deletions.
31 changes: 27 additions & 4 deletions Sources/OpenAPIKit/Schema Object/SchemaObjectContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -504,10 +504,33 @@ extension JSONSchema.IntegerContext: Decodable {
let exclusiveMaximum = try container.decodeIfPresent(Bool.self, forKey: .exclusiveMaximum) ?? false
let exclusiveMinimum = try container.decodeIfPresent(Bool.self, forKey: .exclusiveMinimum) ?? false

maximum = (try container.decodeIfPresent(Int.self, forKey: .maximum))
.map { Bound(value: $0, exclusive: exclusiveMaximum) }
minimum = (try container.decodeIfPresent(Int.self, forKey: .minimum))
.map { Bound(value: $0, exclusive: exclusiveMinimum) }
// the following acrobatics thanks to some libraries (namely Yams) not
// being willing to decode floating point representations of whole numbers
// as integer values.
let maximumAttempt = try container.decodeIfPresent(Double.self, forKey: .maximum)
let minimumAttempt = try container.decodeIfPresent(Double.self, forKey: .minimum)

maximum = try maximumAttempt.map { floatMax in
guard let integer = Int(exactly: floatMax) else {
throw InconsistencyError(
subjectName: "maximum",
details: "Expected an Integer literal but found a floating point value",
codingPath: decoder.codingPath
)
}
return integer
}.map { Bound(value: $0, exclusive: exclusiveMaximum) }

minimum = try minimumAttempt.map { floatMin in
guard let integer = Int(exactly: floatMin) else {
throw InconsistencyError(
subjectName: "minimum",
details: "Expected an Integer literal but found a floating point value",
codingPath: decoder.codingPath
)
}
return integer
}.map { Bound(value: $0, exclusive: exclusiveMinimum) }
}
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/OpenAPIKitCompatibilitySuite/PetStoreAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ final class PetStoreAPICampatibilityTests: XCTestCase {
func test_successfullyParsedDocument() {
switch petStoreAPI {
case nil:
XCTFail("Did not attempt to pull Google Books API documentation like expected.")
XCTFail("Did not attempt to pull Pet Store API documentation like expected.")
case .failure(let error):
let prettyError = OpenAPI.Error(from: error)
XCTFail(prettyError.localizedDescription + "\n coding path: " + prettyError.codingPathString)
Expand Down
2 changes: 1 addition & 1 deletion Tests/OpenAPIKitCompatibilitySuite/TomTomAPITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ final class TomTomAPICampatibilityTests: XCTestCase {
func test_successfullyParsedDocument() {
switch tomtomAPI {
case nil:
XCTFail("Did not attempt to pull Google Books API documentation like expected.")
XCTFail("Did not attempt to pull TomTom API documentation like expected.")
case .failure(let error):
let prettyError = OpenAPI.Error(from: error)
XCTFail(prettyError.localizedDescription + "\n coding path: " + prettyError.codingPathString)
Expand Down
22 changes: 14 additions & 8 deletions Tests/OpenAPIKitTests/Schema Object/SchemaObjectTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3001,18 +3001,21 @@ extension SchemaObjectTests {
])
}

func test_decodeIntegerWithMaximum() {
func test_decodeIntegerWithMaximum() throws {
let integerData = #"{"type": "integer", "maximum": 1}"#.data(using: .utf8)!
let nullableIntegerData = #"{"type": "integer", "maximum": 1, "nullable": true}"#.data(using: .utf8)!
let allowedValueIntegerData = #"{"type": "integer", "maximum": 2, "enum": [1, 2]}"#.data(using: .utf8)!
let integerWithWholeNumberFloatData = #"{"type": "integer", "maximum": 1.0}"#.data(using: .utf8)!

let integer = try! testDecoder.decode(JSONSchema.self, from: integerData)
let nullableInteger = try! testDecoder.decode(JSONSchema.self, from: nullableIntegerData)
let allowedValueInteger = try! testDecoder.decode(JSONSchema.self, from: allowedValueIntegerData)
let integer = try testDecoder.decode(JSONSchema.self, from: integerData)
let nullableInteger = try testDecoder.decode(JSONSchema.self, from: nullableIntegerData)
let allowedValueInteger = try testDecoder.decode(JSONSchema.self, from: allowedValueIntegerData)
let integerWithWholeNumberFloat = try testDecoder.decode(JSONSchema.self, from: integerWithWholeNumberFloatData)

XCTAssertEqual(integer, JSONSchema.integer(.init(format: .generic, required: false), .init(maximum: (1, exclusive:false))))
XCTAssertEqual(nullableInteger, JSONSchema.integer(.init(format: .generic, required: false, nullable: true), .init(maximum: (1, exclusive:false))))
XCTAssertEqual(allowedValueInteger, JSONSchema.integer(.init(format: .generic, required: false, allowedValues: [1, 2]), .init(maximum: (2, exclusive:false))))
XCTAssertEqual(integerWithWholeNumberFloat, JSONSchema.integer(required: false, maximum: (1, exclusive: false)))
}

func test_encodeIntegerWithExclusiveMaximum() {
Expand Down Expand Up @@ -3105,18 +3108,21 @@ extension SchemaObjectTests {
])
}

func test_decodeIntegerWithMinimum() {
func test_decodeIntegerWithMinimum() throws {
let integerData = #"{"type": "integer", "minimum": 1}"#.data(using: .utf8)!
let nullableIntegerData = #"{"type": "integer", "minimum": 1, "nullable": true}"#.data(using: .utf8)!
let allowedValueIntegerData = #"{"type": "integer", "minimum": 1, "enum": [1, 2]}"#.data(using: .utf8)!
let integerWithWholeNumberFloatData = #"{"type": "integer", "minimum": 1.0}"#.data(using: .utf8)!

let integer = try! testDecoder.decode(JSONSchema.self, from: integerData)
let nullableInteger = try! testDecoder.decode(JSONSchema.self, from: nullableIntegerData)
let allowedValueInteger = try! testDecoder.decode(JSONSchema.self, from: allowedValueIntegerData)
let integer = try testDecoder.decode(JSONSchema.self, from: integerData)
let nullableInteger = try testDecoder.decode(JSONSchema.self, from: nullableIntegerData)
let allowedValueInteger = try testDecoder.decode(JSONSchema.self, from: allowedValueIntegerData)
let integerWithWholeNumberFloat = try testDecoder.decode(JSONSchema.self, from: integerWithWholeNumberFloatData)

XCTAssertEqual(integer, JSONSchema.integer(.init(format: .generic, required: false), .init(minimum: (1, exclusive:false))))
XCTAssertEqual(nullableInteger, JSONSchema.integer(.init(format: .generic, required: false, nullable: true), .init(minimum: (1, exclusive:false))))
XCTAssertEqual(allowedValueInteger, JSONSchema.integer(.init(format: .generic, required: false, allowedValues: [1, 2]), .init(minimum: (1, exclusive:false))))
XCTAssertEqual(integerWithWholeNumberFloat, JSONSchema.integer(required: false, minimum: (1, exclusive: false)))
}

func test_encodeIntegerWithExclusiveMinimum() {
Expand Down
56 changes: 56 additions & 0 deletions Tests/OpenAPIKitTests/Schema Object/SchemaObjectYamsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// SchemaObjectYamsTests.swift
//
//
// Created by Mathew Polzin on 4/22/20.
//

///
/// This file exists to facilitate regression tests for Yams-specific problems encountered
/// and fixed.
///

import Foundation
import XCTest
import OpenAPIKit
import Yams

final class SchemaObjectYamsTests: XCTestCase {
func test_floatingPointWholeNumberIntegerDecode() throws {
let integerString =
"""
type: integer
minimum: 1.0
maximum: 10.0
"""

let integer = try YAMLDecoder().decode(JSONSchema.self, from: integerString)

XCTAssertEqual(
integer,
JSONSchema.integer(required: false, maximum: (10, exclusive: false), minimum: (1, exclusive: false))
)
}

func test_floatingPointIntegerDecodeFails() {
let integerString =
"""
type: integer
maximum: 10.2
"""

XCTAssertThrowsError(try YAMLDecoder().decode(JSONSchema.self, from: integerString)) { error in
XCTAssertEqual(OpenAPI.Error(from: error).localizedDescription, "Inconsistency encountered when parsing `maximum`: Expected an Integer literal but found a floating point value.")
}

let integerString2 =
"""
type: integer
minimum: 1.1
"""

XCTAssertThrowsError(try YAMLDecoder().decode(JSONSchema.self, from: integerString2)) { error in
XCTAssertEqual(OpenAPI.Error(from: error).localizedDescription, "Inconsistency encountered when parsing `minimum`: Expected an Integer literal but found a floating point value.")
}
}
}

0 comments on commit 3c7e9f4

Please sign in to comment.