Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Sources/Errors/Error+Additions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Error+Additions.swift
// QuickHatch
//
// Created by Daniel Koster on 3/31/17.
// Copyright © 2019 DaVinci Labs. All rights reserved.
//

import Foundation

extension Error {

public var requestWasCancelled: Bool {
return (self as NSError).code == -999
}

public var isUnauthorized: Bool {
if let error = self as? RequestError {
return error == .unauthorized
}
return false
}
}
139 changes: 139 additions & 0 deletions Sources/Errors/HTTPStatusCode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
//
// HTTPStatusCode.swift
// QuickHatch
//
// Created by Daniel Koster on 3/30/17.
// Copyright © 2019 DaVinci Labs. All rights reserved.
//

import Foundation
/**
HTTP status codes as per http://en.wikipedia.org/wiki/List_of_HTTP_status_codes

The RF2616 standard is completely covered (http://www.ietf.org/rfc/rfc2616.txt)
*/
public enum HTTPStatusCode: Int, Sendable {
// Informational
case httpContinue = 100
case switchingProtocols = 101
case processing = 102

// Success
case oK = 200
case created = 201
case accepted = 202
case nonAuthoritativeInformation = 203
case noContent = 204
case resetContent = 205
case partialContent = 206
case multiStatus = 207
case alreadyReported = 208
case iMUsed = 226

// Redirections
case multipleChoices = 300
case movedPermanently = 301
case found = 302
case seeOther = 303
case notModified = 304
case useProxy = 305
case switchProxy = 306
case temporaryRedirect = 307
case permanentRedirect = 308

// Client Errors
case badRequest = 400
case unauthorized = 401
case paymentRequired = 402
case forbidden = 403
case notFound = 404
case methodNotAllowed = 405
case notAcceptable = 406
case proxyAuthenticationRequired = 407
case requestTimeout = 408
case conflict = 409
case gone = 410
case lengthRequired = 411
case preconditionFailed = 412
case requestEntityTooLarge = 413
case requestURITooLong = 414
case unsupportedMediaType = 415
case requestedRangeNotSatisfiable = 416
case expectationFailed = 417
case imATeapot = 418
case authenticationTimeout = 419
case unprocessableEntity = 422
case locked = 423
case failedDependency = 424
case upgradeRequired = 426
case preconditionRequired = 428
case tooManyRequests = 429
case requestHeaderFieldsTooLarge = 431
case loginTimeout = 440
case noResponse = 444
case retryWith = 449
case unavailableForLegalReasons = 451
case requestHeaderTooLarge = 494
case certError = 495
case noCert = 496
case hTTPToHTTPS = 497
case tokenExpired = 498
case clientClosedRequest = 499

// Server Errors
case internalServerError = 500
case notImplemented = 501
case badGateway = 502
case serviceUnavailable = 503
case gatewayTimeout = 504
case hTTPVersionNotSupported = 505
case variantAlsoNegotiates = 506
case insufficientStorage = 507
case loopDetected = 508
case bandwidthLimitExceeded = 509
case notExtended = 510
case networkAuthenticationRequired = 511
case networkTimeoutError = 599
}

extension HTTPStatusCode {
/// Informational - Request received, continuing process.
public var isInformational: Bool {
return self.rawValue >= 100 && self.rawValue <= 199
}
/// Success - The action was successfully received, understood, and accepted.
public var isSuccess: Bool {
return self.rawValue >= 200 && self.rawValue <= 299
}
/// Redirection - Further action must be taken in order to complete the request.
public var isRedirection: Bool {
return self.rawValue >= 300 && self.rawValue <= 399
}
/// Client Error - The request contains bad syntax or cannot be fulfilled.
public var isClientError: Bool {
return self.rawValue >= 400 && self.rawValue <= 499
}
/// Server Error - The server failed to fulfill an apparently valid request.
public var isServerError: Bool {
return self.rawValue >= 500 && self.rawValue <= 599
}

}

extension HTTPStatusCode {
/// - returns: a localized string suitable for displaying to users that describes the specified status code.
public var localizedReasonPhrase: String {
return HTTPURLResponse.localizedString(forStatusCode: rawValue)
}
}

// MARK: - Printing

extension HTTPStatusCode: CustomDebugStringConvertible, CustomStringConvertible {
public var description: String {
return "\(rawValue) - \(localizedReasonPhrase)"
}
public var debugDescription: String {
return "HTTPStatusCode:\(description)"
}
}
51 changes: 51 additions & 0 deletions Sources/Errors/RequestError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// RequestError.swift
// QuickHatch
//
// Created by Daniel Koster on 3/30/17.
// Copyright © 2019 DaVinci Labs. All rights reserved.
//

import Foundation

public enum RequestPollingError: Error, Sendable {
case attemptsOverflow
}

public enum ImageError: Error {
case malformedError
}

