Remove Alamofire

This commit is contained in:
Justin Mazzocchi 2020-09-23 00:04:37 -07:00
parent 608963567f
commit f95ecb0216
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
20 changed files with 115 additions and 97 deletions

View file

@ -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"]),

View file

@ -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)
}
}

View file

@ -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"
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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 }
}

View file

@ -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 = [

View file

@ -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 = [

View file

@ -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]

View file

@ -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,

View file

@ -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]

View file

@ -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?
//}

View file

@ -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":

View file

@ -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
}

View file

@ -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())
}

View file

@ -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

View file

@ -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",

View file

@ -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,

View file

@ -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 {

View file

@ -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,