Skip to content

Commit

Permalink
Merge pull request #195 from tristanhimmelman/txaiwieser-master
Browse files Browse the repository at this point in the history
ImmutableMappable support
  • Loading branch information
tristanhimmelman authored Feb 23, 2017
2 parents ac24d58 + 262e5c1 commit 39578ad
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 38 deletions.
131 changes: 96 additions & 35 deletions AlamofireObjectMapper/AlamofireObjectMapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,32 +46,67 @@ extension DataRequest {
return returnError
}

/// Utility function for checking for errors in response
internal static func checkResponseForError(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) -> Error? {
if let error = error {
return error
}
guard let _ = data else {
let failureReason = "Data could not be serialized. Input data was nil."
let error = newError(.noData, failureReason: failureReason)
return error
}
return nil
}

/// Utility function for extracting JSON from response
internal static func processResponse(request: URLRequest?, response: HTTPURLResponse?, data: Data?, keyPath: String?) -> Any? {
let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)

let JSON: Any?
if let keyPath = keyPath , keyPath.isEmpty == false {
JSON = (result.value as AnyObject?)?.value(forKeyPath: keyPath)
} else {
JSON = result.value
}

return JSON
}

/// BaseMappable Object Serializer
public static func ObjectMapperSerializer<T: BaseMappable>(_ keyPath: String?, mapToObject object: T? = nil, context: MapContext? = nil) -> DataResponseSerializer<T> {
return DataResponseSerializer { request, response, data, error in
guard error == nil else {
return .failure(error!)
}

guard let _ = data else {
let failureReason = "Data could not be serialized. Input data was nil."
let error = newError(.noData, failureReason: failureReason)
if let error = checkResponseForError(request: request, response: response, data: data, error: error){
return .failure(error)
}

let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonResponseSerializer.serializeResponse(request, response, data, error)

let JSONToMap: Any?
if let keyPath = keyPath , keyPath.isEmpty == false {
JSONToMap = (result.value as AnyObject?)?.value(forKeyPath: keyPath)
} else {
JSONToMap = result.value
}
let JSONObject = processResponse(request: request, response: response, data: data, keyPath: keyPath)

if let object = object {
_ = Mapper<T>(context: context, shouldIncludeNilValues: false).map(JSONObject: JSONToMap, toObject: object)
_ = Mapper<T>(context: context, shouldIncludeNilValues: false).map(JSONObject: JSONObject, toObject: object)
return .success(object)
} else if let parsedObject = Mapper<T>(context: context, shouldIncludeNilValues: false).map(JSONObject: JSONToMap){
} else if let parsedObject = Mapper<T>(context: context, shouldIncludeNilValues: false).map(JSONObject: JSONObject){
return .success(parsedObject)
}

let failureReason = "ObjectMapper failed to serialize response."
let error = newError(.dataSerializationFailed, failureReason: failureReason)
return .failure(error)
}
}

/// ImmutableMappable Array Serializer
public static func ObjectMapperImmutableSerializer<T: ImmutableMappable>(_ keyPath: String?, context: MapContext? = nil) -> DataResponseSerializer<T> {
return DataResponseSerializer { request, response, data, error in
if let error = checkResponseForError(request: request, response: response, data: data, error: error){
return .failure(error)
}

let JSONObject = processResponse(request: request, response: response, data: data, keyPath: keyPath)

if let JSONObject = JSONObject,
let parsedObject = (try? Mapper<T>(context: context, shouldIncludeNilValues: false).map(JSONObject: JSONObject)){
return .success(parsedObject)
}

Expand All @@ -96,30 +131,42 @@ extension DataRequest {
return response(queue: queue, responseSerializer: DataRequest.ObjectMapperSerializer(keyPath, mapToObject: object, context: context), completionHandler: completionHandler)
}

@discardableResult
public func responseObject<T: ImmutableMappable>(queue: DispatchQueue? = nil, keyPath: String? = nil, mapToObject object: T? = nil, context: MapContext? = nil, completionHandler: @escaping (DataResponse<T>) -> Void) -> Self {
return response(queue: queue, responseSerializer: DataRequest.ObjectMapperImmutableSerializer(keyPath, context: context), completionHandler: completionHandler)
}

/// BaseMappable Array Serializer
public static func ObjectMapperArraySerializer<T: BaseMappable>(_ keyPath: String?, context: MapContext? = nil) -> DataResponseSerializer<[T]> {
return DataResponseSerializer { request, response, data, error in
guard error == nil else {
return .failure(error!)
}

guard let _ = data else {
let failureReason = "Data could not be serialized. Input data was nil."
let error = newError(.dataSerializationFailed, failureReason: failureReason)
if let error = checkResponseForError(request: request, response: response, data: data, error: error){
return .failure(error)
}

let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonResponseSerializer.serializeResponse(request, response, data, error)
let JSONObject = processResponse(request: request, response: response, data: data, keyPath: keyPath)

let JSONToMap: Any?
if let keyPath = keyPath, keyPath.isEmpty == false {
JSONToMap = (result.value as AnyObject?)?.value(forKeyPath: keyPath)
} else {
JSONToMap = result.value
if let parsedObject = Mapper<T>(context: context, shouldIncludeNilValues: false).mapArray(JSONObject: JSONObject){
return .success(parsedObject)
}

if let parsedObject = Mapper<T>(context: context, shouldIncludeNilValues: false).mapArray(JSONObject: JSONToMap){
return .success(parsedObject)
let failureReason = "ObjectMapper failed to serialize response."
let error = newError(.dataSerializationFailed, failureReason: failureReason)
return .failure(error)
}
}

/// ImmutableMappable Array Serializer
public static func ObjectMapperImmutableArraySerializer<T: ImmutableMappable>(_ keyPath: String?, context: MapContext? = nil) -> DataResponseSerializer<[T]> {
return DataResponseSerializer { request, response, data, error in
if let error = checkResponseForError(request: request, response: response, data: data, error: error){
return .failure(error)
}

if let JSONObject = processResponse(request: request, response: response, data: data, keyPath: keyPath){

if let parsedObject = try? Mapper<T>(context: context, shouldIncludeNilValues: false).mapArray(JSONObject: JSONObject){
return .success(parsedObject)
}
}

let failureReason = "ObjectMapper failed to serialize response."
Expand All @@ -129,7 +176,7 @@ extension DataRequest {
}

/**
Adds a handler to be called once the request has finished.
Adds a handler to be called once the request has finished. T: BaseMappable

- parameter queue: The queue on which the completion handler is dispatched.
- parameter keyPath: The key path where object mapping should be performed
Expand All @@ -141,4 +188,18 @@ extension DataRequest {
public func responseArray<T: BaseMappable>(queue: DispatchQueue? = nil, keyPath: String? = nil, context: MapContext? = nil, completionHandler: @escaping (DataResponse<[T]>) -> Void) -> Self {
return response(queue: queue, responseSerializer: DataRequest.ObjectMapperArraySerializer(keyPath, context: context), completionHandler: completionHandler)
}

/**
Adds a handler to be called once the request has finished. T: ImmutableMappable

- parameter queue: The queue on which the completion handler is dispatched.
- parameter keyPath: The key path where object mapping should be performed
- parameter completionHandler: A closure to be executed once the request has finished and the data has been mapped by ObjectMapper.

- returns: The request.
*/
@discardableResult
public func responseArray<T: ImmutableMappable>(queue: DispatchQueue? = nil, keyPath: String? = nil, context: MapContext? = nil, completionHandler: @escaping (DataResponse<[T]>) -> Void) -> Self {
return response(queue: queue, responseSerializer: DataRequest.ObjectMapperImmutableArraySerializer(keyPath, context: context), completionHandler: completionHandler)
}
}
137 changes: 134 additions & 3 deletions AlamofireObjectMapperTests/AlamofireObjectMapperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class AlamofireObjectMapperTests: XCTestCase {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}

func testResponseObject() {
// This is an example of a functional test case.
let URL = "https://raw.githubusercontent.com/tristanhimmelman/AlamofireObjectMapper/d8bb95982be8a11a2308e779bb9a9707ebe42ede/sample_json"
Expand Down Expand Up @@ -151,7 +151,7 @@ class AlamofireObjectMapperTests: XCTestCase {
XCTAssertNil(error, "\(error)")
}
}

func testResponseArray() {
// This is an example of a functional test case.
let URL = "https://raw.githubusercontent.com/tristanhimmelman/AlamofireObjectMapper/f583be1121dbc5e9b0381b3017718a70c31054f7/sample_array_json"
Expand All @@ -163,7 +163,9 @@ class AlamofireObjectMapperTests: XCTestCase {
let mappedArray = response.result.value

XCTAssertNotNil(mappedArray, "Response should not be nil")


XCTAssertTrue(mappedArray?.count == 3, "Didn't parse correct amount of objects")

for forecast in mappedArray! {
XCTAssertNotNil(forecast.day, "day should not be nil")
XCTAssertNotNil(forecast.conditions, "conditions should not be nil")
Expand Down Expand Up @@ -225,8 +227,103 @@ class AlamofireObjectMapperTests: XCTestCase {
XCTAssertNil(error, "\(error)")
}
}

// MARK: - Immutable Tests

func testResponseImmutableObject() {
// This is an example of a functional test case.
let URL = "https://raw.githubusercontent.com/tristanhimmelman/AlamofireObjectMapper/d8bb95982be8a11a2308e779bb9a9707ebe42ede/sample_json"
let expectation = self.expectation(description: "\(URL)")

_ = Alamofire.request(URL, method: .get).responseObject { (response: DataResponse<WeatherResponseImmutable>) in
expectation.fulfill()

let mappedObject = response.result.value

XCTAssertNotNil(mappedObject, "Response should not be nil")
XCTAssertNotNil(mappedObject?.location, "Location should not be nil")
XCTAssertNotNil(mappedObject?.threeDayForecast, "ThreeDayForcast should not be nil")

for forecast in mappedObject!.threeDayForecast {
XCTAssertNotNil(forecast.day, "day should not be nil")
XCTAssertNotNil(forecast.conditions, "conditions should not be nil")
XCTAssertNotNil(forecast.temperature, "temperature should not be nil")
}
}

waitForExpectations(timeout: 10) { error in
XCTAssertNil(error, "\(error)")
}
}

func testResponseImmutableArray() {
// This is an example of a functional test case.
let URL = "https://raw.githubusercontent.com/tristanhimmelman/AlamofireObjectMapper/f583be1121dbc5e9b0381b3017718a70c31054f7/sample_array_json"
let expectation = self.expectation(description: "\(URL)")

_ = Alamofire.request(URL, method: .get).responseArray { (response: DataResponse<[ImmutableForecast]>) in
expectation.fulfill()

let mappedArray = response.result.value

XCTAssertNotNil(mappedArray, "Response should not be nil")

XCTAssertTrue(mappedArray?.count == 3, "Didn't parse correct amount of objects")

for forecast in mappedArray! {
XCTAssertNotNil(forecast.day, "day should not be nil")
XCTAssertNotNil(forecast.conditions, "conditions should not be nil")
XCTAssertNotNil(forecast.temperature, "temperature should not be nil")
}
}

waitForExpectations(timeout: 10) { error in
XCTAssertNil(error, "\(error)")
}
}

}

// MARK: - Response classes

// MARK: - ImmutableMappable

class ImmutableWeatherResponse: ImmutableMappable {
let location: String
let threeDayForecast: [ImmutableForecast]

required init(map: Map) throws {
location = try map.value("location")
threeDayForecast = try map.value("three_day_forecast")
}

func mapping(map: Map) {
location >>> map["location"]
threeDayForecast >>> map["three_day_forecast"]
}
}

class ImmutableForecast: ImmutableMappable {
let day: String
let temperature: Int
let conditions: String

required init(map: Map) throws {
day = try map.value("day")
temperature = try map.value("temperature")
conditions = try map.value("conditions")
}

func mapping(map: Map) {
day >>> map["day"]
temperature >>> map["temperature"]
conditions >>> map["conditions"]
}

}

// MARK: - Mappable

class WeatherResponse: Mappable {
var location: String?
var threeDayForecast: [Forecast]?
Expand Down Expand Up @@ -259,3 +356,37 @@ class Forecast: Mappable {
conditions <- map["conditions"]
}
}

struct WeatherResponseImmutable: ImmutableMappable {
let location: String
var threeDayForecast: [Forecast]
var date: Date?

init(map: Map) throws {
location = try map.value("location")
threeDayForecast = try map.value("three_day_forecast")
}

func mapping(map: Map) {
location >>> map["location"]
threeDayForecast >>> map["three_day_forecast"]
}
}

struct ForecastImmutable: ImmutableMappable {
let day: String
var temperature: Int
var conditions: String?

init(map: Map) throws {
day = try map.value("day")
temperature = try map.value("temperature")
conditions = try? map.value("conditions")
}

func mapping(map: Map) {
day >>> map["day"]
temperature >>> map["temperature"]
conditions >>> map["conditions"]
}
}

0 comments on commit 39578ad

Please sign in to comment.