public enum RequestError: Error, Equatable, Sendable {

public static func == (lhs: RequestError, rhs: RequestError) -> Bool {
switch (lhs, rhs) {
case (.unauthorized, .unauthorized): return true
case (.serializationError, .serializationError): return true
case (.unknownError(let statusCodeA), .unknownError(let statusCodeB)): return statusCodeA == statusCodeB
case (.cancelled, .cancelled): return true
case (.noResponse, .noResponse):return true
case (.requestWithError(let statusCodeA), .requestWithError(let statusCodeB)):
return statusCodeA.rawValue == statusCodeB.rawValue
case (.invalidParameters, .invalidParameters): return true
case (.malformedRequest, .malformedRequest): return true
case (.other, .other): return true
default: return false
}
}

public static func map(error: Error) -> RequestError {
if error.requestWasCancelled { return .cancelled }
return (error as? RequestError) ?? .other(error: error)
}

case unauthorized
case unknownError(statusCode: Int)
case cancelled
case noResponse
case requestWithError(statusCode: HTTPStatusCode)
case serializationError(error: Error)
case invalidParameters
case malformedRequest
case other(error: Error)
}
2 changes: 0 additions & 2 deletions Sources/Logger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@

import Foundation

//public let log = Log("🌐QuickHatch🌐 -")

public struct LogsShortcuts {
public static let quickhatch = "🌐QuickHatch🌐 - "
public static let commandModule = "\(LogsShortcuts.quickhatch)Command -> "
Expand Down
24 changes: 0 additions & 24 deletions Sources/Network Settings/HostEnvironment.swift

This file was deleted.

48 changes: 48 additions & 0 deletions Sources/Request/HTTPRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// HTTPRequest.swift
// QuickHatchHTTP
//
// Created by Daniel Koster on 10/13/25.
//
import Foundation
import Combine

public protocol DataTask: NSObjectProtocol {
func resume()
func suspend()
func cancel()
}

public protocol HTTPRequest {
var headers: [String: String] { get }
var body: Data? { get }
var url: String { get }
var method: HTTPMethod { get }
}

public protocol HTTPRequestActionable {
associatedtype ResponseType: Codable
func response(queue: DispatchQueue) async -> Result<ResponseType, RequestError>
var responsePublisher: any Publisher<Result<ResponseType, RequestError>, Never> { get }
}

public struct QHHTTPRequest: HTTPRequest, URLRequestProtocol {
public let headers: [String : String]
public let body: Data?
public let url: String
public let method: HTTPMethod

public init(headers: [String : String] = [:],
body: Data? = nil,
url: String,
method: HTTPMethod) {
self.headers = headers
self.body = body
self.url = url
self.method = method
}

public func asURLRequest() throws -> URLRequest {
return try URLRequest(url: url, method: method, headers: headers)
}
}
62 changes: 62 additions & 0 deletions Sources/Request/HTTPResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// HTTPRespone.swift
// QuickHatchHTTP
//
// Created by Daniel Koster on 10/13/25.
//

import Foundation

public protocol HTTPResponse {
var statusCode: HTTPStatusCode { get }
var headers: [AnyHashable: Any] { get }
var body: Data? { get }
}

public extension HTTPResponse {
func decode<T: Decodable>(decoder: JSONDecoder) throws -> Response<T> {
if let body = body {
let decodedBody = try decoder.decode(T.self, from: body)
return Response(data: decodedBody, statusCode: statusCode, headers: headers)
}
throw RequestError.noResponse
}
}

public struct QHHTTPResponse: HTTPResponse {
public let statusCode: HTTPStatusCode
public let headers: [AnyHashable : Any]
public let body: Data?

public init(body: Data?, urlResponse: URLResponse) {
self.body = body
self.headers = (urlResponse as? HTTPURLResponse)?.allHeaderFields ?? [:]
self.statusCode = HTTPStatusCode(rawValue: (urlResponse as? HTTPURLResponse)?.statusCode ?? -1) ?? .serviceUnavailable
}
}

public struct Response<Value> {
public let data: Value
public let statusCode: HTTPStatusCode
public let headers: [AnyHashable: Any]

public init(data: Value,
statusCode: HTTPStatusCode,
headers: [AnyHashable: Any]) {
self.data = data
self.statusCode = statusCode
self.headers = headers
}

public func map<NewValue>(transform: (Value) -> NewValue) -> Response<NewValue> {
return Response<NewValue>(data: transform(data), statusCode: statusCode, headers: headers)
}

public func flatMap<NewValue> (transform: (Value) -> Response<NewValue>) -> Response<NewValue> {
return transform(data)
}

public func filter(query: (Value) -> Bool) -> Response<Value?> {
return query(data) ? Response<Value?>(data: data, statusCode: statusCode, headers: headers) : Response<Value?>(data: nil, statusCode: statusCode, headers: headers)
}
}
Loading