added KreeRequest as a dependency and adjusted the code to it
This commit is contained in:
parent
d9686dae32
commit
d782a2fcf5
15
Package.resolved
Normal file
15
Package.resolved
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"originHash" : "e6d5e17e67c8a09b38a3e356fa94b64f66f993b6102d12b3e4ad9adc4352c7d0",
|
||||||
|
"pins" : [
|
||||||
|
{
|
||||||
|
"identity" : "kreerequest",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/WilhelmOks/KreeRequest",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "ce0d2ecdf923a9110b1439f22e868d22dd4ca006",
|
||||||
|
"version" : "1.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version" : 3
|
||||||
|
}
|
@ -13,11 +13,15 @@ let package = Package(
|
|||||||
targets: ["SwiftDevRant"]
|
targets: ["SwiftDevRant"]
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
dependencies: [
|
||||||
|
.package(url: "https://github.com/WilhelmOks/KreeRequest", .upToNextMajor(from: "1.0.2")),
|
||||||
|
],
|
||||||
targets: [
|
targets: [
|
||||||
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
||||||
// Targets can depend on other targets in this package and products from dependencies.
|
// Targets can depend on other targets in this package and products from dependencies.
|
||||||
.target(
|
.target(
|
||||||
name: "SwiftDevRant"
|
name: "SwiftDevRant",
|
||||||
|
dependencies: ["KreeRequest"]
|
||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "SwiftDevRantTests",
|
name: "SwiftDevRantTests",
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/// Represents an error coming directly from the devrant API.
|
||||||
public struct DevRantApiError: Swift.Error {
|
public struct DevRantApiError: Swift.Error {
|
||||||
let message: String
|
let message: String
|
||||||
}
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
import KreeRequest
|
||||||
|
|
||||||
struct DevRantBackend: Backend {
|
struct DevRantBackend: Backend {
|
||||||
let baseURL = "https://devrant.com/api/"
|
let baseURL = "https://devrant.com/api/"
|
||||||
}
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import KreeRequest
|
||||||
|
|
||||||
extension JSONEncoder {
|
extension JSONEncoder {
|
||||||
static let devRant: JSONEncoder = {
|
static let devRant: JSONEncoder = {
|
@ -1,3 +0,0 @@
|
|||||||
public protocol Backend {
|
|
||||||
var baseURL: String { get }
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
public extension JSONDecoder.DateDecodingStrategy {
|
|
||||||
nonisolated(unsafe) private static let dateFormatter: ISO8601DateFormatter = {
|
|
||||||
let formatter = ISO8601DateFormatter()
|
|
||||||
formatter.formatOptions = [.withInternetDateTime]
|
|
||||||
return formatter
|
|
||||||
}()
|
|
||||||
|
|
||||||
nonisolated(unsafe) private static let dateFormatterWithFractionalSeconds: ISO8601DateFormatter = {
|
|
||||||
let formatter = ISO8601DateFormatter()
|
|
||||||
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
||||||
return formatter
|
|
||||||
}()
|
|
||||||
|
|
||||||
static let iso8601WithOptionalFractionalSeconds: Self = {
|
|
||||||
return .custom { (decoder) -> Date in
|
|
||||||
let container = try decoder.singleValueContainer()
|
|
||||||
let dateString = try container.decode(String.self)
|
|
||||||
|
|
||||||
// Workaround to work with both, fractional seconds and whole seconds.
|
|
||||||
|
|
||||||
// Try whole seconds first:
|
|
||||||
|
|
||||||
if let date = dateFormatter.date(from: dateString) {
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it fails, try fractional:
|
|
||||||
|
|
||||||
if let date = dateFormatterWithFractionalSeconds.date(from: dateString) {
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Could not decode date from \(dateString).")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
public protocol Logger {
|
|
||||||
func log(_ message: String)
|
|
||||||
}
|
|
@ -1,175 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
public struct Request {
|
|
||||||
public enum Error<ApiError: Decodable & Sendable>: Swift.Error, CustomStringConvertible {
|
|
||||||
case notHttpResponse
|
|
||||||
case notFound
|
|
||||||
case noInternet
|
|
||||||
case apiError(_ error: ApiError)
|
|
||||||
case generalError
|
|
||||||
|
|
||||||
public var description: String {
|
|
||||||
switch self {
|
|
||||||
case .notHttpResponse: "Response is not HTTP"
|
|
||||||
case .notFound: "Not found"
|
|
||||||
case .noInternet: "No internet connection"
|
|
||||||
case .apiError(error: let error): "\(error)"
|
|
||||||
case .generalError: "General error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct EmptyError: Decodable, Swift.Error {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Method: String {
|
|
||||||
case get = "GET"
|
|
||||||
case post = "POST"
|
|
||||||
case put = "PUT"
|
|
||||||
case delete = "DELETE"
|
|
||||||
case patch = "PATCH"
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct Config {
|
|
||||||
let method: Method
|
|
||||||
let backend: Backend
|
|
||||||
let path: String
|
|
||||||
var urlParameters: [String: String] = [:]
|
|
||||||
var headers: [String: String] = [:]
|
|
||||||
}
|
|
||||||
|
|
||||||
let encoder: JSONEncoder
|
|
||||||
let decoder: JSONDecoder
|
|
||||||
let session: URLSession
|
|
||||||
let logger: Logger?
|
|
||||||
|
|
||||||
public init(encoder: JSONEncoder, decoder: JSONDecoder, session: URLSession = .init(configuration: .ephemeral), logger: Logger? = nil) {
|
|
||||||
self.encoder = encoder
|
|
||||||
self.decoder = decoder
|
|
||||||
self.session = session
|
|
||||||
self.logger = logger
|
|
||||||
}
|
|
||||||
|
|
||||||
private func makeURLRequest(config: Config, body: Data?) -> URLRequest {
|
|
||||||
let urlQuery = Self.urlEncodedQueryString(from: config.urlParameters)
|
|
||||||
guard let url = URL(string: config.backend.baseURL + config.path + urlQuery) else {
|
|
||||||
fatalError("Couldn't create a URL")
|
|
||||||
}
|
|
||||||
var urlRequest = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData)
|
|
||||||
urlRequest.httpMethod = config.method.rawValue
|
|
||||||
urlRequest.httpBody = body
|
|
||||||
config.headers.forEach {
|
|
||||||
urlRequest.setValue($0.value, forHTTPHeaderField: $0.key)
|
|
||||||
}
|
|
||||||
return urlRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func urlEncodedQueryString(from query: [String: String]) -> String {
|
|
||||||
guard !query.isEmpty else { return "" }
|
|
||||||
var components = URLComponents()
|
|
||||||
components.queryItems = query.map { URLQueryItem(name: $0.key, value: $0.value) }
|
|
||||||
let absoluteString = components.url?.absoluteString ?? ""
|
|
||||||
let plusCorrection = absoluteString.replacingOccurrences(of: "+", with: "%2b")
|
|
||||||
return plusCorrection
|
|
||||||
}
|
|
||||||
|
|
||||||
@discardableResult private func requestData<ApiError: Decodable & Sendable>(urlRequest: URLRequest, apiError: ApiError.Type = EmptyError.self) async throws -> (data: Data, headers: [AnyHashable: Any]) {
|
|
||||||
let response: (Data, URLResponse)
|
|
||||||
do {
|
|
||||||
response = try await session.data(for: urlRequest)
|
|
||||||
} catch {
|
|
||||||
if let error = error as? URLError, error.code == .notConnectedToInternet {
|
|
||||||
throw Error<ApiError>.noInternet
|
|
||||||
} else {
|
|
||||||
throw Error<ApiError>.generalError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let httpResponse = response.1 as? HTTPURLResponse {
|
|
||||||
let data = response.0
|
|
||||||
|
|
||||||
if let logger {
|
|
||||||
let logInputString = urlRequest.httpBody.flatMap { Self.jsonString(data: $0, prettyPrinted: true) } ?? "(none)"
|
|
||||||
let logOutputString = !data.isEmpty ? Self.jsonString(data: data, prettyPrinted: true) ?? "-" : "(none)"
|
|
||||||
logger.log("\(urlRequest.httpMethod?.uppercased() ?? "?") \(urlRequest.url?.absoluteString ?? "")\nbody: \(logInputString)\nresponse: \(logOutputString)")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (200..<300).contains(httpResponse.statusCode) {
|
|
||||||
return (data, httpResponse.allHeaderFields)
|
|
||||||
} else if httpResponse.statusCode == 404 {
|
|
||||||
throw Error<ApiError>.notFound
|
|
||||||
} else {
|
|
||||||
throw Error<ApiError>.apiError(try decoder.decode(apiError, from: data))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw Error<ApiError>.notHttpResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// JSON Data to String converter for printing/logging purposes
|
|
||||||
public static func jsonString(data: Data, prettyPrinted: Bool) -> String? {
|
|
||||||
do {
|
|
||||||
let writingOptions: JSONSerialization.WritingOptions = prettyPrinted ? [.prettyPrinted] : []
|
|
||||||
let decoded: Data?
|
|
||||||
if String(data: data, encoding: .utf8) == "null" {
|
|
||||||
decoded = nil
|
|
||||||
} else if let string = String(data: data, encoding: .utf8), string.first == "\"", string.last == "\"" {
|
|
||||||
decoded = data
|
|
||||||
} else if let encodedDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
|
|
||||||
decoded = try JSONSerialization.data(withJSONObject: encodedDict, options: writingOptions)
|
|
||||||
} else if let encodedArray = try JSONSerialization.jsonObject(with: data, options: []) as? [Any] {
|
|
||||||
decoded = try JSONSerialization.data(withJSONObject: encodedArray, options: writingOptions)
|
|
||||||
} else {
|
|
||||||
decoded = nil
|
|
||||||
}
|
|
||||||
return decoded.flatMap { String(data: $0, encoding: .utf8) }
|
|
||||||
} catch {
|
|
||||||
return String(data: data, encoding: .utf8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: public
|
|
||||||
|
|
||||||
public func requestJson<ApiError: Decodable & Sendable>(config: Config, apiError: ApiError.Type = EmptyError.self) async throws {
|
|
||||||
let urlRequest = makeURLRequest(config: config, body: nil)
|
|
||||||
try await requestData(urlRequest: urlRequest, apiError: apiError)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestJson<In: Encodable, ApiError: Decodable & Sendable>(config: Config, json: In, apiError: ApiError.Type = EmptyError.self) async throws {
|
|
||||||
let inData = try encoder.encode(json)
|
|
||||||
let urlRequest = makeURLRequest(config: config, body: inData)
|
|
||||||
try await requestData(urlRequest: urlRequest, apiError: apiError)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestJson<Out: Decodable, ApiError: Decodable & Sendable>(config: Config, apiError: ApiError.Type = EmptyError.self) async throws -> Out {
|
|
||||||
let urlRequest = makeURLRequest(config: config, body: nil)
|
|
||||||
let outData = try await requestData(urlRequest: urlRequest, apiError: apiError).data
|
|
||||||
return try decoder.decode(Out.self, from: outData)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestJson<In: Encodable, Out: Decodable, ApiError: Decodable & Sendable>(config: Config, json: In, apiError: ApiError.Type = EmptyError.self) async throws -> Out {
|
|
||||||
let inData = try encoder.encode(json)
|
|
||||||
let urlRequest = makeURLRequest(config: config, body: inData)
|
|
||||||
let outData = try await requestData(urlRequest: urlRequest, apiError: apiError).data
|
|
||||||
return try decoder.decode(Out.self, from: outData)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestJson<Out: Decodable, ApiError: Decodable & Sendable>(config: Config, string: String, apiError: ApiError.Type = EmptyError.self) async throws -> Out {
|
|
||||||
let inData = string.data(using: .utf8)
|
|
||||||
let urlRequest = makeURLRequest(config: config, body: inData)
|
|
||||||
let outData = try await requestData(urlRequest: urlRequest, apiError: apiError).data
|
|
||||||
return try decoder.decode(Out.self, from: outData)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestJson<ApiError: Decodable & Sendable>(config: Config, data: Data, apiError: ApiError.Type = EmptyError.self) async throws {
|
|
||||||
let urlRequest = makeURLRequest(config: config, body: data)
|
|
||||||
try await requestData(urlRequest: urlRequest, apiError: apiError).data
|
|
||||||
}
|
|
||||||
|
|
||||||
public func requestJson<Out: Decodable, ApiError: Decodable & Sendable>(config: Config, data: Data, apiError: ApiError.Type = EmptyError.self) async throws -> Out {
|
|
||||||
let urlRequest = makeURLRequest(config: config, body: data)
|
|
||||||
let outData = try await requestData(urlRequest: urlRequest, apiError: apiError).data
|
|
||||||
return try decoder.decode(Out.self, from: outData)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,15 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import KreeRequest
|
||||||
|
|
||||||
public struct SwiftDevRant {
|
public struct SwiftDevRant {
|
||||||
let request: Request
|
let request: KreeRequest
|
||||||
let backend = DevRantBackend()
|
let backend = DevRantBackend()
|
||||||
|
|
||||||
public init(requestLogger: Logger) {
|
public init(requestLogger: Logger) {
|
||||||
self.request = Request(encoder: .devRant, decoder: .devRant, logger: requestLogger)
|
self.request = KreeRequest(encoder: .devRant, decoder: .devRant, logger: requestLogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeConfig(_ method: Request.Method, path: String, urlParameters: [String: String] = [:], headers: [String: String] = [:], token: AuthToken? = nil) -> Request.Config {
|
private func makeConfig(_ method: KreeRequest.Method, path: String, urlParameters: [String: String] = [:], headers: [String: String] = [:], token: AuthToken? = nil) -> KreeRequest.Config {
|
||||||
var urlParameters = urlParameters
|
var urlParameters = urlParameters
|
||||||
urlParameters["app"] = "3"
|
urlParameters["app"] = "3"
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ public struct SwiftDevRant {
|
|||||||
return .init(method: method, backend: backend, path: path, urlParameters: urlParameters, headers: headers)
|
return .init(method: method, backend: backend, path: path, urlParameters: urlParameters, headers: headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeMultipartConfig(_ method: Request.Method, path: String, parameters: [String: String] = [:], boundary: String, headers: [String: String] = [:], token: AuthToken? = nil) -> Request.Config {
|
private func makeMultipartConfig(_ method: KreeRequest.Method, path: String, parameters: [String: String] = [:], boundary: String, headers: [String: String] = [:], token: AuthToken? = nil) -> KreeRequest.Config {
|
||||||
var parameters = parameters
|
var parameters = parameters
|
||||||
parameters["app"] = "3"
|
parameters["app"] = "3"
|
||||||
|
|
||||||
@ -76,7 +77,7 @@ public extension SwiftDevRant {
|
|||||||
let config = makeConfig(.post, path: "users/auth-token")
|
let config = makeConfig(.post, path: "users/auth-token")
|
||||||
|
|
||||||
// For the log in request the url encoded parameters are passed as a string in the http body instead of in the URL.
|
// For the log in request the url encoded parameters are passed as a string in the http body instead of in the URL.
|
||||||
let body = String(Request.urlEncodedQueryString(from: parameters).dropFirst()) // dropping the first character "?"
|
let body = String(KreeRequest.urlEncodedQueryString(from: parameters).dropFirst()) // dropping the first character "?"
|
||||||
|
|
||||||
let response: AuthToken.CodingData.Container = try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self)
|
let response: AuthToken.CodingData.Container = try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self)
|
||||||
|
|
||||||
@ -492,7 +493,7 @@ public extension SwiftDevRant {
|
|||||||
/// - userId: The id of the user to subscribe to or to unsubscribe from.
|
/// - userId: The id of the user to subscribe to or to unsubscribe from.
|
||||||
/// - subscribe: `true` subscribes to the user, `false` unsubscribes from the user.
|
/// - subscribe: `true` subscribes to the user, `false` unsubscribes from the user.
|
||||||
func subscribeToUser(token: AuthToken, userId: Int, subscribe: Bool) async throws {
|
func subscribeToUser(token: AuthToken, userId: Int, subscribe: Bool) async throws {
|
||||||
let method: Request.Method = subscribe ? .post : .delete
|
let method: KreeRequest.Method = subscribe ? .post : .delete
|
||||||
|
|
||||||
let config = makeConfig(method, path: "users/\(userId)/subscribe", token: token)
|
let config = makeConfig(method, path: "users/\(userId)/subscribe", token: token)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user