Remove Alamofire
This commit is contained in:
parent
608963567f
commit
f95ecb0216
|
@ -16,13 +16,11 @@ let package = Package(
|
|||
name: "Stubbing",
|
||||
targets: ["Stubbing"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.2.2"))
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "HTTP",
|
||||
dependencies: ["Alamofire"]),
|
||||
dependencies: []),
|
||||
.target(
|
||||
name: "Stubbing",
|
||||
dependencies: ["HTTP"]),
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Alamofire
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
public typealias Session = Alamofire.Session
|
||||
public enum HTTPError: Error {
|
||||
case invalidStatusCode(HTTPURLResponse)
|
||||
}
|
||||
|
||||
open class HTTPClient {
|
||||
private let session: Session
|
||||
private let decoder: DataDecoder
|
||||
private let session: URLSession
|
||||
private let decoder: JSONDecoder
|
||||
|
||||
public init(session: Session, decoder: DataDecoder) {
|
||||
public init(session: URLSession, decoder: JSONDecoder) {
|
||||
self.session = session
|
||||
self.decoder = decoder
|
||||
}
|
||||
|
||||
open func request<T: DecodableTarget>(_ target: T) -> AnyPublisher<T.ResultType, Error> {
|
||||
requestPublisher(target).value().mapError { $0.underlyingOrTypeErased }.eraseToAnyPublisher()
|
||||
dataTaskPublisher(target)
|
||||
.map(\.data)
|
||||
.decode(type: T.ResultType.self, decoder: decoder)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public func request<T: DecodableTarget, E: Error & Decodable>(
|
||||
|
@ -24,40 +28,39 @@ open class HTTPClient {
|
|||
decodeErrorsAs errorType: E.Type) -> AnyPublisher<T.ResultType, Error> {
|
||||
let decoder = self.decoder
|
||||
|
||||
return requestPublisher(target)
|
||||
.tryMap { response -> T.ResultType in
|
||||
switch response.result {
|
||||
case let .success(decoded): return decoded
|
||||
case let .failure(error):
|
||||
if
|
||||
let data = response.data,
|
||||
let decodedError = try? decoder.decode(E.self, from: data) {
|
||||
throw decodedError
|
||||
}
|
||||
return dataTaskPublisher(target)
|
||||
.tryMap { result -> Data in
|
||||
if
|
||||
let response = result.response as? HTTPURLResponse,
|
||||
!Self.validStatusCodes.contains(response.statusCode) {
|
||||
|
||||
throw error.underlyingOrTypeErased
|
||||
if let decodedError = try? decoder.decode(E.self, from: result.data) {
|
||||
throw decodedError
|
||||
} else {
|
||||
throw HTTPError.invalidStatusCode(response)
|
||||
}
|
||||
}
|
||||
|
||||
return result.data
|
||||
}
|
||||
.decode(type: T.ResultType.self, decoder: decoder)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
private extension HTTPClient {
|
||||
func requestPublisher<T: DecodableTarget>(_ target: T) -> DataResponsePublisher<T.ResultType> {
|
||||
if let protocolClasses = session.sessionConfiguration.protocolClasses {
|
||||
static let validStatusCodes = 200..<300
|
||||
func dataTaskPublisher<T: DecodableTarget>(_ target: T) -> URLSession.DataTaskPublisher {
|
||||
if let protocolClasses = session.configuration.protocolClasses {
|
||||
for protocolClass in protocolClasses {
|
||||
(protocolClass as? TargetProcessing.Type)?.process(target: target)
|
||||
}
|
||||
}
|
||||
|
||||
return session.request(target)
|
||||
.validate()
|
||||
.publishDecodable(type: T.ResultType.self, queue: session.rootQueue, decoder: decoder)
|
||||
}
|
||||
}
|
||||
return session.dataTaskPublisher(for: target.urlRequest())
|
||||
|
||||
private extension AFError {
|
||||
var underlyingOrTypeErased: Error {
|
||||
underlyingError ?? self
|
||||
// return session.request(target.urlRequest())
|
||||
// .validate()
|
||||
// .publishDecodable(type: T.ResultType.self, queue: session.rootQueue, decoder: decoder)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum HTTPMethod: String {
|
||||
case delete = "DELETE"
|
||||
case get = "GET"
|
||||
case post = "POST"
|
||||
case put = "PUT"
|
||||
}
|
|
@ -1,32 +1,43 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Alamofire
|
||||
import Foundation
|
||||
|
||||
public typealias HTTPMethod = Alamofire.HTTPMethod
|
||||
public typealias HTTPHeaders = Alamofire.HTTPHeaders
|
||||
public typealias ParameterEncoding = Alamofire.ParameterEncoding
|
||||
public typealias URLEncoding = Alamofire.URLEncoding
|
||||
public typealias JSONEncoding = Alamofire.JSONEncoding
|
||||
|
||||
public protocol Target: URLRequestConvertible {
|
||||
public protocol Target {
|
||||
var baseURL: URL { get }
|
||||
var pathComponents: [String] { get }
|
||||
var method: HTTPMethod { get }
|
||||
var encoding: ParameterEncoding { get }
|
||||
var parameters: [String: Any]? { get }
|
||||
var headers: HTTPHeaders? { get }
|
||||
var queryParameters: [String: String]? { get }
|
||||
var jsonBody: [String: Any]? { get }
|
||||
var headers: [String: String]? { get }
|
||||
}
|
||||
|
||||
public extension Target {
|
||||
func asURLRequest() throws -> URLRequest {
|
||||
func urlRequest() -> URLRequest {
|
||||
var url = baseURL
|
||||
|
||||
for pathComponent in pathComponents {
|
||||
url.appendPathComponent(pathComponent)
|
||||
}
|
||||
|
||||
return try encoding.encode(try URLRequest(url: url, method: method, headers: headers), with: parameters)
|
||||
if var components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
||||
let queryItems = queryParameters?.map(URLQueryItem.init(name:value:)) {
|
||||
components.queryItems = queryItems
|
||||
|
||||
if let queryComponentURL = components.url {
|
||||
url = queryComponentURL
|
||||
}
|
||||
}
|
||||
|
||||
var urlRequest = URLRequest(url: url)
|
||||
|
||||
urlRequest.httpMethod = method.rawValue
|
||||
urlRequest.allHTTPHeaderFields = headers
|
||||
|
||||
if let jsonBody = jsonBody {
|
||||
urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: jsonBody)
|
||||
}
|
||||
|
||||
return urlRequest
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ private extension StubbingURLProtocol {
|
|||
|
||||
extension StubbingURLProtocol: TargetProcessing {
|
||||
public static func process(target: Target) {
|
||||
if let url = try? target.asURLRequest().url {
|
||||
if let url = target.urlRequest().url {
|
||||
targetsForURLs[url] = target
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ public protocol Endpoint {
|
|||
var context: [String] { get }
|
||||
var pathComponentsInContext: [String] { get }
|
||||
var method: HTTPMethod { get }
|
||||
var encoding: ParameterEncoding { get }
|
||||
var parameters: [String: Any]? { get }
|
||||
var headers: HTTPHeaders? { get }
|
||||
var queryParameters: [String: String]? { get }
|
||||
var jsonBody: [String: Any]? { get }
|
||||
var headers: [String: String]? { get }
|
||||
}
|
||||
|
||||
public extension Endpoint {
|
||||
|
@ -29,14 +29,9 @@ public extension Endpoint {
|
|||
context + pathComponentsInContext
|
||||
}
|
||||
|
||||
var encoding: ParameterEncoding {
|
||||
switch method {
|
||||
case .get: return URLEncoding.default
|
||||
default: return JSONEncoding.default
|
||||
}
|
||||
}
|
||||
var queryParameters: [String: String]? { nil }
|
||||
|
||||
var parameters: [String: Any]? { nil }
|
||||
var jsonBody: [String: Any]? { nil }
|
||||
|
||||
var headers: HTTPHeaders? { nil }
|
||||
var headers: [String: String]? { nil }
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ extension AccessTokenEndpoint: Endpoint {
|
|||
}
|
||||
}
|
||||
|
||||
public var parameters: [String: Any]? {
|
||||
public var jsonBody: [String: Any]? {
|
||||
switch self {
|
||||
case let .oauthToken(clientID, clientSecret, grantType, scopes, code, redirectURI):
|
||||
var params = [
|
||||
|
|
|
@ -23,7 +23,7 @@ extension AppAuthorizationEndpoint: Endpoint {
|
|||
}
|
||||
}
|
||||
|
||||
public var parameters: [String: Any]? {
|
||||
public var jsonBody: [String: Any]? {
|
||||
switch self {
|
||||
case let .apps(clientName, redirectURI, scopes, website):
|
||||
var params = [
|
||||
|
|
|
@ -42,7 +42,7 @@ extension DeletionEndpoint: Endpoint {
|
|||
}
|
||||
}
|
||||
|
||||
public var parameters: [String: Any]? {
|
||||
public var jsonBody: [String: Any]? {
|
||||
switch self {
|
||||
case let .oauthRevoke(token, clientID, clientSecret):
|
||||
return ["token": token, "client_id": clientID, "client_secret": clientSecret]
|
||||
|
|
|
@ -36,7 +36,7 @@ extension FilterEndpoint: Endpoint {
|
|||
}
|
||||
}
|
||||
|
||||
public var parameters: [String: Any]? {
|
||||
public var jsonBody: [String: Any]? {
|
||||
switch self {
|
||||
case let .create(phrase, context, irreversible, wholeWord, expiresIn):
|
||||
return params(phrase: phrase,
|
||||
|
|
|
@ -22,7 +22,7 @@ extension ListEndpoint: Endpoint {
|
|||
}
|
||||
}
|
||||
|
||||
public var parameters: [String: Any]? {
|
||||
public var jsonBody: [String: Any]? {
|
||||
switch self {
|
||||
case let .create(title):
|
||||
return ["title": title]
|
||||
|
|
|
@ -22,6 +22,7 @@ public struct Paged<T: Endpoint> {
|
|||
|
||||
extension Paged: Endpoint {
|
||||
public typealias ResultType = T.ResultType
|
||||
// public typealias ResultType = PagedResult<T.ResultType>
|
||||
|
||||
public var APIVersion: String { endpoint.APIVersion }
|
||||
|
||||
|
@ -31,18 +32,25 @@ extension Paged: Endpoint {
|
|||
|
||||
public var method: HTTPMethod { endpoint.method }
|
||||
|
||||
public var encoding: ParameterEncoding { endpoint.encoding }
|
||||
public var queryParameters: [String: String]? {
|
||||
var queryParameters = endpoint.queryParameters ?? [String: String]()
|
||||
|
||||
public var parameters: [String: Any]? {
|
||||
var parameters = endpoint.parameters ?? [String: Any]()
|
||||
queryParameters["max_id"] = maxID
|
||||
queryParameters["min_id"] = minID
|
||||
queryParameters["since_id"] = sinceID
|
||||
|
||||
parameters["max_id"] = maxID
|
||||
parameters["min_id"] = minID
|
||||
parameters["since_id"] = sinceID
|
||||
parameters["limit"] = limit
|
||||
if let limit = limit {
|
||||
queryParameters["limit"] = String(limit)
|
||||
}
|
||||
|
||||
return parameters
|
||||
return queryParameters
|
||||
}
|
||||
|
||||
public var headers: HTTPHeaders? { endpoint.headers }
|
||||
public var headers: [String: String]? { endpoint.headers }
|
||||
}
|
||||
|
||||
//public struct PagedResult<T: Decodable>: Decodable {
|
||||
// public let result: T
|
||||
// public let maxID: String?
|
||||
// public let sinceID: String?
|
||||
//}
|
||||
|
|
|
@ -33,7 +33,7 @@ extension PushSubscriptionEndpoint: Endpoint {
|
|||
}
|
||||
}
|
||||
|
||||
public var parameters: [String: Any]? {
|
||||
public var jsonBody: [String: Any]? {
|
||||
switch self {
|
||||
case let .create(endpoint, publicKey, auth, alerts):
|
||||
return ["subscription":
|
||||
|
|
|
@ -39,12 +39,14 @@ extension StatusesEndpoint: Endpoint {
|
|||
}
|
||||
}
|
||||
|
||||
public var parameters: [String: Any]? {
|
||||
public var queryParameters: [String: String]? {
|
||||
switch self {
|
||||
case let .timelinesPublic(local):
|
||||
return ["local": local]
|
||||
return ["local": String(local)]
|
||||
case let .accountsStatuses(_, excludeReplies, onlyMedia, pinned):
|
||||
return ["exclude_replies": excludeReplies, "only_media": onlyMedia, "pinned": pinned]
|
||||
return ["exclude_replies": String(excludeReplies),
|
||||
"only_media": String(onlyMedia),
|
||||
"pinned": String(pinned)]
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ public final class MastodonAPIClient: HTTPClient {
|
|||
public var instanceURL: URL
|
||||
public var accessToken: String?
|
||||
|
||||
public required init(session: Session, instanceURL: URL) {
|
||||
public required init(session: URLSession, instanceURL: URL) {
|
||||
self.instanceURL = instanceURL
|
||||
super.init(session: session, decoder: MastodonDecoder())
|
||||
}
|
||||
|
|
|
@ -22,19 +22,19 @@ extension MastodonAPITarget: DecodableTarget {
|
|||
|
||||
public var method: HTTPMethod { endpoint.method }
|
||||
|
||||
public var encoding: ParameterEncoding { endpoint.encoding }
|
||||
public var queryParameters: [String: String]? { endpoint.queryParameters }
|
||||
|
||||
public var parameters: [String: Any]? { endpoint.parameters }
|
||||
public var jsonBody: [String: Any]? { endpoint.jsonBody }
|
||||
|
||||
public var headers: HTTPHeaders? {
|
||||
public var headers: [String: String]? {
|
||||
var headers = endpoint.headers
|
||||
|
||||
if let accessToken = accessToken {
|
||||
if headers == nil {
|
||||
headers = HTTPHeaders()
|
||||
headers = [String: String]()
|
||||
}
|
||||
|
||||
headers?.add(.authorization(bearerToken: accessToken))
|
||||
headers?["Authorization"] = "Bearer ".appending(accessToken)
|
||||
}
|
||||
|
||||
return headers
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
{
|
||||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "Alamofire",
|
||||
"repositoryURL": "https://github.com/Alamofire/Alamofire.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "becd9a729a37bdbef5bc39dc3c702b99f9e3d046",
|
||||
"version": "5.2.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"package": "CombineExpectations",
|
||||
"repositoryURL": "https://github.com/groue/CombineExpectations.git",
|
||||
|
|
|
@ -8,7 +8,7 @@ import Mastodon
|
|||
import UserNotifications
|
||||
|
||||
public struct AppEnvironment {
|
||||
let session: Session
|
||||
let session: URLSession
|
||||
let webAuthSessionType: WebAuthSession.Type
|
||||
let keychain: Keychain.Type
|
||||
let userDefaults: UserDefaults
|
||||
|
@ -17,7 +17,7 @@ public struct AppEnvironment {
|
|||
let inMemoryContent: Bool
|
||||
let fixtureDatabase: IdentityDatabase?
|
||||
|
||||
public init(session: Session,
|
||||
public init(session: URLSession,
|
||||
webAuthSessionType: WebAuthSession.Type,
|
||||
keychain: Keychain.Type,
|
||||
userDefaults: UserDefaults,
|
||||
|
@ -39,7 +39,7 @@ public struct AppEnvironment {
|
|||
public extension AppEnvironment {
|
||||
static func live(userNotificationCenter: UNUserNotificationCenter) -> Self {
|
||||
Self(
|
||||
session: Session(configuration: .default),
|
||||
session: URLSession.shared,
|
||||
webAuthSessionType: LiveWebAuthSession.self,
|
||||
keychain: LiveKeychain.self,
|
||||
userDefaults: .standard,
|
||||
|
|
|
@ -73,9 +73,9 @@ private struct UpdatedFilterTarget: DecodableTarget {
|
|||
let baseURL = URL(string: "https://filter.metabolist.com")!
|
||||
let pathComponents = ["filter"]
|
||||
let method = HTTPMethod.get
|
||||
let encoding: ParameterEncoding = JSONEncoding.default
|
||||
let parameters: [String: Any]? = nil
|
||||
let headers: HTTPHeaders? = nil
|
||||
let queryParameters: [String: String]? = nil
|
||||
let jsonBody: [String: Any]? = nil
|
||||
let headers: [String: String]? = nil
|
||||
}
|
||||
|
||||
private extension InstanceURLService {
|
||||
|
|
|
@ -9,7 +9,7 @@ import ServiceLayer
|
|||
import Stubbing
|
||||
|
||||
public extension AppEnvironment {
|
||||
static func mock(session: Session = Session(configuration: .stubbing),
|
||||
static func mock(session: URLSession = URLSession(configuration: .stubbing),
|
||||
webAuthSessionType: WebAuthSession.Type = SuccessfulMockWebAuthSession.self,
|
||||
keychain: Keychain.Type = MockKeychain.self,
|
||||
userDefaults: UserDefaults = MockUserDefaults(),
|
||||
|
@ -18,7 +18,7 @@ public extension AppEnvironment {
|
|||
inMemoryContent: Bool = true,
|
||||
fixtureDatabase: IdentityDatabase? = nil) -> Self {
|
||||
AppEnvironment(
|
||||
session: Session(configuration: .stubbing),
|
||||
session: session,
|
||||
webAuthSessionType: webAuthSessionType,
|
||||
keychain: keychain,
|
||||
userDefaults: userDefaults,
|
||||
|
|
Loading…
Reference in New Issue