Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ jobs:
- "swift:5.4"
- "swift:5.5"
- "swift:5.6"
- "swift:5.7"
- "swift:5.8"
- "swift:5.9"
- "swift:5.10"
name: Linux
runs-on: ubuntu-latest
container:
Expand Down
4 changes: 2 additions & 2 deletions Sources/Resourceful/Resource+Combine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ extension URLSession {
/// - Returns: A publisher wrapping a data task.
public func publisher<Value>(for resource: Resource<Value>) -> AnyPublisher<Value, Error> {

Future { $0(Result { try resource.makeRequest() }) }
Future { $0(Result { try resource.request }) }
.flatMap { self.dataTaskPublisher(for: $0).mapError { $0 } }
.tryMap(resource.transform)
.tryMap(resource.value)
.eraseToAnyPublisher()
}
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/Resourceful/Resource+Fetch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ extension URLSession {
) -> URLSessionDataTask {

do {
let request = try resource.makeRequest()
let request = try resource.request
return perform(request: request) { result in
let value = Result { try resource.transform(result.get()) }
let value = Result { try resource.value(for: result.get()) }
queue.async {
completion(value)
}
Expand Down
106 changes: 76 additions & 30 deletions Sources/Resourceful/Resource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,41 +11,54 @@ public struct Resource<Value> {

public typealias Response = (data: Data, response: URLResponse)

/// A request used to fetch the data.
@available(*, deprecated, message: "Use makeRequest() instead.")
public var request: URLRequest { return try! makeRequest() }

/// Makes the request for the resource.
public let makeRequest: () throws -> URLRequest

/// Transforms the network response into the desired value.
public let transform: (Response) throws -> Value
private let _request: () throws -> URLRequest
private let _success: (Response) throws -> Value

/// Creates a resource located with the request and transformed from data
/// using the given transform.
///
/// Any failures from the makeRequest or transform functions will be
/// surfaced when performing the network request using the fetch or
/// publisher methods on URLSession.
///
/// - Parameters:
/// - request: A request for this resource.
/// - transform: Used to transform the response into the desired value.
public init(request: URLRequest,
transform: @escaping (Response) throws -> Value) {
self.init(makeRequest: { request }, transform: transform)
/// - request: Creates a request for this resource.
/// - success: Used to transform the response into the desired value.
public init(
request: @escaping () throws -> URLRequest,
success: @escaping (Response) throws -> Value
) {
_request = request
_success = success
}
}

extension Resource {

/// Creates a resource located with the request and transformed from data
/// using the given transform.
///
/// Any failures from the makeRequest or transform functions will be
/// surfaced when performing the network request using the fetch or
/// publisher methods on URLSession.
///
/// - Parameters:
/// - makeRequest: Used to create a request for this resource.
/// - transform: Used to transform the response into the desired value.
public init(makeRequest: @escaping () throws -> URLRequest,
transform: @escaping (Response) throws -> Value) {
self.makeRequest = makeRequest
self.transform = transform
/// - request: A request for this resource.
/// - success: Used to transform the response into the desired value.
public init(
request: URLRequest,
success: @escaping (Response) throws -> Value
) {
self.init(request: { request }, success: success)
}
}

extension Resource {

/// A request used to fetch the data.
public var request: URLRequest {
get throws { try _request() }
}

/// Transforms the network response into the desired value.
public func value(for response: Response) throws -> Value {
try _success(response)
}
}

Expand All @@ -61,26 +74,59 @@ extension Resource {
_ transform: @escaping (Value) throws -> NewValue
) -> Resource<NewValue> {

return Resource<NewValue>(makeRequest: makeRequest) { response in
try transform(self.transform(response))
Resource<NewValue>(request: _request) { response in
try transform(value(for: response))
}
}

public func mapRequest(
_ modify: @escaping (URLRequest) throws -> URLRequest
) -> Resource {
return Resource(
makeRequest: { try modify(self.makeRequest()) },
transform: transform)
Resource(
request: { try modify(self._request()) },
success: _success
)
}

public func modifyRequest(
_ modify: @escaping (inout URLRequest) throws -> ()
) -> Resource {
return mapRequest { request in
mapRequest { request in
var request = request
try modify(&request)
return request
}
}
}

// MARK: - Deprecations

extension Resource {

/// Transforms the network response into the desired value.
@available(*, deprecated, message: "Use value(for:) instead.")
public var transform: (Response) throws -> Value { _success }

/// Makes the request for the resource.
@available(*, deprecated, message: "Use request instead.")
public var makeRequest: () throws -> URLRequest { _request }

/// Creates a resource located with the request and transformed from data
/// using the given transform.
///
/// Any failures from the makeRequest or transform functions will be
/// surfaced when performing the network request using the fetch or
/// publisher methods on URLSession.
///
/// - Parameters:
/// - makeRequest: Used to create a request for this resource.
/// - transform: Used to transform the response into the desired value.
@available(*, deprecated, message: "Use init(request:success:) instead.")
public init(
makeRequest: @escaping () throws -> URLRequest,
transform: @escaping (Response) throws -> Value
) {
_request = makeRequest
_success = transform
}
}
6 changes: 4 additions & 2 deletions Tests/ResourcefulTests/Helper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ extension XCTestCase {
/// - Parameter url: The location of the data.
var failingRequestResource: Resource<String> {
struct TestError: Error {}
return Resource<String>(makeRequest: { throw TestError() }, transform: {
return Resource<String> {
throw TestError()
} success: {
String(data: $0.data, encoding: .utf8) ?? ""
})
}
}
}

Expand Down
54 changes: 32 additions & 22 deletions Tests/ResourcefulTests/ResourceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,51 @@ import FoundationNetworking
final class ResourceTests: XCTestCase {

func testInit() throws {
let resource = Resource(request: request) { _ in return "Hello" }
XCTAssertEqual(try resource.makeRequest(), request)
XCTAssertEqual(try resource.transform(response), "Hello")
let resource = try Resource(request: request) { _ in return "Hello" }
try XCTAssertEqual(resource.request, request)
try XCTAssertEqual(resource.value(for: response), "Hello")
}

func testMapRequest() {
let resource = Resource(request: request) { _ in return 20 }
func testMapRequest() throws {
let resource = try Resource(request: request) { _ in return 20 }
.mapRequest { request in
URLRequest(url: request.url!.appendingPathComponent("test"))
}
XCTAssertEqual(try resource.makeRequest().url, url.appendingPathComponent("test"))
try XCTAssertEqual(try resource.request.url, url.appendingPathComponent("test"))
}

func testModifyRequest() {
let resource = Resource(request: request) { _ in return 20 }
func testModifyRequest() throws {
let resource = try Resource(request: request) { _ in return 20 }
.modifyRequest { $0.url?.appendPathComponent("test") }
XCTAssertEqual(try resource.makeRequest().url, url.appendingPathComponent("test"))
try XCTAssertEqual(resource.request.url, url.appendingPathComponent("test"))
}

func testTryMap() {
let integer = Resource(request: request) { _ in return 20 }
func testTryMap() throws {
let integer = try Resource(request: request) { _ in return 20 }
let string = integer.tryMap { String($0) }
XCTAssertEqual(try integer.transform(response), 20)
XCTAssertEqual(try string.transform(response), "20")
XCTAssertEqual(try integer.value(for: response), 20)
XCTAssertEqual(try string.value(for: response), "20")
}

// swiftlint:disable force_unwrapping
private var url: URL { return URL(string: "http://example.com")! }
// swiftlint:enable force_unwrapping
private var url: URL {
get throws { try XCTUnwrap(URL(string: "http://example.com")) }
}

private var request: URLRequest {
get throws { try URLRequest(url: url) }
}

private var request: URLRequest { return URLRequest(url: url) }
private var response: (Data, URLResponse) {
return (Data(), URLResponse(url: url,
mimeType: nil,
expectedContentLength: 0,
textEncodingName: nil))
private var response: Resource.Response {
get throws {
try (
Data(),
URLResponse(
url: url,
mimeType: nil,
expectedContentLength: 0,
textEncodingName: nil
)
)
}
}
}