Skip to content

Commit 9205a8b

Browse files
authored
Merge pull request #4 from dkoster95/feature/QHRequest
NetworkRequestFactory ported
2 parents c4f4be9 + 5f36dbe commit 9205a8b

29 files changed

Lines changed: 1316 additions & 32 deletions
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// Error+Additions.swift
3+
// QuickHatch
4+
//
5+
// Created by Daniel Koster on 3/31/17.
6+
// Copyright © 2019 DaVinci Labs. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
extension Error {
12+
13+
public var requestWasCancelled: Bool {
14+
return (self as NSError).code == -999
15+
}
16+
17+
public var isUnauthorized: Bool {
18+
if let error = self as? RequestError {
19+
return error == .unauthorized
20+
}
21+
return false
22+
}
23+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
//
2+
// HTTPStatusCode.swift
3+
// QuickHatch
4+
//
5+
// Created by Daniel Koster on 3/30/17.
6+
// Copyright © 2019 DaVinci Labs. All rights reserved.
7+
//
8+
9+
import Foundation
10+
/**
11+
HTTP status codes as per http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
12+
13+
The RF2616 standard is completely covered (http://www.ietf.org/rfc/rfc2616.txt)
14+
*/
15+
public enum HTTPStatusCode: Int, Sendable {
16+
// Informational
17+
case httpContinue = 100
18+
case switchingProtocols = 101
19+
case processing = 102
20+
21+
// Success
22+
case oK = 200
23+
case created = 201
24+
case accepted = 202
25+
case nonAuthoritativeInformation = 203
26+
case noContent = 204
27+
case resetContent = 205
28+
case partialContent = 206
29+
case multiStatus = 207
30+
case alreadyReported = 208
31+
case iMUsed = 226
32+
33+
// Redirections
34+
case multipleChoices = 300
35+
case movedPermanently = 301
36+
case found = 302
37+
case seeOther = 303
38+
case notModified = 304
39+
case useProxy = 305
40+
case switchProxy = 306
41+
case temporaryRedirect = 307
42+
case permanentRedirect = 308
43+
44+
// Client Errors
45+
case badRequest = 400
46+
case unauthorized = 401
47+
case paymentRequired = 402
48+
case forbidden = 403
49+
case notFound = 404
50+
case methodNotAllowed = 405
51+
case notAcceptable = 406
52+
case proxyAuthenticationRequired = 407
53+
case requestTimeout = 408
54+
case conflict = 409
55+
case gone = 410
56+
case lengthRequired = 411
57+
case preconditionFailed = 412
58+
case requestEntityTooLarge = 413
59+
case requestURITooLong = 414
60+
case unsupportedMediaType = 415
61+
case requestedRangeNotSatisfiable = 416
62+
case expectationFailed = 417
63+
case imATeapot = 418
64+
case authenticationTimeout = 419
65+
case unprocessableEntity = 422
66+
case locked = 423
67+
case failedDependency = 424
68+
case upgradeRequired = 426
69+
case preconditionRequired = 428
70+
case tooManyRequests = 429
71+
case requestHeaderFieldsTooLarge = 431
72+
case loginTimeout = 440
73+
case noResponse = 444
74+
case retryWith = 449
75+
case unavailableForLegalReasons = 451
76+
case requestHeaderTooLarge = 494
77+
case certError = 495
78+
case noCert = 496
79+
case hTTPToHTTPS = 497
80+
case tokenExpired = 498
81+
case clientClosedRequest = 499
82+
83+
// Server Errors
84+
case internalServerError = 500
85+
case notImplemented = 501
86+
case badGateway = 502
87+
case serviceUnavailable = 503
88+
case gatewayTimeout = 504
89+
case hTTPVersionNotSupported = 505
90+
case variantAlsoNegotiates = 506
91+
case insufficientStorage = 507
92+
case loopDetected = 508
93+
case bandwidthLimitExceeded = 509
94+
case notExtended = 510
95+
case networkAuthenticationRequired = 511
96+
case networkTimeoutError = 599
97+
}
98+
99+
extension HTTPStatusCode {
100+
/// Informational - Request received, continuing process.
101+
public var isInformational: Bool {
102+
return self.rawValue >= 100 && self.rawValue <= 199
103+
}
104+
/// Success - The action was successfully received, understood, and accepted.
105+
public var isSuccess: Bool {
106+
return self.rawValue >= 200 && self.rawValue <= 299
107+
}
108+
/// Redirection - Further action must be taken in order to complete the request.
109+
public var isRedirection: Bool {
110+
return self.rawValue >= 300 && self.rawValue <= 399
111+
}
112+
/// Client Error - The request contains bad syntax or cannot be fulfilled.
113+
public var isClientError: Bool {
114+
return self.rawValue >= 400 && self.rawValue <= 499
115+
}
116+
/// Server Error - The server failed to fulfill an apparently valid request.
117+
public var isServerError: Bool {
118+
return self.rawValue >= 500 && self.rawValue <= 599
119+
}
120+
121+
}
122+
123+
extension HTTPStatusCode {
124+
/// - returns: a localized string suitable for displaying to users that describes the specified status code.
125+
public var localizedReasonPhrase: String {
126+
return HTTPURLResponse.localizedString(forStatusCode: rawValue)
127+
}
128+
}
129+
130+
// MARK: - Printing
131+
132+
extension HTTPStatusCode: CustomDebugStringConvertible, CustomStringConvertible {
133+
public var description: String {
134+
return "\(rawValue) - \(localizedReasonPhrase)"
135+
}
136+
public var debugDescription: String {
137+
return "HTTPStatusCode:\(description)"
138+
}
139+
}

Sources/Errors/RequestError.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// RequestError.swift
3+
// QuickHatch
4+
//
5+
// Created by Daniel Koster on 3/30/17.
6+
// Copyright © 2019 DaVinci Labs. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
public enum RequestPollingError: Error, Sendable {
12+
case attemptsOverflow
13+
}
14+
15+
public enum ImageError: Error {
16+
case malformedError
17+
}
18+
19+
public enum RequestError: Error, Equatable, Sendable {
20+
21+
public static func == (lhs: RequestError, rhs: RequestError) -> Bool {
22+
switch (lhs, rhs) {
23+
case (.unauthorized, .unauthorized): return true
24+
case (.serializationError, .serializationError): return true
25+
case (.unknownError(let statusCodeA), .unknownError(let statusCodeB)): return statusCodeA == statusCodeB
26+
case (.cancelled, .cancelled): return true
27+
case (.noResponse, .noResponse):return true
28+
case (.requestWithError(let statusCodeA), .requestWithError(let statusCodeB)):
29+
return statusCodeA.rawValue == statusCodeB.rawValue
30+
case (.invalidParameters, .invalidParameters): return true
31+
case (.malformedRequest, .malformedRequest): return true
32+
case (.other, .other): return true
33+
default: return false
34+
}
35+
}
36+
37+
public static func map(error: Error) -> RequestError {
38+
if error.requestWasCancelled { return .cancelled }
39+
return (error as? RequestError) ?? .other(error: error)
40+
}
41+
42+
case unauthorized
43+
case unknownError(statusCode: Int)
44+
case cancelled
45+
case noResponse
46+
case requestWithError(statusCode: HTTPStatusCode)
47+
case serializationError(error: Error)
48+
case invalidParameters
49+
case malformedRequest
50+
case other(error: Error)
51+
}

Sources/Logger.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88

99
import Foundation
1010

11-
//public let log = Log("🌐QuickHatch🌐 -")
12-
1311
public struct LogsShortcuts {
1412
public static let quickhatch = "🌐QuickHatch🌐 - "
1513
public static let commandModule = "\(LogsShortcuts.quickhatch)Command -> "

Sources/Network Settings/HostEnvironment.swift

Lines changed: 0 additions & 24 deletions
This file was deleted.

Sources/Request/HTTPRequest.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// HTTPRequest.swift
3+
// QuickHatchHTTP
4+
//
5+
// Created by Daniel Koster on 10/13/25.
6+
//
7+
import Foundation
8+
import Combine
9+
10+
public protocol DataTask: NSObjectProtocol {
11+
func resume()
12+
func suspend()
13+
func cancel()
14+
}
15+
16+
public protocol HTTPRequest {
17+
var headers: [String: String] { get }
18+
var body: Data? { get }
19+
var url: String { get }
20+
var method: HTTPMethod { get }
21+
}
22+
23+
public protocol HTTPRequestActionable {
24+
associatedtype ResponseType: Codable
25+
func response(queue: DispatchQueue) async -> Result<ResponseType, RequestError>
26+
var responsePublisher: any Publisher<Result<ResponseType, RequestError>, Never> { get }
27+
}
28+
29+
public struct QHHTTPRequest: HTTPRequest, URLRequestProtocol {
30+
public let headers: [String : String]
31+
public let body: Data?
32+
public let url: String
33+
public let method: HTTPMethod
34+
35+
public init(headers: [String : String] = [:],
36+
body: Data? = nil,
37+
url: String,
38+
method: HTTPMethod) {
39+
self.headers = headers
40+
self.body = body
41+
self.url = url
42+
self.method = method
43+
}
44+
45+
public func asURLRequest() throws -> URLRequest {
46+
return try URLRequest(url: url, method: method, headers: headers)
47+
}
48+
}

Sources/Request/HTTPResponse.swift

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//
2+
// HTTPRespone.swift
3+
// QuickHatchHTTP
4+
//
5+
// Created by Daniel Koster on 10/13/25.
6+
//
7+
8+
import Foundation
9+
10+
public protocol HTTPResponse {
11+
var statusCode: HTTPStatusCode { get }
12+
var headers: [AnyHashable: Any] { get }
13+
var body: Data? { get }
14+
}
15+
16+
public extension HTTPResponse {
17+
func decode<T: Decodable>(decoder: JSONDecoder) throws -> Response<T> {
18+
if let body = body {
19+
let decodedBody = try decoder.decode(T.self, from: body)
20+
return Response(data: decodedBody, statusCode: statusCode, headers: headers)
21+
}
22+
throw RequestError.noResponse
23+
}
24+
}
25+
26+
public struct QHHTTPResponse: HTTPResponse {
27+
public let statusCode: HTTPStatusCode
28+
public let headers: [AnyHashable : Any]
29+
public let body: Data?
30+
31+
public init(body: Data?, urlResponse: URLResponse) {
32+
self.body = body
33+
self.headers = (urlResponse as? HTTPURLResponse)?.allHeaderFields ?? [:]
34+
self.statusCode = HTTPStatusCode(rawValue: (urlResponse as? HTTPURLResponse)?.statusCode ?? -1) ?? .serviceUnavailable
35+
}
36+
}
37+
38+
public struct Response<Value> {
39+
public let data: Value
40+
public let statusCode: HTTPStatusCode
41+
public let headers: [AnyHashable: Any]
42+
43+
public init(data: Value,
44+
statusCode: HTTPStatusCode,
45+
headers: [AnyHashable: Any]) {
46+
self.data = data
47+
self.statusCode = statusCode
48+
self.headers = headers
49+
}
50+
51+
public func map<NewValue>(transform: (Value) -> NewValue) -> Response<NewValue> {
52+
return Response<NewValue>(data: transform(data), statusCode: statusCode, headers: headers)
53+
}
54+
55+
public func flatMap<NewValue> (transform: (Value) -> Response<NewValue>) -> Response<NewValue> {
56+
return transform(data)
57+
}
58+
59+
public func filter(query: (Value) -> Bool) -> Response<Value?> {
60+
return query(data) ? Response<Value?>(data: data, statusCode: statusCode, headers: headers) : Response<Value?>(data: nil, statusCode: statusCode, headers: headers)
61+
}
62+
}

0 commit comments

Comments
 (0)