Skip to content

Commit

Permalink
Merge pull request #160 from theleftbit/prettify-stuff
Browse files Browse the repository at this point in the history
Prettify stuff
  • Loading branch information
piercifani authored Jan 9, 2025
2 parents 1c54bac + ce6de4d commit 399d4c2
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 149 deletions.
44 changes: 44 additions & 0 deletions Sources/BSWFoundation/APIClient/APIClient+Logging.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Foundation
import OSLog

//MARK: Logging

extension APIClient {

func logRequest(request: URLRequest) {
let logger = Logger(subsystem: submoduleName("APIClient"), category: "APIClient.Request")
switch loggingConfiguration.requestBehaviour {
case .all:
let httpMethod = request.httpMethod ?? "GET"
let path = request.url?.path ?? ""
logger.debug("Method: \(httpMethod) Path: \(path)")
if let data = request.httpBody, let prettyString = String(data: data, encoding: .utf8) {
logger.debug("Body: \(prettyString)")
}
default:
break
}
}

func logResponse(_ response: Response) {
let logger = Logger(subsystem: submoduleName("APIClient"), category: "APIClient.Response")
let isError = !(200..<300).contains(response.httpResponse.statusCode)
let shouldLogThis: Bool = {
switch loggingConfiguration.responseBehaviour {
case .all:
return true
case .none:
return false
case .onlyFailing:
return isError
}
}()
guard shouldLogThis else { return }
let logType: OSLogType = isError ? .error : .debug
let path = response.httpResponse.url?.path ?? ""
logger.log(level: logType, "StatusCode: \(response.httpResponse.statusCode) Path: \(path)")
if isError, let errorString = String(data: response.data, encoding: .utf8) {
logger.log(level: logType, "Error Message: \(errorString)")
}
}
}
69 changes: 69 additions & 0 deletions Sources/BSWFoundation/APIClient/APIClient+URLSession.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//
// Created by Pierluigi Cifani on 8/1/25.
//

import Foundation

//MARK: APIClientNetworkFetcher

extension URLSession: APIClientNetworkFetcher {

public func fetchData(with urlRequest: URLRequest) async throws -> APIClient.Response {
let tuple = try await data(for: urlRequest)
guard let httpResponse = tuple.1 as? HTTPURLResponse else {
throw APIClient.Error.malformedResponse
}
return .init(data: tuple.0, httpResponse: httpResponse)
}

public func uploadFile(with urlRequest: URLRequest, fileURL: URL) async throws -> APIClient.Response {
let task = Task.detached {
try await self.upload(for: urlRequest, fromFile: fileURL)
}
let cancelTask: @Sendable () -> () = {
task.cancel()
}
let wrapper = APIClient.ApplicationWrapper()
let backgroundTaskID = await wrapper.generateBackgroundTaskID(cancelTask: cancelTask)
let (data, response) = try await task.value
await wrapper.endBackgroundTask(id: backgroundTaskID)
guard let httpResponse = response as? HTTPURLResponse else {
throw APIClient.Error.malformedResponse
}
return .init(data: data, httpResponse: httpResponse)
}
}

public typealias HTTPHeaders = [String: String]
public struct VoidResponse: Decodable, Hashable, Sendable {}

// MARK: UIApplicationWrapper
/// This is here just to make sure that on non-UIKit
/// platforms we have a nice API to call to.
#if canImport(UIKit)
import UIKit
private extension APIClient {
class ApplicationWrapper {
func generateBackgroundTaskID(cancelTask: @escaping (@MainActor @Sendable () -> Void)) async -> UIBackgroundTaskIdentifier {
return await UIApplication.shared.beginBackgroundTask(expirationHandler: cancelTask)
}

func endBackgroundTask(id: UIBackgroundTaskIdentifier) async {
await UIApplication.shared.endBackgroundTask(id)
}
}
}
#else
private extension APIClient {
class ApplicationWrapper {
func generateBackgroundTaskID(cancelTask: @escaping (@MainActor @Sendable () -> Void)) async -> Int {
return 0
}

func endBackgroundTask(id: Int) async {

}
}
}
#endif

71 changes: 0 additions & 71 deletions Sources/BSWFoundation/APIClient/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -238,77 +238,6 @@ private extension APIClient {
}
}

import OSLog

//MARK: Logging

private extension APIClient {
private func logRequest(request: URLRequest) {
let logger = Logger(subsystem: submoduleName("APIClient"), category: "APIClient.Request")
switch loggingConfiguration.requestBehaviour {
case .all:
let httpMethod = request.httpMethod ?? "GET"
let path = request.url?.path ?? ""
logger.debug("Method: \(httpMethod) Path: \(path)")
if let data = request.httpBody, let prettyString = String(data: data, encoding: .utf8) {
logger.debug("Body: \(prettyString)")
}
default:
break
}
}

private func logResponse(_ response: Response) {
let logger = Logger(subsystem: submoduleName("APIClient"), category: "APIClient.Response")
let isError = !(200..<300).contains(response.httpResponse.statusCode)
let shouldLogThis: Bool = {
switch loggingConfiguration.responseBehaviour {
case .all:
return true
case .none:
return false
case .onlyFailing:
return isError
}
}()
guard shouldLogThis else { return }
let path = response.httpResponse.url?.path ?? ""
logger.debug("StatusCode: \(response.httpResponse.statusCode) Path: \(path)")
if isError, let errorString = String(data: response.data, encoding: .utf8) {
logger.debug("Error Message: \(errorString)")
}
}
}

