Skip to content

Commit

Permalink
Merge pull request #20 from wlsdms0122/develop
Browse files Browse the repository at this point in the history
[RELEASE] Dyson/2.6.0
  • Loading branch information
wlsdms0122 authored Nov 3, 2024
2 parents baf6aa3 + e21090e commit 8d4d162
Show file tree
Hide file tree
Showing 14 changed files with 72 additions and 100 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,10 @@ dependencies: [
```

# Basic Usage
You should start by creating a `Dyson` object. `Dyson` takes parameters such as `NetworkProvider`, `Responser`, and `Interceptor`, which will be discussed below.
You should start by creating a `Dyson` object. `Dyson` takes parameters such as `NetworkProvider` and `Interceptor`, which will be discussed below.
```swift
let dyson = Dyson(
provider: .url(),
responser: MyResponser(),
defaultHeaders: [
"Content-Type": "application/json"
],
Expand All @@ -58,6 +57,7 @@ dyson.data(GetInfoSpec(.init())) { result in
// result's type is Result<GetInfoSpec.Result, any Error>
}
```

## NetworkProvider
`NetworkProvider` is the protocol that abstracts the functionality of network communication.

Expand All @@ -73,7 +73,7 @@ public protocol NetworkProvider {
`Dyson` serve default network provider, `URLNetworkProvider` that implemented using `URLSession`.

## Responser
`Responder` is a protocol that abstracts how to respond to a network response.
`Responser` is a protocol that abstracts how to respond to a network response.

Responsible for the implementation of how to handle the conventions for network responses between servers and clients.

Expand All @@ -88,9 +88,10 @@ public protocol Responser {

For example, you might need to filter by a range of status codes with your own communication protocols, or parse response data and header values together.

These characteristics usually depend on the server you're communicating with(just as different OpenAPIs have different communication protocols).
These characteristics usually depend on the server you're communicating with (just as different OpenAPIs have different communication protocols).

Typically, the implementation of a 'responser' looks like this.

Typically, the implementation of a 'responder' looks like this.
```swift
func response<S: Spec>(
_ response: Result<(Data, URLResponse), any Error>,
Expand Down Expand Up @@ -277,6 +278,7 @@ public protocol Spec {
var headers: HTTPHeaders { get }

var request: any Request { get }
var responser: (any Responser)? { get }
var result: Mapper<Result> { get }
var error: Mapper<Error> { get }
}
Expand All @@ -299,6 +301,7 @@ public struct GetInfoSpec: Spec {
"id": parameter.id
])
}
var responser: (any Responser)? { YourServerResponser() }
var result: Mapper<Result> { .codable }
var error: Mapper<Error> { .codable }

Expand Down Expand Up @@ -341,7 +344,7 @@ public protocol Request {
The `Dyson` serve some requests.
- none </br>
Not exist additional process.
- query(_:) </br>
- query(_:isEncoded:) </br>
Add the query parameters as a query to the URL.
- body(_:encoder:) </br>
After encoding with the encoder, add the parameters as data to the HTTP body. </br>
Expand Down
2 changes: 0 additions & 2 deletions Sources/Dyson/Dyson+Async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ public extension Dyson {
@discardableResult
func data<S: Spec>(
_ spec: S,
responser: (any Responser)? = nil,
progress: ((Progress) -> Void)? = nil,
requestModifier: ((URLRequest) -> URLRequest)? = nil
) async throws -> S.Result {
Expand All @@ -77,7 +76,6 @@ public extension Dyson {
await cancellableTask.perform(
data(
spec,
responser: responser,
progress: progress,
requestModifier: requestModifier
) { result in
Expand Down
11 changes: 2 additions & 9 deletions Sources/Dyson/Dyson.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,16 @@ open class Dyson {
// MARK: - Property
private let provider: any NetworkProvider

public let responser: (any Responser)?

public let defaultHeaders: HTTPHeaders
public let interceptors: [any Interceptor]

// MARK: - Initializer
public init(
provider: any NetworkProvider,
responser: (any Responser)? = nil,
defaultHeaders: HTTPHeaders = [:],
interceptors: [any Interceptor] = []
) {
self.provider = provider
self.responser = responser
self.defaultHeaders = defaultHeaders
self.interceptors = interceptors
}
Expand Down Expand Up @@ -66,7 +62,6 @@ open class Dyson {
@discardableResult
open func data<S: Spec>(
_ spec: S,
responser: (any Responser)? = nil,
progress: ((Progress) -> Void)? = nil,
requestModifier: ((URLRequest) -> URLRequest)? = nil,
completion: @escaping (Result<S.Result, any Error>) -> Void
Expand Down Expand Up @@ -101,7 +96,6 @@ open class Dyson {
// Responser handle response.
self.response(
response,
responser: responser ?? self.responser,
task: task,
spec: spec,
dyson: self,
Expand Down Expand Up @@ -263,15 +257,14 @@ open class Dyson {

private func response<S: Spec>(
_ response: Result<(Data, URLResponse), any Error>,
responser: (any Responser)?,
task: ContainerSessionTask,
spec: S,
dyson: Dyson,
interceptors: [any Interceptor],
completion: @escaping (Result<S.Result, any Error>) -> Void
) {
guard let responser else {
completion(.failure(DysonError.responserNotRegistered))
guard let responser = spec.responser else {
completion(.failure(DysonError.responserDoseNotExist))
return
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/Dyson/Model/DysonError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

public enum DysonError: Error {
case responserNotRegistered
case responserDoseNotExist
case invalidURL
case failedToParse(Error?)
case failedToLoadData
Expand Down
17 changes: 12 additions & 5 deletions Sources/Dyson/Request/QueryRequest.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// QueryRequest.swift
//
//
//
// Created by jsilver on 2022/01/16.
//
Expand All @@ -10,10 +10,12 @@ import Foundation
public struct QueryRequest: Request {
// MARK: - Property
private let parameter: [String: String]
private let isEncoded: Bool

// MARK: - Initializer
public init(_ parameter: [String: String]) {
public init(_ parameter: [String: String], isEncoded: Bool = false) {
self.parameter = parameter
self.isEncoded = isEncoded
}

// MARK: - Public
Expand All @@ -26,7 +28,11 @@ public struct QueryRequest: Request {
throw DysonError.invalidURL
}

components.queryItems = parameter.map { .init(name: $0, value: $1) }
if isEncoded {
components.percentEncodedQueryItems = parameter.map { .init(name: $0, value: $1) }
} else {
components.queryItems = parameter.map { .init(name: $0, value: $1) }
}

guard let url = components.url else {
throw DysonError.invalidURL
Expand All @@ -40,8 +46,9 @@ public struct QueryRequest: Request {

public extension Request {
static func query(
_ parameter: [String: String]
_ parameter: [String: String],
isEncoded: Bool = false
) -> Self where Self == QueryRequest {
QueryRequest(parameter)
QueryRequest(parameter, isEncoded: isEncoded)
}
}
2 changes: 2 additions & 0 deletions Sources/Dyson/Spec/Spec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public protocol Spec {
var headers: HTTPHeaders { get }

var request: any Request { get }
var responser: (any Responser)? { get }

var result: Mapper<Result> { get }
var error: Mapper<Error> { get }
}
Expand Down
88 changes: 19 additions & 69 deletions Tests/DysonTests/DysonTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ final class DysonTests: XCTestCase {
dataTask: { request, completion in
completion(.success((data, .http(request, status: 200))))
}
),
responser: .default
)
)
let spec = MockSpec<Empty, Empty, Empty>()

Expand All @@ -41,20 +40,24 @@ final class DysonTests: XCTestCase {
func test_that_dyson_response_data_using_spec() async throws {
// Given
let person = Person(name: "dyson")
let json = """
{
"name": "dyson"
}
"""

let jsonEncoder = JSONEncoder()
let data = try jsonEncoder.encode(person)
let data = json.data(using: .utf8) ?? Data()

let sut = Dyson(
provider: .mock(
dataTask: { request, completion in
completion(.success((data, .http(request, status: 200))))
}
),
responser: .default
)
)
let spec = MockSpec<Empty, Person, Empty>(
result: .codable
responser: .default,
result: .codable()
)

// When
Expand All @@ -64,41 +67,15 @@ final class DysonTests: XCTestCase {
XCTAssertEqual(response, person)
}

func test_that_dyson_throw_error_when_response_data_without_responser() async throws {
// Give
let person = Person(name: "dyson")

let jsonEncoder = JSONEncoder()
let data = try jsonEncoder.encode(person)

let sut = Dyson(
provider: .mock(
dataTask: { request, completion in
completion(.success((data, .http(request, status: 200))))
}
)
)
let spec = MockSpec<Empty, Person, Empty>(
result: .codable
)

// When
do {
try await sut.data(spec)
XCTFail()
} catch {

func test_that_dyson_throw_error_when_spec_does_not_specifiy_responser() async throws {
// Given
let json = """
{
"name": "dyson"
}
"""

// Then
}

func test_that_dyson_response_data_when_request_with_responser_even_if_responser_not_registered() async throws {
// Give
let person = Person(name: "dyson")

let jsonEncoder = JSONEncoder()
let data = try jsonEncoder.encode(person)
let data = json.data(using: .utf8) ?? Data()

let sut = Dyson(
provider: .mock(
Expand All @@ -108,42 +85,15 @@ final class DysonTests: XCTestCase {
)
)
let spec = MockSpec<Empty, Person, Empty>(
result: .codable
)

// When
try await sut.data(spec, responser: .default)

// Then
}

func test_that_dyson_throw_error_when_response_data_even_if_responser_registered() async throws {
// Give
let person = Person(name: "dyson")

let jsonEncoder = JSONEncoder()
let data = try jsonEncoder.encode(person)

let sut = Dyson(
provider: .mock(
dataTask: { request, completion in
completion(.success((data, .http(request, status: 200))))
}
),
responser: .default
)
let spec = MockSpec<Empty, Person, Empty>(
result: .codable
result: .codable()
)

// When
do {
try await sut.data(spec, responser: .error(TestError(message: "error")))
try await sut.data(spec)
XCTFail()
} catch {

}

// Then
}
}
2 changes: 1 addition & 1 deletion Tests/DysonTests/EncoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ final class EncoderTests: XCTestCase {
func test_that_codable_encoder_succeed_in_encoding_with_encodable_data() async throws {
// Given
let person = Person(name: "dyson")
let sut: Encoder<Person> = .codable
let sut: Encoder<Person> = .codable()

// When
let jsonEncoder = JSONEncoder()
Expand Down
3 changes: 1 addition & 2 deletions Tests/DysonTests/Helper/Extension/XCTestCase+Helper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ extension XCTestCase {
dataTask: dataTask,
uploadTask: uploadTask,
downloadTask: downloadTask
),
responser: responser
)
)

return try await dyson.response(spec)
Expand Down
3 changes: 3 additions & 0 deletions Tests/DysonTests/Helper/MockTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct MockSpec<Parameter, Result, Error: Swift.Error>: Spec {

let headers: HTTPHeaders
let request: Request
let responser: (any Responser)?

let result: Mapper<Result>
let error: Mapper<Error>
Expand All @@ -32,6 +33,7 @@ struct MockSpec<Parameter, Result, Error: Swift.Error>: Spec {
transaction: Transaction = .data,
headers: HTTPHeaders = [:],
request: Request = .none,
responser: (any Responser)? = nil,
result: Mapper<Result> = .none,
error: Mapper<Error> = .none,
parameter: Parameter = Empty()
Expand All @@ -42,6 +44,7 @@ struct MockSpec<Parameter, Result, Error: Swift.Error>: Spec {
self.transaction = transaction
self.headers = headers
self.request = request
self.responser = responser
self.result = result
self.error = error
self.parameter = parameter
Expand Down
2 changes: 0 additions & 2 deletions Tests/DysonTests/InterceptorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ final class InterceptorTests: XCTestCase {
completion(.success((Data(), .http(request, status: 200))))
}
),
responser: .default,
interceptors: [sut]
)
let spec = MockSpec<Empty, Empty, Empty>()
Expand All @@ -56,7 +55,6 @@ final class InterceptorTests: XCTestCase {
completion(.success((Data(), .http(request, status: 200))))
}
),
responser: .default,
interceptors: [sut]
)
let spec = MockSpec<Empty, Empty, Empty>()
Expand Down
Loading

0 comments on commit 8d4d162

Please sign in to comment.