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"] | ||||
|         ), | ||||
|     ], | ||||
|     dependencies: [ | ||||
|         .package(url: "https://github.com/WilhelmOks/KreeRequest", .upToNextMajor(from: "1.0.2")), | ||||
|     ], | ||||
|     targets: [ | ||||
|         // 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. | ||||
|         .target( | ||||
|             name: "SwiftDevRant" | ||||
|             name: "SwiftDevRant", | ||||
|             dependencies: ["KreeRequest"] | ||||
|         ), | ||||
|         .testTarget( | ||||
|             name: "SwiftDevRantTests", | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| /// Represents an error coming directly from the devrant API. | ||||
| public struct DevRantApiError: Swift.Error { | ||||
|     let message: String | ||||
| } | ||||
| @ -1,3 +1,5 @@ | ||||
| import KreeRequest | ||||
| 
 | ||||
| struct DevRantBackend: Backend { | ||||
|     let baseURL = "https://devrant.com/api/" | ||||
| } | ||||
| @ -1,4 +1,5 @@ | ||||
| import Foundation | ||||
| import KreeRequest | ||||
| 
 | ||||
| extension 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 KreeRequest | ||||
| 
 | ||||
| public struct SwiftDevRant { | ||||
|     let request: Request | ||||
|     let request: KreeRequest | ||||
|     let backend = DevRantBackend() | ||||
|      | ||||
|     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 | ||||
|         urlParameters["app"] = "3" | ||||
|          | ||||
| @ -24,7 +25,7 @@ public struct SwiftDevRant { | ||||
|         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 | ||||
|         parameters["app"] = "3" | ||||
|          | ||||
| @ -76,7 +77,7 @@ public extension SwiftDevRant { | ||||
|         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. | ||||
|         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) | ||||
|          | ||||
| @ -492,7 +493,7 @@ public extension SwiftDevRant { | ||||
|     ///    - userId: The id of the user to subscribe to or to unsubscribe from. | ||||
|     ///    - subscribe: `true` subscribes to the user, `false` unsubscribes from the user. | ||||
|     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) | ||||
|          | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user