diff --git a/Sources/SwiftDevRant/DevRantJSONCoder.swift b/Sources/SwiftDevRant/DevRantJSONCoder.swift index 9b38d4e..8c104d3 100644 --- a/Sources/SwiftDevRant/DevRantJSONCoder.swift +++ b/Sources/SwiftDevRant/DevRantJSONCoder.swift @@ -4,7 +4,6 @@ extension JSONEncoder { static let devRant: JSONEncoder = { let encoder = JSONEncoder() encoder.dateEncodingStrategy = .iso8601 - encoder.keyEncodingStrategy = .convertToSnakeCase return encoder }() } @@ -13,7 +12,6 @@ extension JSONDecoder { static let devRant: JSONDecoder = { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601WithOptionalFractionalSeconds - decoder.keyDecodingStrategy = .convertFromSnakeCase return decoder }() } diff --git a/Sources/SwiftDevRant/Request/Request.swift b/Sources/SwiftDevRant/Request/Request.swift index 8c3c87f..f46c5f8 100644 --- a/Sources/SwiftDevRant/Request/Request.swift +++ b/Sources/SwiftDevRant/Request/Request.swift @@ -52,7 +52,7 @@ public struct Request { } private func makeURLRequest(config: Config, body: Data?) -> URLRequest { - let urlQuery = urlEncodedQueryString(from: config.urlParameters) + 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") } @@ -65,7 +65,7 @@ public struct Request { return urlRequest } - private func urlEncodedQueryString(from query: [String: String]) -> String { + 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) } @@ -74,29 +74,10 @@ public struct Request { return plusCorrection } - @discardableResult private func requestData(urlRequest: URLRequest, apiError: ApiError.Type = EmptyError.self) async throws(Error) -> (data: Data, headers: [AnyHashable: Any]) { + @discardableResult private func requestData(urlRequest: URLRequest, apiError: ApiError.Type = EmptyError.self) async throws -> (data: Data, headers: [AnyHashable: Any]) { + let response: (Data, URLResponse) do { - let response = try await session.data(for: urlRequest) - - if let httpResponse = response.1 as? HTTPURLResponse { - let data = response.0 - - if let logger { - let logInputString = urlRequest.httpBody.flatMap { jsonString(data: $0, prettyPrinted: true) } ?? "(none)" - let logOutputString = !data.isEmpty ? 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.notFound - } else { - throw Error.apiError(try decoder.decode(apiError, from: data)) - } - } else { - throw Error.notHttpResponse - } + response = try await session.data(for: urlRequest) } catch { if let error = error as? URLError, error.code == .notConnectedToInternet { throw Error.noInternet @@ -104,10 +85,30 @@ public struct Request { throw Error.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.notFound + } else { + throw Error.apiError(try decoder.decode(apiError, from: data)) + } + } else { + throw Error.notHttpResponse + } } /// JSON Data to String converter for printing/logging purposes - private func jsonString(data: Data, prettyPrinted: Bool) -> String? { + public static func jsonString(data: Data, prettyPrinted: Bool) -> String? { do { let writingOptions: JSONSerialization.WritingOptions = prettyPrinted ? [.prettyPrinted] : [] let decoded: Data? @@ -124,14 +125,13 @@ public struct Request { } return decoded.flatMap { String(data: $0, encoding: .utf8) } } catch { - print(error) return String(data: data, encoding: .utf8) } } // MARK: public - public func requestJson(config: Config, apiError: ApiError.Type = EmptyError.self) async throws(Error) { + public func requestJson(config: Config, apiError: ApiError.Type = EmptyError.self) async throws { let urlRequest = makeURLRequest(config: config, body: nil) try await requestData(urlRequest: urlRequest, apiError: apiError) } @@ -154,4 +154,11 @@ public struct Request { let outData = try await requestData(urlRequest: urlRequest, apiError: apiError).data return try decoder.decode(Out.self, from: outData) } + + public func requestJson(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) + } } diff --git a/Sources/SwiftDevRant/SwiftDevRant.swift b/Sources/SwiftDevRant/SwiftDevRant.swift index 0283c4e..bb211ac 100644 --- a/Sources/SwiftDevRant/SwiftDevRant.swift +++ b/Sources/SwiftDevRant/SwiftDevRant.swift @@ -1,7 +1,11 @@ public struct SwiftDevRant { - let request = Request(encoder: .devRant, decoder: .devRant) + let request: Request let backend = DevRantBackend() + public init(requestLogger: Logger) { + self.request = Request(encoder: .devRant, decoder: .devRant, logger: requestLogger) + } + func makeConfig(_ method: Request.Method, path: String, urlParameters: [String: String] = [:], headers: [String: String] = [:], token: AuthToken? = nil) -> Request.Config { var urlParameters = urlParameters urlParameters["app"] = "3" @@ -19,12 +23,16 @@ public struct SwiftDevRant { public func logIn(username: String, password: String) async throws -> AuthToken { var parameters: [String: String] = [:] + parameters["app"] = "3" parameters["username"] = username parameters["password"] = password - let config = makeConfig(.post, path: "users/auth-token", urlParameters: parameters) + let config = makeConfig(.post, path: "users/auth-token") - let response: AuthToken.CodingData.Container = try await request.requestJson(config: config, apiError: DevRantApiError.CodingData.self) + // 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 response: AuthToken.CodingData.Container = try await request.requestJson(config: config, string: body, apiError: DevRantApiError.CodingData.self) return response.auth_token.decoded }