Skip to content

Commit

Permalink
Fix #1424 [SWIFT4] Date Encoding Issues (#1442)
Browse files Browse the repository at this point in the history
Ensure the same date format string is used throughout the generated code (use the one set in Configuration.swift).

Ensure the same date formatter options are used when encoding dates as well as decoding dates. If a consumer has set their own date formatter on CodableHelper, use that when encoding dates too.

Adds DateFormatTests to the SWIFT4 unit tests.

Updates the SWIFT4 petstore samples
  • Loading branch information
james-rant authored and Daiki Matsudate committed Nov 15, 2018
1 parent 7564d62 commit 653601b
Show file tree
Hide file tree
Showing 47 changed files with 538 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ open class CodableHelper {
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
formatter.dateFormat = Configuration.dateFormat
decoder.dateDecodingStrategy = .formatted(formatter)
}

Expand Down Expand Up @@ -55,7 +55,7 @@ open class CodableHelper {
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
formatter.dateFormat = Configuration.dateFormat
encoder.dateEncodingStrategy = .formatted(formatter)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,16 @@ extension Data: JSONEncodable {
}

private let dateFormatter: DateFormatter = {
let fmt = DateFormatter()
fmt.dateFormat = Configuration.dateFormat
fmt.locale = Locale(identifier: "en_US_POSIX")
return fmt
if let formatter = CodableHelper.dateformatter {
return formatter
} else {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = Configuration.dateFormat
return formatter
}
}()

extension Date: JSONEncodable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,13 +455,16 @@ open class FakeAPI {
/**
Fake endpoint to test group parameters (optional)
- parameter requiredStringGroup: (query) Required String in group parameters
- parameter requiredBooleanGroup: (header) Required Boolean in group parameters
- parameter requiredInt64Group: (query) Required Integer in group parameters
- parameter stringGroup: (query) String in group parameters (optional)
- parameter booleanGroup: (header) Boolean in group parameters (optional)
- parameter int64Group: (query) Integer in group parameters (optional)
- parameter completion: completion handler to receive the data and the error objects
*/
open class func testGroupParameters(stringGroup: Int? = nil, booleanGroup: Bool? = nil, int64Group: Int64? = nil, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) {
testGroupParametersWithRequestBuilder(stringGroup: stringGroup, booleanGroup: booleanGroup, int64Group: int64Group).execute { (response, error) -> Void in
open class func testGroupParameters(requiredStringGroup: Int, requiredBooleanGroup: Bool, requiredInt64Group: Int64, stringGroup: Int? = nil, booleanGroup: Bool? = nil, int64Group: Int64? = nil, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) {
testGroupParametersWithRequestBuilder(requiredStringGroup: requiredStringGroup, requiredBooleanGroup: requiredBooleanGroup, requiredInt64Group: requiredInt64Group, stringGroup: stringGroup, booleanGroup: booleanGroup, int64Group: int64Group).execute { (response, error) -> Void in
if error == nil {
completion((), error)
} else {
Expand All @@ -475,22 +478,28 @@ open class FakeAPI {
Fake endpoint to test group parameters (optional)
- DELETE /fake
- Fake endpoint to test group parameters (optional)
- parameter requiredStringGroup: (query) Required String in group parameters
- parameter requiredBooleanGroup: (header) Required Boolean in group parameters
- parameter requiredInt64Group: (query) Required Integer in group parameters
- parameter stringGroup: (query) String in group parameters (optional)
- parameter booleanGroup: (header) Boolean in group parameters (optional)
- parameter int64Group: (query) Integer in group parameters (optional)
- returns: RequestBuilder<Void>
*/
open class func testGroupParametersWithRequestBuilder(stringGroup: Int? = nil, booleanGroup: Bool? = nil, int64Group: Int64? = nil) -> RequestBuilder<Void> {
open class func testGroupParametersWithRequestBuilder(requiredStringGroup: Int, requiredBooleanGroup: Bool, requiredInt64Group: Int64, stringGroup: Int? = nil, booleanGroup: Bool? = nil, int64Group: Int64? = nil) -> RequestBuilder<Void> {
let path = "/fake"
let URLString = PetstoreClientAPI.basePath + path
let parameters: [String:Any]? = nil

var url = URLComponents(string: URLString)
url?.queryItems = APIHelper.mapValuesToQueryItems([
"required_string_group": requiredStringGroup.encodeToJSON(),
"required_int64_group": requiredInt64Group.encodeToJSON(),
"string_group": stringGroup?.encodeToJSON(),
"int64_group": int64Group?.encodeToJSON()
])
let nillableHeaders: [String: Any?] = [
"required_boolean_group": requiredBooleanGroup,
"boolean_group": booleanGroup
]
let headerParameters = APIHelper.rejectNilHeaders(nillableHeaders)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ open class CodableHelper {
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
formatter.dateFormat = Configuration.dateFormat
decoder.dateDecodingStrategy = .formatted(formatter)
}

Expand Down Expand Up @@ -55,7 +55,7 @@ open class CodableHelper {
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
formatter.dateFormat = Configuration.dateFormat
encoder.dateEncodingStrategy = .formatted(formatter)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,16 @@ extension Data: JSONEncodable {
}

private let dateFormatter: DateFormatter = {
let fmt = DateFormatter()
fmt.dateFormat = Configuration.dateFormat
fmt.locale = Locale(identifier: "en_US_POSIX")
return fmt
if let formatter = CodableHelper.dateformatter {
return formatter
} else {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = Configuration.dateFormat
return formatter
}
}()

extension Date: JSONEncodable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
1A501F48219C3DC600F372F6 /* DateFormatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A501F47219C3DC600F372F6 /* DateFormatTests.swift */; };
54DA06C1D70D78EC0EC72B61 /* Pods_SwaggerClientTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F65B6638217EDDC99D103B16 /* Pods_SwaggerClientTests.framework */; };
6D4EFB951C692C6300B96B06 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFB941C692C6300B96B06 /* AppDelegate.swift */; };
6D4EFB971C692C6300B96B06 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4EFB961C692C6300B96B06 /* ViewController.swift */; };
Expand All @@ -30,6 +31,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
1A501F47219C3DC600F372F6 /* DateFormatTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateFormatTests.swift; sourceTree = "<group>"; };
289E8A9E9C0BB66AD190C7C6 /* Pods-SwaggerClientTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwaggerClientTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwaggerClientTests/Pods-SwaggerClientTests.debug.xcconfig"; sourceTree = "<group>"; };
6D4EFB911C692C6300B96B06 /* SwaggerClient.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwaggerClient.app; sourceTree = BUILT_PRODUCTS_DIR; };
6D4EFB941C692C6300B96B06 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -132,6 +134,7 @@
6D4EFBB41C693BE200B96B06 /* PetAPITests.swift */,
6D4EFBB61C693BED00B96B06 /* StoreAPITests.swift */,
6D4EFBB81C693BFC00B96B06 /* UserAPITests.swift */,
1A501F47219C3DC600F372F6 /* DateFormatTests.swift */,
);
path = SwaggerClientTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -360,6 +363,7 @@
files = (
6D4EFBB71C693BED00B96B06 /* StoreAPITests.swift in Sources */,
6D4EFBB91C693BFC00B96B06 /* UserAPITests.swift in Sources */,
1A501F48219C3DC600F372F6 /* DateFormatTests.swift in Sources */,
6D4EFBB51C693BE200B96B06 /* PetAPITests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//
// DateFormatTests.swift
// SwaggerClientTests
//
// Created by James on 14/11/2018.
// Copyright © 2018 Swagger. All rights reserved.
//

import Foundation
import XCTest
@testable import PetstoreClient
@testable import SwaggerClient

class DateFormatTests: XCTestCase {

struct DateTest: Codable {
let date: Date
}

override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}

override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}

func testEncodeToJSONAlwaysResultsInUTCEncodedDate() {
var dateComponents = DateComponents()
dateComponents.calendar = Calendar(identifier: .gregorian)
dateComponents.year = 2018
dateComponents.month = 11
dateComponents.day = 14
dateComponents.hour = 11
dateComponents.minute = 35
dateComponents.second = 43
dateComponents.nanosecond = 500

// Testing a date with a timezone of +00:00 (UTC)
dateComponents.timeZone = TimeZone(secondsFromGMT: 0)
XCTAssert(dateComponents.isValidDate)

guard let utcDate = dateComponents.date else {
XCTFail("Couldn't get a valid date")
return
}

var encodedDate = utcDate.encodeToJSON() as! String
XCTAssert(encodedDate.hasSuffix("Z"))

// test with a positive timzone offset from UTC
dateComponents.timeZone = TimeZone(secondsFromGMT: 60 * 60) // +01:00
XCTAssert(dateComponents.isValidDate)

guard let nonUTCDate1 = dateComponents.date else {
XCTFail("Couldn't get a valid date")
return
}

encodedDate = nonUTCDate1.encodeToJSON() as! String
XCTAssert(encodedDate.hasSuffix("Z"))

// test with a negative timzone offset from UTC
dateComponents.timeZone = TimeZone(secondsFromGMT: -(60 * 60)) // -01:00
XCTAssert(dateComponents.isValidDate)

guard let nonUTCDate2 = dateComponents.date else {
XCTFail("Couldn't get a valid date")
return
}

encodedDate = nonUTCDate2.encodeToJSON() as! String
XCTAssert(encodedDate.hasSuffix("Z"))
}

func testCodableAlwaysResultsInUTCEncodedDate() {
let jsonData = "{\"date\":\"1970-01-01T00:00:00.000Z\"}".data(using: .utf8)!
let decodeResult = CodableHelper.decode(DateTest.self, from: jsonData)
XCTAssert(decodeResult.decodableObj != nil && decodeResult.error == nil)

var dateComponents = DateComponents()
dateComponents.calendar = Calendar(identifier: .gregorian)
dateComponents.year = 1970
dateComponents.month = 01
dateComponents.day = 01
dateComponents.hour = 00
dateComponents.minute = 00
dateComponents.second = 00

// Testing a date with a timezone of +00:00 (UTC)
dateComponents.timeZone = TimeZone(secondsFromGMT: 0)
XCTAssert(dateComponents.isValidDate)

guard let date = dateComponents.date else {
XCTFail("Couldn't get a valid date")
return
}

let dateTest = DateTest(date: date)
let encodeResult = CodableHelper.encode(dateTest)
XCTAssert(encodeResult.data != nil && encodeResult.error == nil)
guard let jsonString = String(data: encodeResult.data!, encoding: .utf8) else {
XCTFail("Unable to convert encoded data to string.")
return
}

let exampleJSONString = "{\"date\":\"1970-01-01T00:00:00.000Z\"}"
XCTAssert(jsonString == exampleJSONString, "Encoded JSON String: \(jsonString) should match: \(exampleJSONString)")
}

}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.3.0-SNAPSHOT
3.3.3-SNAPSHOT
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
import Foundation

open class PetstoreClientAPI {
open static var basePath = "http://petstore.swagger.io:80/v2"
open static var credential: URLCredential?
open static var customHeaders: [String:String] = [:]
open static var requestBuilderFactory: RequestBuilderFactory = AlamofireRequestBuilderFactory()
public static var basePath = "http://petstore.swagger.io:80/v2"
public static var credential: URLCredential?
public static var customHeaders: [String:String] = [:]
public static var requestBuilderFactory: RequestBuilderFactory = AlamofireRequestBuilderFactory()
}

open class RequestBuilder<T> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,63 @@ open class FakeAPI {
return requestBuilder.init(method: "GET", URLString: (url?.string ?? URLString), parameters: parameters, isBody: false, headers: headerParameters)
}

/**
Fake endpoint to test group parameters (optional)
- parameter requiredStringGroup: (query) Required String in group parameters
- parameter requiredBooleanGroup: (header) Required Boolean in group parameters
- parameter requiredInt64Group: (query) Required Integer in group parameters
- parameter stringGroup: (query) String in group parameters (optional)
- parameter booleanGroup: (header) Boolean in group parameters (optional)
- parameter int64Group: (query) Integer in group parameters (optional)
- parameter completion: completion handler to receive the data and the error objects
*/
open class func testGroupParameters(requiredStringGroup: Int, requiredBooleanGroup: Bool, requiredInt64Group: Int64, stringGroup: Int? = nil, booleanGroup: Bool? = nil, int64Group: Int64? = nil, completion: @escaping ((_ data: Void?,_ error: Error?) -> Void)) {
testGroupParametersWithRequestBuilder(requiredStringGroup: requiredStringGroup, requiredBooleanGroup: requiredBooleanGroup, requiredInt64Group: requiredInt64Group, stringGroup: stringGroup, booleanGroup: booleanGroup, int64Group: int64Group).execute { (response, error) -> Void in
if error == nil {
completion((), error)
} else {
completion(nil, error)
}
}
}


/**
Fake endpoint to test group parameters (optional)
- DELETE /fake
- Fake endpoint to test group parameters (optional)
- parameter requiredStringGroup: (query) Required String in group parameters
- parameter requiredBooleanGroup: (header) Required Boolean in group parameters
- parameter requiredInt64Group: (query) Required Integer in group parameters
- parameter stringGroup: (query) String in group parameters (optional)
- parameter booleanGroup: (header) Boolean in group parameters (optional)
- parameter int64Group: (query) Integer in group parameters (optional)
- returns: RequestBuilder<Void>
*/
open class func testGroupParametersWithRequestBuilder(requiredStringGroup: Int, requiredBooleanGroup: Bool, requiredInt64Group: Int64, stringGroup: Int? = nil, booleanGroup: Bool? = nil, int64Group: Int64? = nil) -> RequestBuilder<Void> {
let path = "/fake"
let URLString = PetstoreClientAPI.basePath + path
let parameters: [String:Any]? = nil

var url = URLComponents(string: URLString)
url?.queryItems = APIHelper.mapValuesToQueryItems([
"required_string_group": requiredStringGroup.encodeToJSON(),
"required_int64_group": requiredInt64Group.encodeToJSON(),
"string_group": stringGroup?.encodeToJSON(),
"int64_group": int64Group?.encodeToJSON()
])
let nillableHeaders: [String: Any?] = [
"required_boolean_group": requiredBooleanGroup,
"boolean_group": booleanGroup
]
let headerParameters = APIHelper.rejectNilHeaders(nillableHeaders)

let requestBuilder: RequestBuilder<Void>.Type = PetstoreClientAPI.requestBuilderFactory.getNonDecodableBuilder()

return requestBuilder.init(method: "DELETE", URLString: (url?.string ?? URLString), parameters: parameters, isBody: false, headers: headerParameters)
}

/**
test inline additionalProperties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ open class AlamofireRequestBuilder<T>: RequestBuilder<T> {
if stringResponse.result.isFailure {
completion(
nil,
ErrorResponse.error(stringResponse.response?.statusCode ?? 500, stringResponse.data, stringResponse.result.error as Error!)
ErrorResponse.error(stringResponse.response?.statusCode ?? 500, stringResponse.data, stringResponse.result.error!)
)
return
}
Expand Down Expand Up @@ -356,7 +356,7 @@ open class AlamofireDecodableRequestBuilder<T:Decodable>: AlamofireRequestBuilde
if stringResponse.result.isFailure {
completion(
nil,
ErrorResponse.error(stringResponse.response?.statusCode ?? 500, stringResponse.data, stringResponse.result.error as Error!)
ErrorResponse.error(stringResponse.response?.statusCode ?? 500, stringResponse.data, stringResponse.result.error!)
)
return
}
Expand Down
Loading

0 comments on commit 653601b

Please sign in to comment.