Skip to content

Commit

Permalink
Convert to await / async across the board
Browse files Browse the repository at this point in the history
  • Loading branch information
viktorgvoi committed Nov 13, 2024
1 parent 063f047 commit 3d72e3c
Show file tree
Hide file tree
Showing 10 changed files with 381 additions and 285 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 Viktor Gidlöf
Copyright (c) 2024 Viktor Gidlöf

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
58 changes: 0 additions & 58 deletions Sources/Extensions/DataTaskPublisher.swift

This file was deleted.

45 changes: 43 additions & 2 deletions Sources/Extensions/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import Foundation
public extension String {
/// Create a URL from the string
/// - returns: A new URL based on the given string value
func asURL() -> URL {
guard let url = URL(string: self) else { fatalError("The URL could not be created ❌ This should never happen!") }
func asURL() -> URL? {
guard let url = URL(string: self) else { return nil }
return url
}

Expand All @@ -21,4 +21,45 @@ public extension String {
func basicAuthentication(password: String) -> String {
Data("\(self):\(password)".utf8).base64EncodedString()
}

static func logResponse(_ value: (data: Data, response: URLResponse), printJSON: Bool) {
guard let httpResponse = value.response as? HTTPURLResponse,
let url = httpResponse.url?.absoluteString,
let comps = URLComponents(string: url),
let host = comps.host
else { return }

print("♻️ Incoming response from \(host) @ \(Date())")

let statusCode = httpResponse.statusCode
let statusCodeString = HTTPURLResponse.localizedString(forStatusCode: statusCode)
let path = comps.path

var printOutput = "~ \(path)\n"
printOutput += "Status-Code: \(statusCode)\n"
printOutput += "Localized Status-Code: \(statusCodeString)\n"

httpResponse.allHeaderFields.forEach { key, value in
if key.description == HTTP.Header.Field.contentLength || key.description == HTTP.Header.Field.contentType {
printOutput += "\(key): \(value)\n"
}
}

print(printOutput)
if printJSON {
print("JSON response:")
print(value.data.prettyPrinted ?? "")
}
}
}

// MARK: -
private extension Data {
/// Convert data into an optional pretty printed json string.
var prettyPrinted: String? {
guard let object = try? JSONSerialization.jsonObject(with: self, options: []),
let data = try? JSONSerialization.data(withJSONObject: object, options: [.prettyPrinted])
else { return nil }
return String(data: data, encoding: .utf8)
}
}
6 changes: 5 additions & 1 deletion Sources/Extensions/URLRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ public extension URLRequest {

/// Print outgoing request information to the console
func log() {
guard let url = url?.absoluteString, let components = URLComponents(string: url), let method = httpMethod, let host = components.host else { return }
guard let url = url?.absoluteString,
let components = URLComponents(string: url),
let method = httpMethod,
let host = components.host
else { return }

print("⚡️ Outgoing request to \(host) @ \(Date())")

Expand Down
101 changes: 69 additions & 32 deletions Sources/ServerConfig/ServerConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,93 @@
// ServerConfig.swift
// Networking
//
// Created by Viktor Gidlöf.
// Created by VG on 2024-11-13.
//

import Foundation

/// An object for creating a server configuration for the backend API
open class ServerConfig {
public protocol ServerConfigurable {
var baseURL: URL { get }
func header(forRequest request: Requestable) -> HTTP.Header
}

// MARK: -
public struct ServerConfig: ServerConfigurable {
// MARK: Private properties
private let tokenProvider: TokenProvidable?
private let additionalHeaders: HTTP.Header

// MARK: - Public properties
public let userAgent: String?
/// A provider for authorization tokens used to authenticate requests; `nil` if no authentication is needed.
public let tokenProvider: TokenProvidable?
/// The base URL for the server
public let baseURL: URL

/// Init the server configuration
/// - parameters:
/// - baseURL: The given base URL used for this server config
/// - tokenProvider: An optional token provider object used to authenticate requests. Defaults to `nil`.
public init(baseURL: String, tokenProvider: TokenProvidable? = nil) {
self.baseURL = baseURL.asURL()
// MARK: - Initialization
/// Initializes a new instance of `ServerConfigV2` with the specified configuration details.
/// - Parameters:
/// - baseURL: A `String` representing the base URL for the server. This URL will be used as the primary endpoint for all requests.
/// - userAgent: An optional `String` representing the user agent to include in the request headers. If not provided, it defaults to a string combining `name` and `version`.
/// - additionalHeaders: An optional dictionary of additional headers to be merged into the default headers for each request. The default value is an empty dictionary.
/// - tokenProvider: An optional `TokenProvidable` object used to authenticate requests. This provider supplies authorization tokens when required by a request. Defaults to `nil`, meaning no token is provided.
/// - Returns: A configured instance of `ServerConfigV2` with the specified parameters.
public init?(
baseURL: String,
userAgent: String? = "\(name)/\(version)",
additionalHeaders: HTTP.Header = [:],
tokenProvider: TokenProvidable? = nil
) {
guard let url = baseURL.asURL() else { return nil }
self.baseURL = url
self.userAgent = userAgent
self.additionalHeaders = additionalHeaders
self.tokenProvider = tokenProvider
}
}

/// Create a HTTP header for the requests.
/// Subclasses can call `super` if they need to implement the standard authentication.
/// Don't call `super` if you want to have a fully custom HTTP header implementation.
/// - parameter request: The given request to set up the header with
/// - returns: A new `HTTP.Header` dictionary
open func header(forRequest request: Requestable) -> HTTP.Header {
var header = HTTP.Header()
header[HTTP.Header.Field.userAgent] = "\(name)/\(version)"
header[HTTP.Header.Field.host] = baseURL.host

if let contentType = request.contentType {
header[HTTP.Header.Field.contentType] = contentType
}
// MARK: - Public functions
public extension ServerConfig {
func header(forRequest request: Requestable) -> HTTP.Header {
var headers = HTTP.Header()

// Base headers
if let host = baseURL.host { headers[HTTP.Header.Field.host] = host }
if let userAgent = userAgent { headers[HTTP.Header.Field.userAgent] = userAgent }
if let contentType = request.contentType { headers[HTTP.Header.Field.contentType] = contentType }

guard let tokenProvider = tokenProvider else { return header }
// Add any additional configured headers
headers.merge(additionalHeaders) { _, new in new }

guard let tokenProvider else { return headers }

switch tokenProvider.token {
case .success(let token):
switch request.authorization {
case .bearer: header[HTTP.Header.Field.auth] = String(format: HTTP.Header.Field.bearer, token)
case .basic: header[HTTP.Header.Field.auth] = String(format: HTTP.Header.Field.basic, token)
case .none: break
}
case .failure:
break
guard let authHeader = authorizationHeader(for: request.authorization, token: token) else { break }
headers[HTTP.Header.Field.auth] = authHeader
case .failure: break
}
return headers
}
}

// MARK: - Convenience Initializers
public extension ServerConfig {
static func basic(baseURL: String) -> ServerConfig? {
.init(baseURL: baseURL)
}

static func authenticated(baseURL: String, tokenProvider: TokenProvidable) -> ServerConfig? {
.init(baseURL: baseURL, tokenProvider: tokenProvider)
}
}

// MARK: - Private functions
private extension ServerConfig {
func authorizationHeader(for type: Request.Authorization, token: String) -> String? {
switch type {
case .bearer: return String(format: HTTP.Header.Field.bearer, token)
case .basic: return String(format: HTTP.Header.Field.basic, token)
case .none: return nil
}
return header
}
}
101 changes: 0 additions & 101 deletions Sources/Service/DownloadPublisher.swift

This file was deleted.

Loading

0 comments on commit 3d72e3c

Please sign in to comment.