Commit 1faa9293 authored by Mauro E. Bender's avatar Mauro E. Bender

Merge branch '11-mb-update_to_swift_3' into 'master'

Close #11 - Update code to Swift 3

Closes #11

See merge request !10
parents 577a5f81 4222277c
...@@ -27,9 +27,9 @@ TODO: Add long description of the pod here. ...@@ -27,9 +27,9 @@ TODO: Add long description of the pod here.
s.author = { 'Mauro E. Bender' => 'mauro@theamalgama.com' } s.author = { 'Mauro E. Bender' => 'mauro@theamalgama.com' }
s.source = { :git => 'https://git.theamalgama.com/ios/api.git', :tag => s.version.to_s } s.source = { :git => 'https://git.theamalgama.com/ios/api.git', :tag => s.version.to_s }
s.ios.deployment_target = '8.0' s.ios.deployment_target = '9.0'
s.source_files = 'Api/Classes/**/*' s.source_files = 'Api/Classes/**/*'
s.dependency 'Alamofire', '~> 3.4' s.dependency 'Alamofire', '~> 4.0.0'
end end
...@@ -8,70 +8,70 @@ ...@@ -8,70 +8,70 @@
import Alamofire import Alamofire
public typealias ApiMethod = Alamofire.Method public typealias ApiMethod = Alamofire.HTTPMethod
public class Api { open class Api {
let baseURL: NSURL let baseURL: URL
let apiPath: String let apiPath: String
var apiURL: NSURL { return baseURL.URLByAppendingPathComponent( apiPath ) } var apiURL: URL { return baseURL.appendingPathComponent( apiPath ) }
public init( baseURL: NSURL, apiPath: String ) { public init( baseURL: URL, apiPath: String ) {
self.baseURL = baseURL self.baseURL = baseURL
self.apiPath = apiPath self.apiPath = apiPath
} }
// RESOURCE // RESOURCE
public func resource( path: String ) -> Resource { open func resource( _ path: String ) -> Resource {
return Resource( api: self, path: path ) return Resource( api: self, path: path )
} }
// REQUESTS // REQUESTS
public func request( method: ApiMethod, path: String, open func request( _ method: ApiMethod, path: String,
parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default,
headers: [String: String]? = nil, completion: Response<AnyObject, NSError> -> Void ) headers: [String: String]? = nil, completion: @escaping (DataResponse<Any>) -> Void )
{ {
Alamofire.request( method, urlForPath( path ).absoluteString, parameters: parameters, Alamofire.request( urlForPath( path ).absoluteString, method: method, parameters: parameters,
encoding: encoding, headers: headers ).responseJSON( completionHandler: completion ) encoding: encoding, headers: headers ).responseJSON( completionHandler: completion )
} }
public func get( path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, open func get( _ path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default,
headers: [String: String]? = nil, completion: Response<AnyObject, NSError> -> Void ) headers: [String: String]? = nil, completion: @escaping (DataResponse<Any>) -> Void )
{ {
return request( .GET, path: path, parameters: parameters, encoding: encoding, return request( .get, path: path, parameters: parameters, encoding: encoding,
headers: headers, completion: completion) headers: headers, completion: completion)
} }
public func post( path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, open func post( _ path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default,
headers: [String: String]? = nil, completion: Response<AnyObject, NSError> -> Void ) headers: [String: String]? = nil, completion: @escaping (DataResponse<Any>) -> Void )
{ {
return request( .POST, path: path, parameters: parameters, encoding: encoding, return request( .post, path: path, parameters: parameters, encoding: encoding,
headers: headers, completion: completion) headers: headers, completion: completion)
} }
public func put( path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, open func put( _ path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default,
headers: [String: String]? = nil, completion: Response<AnyObject, NSError> -> Void ) headers: [String: String]? = nil, completion: @escaping (DataResponse<Any>) -> Void )
{ {
return request( .PUT, path: path, parameters: parameters, encoding: encoding, return request( .put, path: path, parameters: parameters, encoding: encoding,
headers: headers, completion: completion) headers: headers, completion: completion)
} }
public func patch( path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, open func patch( _ path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default,
headers: [String: String]? = nil, completion: Response<AnyObject, NSError> -> Void ) headers: [String: String]? = nil, completion: @escaping (DataResponse<Any>) -> Void )
{ {
return request( .PATCH, path: path, parameters: parameters, encoding: encoding, return request( .patch, path: path, parameters: parameters, encoding: encoding,
headers: headers, completion: completion) headers: headers, completion: completion)
} }
public func delete( path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, open func delete( _ path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default,
headers: [String: String]? = nil, completion: Response<AnyObject, NSError> -> Void ) headers: [String: String]? = nil, completion: @escaping (DataResponse<Any>) -> Void )
{ {
return request( .DELETE, path: path, parameters: parameters, encoding: encoding, return request( .delete, path: path, parameters: parameters, encoding: encoding,
headers: headers, completion: completion) headers: headers, completion: completion)
} }
func urlForPath( path: String ) -> NSURL { func urlForPath( _ path: String ) -> URL {
return apiURL.URLByAppendingPathComponent( path ) return apiURL.appendingPathComponent( path )
} }
} }
...@@ -80,4 +80,4 @@ extension Api: Equatable {} ...@@ -80,4 +80,4 @@ extension Api: Equatable {}
public func ==(lhs: Api, rhs: Api) -> Bool { public func ==(lhs: Api, rhs: Api) -> Bool {
return lhs.baseURL == rhs.baseURL && lhs.apiPath == rhs.apiPath return lhs.baseURL == rhs.baseURL && lhs.apiPath == rhs.apiPath
} }
\ No newline at end of file
...@@ -8,22 +8,30 @@ ...@@ -8,22 +8,30 @@
import Alamofire import Alamofire
public enum ApiError: ErrorType { public enum ApiError: Error {
case Network( error: NSError, response: NSURLResponse ) case network( error: Error, response: DataResponse<Any> )
case JSONSerialization(error: NSError) case invalidURL(url: URLConvertible)
case Unknown( error: NSError ) case jsonSerialization(error: AFError)
case unknown( error: Error )
} }
public extension ApiError { public extension ApiError {
init( response: Response<AnyObject, NSError> ) { init( response: DataResponse<Any> ) {
let error = response.result.error! let error = response.result.error!
if let statusCode = response.response?.statusCode where !(200...299).contains( statusCode ) { if let statusCode = response.response?.statusCode , !(200...299).contains( statusCode ) {
self = .Network( error: error, response: response.response! ) self = .network( error: error, response: response )
} else if error.code == -6006 { } else if let error = error as? AFError {
self = .JSONSerialization( error: error ) switch error {
case .responseSerializationFailed(_):
self = .jsonSerialization( error: error )
case .invalidURL(let url):
self = .invalidURL(url: url)
default:
self = .unknown(error: error)
}
} else { } else {
self = .Unknown( error: error ) self = .unknown(error: error)
} }
} }
} }
\ No newline at end of file
...@@ -9,15 +9,15 @@ ...@@ -9,15 +9,15 @@
import Alamofire import Alamofire
public enum ApiResult<Value> { public enum ApiResult<Value> {
case Success(Value) case success(Value)
case Failure(ApiError) case failure(ApiError)
/// Returns `true` if the result is a success, `false` otherwise. /// Returns `true` if the result is a success, `false` otherwise.
public var isSuccess: Bool { public var isSuccess: Bool {
switch self { switch self {
case .Success: case .success:
return true return true
case .Failure: case .failure:
return false return false
} }
} }
...@@ -30,9 +30,9 @@ public enum ApiResult<Value> { ...@@ -30,9 +30,9 @@ public enum ApiResult<Value> {
/// Returns the associated value if the result is a success, `nil` otherwise. /// Returns the associated value if the result is a success, `nil` otherwise.
public var value: Value? { public var value: Value? {
switch self { switch self {
case .Success(let value): case .success(let value):
return value return value
case .Failure: case .failure:
return nil return nil
} }
} }
...@@ -40,24 +40,24 @@ public enum ApiResult<Value> { ...@@ -40,24 +40,24 @@ public enum ApiResult<Value> {
/// Returns the associated error value if the result is a failure, `nil` otherwise. /// Returns the associated error value if the result is a failure, `nil` otherwise.
public var error: ApiError? { public var error: ApiError? {
switch self { switch self {
case .Success: case .success:
return nil return nil
case .Failure(let error): case .failure(let error):
return error return error
} }
} }
} }
public enum EmptyResult { public enum EmptyResult {
case Success case success
case Failure(ApiError) case failure(ApiError)
/// Returns `true` if the result is a success, `false` otherwise. /// Returns `true` if the result is a success, `false` otherwise.
public var isSuccess: Bool { public var isSuccess: Bool {
switch self { switch self {
case .Success: case .success:
return true return true
case .Failure: case .failure:
return false return false
} }
} }
...@@ -70,9 +70,9 @@ public enum EmptyResult { ...@@ -70,9 +70,9 @@ public enum EmptyResult {
/// Returns the associated error value if the result is a failure, `nil` otherwise. /// Returns the associated error value if the result is a failure, `nil` otherwise.
public var error: ApiError? { public var error: ApiError? {
switch self { switch self {
case .Success: case .success:
return nil return nil
case .Failure(let error): case .failure(let error):
return error return error
} }
} }
...@@ -85,9 +85,9 @@ extension ApiResult: CustomStringConvertible { ...@@ -85,9 +85,9 @@ extension ApiResult: CustomStringConvertible {
/// success or failure. /// success or failure.
public var description: String { public var description: String {
switch self { switch self {
case .Success: case .success:
return "SUCCESS" return "SUCCESS"
case .Failure: case .failure:
return "FAILURE" return "FAILURE"
} }
} }
...@@ -98,9 +98,9 @@ extension ApiResult: CustomDebugStringConvertible { ...@@ -98,9 +98,9 @@ extension ApiResult: CustomDebugStringConvertible {
/// success or failure in addition to the value or error. /// success or failure in addition to the value or error.
public var debugDescription: String { public var debugDescription: String {
switch self { switch self {
case .Success(let value): case .success(let value):
return "SUCCESS: \(value)" return "SUCCESS: \(value)"
case .Failure(let error): case .failure(let error):
return "FAILURE: \(error)" return "FAILURE: \(error)"
} }
} }
...@@ -109,63 +109,70 @@ extension ApiResult: CustomDebugStringConvertible { ...@@ -109,63 +109,70 @@ extension ApiResult: CustomDebugStringConvertible {
// MARK: Factories // MARK: Factories
// FIXME: Encapsulate // FIXME: Encapsulate
public func itemResult<T: JSONSerializable>( response: Response<AnyObject, NSError>, func resultJSON (_ responseData: Any?, keyPath: String?) -> Any? {
guard let keyPath = keyPath else { return responseData }
guard let responseData = responseData as? [String: Any] else { return nil }
return responseData[ keyPath ];
}
public func itemResult<T: JSONSerializable>( _ response: DataResponse<Any>,
keyPath: String? ) -> ApiResult< T? > keyPath: String? ) -> ApiResult< T? >
{ {
switch(response.result) { switch(response.result) {
case .Success(let JSON): case .success(let JSON):
if let itemJSON = (keyPath != nil ? JSON[ keyPath! ] : JSON) as? [String : AnyObject] { if let itemJSON = resultJSON(JSON, keyPath: keyPath) as? [String : AnyObject] {
return .Success( T( fromJSON: itemJSON ) ) return .success( T( fromJSON: itemJSON ) )
} else { } else {
return .Success( nil ) return .success( nil )
} }
case .Failure(let error): case .failure(_):
return .Failure( ApiError( response: response ) ) return .failure( ApiError( response: response ) )
} }
} }
public func emptyResult( response: Response<AnyObject, NSError> ) -> EmptyResult { public func emptyResult( _ response: DataResponse<Any> ) -> EmptyResult {
switch(response.result) { switch(response.result) {
case .Success: case .success:
return .Success return .success
case .Failure(let error): case .failure(_):
return .Failure( ApiError( response: response ) ) return .failure( ApiError( response: response ) )
} }
} }
public func listResult<T: JSONSerializable>( response: Response<AnyObject, NSError>, public func listResult<T: JSONSerializable>( _ response: DataResponse<Any>,
keyPath: String? ) -> ApiResult< [T]? > keyPath: String? ) -> ApiResult< [T]? >
{ {
switch(response.result) { switch(response.result) {
case .Success(let JSON): case .success(let JSON):
if let listJSON = (keyPath != nil ? JSON[ keyPath! ] : JSON) as? [[String : AnyObject]] { if let listJSON = resultJSON(JSON, keyPath: keyPath) as? [[String : AnyObject]] {
let list = listJSON.map { T( fromJSON: $0 ) } let list = listJSON.map { T( fromJSON: $0 ) }
return .Success( list ) return .success( list )
} else { } else {
return .Success( nil ) return .success( nil )
} }
case .Failure(let error): case .failure(_):
return .Failure( ApiError( response: response ) ) return .failure( ApiError( response: response ) )
} }
} }
public func paginatedListResult<T: JSONSerializable>( response: Response<AnyObject, NSError>, public func paginatedListResult<T: JSONSerializable>( _ response: DataResponse<Any>,
keyPath: String?, paginationKeyPath: String? ) -> ApiResult< ([T], Pagination)? > keyPath: String?, paginationKeyPath: String? ) -> ApiResult< ([T], Pagination)? >
{ {
switch(response.result) { switch(response.result) {
case .Success(let JSON): case .success(let JSON):
if let listJSON = (keyPath != nil ? JSON[ keyPath! ] : JSON) as? [[String : AnyObject]], if let listJSON = resultJSON(JSON, keyPath: keyPath) as? [[String : AnyObject]],
let paginationJSON = (paginationKeyPath != nil ? JSON[ paginationKeyPath! ] : JSON) as? [String : AnyObject] let paginationJSON = resultJSON(JSON, keyPath: paginationKeyPath) as? [String : AnyObject]
{ {
let list = listJSON.map { T( fromJSON: $0 ) } let list = listJSON.map { T( fromJSON: $0 ) }
let pagination = Pagination( fromJSON: paginationJSON ) let pagination = Pagination( fromJSON: paginationJSON )
return .Success( (list, pagination) ) return .success( (list, pagination) )
} else { } else {
return .Success( nil ) return .success( nil )
} }
case .Failure(let error): case .failure(_):
return .Failure( ApiError( response: response ) ) return .failure( ApiError( response: response ) )
} }
} }
...@@ -13,11 +13,11 @@ public protocol JSONSerializable { ...@@ -13,11 +13,11 @@ public protocol JSONSerializable {
func toJSON() -> [ String: AnyObject ] func toJSON() -> [ String: AnyObject ]
} }
public class Resource { open class Resource {
public let api: Api open let api: Api
public let path: String open let path: String
public var responseKeyPath: String? = "response" open var responseKeyPath: String? = "response"
public var paginationKeyPath: String? = "pagination" open var paginationKeyPath: String? = "pagination"
init( api: Api, path: String ) { init( api: Api, path: String ) {
self.api = api self.api = api
...@@ -27,71 +27,71 @@ public class Resource { ...@@ -27,71 +27,71 @@ public class Resource {
// MARK: REQUESTS // MARK: REQUESTS
extension Resource { extension Resource {
public func request( method: ApiMethod, path: String? = nil, public func request( _ method: ApiMethod, path: String? = nil,
parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default,
headers: [String: String]? = nil, completion: Response<AnyObject, NSError> -> Void ) headers: [String: String]? = nil, completion: @escaping (DataResponse<Any>) -> Void )
{ {
let requestPath = path != nil ? "\(self.path)/\(path!)" : self.path let requestPath = path != nil ? "\(self.path)/\(path!)" : self.path
api.request( method, path: requestPath, parameters: parameters, encoding: encoding, api.request( method, path: requestPath, parameters: parameters, encoding: encoding,
headers: headers, completion: completion ) headers: headers, completion: completion )
} }
public func get( path: String? = nil, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, public func get( _ path: String? = nil, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default,
headers: [String: String]? = nil, completion: Response<AnyObject, NSError> -> Void ) headers: [String: String]? = nil, completion: @escaping (DataResponse<Any>) -> Void )
{ {
return request( .GET, path: path, parameters: parameters, encoding: encoding, return request( .get, path: path, parameters: parameters, encoding: encoding,
headers: headers, completion: completion) headers: headers, completion: completion)
} }
public func post( path: String? = nil, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, public func post( _ path: String? = nil, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default,
headers: [String: String]? = nil, completion: Response<AnyObject, NSError> -> Void ) headers: [String: String]? = nil, completion: @escaping (DataResponse<Any>) -> Void )
{ {
return request( .POST, path: path, parameters: parameters, encoding: encoding, return request( .post, path: path, parameters: parameters, encoding: encoding,
headers: headers, completion: completion) headers: headers, completion: completion)
} }
public func put( path: String? = nil, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, public func put( _ path: String? = nil, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default,
headers: [String: String]? = nil, completion: Response<AnyObject, NSError> -> Void ) headers: [String: String]? = nil, completion: @escaping (DataResponse<Any>) -> Void )
{ {
return request( .PUT, path: path, parameters: parameters, encoding: encoding, return request( .put, path: path, parameters: parameters, encoding: encoding,
headers: headers, completion: completion) headers: headers, completion: completion)
} }
public func patch( path: String? = nil, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, public func patch( _ path: String? = nil, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default,
headers: [String: String]? = nil, completion: Response<AnyObject, NSError> -> Void ) headers: [String: String]? = nil, completion: @escaping (DataResponse<Any>) -> Void )
{ {
return request( .PATCH, path: path, parameters: parameters, encoding: encoding, return request( .patch, path: path, parameters: parameters, encoding: encoding,
headers: headers, completion: completion) headers: headers, completion: completion)
} }
public func delete( path: String? = nil, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL, public func delete( _ path: String? = nil, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default,
headers: [String: String]? = nil, completion: Response<AnyObject, NSError> -> Void ) headers: [String: String]? = nil, completion: @escaping (DataResponse<Any>) -> Void )
{ {
return request( .DELETE, path: path, parameters: parameters, encoding: encoding, return request( .delete, path: path, parameters: parameters, encoding: encoding,
headers: headers, completion: completion) headers: headers, completion: completion)
} }
} }
// MARK: CRUD // MARK: CRUD
extension Resource { extension Resource {
public func item<T: JSONSerializable>( id: AnyObject, parameters: [ String: String ]? = nil, public func item<T: JSONSerializable>( _ id: Any, parameters: [ String: AnyObject ]? = nil,
headers: [ String: String]? = nil, completion: ( ( ApiResult< T? > ) -> Void ) ) headers: [ String: String]? = nil, completion: @escaping ( ( ApiResult< T? > ) -> Void ) )
{ {
get( "\(id)", parameters: parameters, headers: headers ) { response in get( "\(id)", parameters: parameters, headers: headers ) { response in
completion( itemResult( response, keyPath: self.responseKeyPath ) ) completion( itemResult( response, keyPath: self.responseKeyPath ) )
} }
} }
public func list<T: JSONSerializable>( parameters: [ String: String ]? = nil, public func list<T: JSONSerializable>( _ parameters: [ String: AnyObject ]? = nil,
headers: [ String: String]? = nil, completion: (