mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-27 18:41:01 +00:00
Modularize HTTP code
This commit is contained in:
parent
43f781c182
commit
71c8861600
34 changed files with 101 additions and 28 deletions
|
@ -2,6 +2,7 @@
|
|||
|
||||
import Foundation
|
||||
import Combine
|
||||
import HTTP
|
||||
import Mastodon
|
||||
|
||||
// swiftlint:disable force_try
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Mastodon
|
||||
import HTTP
|
||||
|
||||
struct HTTPStubs {
|
||||
static func stub(
|
||||
request: URLRequest,
|
||||
target: HTTPTarget? = nil,
|
||||
target: Target? = nil,
|
||||
userInfo: [String: Any] = [:]) -> HTTPStub? {
|
||||
guard let url = request.url else {
|
||||
return nil
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import Foundation
|
||||
import Mastodon
|
||||
|
||||
extension Target: Stubbing {
|
||||
extension APITarget: Stubbing {
|
||||
func stub(url: URL) -> HTTPStub? {
|
||||
(endpoint as? Stubbing)?.stub(url: url)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Mastodon
|
||||
import HTTP
|
||||
|
||||
class StubbingURLProtocol: URLProtocol {
|
||||
private static var targetsForURLs = [URL: HTTPTarget]()
|
||||
|
||||
class func setTarget(_ target: HTTPTarget, forURL url: URL) {
|
||||
targetsForURLs[url] = target
|
||||
}
|
||||
private static var targetsForURLs = [URL: Target]()
|
||||
|
||||
override class func canInit(with task: URLSessionTask) -> Bool {
|
||||
true
|
||||
|
@ -41,3 +37,11 @@ class StubbingURLProtocol: URLProtocol {
|
|||
|
||||
override func stopLoading() {}
|
||||
}
|
||||
|
||||
extension StubbingURLProtocol: TargetProcessing {
|
||||
static func process(target: Target) {
|
||||
if let url = try? target.asURLRequest().url {
|
||||
targetsForURLs[url] = target
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
5
HTTP/.gitignore
vendored
Normal file
5
HTTP/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
27
HTTP/Package.swift
Normal file
27
HTTP/Package.swift
Normal file
|
@ -0,0 +1,27 @@
|
|||
// swift-tools-version:5.3
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "HTTP",
|
||||
platforms: [
|
||||
.iOS(.v14),
|
||||
.macOS(.v11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "HTTP",
|
||||
targets: ["HTTP"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.2.2"))
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "HTTP",
|
||||
dependencies: ["Alamofire"]),
|
||||
.testTarget(
|
||||
name: "HTTPTests",
|
||||
dependencies: ["HTTP"])
|
||||
]
|
||||
)
|
|
@ -6,7 +6,7 @@ import Alamofire
|
|||
|
||||
public typealias Session = Alamofire.Session
|
||||
|
||||
public class HTTPClient {
|
||||
open class Client {
|
||||
private let session: Session
|
||||
private let decoder: DataDecoder
|
||||
|
||||
|
@ -15,7 +15,7 @@ public class HTTPClient {
|
|||
self.decoder = decoder
|
||||
}
|
||||
|
||||
public func request<T: DecodableTarget>(_ target: T) -> AnyPublisher<T.ResultType, Error> {
|
||||
open func request<T: DecodableTarget>(_ target: T) -> AnyPublisher<T.ResultType, Error> {
|
||||
requestPublisher(target).value().mapError { $0 as Error }.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
@ -42,13 +42,13 @@ public class HTTPClient {
|
|||
}
|
||||
}
|
||||
|
||||
private extension HTTPClient {
|
||||
private extension Client {
|
||||
func requestPublisher<T: DecodableTarget>(_ target: T) -> DataResponsePublisher<T.ResultType> {
|
||||
// #if DEBUG
|
||||
// if let url = try? target.asURLRequest().url {
|
||||
// StubbingURLProtocol.setTarget(target, forURL: url)
|
||||
// }
|
||||
// #endif
|
||||
if let protocolClasses = session.sessionConfiguration.protocolClasses {
|
||||
for protocolClass in protocolClasses {
|
||||
(protocolClass as? TargetProcessing.Type)?.process(target: target)
|
||||
}
|
||||
}
|
||||
|
||||
return session.request(target)
|
||||
.validate()
|
|
@ -9,7 +9,7 @@ public typealias ParameterEncoding = Alamofire.ParameterEncoding
|
|||
public typealias URLEncoding = Alamofire.URLEncoding
|
||||
public typealias JSONEncoding = Alamofire.JSONEncoding
|
||||
|
||||
public protocol HTTPTarget: URLRequestConvertible {
|
||||
public protocol Target: URLRequestConvertible {
|
||||
var baseURL: URL { get }
|
||||
var pathComponents: [String] { get }
|
||||
var method: HTTPMethod { get }
|
||||
|
@ -18,7 +18,7 @@ public protocol HTTPTarget: URLRequestConvertible {
|
|||
var headers: HTTPHeaders? { get }
|
||||
}
|
||||
|
||||
public extension HTTPTarget {
|
||||
public extension Target {
|
||||
func asURLRequest() throws -> URLRequest {
|
||||
var url = baseURL
|
||||
|
||||
|
@ -30,6 +30,10 @@ public extension HTTPTarget {
|
|||
}
|
||||
}
|
||||
|
||||
public protocol DecodableTarget: HTTPTarget {
|
||||
public protocol DecodableTarget: Target {
|
||||
associatedtype ResultType: Decodable
|
||||
}
|
||||
|
||||
public protocol TargetProcessing {
|
||||
static func process(target: Target)
|
||||
}
|
10
HTTP/Tests/HTTPTests/HTTPTests.swift
Normal file
10
HTTP/Tests/HTTPTests/HTTPTests.swift
Normal file
|
@ -0,0 +1,10 @@
|
|||
import XCTest
|
||||
@testable import HTTP
|
||||
|
||||
final class HTTPTests: XCTestCase {
|
||||
func testExample() {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
}
|
||||
}
|
|
@ -14,12 +14,12 @@ let package = Package(
|
|||
targets: ["Mastodon"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/Alamofire/Alamofire.git", .upToNextMajor(from: "5.2.2"))
|
||||
.package(path: "HTTP")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Mastodon",
|
||||
dependencies: ["Alamofire"]),
|
||||
dependencies: ["HTTP"]),
|
||||
.testTarget(
|
||||
name: "MastodonTests",
|
||||
dependencies: ["Mastodon"])
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
import Foundation
|
||||
import Combine
|
||||
import HTTP
|
||||
|
||||
public final class APIClient: HTTPClient {
|
||||
public final class APIClient: Client {
|
||||
public var instanceURL: URL?
|
||||
public var accessToken: String?
|
||||
|
||||
|
@ -23,7 +24,7 @@ extension APIClient {
|
|||
}
|
||||
|
||||
return super.request(
|
||||
Target(baseURL: instanceURL, endpoint: endpoint, accessToken: accessToken),
|
||||
APITarget(baseURL: instanceURL, endpoint: endpoint, accessToken: accessToken),
|
||||
decodeErrorsAs: APIError.self)
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public struct Target<E: Endpoint> {
|
||||
public struct APITarget<E: Endpoint> {
|
||||
public let baseURL: URL
|
||||
public let endpoint: E
|
||||
public let accessToken: String?
|
||||
|
@ -14,7 +15,7 @@ public struct Target<E: Endpoint> {
|
|||
}
|
||||
}
|
||||
|
||||
extension Target: DecodableTarget {
|
||||
extension APITarget: DecodableTarget {
|
||||
public typealias ResultType = E.ResultType
|
||||
|
||||
public var pathComponents: [String] { endpoint.pathComponents }
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public protocol Endpoint {
|
||||
associatedtype ResultType: Decodable
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum AccessTokenEndpoint {
|
||||
case oauthToken(
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum AccountEndpoint {
|
||||
case verifyCredentials
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum AppAuthorizationEndpoint {
|
||||
case apps(clientName: String, redirectURI: String, scopes: String, website: URL?)
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum ContextEndpoint {
|
||||
case context(id: String)
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum DeletionEndpoint {
|
||||
case oauthRevoke(token: String, clientID: String, clientSecret: String)
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum FilterEndpoint {
|
||||
case create(
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum FiltersEndpoint {
|
||||
case filters
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum InstanceEndpoint {
|
||||
case instance
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum ListEndpoint {
|
||||
case create(title: String)
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum ListsEndpoint {
|
||||
case lists
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public struct Paged<T: Endpoint> {
|
||||
public let endpoint: T
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum PreferencesEndpoint {
|
||||
case preferences
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum PushSubscriptionEndpoint {
|
||||
case create(
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum StatusEndpoint {
|
||||
case status(id: String)
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
|
||||
public enum TimelinesEndpoint {
|
||||
case `public`(local: Bool)
|
|
@ -164,6 +164,7 @@
|
|||
D0BEB20624FA1121001B0F04 /* FiltersViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersViewModel.swift; sourceTree = "<group>"; };
|
||||
D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFilterView.swift; sourceTree = "<group>"; };
|
||||
D0BEB21224FA2C0A001B0F04 /* EditFilterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFilterViewModel.swift; sourceTree = "<group>"; };
|
||||
D0BFDAF524FC7C5300C86618 /* HTTP */ = {isa = PBXFileReference; lastKnownFileType = folder; path = HTTP; sourceTree = "<group>"; };
|
||||
D0C7D41E24F76169001EBDBB /* Metatext.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Metatext.entitlements; sourceTree = "<group>"; };
|
||||
D0C7D41F24F76169001EBDBB /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
D0C7D42224F76169001EBDBB /* IdentitiesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentitiesView.swift; sourceTree = "<group>"; };
|
||||
|
@ -292,6 +293,7 @@
|
|||
D0ED1BB224CE3A1600B4899C /* Development Assets */,
|
||||
D0C7D46824F76169001EBDBB /* Extensions */,
|
||||
D0666A7924C7745A00F3F04B /* Frameworks */,
|
||||
D0BFDAF524FC7C5300C86618 /* HTTP */,
|
||||
D0C7D45624F76169001EBDBB /* Localizations */,
|
||||
D0E0F1E424FC49FC002C04BF /* Mastodon */,
|
||||
D0C7D43824F76169001EBDBB /* Model */,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
import Mastodon
|
||||
|
||||
struct AppEnvironment {
|
||||
|
|
|
@ -22,7 +22,7 @@ extension AuthenticationService {
|
|||
redirectURI: OAuth.callbackURL.absoluteString,
|
||||
scopes: OAuth.scopes,
|
||||
website: OAuth.website)
|
||||
let target = Target(baseURL: instanceURL, endpoint: endpoint, accessToken: nil)
|
||||
let target = APITarget(baseURL: instanceURL, endpoint: endpoint, accessToken: nil)
|
||||
|
||||
return networkClient.request(target)
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ extension AuthenticationService {
|
|||
grantType: OAuth.grantType,
|
||||
scopes: OAuth.scopes,
|
||||
redirectURI: OAuth.callbackURL.absoluteString)
|
||||
let target = Target(baseURL: instanceURL, endpoint: endpoint, accessToken: nil)
|
||||
let target = APITarget(baseURL: instanceURL, endpoint: endpoint, accessToken: nil)
|
||||
|
||||
return networkClient.request(target)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import XCTest
|
||||
import Combine
|
||||
import CombineExpectations
|
||||
import HTTP
|
||||
import Mastodon
|
||||
@testable import Metatext
|
||||
|
||||
|
|
Loading…
Reference in a new issue