Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(client): Support more detailed customization options for client #14

Merged
merged 5 commits into from
May 6, 2024
Merged
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 .swiftlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ type_name:
min_length: 2
max_length: 50

type_body_length:
warning: 400
error: 600

identifier_name:
min_length: 2
max_length: 50
Expand Down
110 changes: 101 additions & 9 deletions Sources/HttpX/Client/BaseClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class BaseClient {
/// Every Requests' URL will be merged with this URL before sending.
/// - defaultEncoding: The default string encoding for the request. Defaults to `.utf8`.
/// - configuration: The configuration for the `URLSession`. Defaults to `.default`.
public init(
public required init(
auth: AuthType? = nil,
params: QueryParamsType? = nil,
headers: HeadersType? = nil,
Expand Down Expand Up @@ -104,7 +104,7 @@ public class BaseClient {
/// Returns the cookies sent with every request.
public var cookies: [HTTPCookie] {
let storage = session.configuration.httpCookieStorage
return storage!.cookies!
return storage?.cookies ?? []
}

/// Returns the query parameters appended to every request.
Expand All @@ -126,40 +126,132 @@ public class BaseClient {
session.configuration.httpCookieStorage
}

/// Set the timeout interval for the request.
/// **Important**: This method will invalidate the current `URLSession` instance and create a new one.
/// It's necessary to call this method when no request is in progress.
@discardableResult
public func timeout(_ timeout: Timeout) -> Self {
session.invalidateAndCancel()
configurationPrivate.timeoutIntervalForRequest = timeout.request
configurationPrivate.timeoutIntervalForResource = timeout.resource
session = URLSession(configuration: configurationPrivate, delegate: HttpXDelegate(), delegateQueue: nil)
timeoutPrivate = timeout
return self
}

/// Set the timeout interval for the request.
/// **Important**: This method will invalidate the current `URLSession` instance and create a new one.
/// It's necessary to call this method when no request is in progress.
@discardableResult
public func timeout(
connect: TimeInterval? = nil,
request: TimeInterval? = nil,
resource: TimeInterval? = nil
) -> Self {
let timeout = Timeout(
connect: connect ?? timeout.connect,
request: request ?? timeout.request,
resource: resource ?? timeout.resource
)
return self.timeout(timeout)
}

/// Sets the event hooks allowing for observing and mutating request and response.
public func setEventHooks(_ eventHooks: EventHooks) {
@discardableResult
public func eventHooks(_ eventHooks: EventHooks) -> Self {
eventHooksPrivate = eventHooks
return self
}

/// Sets the authentication strategy used for network requests.
public func setAuth(_ auth: AuthType) {
@discardableResult
public func auth(_ auth: AuthType) -> Self {
authPrivate = auth.buildAuth()
return self
}

/// Sets the base URL for the network requests.
public func setBaseURL(_ baseURL: URLType) {
@discardableResult
public func baseURL(_ baseURL: URLType) -> Self {
baseURLPrivate = baseURL.buildURL()
return self
}

/// Sets the HTTP headers to be sent with every request.
public func setHeaders(_ headers: HeadersType) {
@discardableResult
public func headers(_ headers: HeadersType) -> Self {
headersPrivate = headers.buildHeaders()
return self
}

/// Sets the cookies to be sent with every request.
@discardableResult
public func cookies(_ cookies: CookiesType) -> Self {
let storage = session.configuration.httpCookieStorage
for cookie in cookies.buildCookies() {
storage?.setCookie(cookie)
}
return self
}

/// Sets the query parameters to be appended to every request.
public func setParams(_ params: QueryParamsType) {
@discardableResult
public func params(_ params: QueryParamsType) -> Self {
paramsPrivate = params.buildQueryItems()
return self
}

/// Sets the redirect behavior for the client.
public func setRedirects(follow: Bool = false, max: Int = kDefaultMaxRedirects) {
@discardableResult
public func followRedirects(_ follow: Bool) -> Self {
followRedirectsPrivate = follow
return self
}

/// Sets the maximum number of redirects to follow.
@discardableResult
public func maxRedirects(_ max: Int) -> Self {
maxRedirectsPrivate = max
return self
}

/// Sets the default string encoding for the request.
public func setDefaultEncoding(_ encoding: String.Encoding) {
@discardableResult
public func defaultEncoding(_ encoding: String.Encoding) -> Self {
defaultEncodingPrivate = encoding
return self
}

/// Sets the configuration for the client.
/// **Important**: This method will invalidate the current `URLSession` instance and create a new one.
/// It's necessary to call this method when no request is in progress.
@discardableResult
public func configurations(_ newConfig: URLSessionConfiguration) -> Self {
newConfig.httpCookieStorage = session.configuration.httpCookieStorage
newConfig.timeoutIntervalForRequest = timeoutPrivate.request
newConfig.timeoutIntervalForResource = timeoutPrivate.resource
configurationPrivate = newConfig
session.invalidateAndCancel()
session = URLSession(configuration: configurationPrivate, delegate: HttpXDelegate(), delegateQueue: nil)
return self
}

/// Make a copy of the client.
public func copy() -> Self {
Self(
auth: .class(authPrivate),
params: .class(paramsPrivate),
headers: .array(headersPrivate),
cookies: .cookieArray(cookies),
cookieIdentifier: cookieIdentifier,
timeout: timeoutPrivate,
followRedirects: followRedirectsPrivate,
maxRedirects: maxRedirectsPrivate,
eventHooks: eventHooksPrivate,
baseURL: baseURLPrivate == nil ? nil : .class(baseURLPrivate!),
defaultEncoding: defaultEncodingPrivate,
configuration: configurationPrivate
)
}

/// Builds a URLRequest with the specified parameters.
Expand Down
1 change: 0 additions & 1 deletion Sources/HttpX/Response.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import os.log

// MARK: - Response

// swiftlint:disable:next type_body_length
public class Response: CustomStringConvertible, IteratorProtocol, Sequence, AsyncIteratorProtocol, AsyncSequence {
// MARK: Lifecycle

Expand Down
2 changes: 1 addition & 1 deletion Tests/HttpXTests/Client/AsyncClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ final class AsyncClientTests: XCTestCase {
{ $0.defaultEncoding = .iso2022JP },
]
)
client.setEventHooks(eventHooks)
client.eventHooks(eventHooks)

let response = try await client.request(
method: .post,
Expand Down
23 changes: 16 additions & 7 deletions Tests/HttpXTests/Client/BaseClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,26 +38,35 @@ final class BaseClientTests: XCTestCase {
func testSetAndGet() {
let emptyClient = BaseClient()

emptyClient.setEventHooks(EventHooks())
emptyClient.timeout(connect: 1, request: 2, resource: 3)
XCTAssertEqual(emptyClient.timeout.connect, 1)
XCTAssertEqual(emptyClient.timeout.request, 2)
XCTAssertEqual(emptyClient.timeout.resource, 3)

emptyClient.eventHooks(EventHooks())
XCTAssertTrue(emptyClient.eventHooks.request.isEmpty && emptyClient.eventHooks.response.isEmpty)

emptyClient.setAuth(AuthType.basic(("user", "pass")))
emptyClient.auth(AuthType.basic(("user", "pass")))
XCTAssertTrue(emptyClient.auth is BasicAuth)

emptyClient.setBaseURL(URLType.string("https://example.com"))
emptyClient.baseURL(URLType.string("https://example.com"))
XCTAssertEqual(emptyClient.baseURL, URL(string: "https://example.com"))

emptyClient.setHeaders(HeadersType.array([("key", "value")]))
emptyClient.cookies(CookiesType.cookieArray([HTTPCookie(properties: [.domain: "example.com", .path: "/", .name: "TestCookie", .value: "TestValue"])!]))
XCTAssertEqual(emptyClient.cookies.count, 1)

emptyClient.headers(HeadersType.array([("key", "value")]))
XCTAssertTrue(emptyClient.headers.contains { $0.0 == "key" && $0.1 == "value" })

emptyClient.setParams(QueryParamsType.class([URLQueryItem(name: "key", value: "value")]))
emptyClient.params(QueryParamsType.class([URLQueryItem(name: "key", value: "value")]))
XCTAssertTrue(emptyClient.params.contains { $0.name == "key" && $0.value == "value" })

emptyClient.setRedirects(follow: false, max: 10)
emptyClient.followRedirects(false)
emptyClient.maxRedirects(10)
XCTAssertFalse(emptyClient.followRedirects)
XCTAssertEqual(emptyClient.maxRedirects, 10)

emptyClient.setDefaultEncoding(.utf8)
emptyClient.defaultEncoding(.utf8)
XCTAssertEqual(emptyClient.defaultEncoding, .utf8)
}

Expand Down
2 changes: 1 addition & 1 deletion Tests/HttpXTests/Client/SyncClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ final class SyncClientTests: XCTestCase {
{ $0.defaultEncoding = .iso2022JP },
]
)
client.setEventHooks(eventHooks)
client.eventHooks(eventHooks)

let response = try client.request(
method: .get,
Expand Down