From f95ecb0216be9d7907d4432afffa626994d21eaa Mon Sep 17 00:00:00 2001 From: Justin Mazzocchi <2831158+jzzocc@users.noreply.github.com> Date: Wed, 23 Sep 2020 00:04:37 -0700 Subject: [PATCH] Remove Alamofire --- HTTP/Package.swift | 6 +- HTTP/Sources/HTTP/HTTPClient.swift | 57 ++++++++++--------- HTTP/Sources/HTTP/HTTPMethod.swift | 10 ++++ HTTP/Sources/HTTP/Target.swift | 37 +++++++----- .../Stubbing/StubbingURLProtocol.swift | 2 +- .../Sources/MastodonAPI/Endpoint.swift | 17 ++---- .../Endpoints/AccessTokenEndpoint.swift | 2 +- .../Endpoints/AppAuthorizationEndpoint.swift | 2 +- .../Endpoints/DeletionEndpoint.swift | 2 +- .../Endpoints/FilterEndpoint.swift | 2 +- .../MastodonAPI/Endpoints/ListEndpoint.swift | 2 +- .../Sources/MastodonAPI/Endpoints/Paged.swift | 26 ++++++--- .../Endpoints/PushSubscriptionEndpoint.swift | 2 +- .../Endpoints/StatusesEndpoint.swift | 8 ++- .../MastodonAPI/MastodonAPIClient.swift | 2 +- .../MastodonAPI/MastodonAPITarget.swift | 10 ++-- .../xcshareddata/swiftpm/Package.resolved | 9 --- .../Entities/AppEnvironment.swift | 6 +- .../Services/InstanceURLService.swift | 6 +- .../MockAppEnvironment.swift | 4 +- 20 files changed, 115 insertions(+), 97 deletions(-) create mode 100644 HTTP/Sources/HTTP/HTTPMethod.swift diff --git a/HTTP/Package.swift b/HTTP/Package.swift index 1e0cdcb..97af9dc 100644 --- a/HTTP/Package.swift +++ b/HTTP/Package.swift @@ -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"]), diff --git a/HTTP/Sources/HTTP/HTTPClient.swift b/HTTP/Sources/HTTP/HTTPClient.swift index 6f3fec9..e108273 100644 --- a/HTTP/Sources/HTTP/HTTPClient.swift +++ b/HTTP/Sources/HTTP/HTTPClient.swift @@ -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(_ target: T) -> AnyPublisher { - requestPublisher(target).value().mapError { $0.underlyingOrTypeErased }.eraseToAnyPublisher() + dataTaskPublisher(target) + .map(\.data) + .decode(type: T.ResultType.self, decoder: decoder) + .eraseToAnyPublisher() } public func request( @@ -24,40 +28,39 @@ open class HTTPClient { decodeErrorsAs errorType: E.Type) -> AnyPublisher { 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(_ target: T) -> DataResponsePublisher { - if let protocolClasses = session.sessionConfiguration.protocolClasses { + static let validStatusCodes = 200..<300 + func dataTaskPublisher(_ 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) } } diff --git a/HTTP/Sources/HTTP/HTTPMethod.swift b/HTTP/Sources/HTTP/HTTPMethod.swift new file mode 100644 index 0000000..7751c6a --- /dev/null +++ b/HTTP/Sources/HTTP/HTTPMethod.swift @@ -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" +} diff --git a/HTTP/Sources/HTTP/Target.swift b/HTTP/Sources/HTTP/Target.swift index 47dd6de..e88ccd2 100644 --- a/HTTP/Sources/HTTP/Target.swift +++ b/HTTP/Sources/HTTP/Target.swift @@ -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 } } diff --git a/HTTP/Sources/Stubbing/StubbingURLProtocol.swift b/HTTP/Sources/Stubbing/StubbingURLProtocol.swift index 1488933..8e62d7a 100644 --- a/HTTP/Sources/Stubbing/StubbingURLProtocol.swift +++ b/HTTP/Sources/Stubbing/StubbingURLProtocol.swift @@ -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 } } diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoint.swift index 44052b7..ba0b5b5 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoint.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoint.swift @@ -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 } } diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/AccessTokenEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/AccessTokenEndpoint.swift index 3f259d8..c812384 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoints/AccessTokenEndpoint.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/AccessTokenEndpoint.swift @@ -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 = [ diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/AppAuthorizationEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/AppAuthorizationEndpoint.swift index ed0bc8b..c92d966 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoints/AppAuthorizationEndpoint.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/AppAuthorizationEndpoint.swift @@ -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 = [ diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/DeletionEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/DeletionEndpoint.swift index 46eec07..c23075c 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoints/DeletionEndpoint.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/DeletionEndpoint.swift @@ -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] diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/FilterEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/FilterEndpoint.swift index 351f7b2..7df4e29 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoints/FilterEndpoint.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/FilterEndpoint.swift @@ -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, diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/ListEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/ListEndpoint.swift index c918339..522cc87 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoints/ListEndpoint.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/ListEndpoint.swift @@ -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] diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/Paged.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/Paged.swift index 1fde781..af36661 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoints/Paged.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/Paged.swift @@ -22,6 +22,7 @@ public struct Paged { extension Paged: Endpoint { public typealias ResultType = T.ResultType + // public typealias ResultType = PagedResult 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: Decodable { +// public let result: T +// public let maxID: String? +// public let sinceID: String? +//} diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/PushSubscriptionEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/PushSubscriptionEndpoint.swift index a93194d..37376e1 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoints/PushSubscriptionEndpoint.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/PushSubscriptionEndpoint.swift @@ -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": diff --git a/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusesEndpoint.swift b/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusesEndpoint.swift index 3b140d9..136ca9f 100644 --- a/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusesEndpoint.swift +++ b/MastodonAPI/Sources/MastodonAPI/Endpoints/StatusesEndpoint.swift @@ -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 } diff --git a/MastodonAPI/Sources/MastodonAPI/MastodonAPIClient.swift b/MastodonAPI/Sources/MastodonAPI/MastodonAPIClient.swift index 9e64ecd..ed453d2 100644 --- a/MastodonAPI/Sources/MastodonAPI/MastodonAPIClient.swift +++ b/MastodonAPI/Sources/MastodonAPI/MastodonAPIClient.swift @@ -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()) } diff --git a/MastodonAPI/Sources/MastodonAPI/MastodonAPITarget.swift b/MastodonAPI/Sources/MastodonAPI/MastodonAPITarget.swift index 115d1fd..8c1f719 100644 --- a/MastodonAPI/Sources/MastodonAPI/MastodonAPITarget.swift +++ b/MastodonAPI/Sources/MastodonAPI/MastodonAPITarget.swift @@ -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 diff --git a/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1b6bf76..5421adb 100644 --- a/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Metatext.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -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", diff --git a/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift b/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift index a0fcdda..a170500 100644 --- a/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift +++ b/ServiceLayer/Sources/ServiceLayer/Entities/AppEnvironment.swift @@ -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, diff --git a/ServiceLayer/Sources/ServiceLayer/Services/InstanceURLService.swift b/ServiceLayer/Sources/ServiceLayer/Services/InstanceURLService.swift index 27b7cac..366a2c4 100644 --- a/ServiceLayer/Sources/ServiceLayer/Services/InstanceURLService.swift +++ b/ServiceLayer/Sources/ServiceLayer/Services/InstanceURLService.swift @@ -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 { diff --git a/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift b/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift index ac7c12b..7581086 100644 --- a/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift +++ b/ServiceLayer/Sources/ServiceLayerMocks/MockAppEnvironment.swift @@ -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,