[Swift3] Don't crash on invalid responses (#4547)

* [Swift 3] Return an error when dealing with invalid responses, instead of crashing.

Compatiblity: ErrorResponse.Error becomes ErrorResponse.HttpError

* Cleanup and work around existing swift3 generator bug/missing feature

* Generate PetStore samples for swift3
This commit is contained in:
Philippe Latulippe
2017-01-13 04:29:25 +01:00
committed by wing328
parent 5f6dcf07b2
commit 2faad6497d
78 changed files with 6192 additions and 5990 deletions

View File

@@ -92,7 +92,7 @@ open class AlamofireRequestBuilder<T>: RequestBuilder<T> {
}
self.processRequest(request: upload, managerId, completion)
case .failure(let encodingError):
completion(nil, ErrorResponse(statusCode: 415, data: nil, error: encodingError))
completion(nil, ErrorResponse.HttpError(statusCode: 415, data: nil, error: encodingError))
}
})
} else {
@@ -124,7 +124,7 @@ open class AlamofireRequestBuilder<T>: RequestBuilder<T> {
if stringResponse.result.isFailure {
completion(
nil,
ErrorResponse(statusCode: stringResponse.response?.statusCode ?? 500, data: stringResponse.data, error: stringResponse.result.error as Error!)
ErrorResponse.HttpError(statusCode: stringResponse.response?.statusCode ?? 500, data: stringResponse.data, error: stringResponse.result.error as Error!)
)
return
}
@@ -144,7 +144,7 @@ open class AlamofireRequestBuilder<T>: RequestBuilder<T> {
if voidResponse.result.isFailure {
completion(
nil,
ErrorResponse(statusCode: voidResponse.response?.statusCode ?? 500, data: voidResponse.data, error: voidResponse.result.error!)
ErrorResponse.HttpError(statusCode: voidResponse.response?.statusCode ?? 500, data: voidResponse.data, error: voidResponse.result.error!)
)
return
}
@@ -163,7 +163,7 @@ open class AlamofireRequestBuilder<T>: RequestBuilder<T> {
if (dataResponse.result.isFailure) {
completion(
nil,
ErrorResponse(statusCode: dataResponse.response?.statusCode ?? 500, data: dataResponse.data, error: dataResponse.result.error!)
ErrorResponse.HttpError(statusCode: dataResponse.response?.statusCode ?? 500, data: dataResponse.data, error: dataResponse.result.error!)
)
return
}
@@ -181,7 +181,7 @@ open class AlamofireRequestBuilder<T>: RequestBuilder<T> {
cleanupRequest()
if response.result.isFailure {
completion(nil, ErrorResponse(statusCode: response.response?.statusCode ?? 500, data: response.data, error: response.result.error!))
completion(nil, ErrorResponse.HttpError(statusCode: response.response?.statusCode ?? 500, data: response.data, error: response.result.error!))
return
}
@@ -197,8 +197,11 @@ open class AlamofireRequestBuilder<T>: RequestBuilder<T> {
return
}
if let json: Any = response.result.value {
let body = Decoders.decode(clazz: T.self, source: json as AnyObject)
completion(Response(response: response.response!, body: body), nil)
let decoded = Decoders.decode(clazz: T.self, source: json as AnyObject)
switch decoded {
case let .success(object): completion(Response(response: response.response!, body: object), nil)
case let .failure(error): completion(nil, ErrorResponse.DecodeError(response: response.data, decodeError: error))
}
return
} else if "" is T {
// swagger-parser currently doesn't support void, which will be fixed in future swagger-parser release
@@ -207,7 +210,7 @@ open class AlamofireRequestBuilder<T>: RequestBuilder<T> {
return
}
completion(nil, ErrorResponse(statusCode: 500, data: nil, error: NSError(domain: "localhost", code: 500, userInfo: ["reason": "unreacheable code"])))
completion(nil, ErrorResponse.HttpError(statusCode: 500, data: nil, error: NSError(domain: "localhost", code: 500, userInfo: ["reason": "unreacheable code"])))
}
}
}

View File

@@ -10,10 +10,9 @@ protocol JSONEncodable {
func encodeToJSON() -> Any
}
public struct ErrorResponse : Error {
public let statusCode: Int
public let data: Data?
public let error: Error
public enum ErrorResponse : Error {
case HttpError(statusCode: Int, data: Data?, error: Error)
case DecodeError(response: Data?, decodeError: DecodeError)
}
open class Response<T> {
@@ -37,90 +36,181 @@ open class Response<T> {
}
}
public enum Decoded<ValueType> {
case success(ValueType)
case failure(DecodeError)
}
public extension Decoded {
var value: ValueType? {
switch self {
case let .success(value):
return value
break
case .failure:
return nil
break
}
}
}
public enum DecodeError {
case typeMismatch(expected: String, actual: String)
case missingKey(key: String)
case parseError(message: String)
}
private var once = Int()
class Decoders {
static fileprivate var decoders = Dictionary<String, ((AnyObject) -> AnyObject)>()
static func addDecoder<T>(clazz: T.Type, decoder: @escaping ((AnyObject) -> T)) {
static func addDecoder<T>(clazz: T.Type, decoder: @escaping ((AnyObject) -> Decoded<T>)) {
let key = "\(T.self)"
decoders[key] = { decoder($0) as AnyObject }
}
static func decode<T>(clazz: T.Type, discriminator: String, source: AnyObject) -> T {
static func decode<T>(clazz: T.Type, discriminator: String, source: AnyObject) -> Decoded<T> {
let key = discriminator;
if let decoder = decoders[key] {
return decoder(source) as! T
if let decoder = decoders[key], let value = decoder(source) as? Decoded<T> {
return value
} else {
fatalError("Source \(source) is not convertible to type \(clazz): Maybe swagger file is insufficient")
return .failure(.typeMismatch(expected: String(describing: clazz), actual: String(describing: source)))
}
}
static func decode<T>(clazz: [T].Type, source: AnyObject) -> [T] {
let array = source as! [AnyObject]
return array.map { Decoders.decode(clazz: T.self, source: $0) }
}
static func decode<T, Key: Hashable>(clazz: [Key:T].Type, source: AnyObject) -> [Key:T] {
let sourceDictionary = source as! [Key: AnyObject]
var dictionary = [Key:T]()
for (key, value) in sourceDictionary {
dictionary[key] = Decoders.decode(clazz: T.self, source: value)
static func decode<T>(clazz: [T].Type, source: AnyObject) -> Decoded<[T]> {
if let sourceArray = source as? [AnyObject] {
var values = [T]()
for sourceValue in sourceArray {
switch Decoders.decode(clazz: T.self, source: sourceValue) {
case let .success(value):
values.append(value)
case let .failure(error):
return .failure(error)
break
}
}
return .success(values)
} else {
return .failure(.typeMismatch(expected: String(describing: clazz), actual: String(describing: source)))
}
return dictionary
}
static func decode<T>(clazz: T.Type, source: AnyObject) -> T {
static func decode<T, Key: Hashable>(clazz: [Key:T].Type, source: AnyObject) -> Decoded<[Key:T]> {
if let sourceDictionary = source as? [Key: AnyObject] {
var dictionary = [Key:T]()
for (key, value) in sourceDictionary {
switch Decoders.decode(clazz: T.self, source: value) {
case let .success(value):
dictionary[key] = value
break
case let .failure(error):
return .failure(error)
}
}
return .success(dictionary)
} else {
return .failure(.typeMismatch(expected: String(describing: clazz), actual: String(describing: source)))
}
}
static func decodeOptional<T: RawRepresentable>(clazz: T.Type, source: AnyObject?) -> Decoded<T?> {
if let value = source as? T.RawValue {
if let enumValue = T.init(rawValue: value) {
return .success(enumValue)
} else {
return .failure(.typeMismatch(expected: "A value from the enumeration \(T.self)", actual: "\(value)"))
}
} else {
return .failure(.typeMismatch(expected: "\(T.RawValue.self) matching a case from the enumeration \(T.self)", actual: String(describing: type(of: source))))
}
}
static func decode<T>(clazz: T.Type, source: AnyObject) -> Decoded<T> {
initialize()
if T.self is Int32.Type && source is NSNumber {
return source.int32Value as! T;
if let value = source.int32Value as? T, source is NSNumber, T.self is Int32.Type {
return .success(value)
}
if T.self is Int64.Type && source is NSNumber {
return source.int64Value as! T;
if let value = source.int32Value as? T, source is NSNumber, T.self is Int64.Type {
return .success(value)
}
if T.self is UUID.Type && source is String {
return UUID(uuidString: source as! String) as! T
if let intermediate = source as? String, let value = UUID(uuidString: intermediate) as? T, source is String, T.self is UUID.Type {
return .success(value)
}
if source is T {
return source as! T
if let value = source as? T {
return .success(value)
}
if T.self is Data.Type && source is String {
return Data(base64Encoded: source as! String) as! T
if let intermediate = source as? String, let value = Data(base64Encoded: intermediate) as? T {
return .success(value)
}
let key = "\(T.self)"
if let decoder = decoders[key] {
return decoder(source) as! T
if let decoder = decoders[key], let value = decoder(source) as? Decoded<T> {
return value
} else {
fatalError("Source \(source) is not convertible to type \(clazz): Maybe swagger file is insufficient")
return .failure(.typeMismatch(expected: String(describing: clazz), actual: String(describing: source)))
}
}
static func decodeOptional<T>(clazz: T.Type, source: AnyObject?) -> T? {
if source is NSNull {
return nil
}
return source.map { (source: AnyObject) -> T in
Decoders.decode(clazz: clazz, source: source)
//Convert a Decoded so that its value is optional. DO WE STILL NEED THIS?
static func toOptional<T>(decoded: Decoded<T>) -> Decoded<T?> {
return .success(decoded.value)
}
static func decodeOptional<T>(clazz: T.Type, source: AnyObject?) -> Decoded<T?> {
if let source = source, !(source is NSNull) {
switch Decoders.decode(clazz: clazz, source: source) {
case let .success(value): return .success(value)
case let .failure(error): return .failure(error)
}
} else {
return .success(nil)
}
}
static func decodeOptional<T>(clazz: [T].Type, source: AnyObject?) -> [T]? {
if source is NSNull {
return nil
}
return source.map { (someSource: AnyObject) -> [T] in
Decoders.decode(clazz: clazz, source: someSource)
static func decodeOptional<T>(clazz: [T].Type, source: AnyObject?) -> Decoded<[T]?> {
if let source = source as? [AnyObject] {
var values = [T]()
for sourceValue in source {
switch Decoders.decode(clazz: T.self, source: sourceValue) {
case let .success(value): values.append(value)
case let .failure(error): return .failure(error)
}
}
return .success(values)
} else {
return .success(nil)
}
}
static func decodeOptional<T, Key: Hashable>(clazz: [Key:T].Type, source: AnyObject?) -> [Key:T]? {
if source is NSNull {
return nil
}
return source.map { (someSource: AnyObject) -> [Key:T] in
Decoders.decode(clazz: clazz, source: someSource)
static func decodeOptional<T, Key: Hashable>(clazz: [Key:T].Type, source: AnyObject?) -> Decoded<[Key:T]?> {
if let sourceDictionary = source as? [Key: AnyObject] {
var dictionary = [Key:T]()
for (key, value) in sourceDictionary {
switch Decoders.decode(clazz: T.self, source: value) {
case let .success(value): dictionary[key] = value
case let .failure(error): return .failure(error)
}
}
return .success(dictionary)
} else {
return .success(nil)
}
}
static func decodeOptional<T: RawRepresentable, U: AnyObject where T.RawValue == U>(clazz: T, source: AnyObject) -> Decoded<T?> {
if let value = source as? U {
if let enumValue = T.init(rawValue: value) {
return .success(enumValue)
} else {
return .failure(.typeMismatch(expected: "A value from the enumeration \(T.self)", actual: "\(value)"))
}
} else {
return .failure(.typeMismatch(expected: "String", actual: String(describing: type(of: source))))
}
}
private static var __once: () = {
let formatters = [
@@ -136,88 +226,97 @@ class Decoders {
return formatter
}
// Decoder for Date
Decoders.addDecoder(clazz: Date.self) { (source: AnyObject) -> Date in
Decoders.addDecoder(clazz: Date.self) { (source: AnyObject) -> Decoded<Date> in
if let sourceString = source as? String {
for formatter in formatters {
if let date = formatter.date(from: sourceString) {
return date
return .success(date)
}
}
}
if let sourceInt = source as? Int64 {
if let sourceInt = source as? Int {
// treat as a java date
return Date(timeIntervalSince1970: Double(sourceInt / 1000) )
return .success(Date(timeIntervalSince1970: Double(sourceInt / 1000) ))
}
if source is String || source is Int {
return .failure(.parseError(message: "Could not decode date"))
} else {
return .failure(.typeMismatch(expected: "String or Int", actual: "\(source)"))
}
fatalError("formatter failed to parse \(source)")
}
// Decoder for ISOFullDate
Decoders.addDecoder(clazz: ISOFullDate.self) { (source: AnyObject) -> ISOFullDate in
Decoders.addDecoder(clazz: ISOFullDate.self) { (source: AnyObject) -> Decoded<ISOFullDate> in
if let string = source as? String,
let isoDate = ISOFullDate.from(string: string) {
return isoDate
return .success(isoDate)
} else {
return .failure(.typeMismatch(expected: "ISO date", actual: "\(source)"))
}
fatalError("formatter failed to parse \(source)")
} {{#models}}{{#model}}
{{^isArrayModel}}
// Decoder for [{{{classname}}}]
Decoders.addDecoder(clazz: [{{{classname}}}].self) { (source: AnyObject) -> [{{{classname}}}] in
Decoders.addDecoder(clazz: [{{{classname}}}].self) { (source: AnyObject) -> Decoded<[{{{classname}}}]> in
return Decoders.decode(clazz: [{{{classname}}}].self, source: source)
}
// Decoder for {{{classname}}}
Decoders.addDecoder(clazz: {{{classname}}}.self) { (source: AnyObject) -> {{{classname}}} in
{{#isArrayModel}}
let sourceArray = source as! [AnyObject]
return sourceArray.map({ Decoders.decode(clazz: {{{arrayModelType}}}.self, source: $0) })
{{/isArrayModel}}
{{^isArrayModel}}
Decoders.addDecoder(clazz: {{{classname}}}.self) { (source: AnyObject) -> Decoded<{{{classname}}}> in
{{#isEnum}}
if let source = source as? {{dataType}} {
if let result = {{classname}}(rawValue: source) {
return result
}
}
fatalError("Source \(source) is not convertible to enum type {{classname}}: Maybe swagger file is insufficient")
//TODO: I don't think we need this anymore
return Decoders.decode(clazz: {{{classname}}}.self, source: source)
{{/isEnum}}
{{^isEnum}}
{{#allVars.isEmpty}}
if let source = source as? {{dataType}} {
return source
return .success(source)
} else {
return .failure(.typeMismatch(expected: "Typealias {{classname}}", actual: "\(source)"))
}
fatalError("Source \(source) is not convertible to typealias {{classname}}: Maybe swagger file is insufficient")
{{/allVars.isEmpty}}
{{^allVars.isEmpty}}
let sourceDictionary = source as! [AnyHashable: Any]
if let sourceDictionary = source as? [AnyHashable: Any] {
{{#discriminator}}
// Check discriminator to support inheritance
if let discriminator = sourceDictionary["{{discriminator}}"] as? String, discriminator != "{{classname}}"{
return Decoders.decode(clazz: {{classname}}.self, discriminator: discriminator, source: source)
}
// Check discriminator to support inheritance
if let discriminator = sourceDictionary["{{discriminator}}"] as? String, discriminator != "{{classname}}"{
return Decoders.decode(clazz: {{classname}}.self, discriminator: discriminator, source: source)
}
{{/discriminator}}
{{#unwrapRequired}}
let instance = {{classname}}({{#requiredVars}}{{^-first}}, {{/-first}}{{#isEnum}}{{name}}: {{classname}}.{{datatypeWithEnum}}(rawValue: (sourceDictionary["{{baseName}}"] as! {{datatype}}))! {{/isEnum}}{{^isEnum}}{{name}}: Decoders.decode(clazz: {{{baseType}}}.self, source: sourceDictionary["{{baseName}}"]! as AnyObject){{/isEnum}}{{/requiredVars}})
{{#optionalVars}}{{#isEnum}}
if let {{name}} = sourceDictionary["{{baseName}}"] as? {{datatype}} { {{^isContainer}}
instance.{{name}} = {{classname}}.{{datatypeWithEnum}}(rawValue: ({{name}})){{/isContainer}}{{#isListContainer}}
instance.{{name}} = {{name}}.map ({ {{classname}}.{{enumName}}(rawValue: $0)! }){{/isListContainer}}
}{{/isEnum}}{{^isEnum}}
instance.{{name}} = Decoders.decodeOptional(clazz: {{{baseType}}}.self, source: sourceDictionary["{{baseName}}"] as AnyObject?){{/isEnum}}
{{#requiredVars}}
guard let {{name}}Source = sourceDictionary["{{baseName}}"] as AnyObject? else {
return .failure(.missingKey(key: "{{baseName}}"))
}
guard let {{name}} = Decoders.decode(clazz: {{#isEnum}}{{^isListContainer}}{{classname}}.{{enumName}}.self{{/isListContainer}}{{#isListContainer}}Array<{{classname}}.{{enumName}}>.self{{/isListContainer}}{{/isEnum}}{{^isEnum}}{{{datatype}}}.self{{/isEnum}}.self, source: {{name}}Source).value else {
return .failure(.typeMismatch(expected: "{{classname}}", actual: "\({{name}}Source)"))
}
{{/requiredVars}}
let instance = {{classname}}({{#requiredVars}}{{^-first}}, {{/-first}}{{name}}: {{name}}{{/requiredVars}})
{{#optionalVars}}
switch Decoders.decodeOptional(clazz: {{#isEnum}}{{^isListContainer}}{{classname}}.{{enumName}}.self{{/isListContainer}}{{#isListContainer}}Array<{{classname}}.{{enumName}}>.self{{/isListContainer}}{{/isEnum}}{{^isEnum}}{{{datatype}}}.self{{/isEnum}}, source: sourceDictionary["{{baseName}}"] as AnyObject?) {
case let .success(value): instance.{{name}} = value
case let .failure(error): return .failure(error)
}
{{/optionalVars}}
{{/unwrapRequired}}
{{^unwrapRequired}}
let instance = {{classname}}(){{#allVars}}{{#isEnum}}
if let {{name}} = sourceDictionary["{{baseName}}"] as? {{datatype}} { {{^isContainer}}
instance.{{name}} = {{classname}}.{{datatypeWithEnum}}(rawValue: ({{name}})){{/isContainer}}{{#isListContainer}}
instance.{{name}} = {{name}}.map ({ {{classname}}.{{enumName}}(rawValue: $0)! }){{/isListContainer}}{{#isMapContainer}}//TODO: handle enum map scenario{{/isMapContainer}}
}{{/isEnum}}
{{^isEnum}}instance.{{name}} = Decoders.decodeOptional(clazz: {{{baseType}}}.self, source: sourceDictionary["{{baseName}}"] as AnyObject?){{/isEnum}}{{/allVars}}
let instance = {{classname}}(){{#allVars}}
switch Decoders.decodeOptional(clazz: {{#isEnum}}{{^isListContainer}}{{classname}}.{{enumName}}.self{{/isListContainer}}{{#isListContainer}}Array<{{classname}}.{{enumName}}>.self{{/isListContainer}}{{/isEnum}}{{^isEnum}}{{{datatype}}}.self{{/isEnum}}, source: sourceDictionary["{{baseName}}"] as AnyObject?) {
{{#isEnum}}{{#isMapContainer}}/*{{/isMapContainer}}{{/isEnum}}
case let .success(value): instance.{{name}} = value
case let .failure(error): return .failure(error)
{{#isEnum}}{{#isMapContainer}}*/ default: break //TODO: handle enum map scenario{{/isMapContainer}}{{/isEnum}}
}
{{/allVars}}
{{/unwrapRequired}}
return instance
return .success(instance)
} else {
return .failure(.typeMismatch(expected: "{{classname}}", actual: "\(source)"))
}
{{/allVars.isEmpty}}
{{/isEnum}}
{{/isArrayModel}}
}{{/model}}
}{{/isArrayModel}}{{/model}}
{{/models}}
}()