extension URLSession: APIClientNetworkFetcher {

public func fetchData(with urlRequest: URLRequest) async throws -> APIClient.Response {
let tuple = try await self.data(for: urlRequest)
guard let httpResponse = tuple.1 as? HTTPURLResponse else {
throw APIClient.Error.malformedResponse
}
return .init(data: tuple.0, httpResponse: httpResponse)
}

public func uploadFile(with urlRequest: URLRequest, fileURL: URL) async throws -> APIClient.Response {
let cancelTask: @Sendable () -> () = {}
#if os(iOS)
let backgroundTask = await UIApplication.shared.beginBackgroundTask(expirationHandler: cancelTask)
#endif
let (data, response) = try await self.upload(for: urlRequest, fromFile: fileURL)
#if os(iOS)
await UIApplication.shared.endBackgroundTask(backgroundTask)
#endif
guard let httpResponse = response as? HTTPURLResponse else {
throw APIClient.Error.malformedResponse
}
return .init(data: data, httpResponse: httpResponse)
}
}

public typealias HTTPHeaders = [String: String]
public struct VoidResponse: Decodable, Hashable, Sendable {}

private extension Swift.Error {
var is401: Bool {
guard
Expand Down
13 changes: 8 additions & 5 deletions Sources/BSWFoundation/APIClient/Router.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,27 @@ private enum URLEncoding {

static func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
var components: [(String, String)] = []

if let dictionary = value as? [String: Any] {
for (nestedKey, value) in dictionary {
components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
}
} else if let array = value as? [Any] {
}
else if let array = value as? [Any] {
for value in array {
components += queryComponents(fromKey: "\(key)[]", value: value)
}
} else if let value = value as? NSNumber {
}
else if let value = value as? NSNumber {
if value.isBool {
components.append((escape(key), escape((value.boolValue ? "1" : "0"))))
} else {
components.append((escape(key), escape("\(value)")))
}
} else if let bool = value as? Bool {
}
else if let bool = value as? Bool {
components.append((escape(key), escape((bool ? "1" : "0"))))
} else {
}
else {
components.append((escape(key), escape("\(value)")))
}

Expand Down
16 changes: 0 additions & 16 deletions Sources/BSWFoundation/Extensions/Collections.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
// Copyright © 2018 TheLeftBit SL SL. All rights reserved.
//

import Foundation

public extension Sequence {
func find(predicate: (Self.Iterator.Element) throws -> Bool) rethrows -> Self.Iterator.Element? {
for element in self {
Expand All @@ -30,20 +28,6 @@ public extension Collection {
}
}

public extension MutableCollection where Index == Int {
/// Shuffle the elements of `self` in-place.
mutating func shuffle() {
// empty and single-element collections don't shuffle
if count < 2 { return }

for i in startIndex ..< endIndex - 1 {
let j = Int(arc4random_uniform(UInt32(endIndex - i))) + i
guard i != j else { continue }
self.swapAt(i, j)
}
}
}

public extension Array {
mutating func moveItem(fromIndex oldIndex: Index, toIndex newIndex: Index) {
insert(remove(at: oldIndex), at: newIndex)
Expand Down
1 change: 0 additions & 1 deletion Sources/BSWFoundation/Extensions/Task.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Foundation

public extension Task where Success == Never, Failure == Never {
/// Returns a Task that will never return... Well, actually, it'll complete in 1000 seconds
Expand Down
2 changes: 0 additions & 2 deletions Sources/BSWFoundation/ModuleConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
// Copyright (c) 2016 TheLeftBit SL. All rights reserved.
//

import Foundation

nonisolated func submoduleName(_ submodule : String) -> String {
let ModuleName = "com.bswfoundation"
return ModuleName + "." + submodule
Expand Down
2 changes: 1 addition & 1 deletion Sources/BSWFoundation/Parse/JSONParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public enum JSONParser {
throw Error.malformedSchema
case .dataCorrupted(let context):
print("*ERROR* Data Corrupted \"\(context)\")")
if let string = NSString(data: data, encoding: String.Encoding.utf8.rawValue) {
if let string = String(data: data, encoding: .utf8) {
print("*ERROR* incoming JSON: \(string)")
}
throw Error.malformedJSON
Expand Down
47 changes: 0 additions & 47 deletions Sources/BSWFoundation/PropertyWrappers/CodableKeychainBacked.swift

This file was deleted.

42 changes: 42 additions & 0 deletions Sources/BSWFoundation/PropertyWrappers/KeychainBacked.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,45 @@ public extension KeychainBacked {
wrappedValue = nil
}
}

/// Stores the given `T` type on the Keychain (as long as it's `Codable`)
@propertyWrapper
public class CodableKeychainBacked<T: Codable> {
private let key: String
private let keychain: Keychain

public init(key: String) {
self.key = key
self.keychain = Keychain(service: Bundle.main.bundleIdentifier!)
}

public var wrappedValue: T? {
get {
return keychain[key]?.decoded()
} set {
keychain[key] = newValue.encodedAsString()
}
}
}

public extension CodableKeychainBacked {
func reset() {
wrappedValue = nil
}
}

private extension String {
func decoded<T: Decodable>() -> T? {
guard let data = self.data(using: .utf8) else { return nil }
return try? JSONDecoder().decode(T.self, from: data)
}
}

private extension Encodable {
func encodedAsString() -> String? {
guard let data = try? JSONEncoder().encode(self), let string = String(data: data, encoding: .utf8) else {
return nil
}
return string
}
}
Loading

0 comments on commit 399d4c2

Please sign in to comment.