Skip to content

Commit

Permalink
Allow DataParser to return typed objects
Browse files Browse the repository at this point in the history
DataParser should not be required to return Any and have objects checked for type safety at runtime. This allows us to use the Swift build time type checking system with very minimal changes to existing code. The only API breaking change is that Request classes using the default JSONDataParser should now conform to JSONRequest, instead of just Request. This is because protocol extensions cannot declare default associated types.

(cherry picked from commit 261af5f)
  • Loading branch information
gormster authored and Econa77 committed Oct 22, 2022
1 parent 5fac79c commit 2e33919
Show file tree
Hide file tree
Showing 4 changed files with 20 additions and 11 deletions.
4 changes: 3 additions & 1 deletion Sources/APIKit/DataParser/DataParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import Foundation

/// `DataParser` protocol provides interface to parse HTTP response body and to state Content-Type to accept.
public protocol DataParser {
associatedtype Parsed

/// Value for `Accept` header field of HTTP request.
var contentType: String? { get }

/// Return `Any` that expresses structure of response such as JSON and XML.
/// - Throws: `Error` when parser encountered invalid format data.
func parse(data: Data) throws -> Any
func parse(data: Data) throws -> Parsed
}
23 changes: 15 additions & 8 deletions Sources/APIKit/Request.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Foundation
public protocol Request {
/// The response type associated with the request type.
associatedtype Response
associatedtype Parser: DataParser

/// The base URL.
var baseURL: URL { get }
Expand Down Expand Up @@ -40,7 +41,7 @@ public protocol Request {
var headerFields: [String: String] { get }

/// The parser object that states `Content-Type` to accept and parses response body.
var dataParser: DataParser { get }
var dataParser: Parser { get }

/// Intercepts `URLRequest` which is created by `Request.buildURLRequest()`. If an error is
/// thrown in this method, the result of `Session.send()` turns `.failure(.requestError(error))`.
Expand All @@ -52,12 +53,12 @@ public protocol Request {
/// The default implementation of this method is provided to throw `ResponseError.unacceptableStatusCode`
/// if the HTTP status code is not in `200..<300`.
/// - Throws: `Error`
func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any
func intercept(object: Parser.Parsed, urlResponse: HTTPURLResponse) throws -> Parser.Parsed

/// Build `Response` instance from raw response object. This method is called after
/// `intercept(object:urlResponse:)` if it does not throw any error.
/// - Throws: `Error`
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response
func response(from object: Parser.Parsed, urlResponse: HTTPURLResponse) throws -> Response
}

public extension Request {
Expand Down Expand Up @@ -85,15 +86,11 @@ public extension Request {
return [:]
}

var dataParser: DataParser {
return JSONDataParser(readingOptions: [])
}

func intercept(urlRequest: URLRequest) throws -> URLRequest {
return urlRequest
}

func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any {
func intercept(object: Parser.Parsed, urlResponse: HTTPURLResponse) throws -> Parser.Parsed {
guard 200..<300 ~= urlResponse.statusCode else {
throw ResponseError.unacceptableStatusCode(urlResponse.statusCode)
}
Expand Down Expand Up @@ -151,3 +148,13 @@ public extension Request where Response == Void {
return
}
}

public protocol JSONRequest: Request {}

public extension JSONRequest {

var dataParser: JSONDataParser {
return JSONDataParser(readingOptions: [])
}

}
2 changes: 1 addition & 1 deletion Tests/APIKitTests/SessionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class SessionTests: XCTestCase {
waitForExpectations(timeout: 1.0, handler: nil)
}

struct AnotherTestRequest: Request {
struct AnotherTestRequest: JSONRequest {
typealias Response = Void

var baseURL: URL {
Expand Down
2 changes: 1 addition & 1 deletion Tests/APIKitTests/TestComponents/TestRequest.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import APIKit

struct TestRequest: Request {
struct TestRequest: JSONRequest {
var absoluteURL: URL? {
let urlRequest = try? buildURLRequest()
return urlRequest?.url
Expand Down

0 comments on commit 2e33919

Please sign in to comment.