this repo has no description

networking and general project setup

+616
+10
Sources/CoreATProtocol/APActor.swift
··· 1 + // 2 + // APActor.swift 3 + // CoreATProtocol 4 + // 5 + // Created by Thomas Rademaker on 10/8/25. 6 + // 7 + 8 + @globalActor public actor APActor { 9 + public static let shared = APActor() 10 + }
+26
Sources/CoreATProtocol/APEnvironment.swift
··· 1 + // 2 + // APEnvironment.swift 3 + // CoreATProtocol 4 + // 5 + // Created by Thomas Rademaker on 10/10/25. 6 + // 7 + 8 + @APActor 9 + public class APEnvironment { 10 + public static var current: APEnvironment = APEnvironment() 11 + 12 + public var host: String? 13 + public var accessToken: String? 14 + public var refreshToken: String? 15 + public var atProtocoldelegate: CoreATProtocolDelegate? 16 + public let routerDelegate = APRouterDelegate() 17 + 18 + private init() {} 19 + 20 + // func setup(apiKey: String, apiSecret: String, userAgent: String) { 21 + // self.apiKey = apiKey 22 + // self.apiSecret = apiSecret 23 + // self.userAgent = userAgent 24 + // } 25 + } 26 +
+26
Sources/CoreATProtocol/CoreATProtocol.swift
··· 1 1 // The Swift Programming Language 2 2 // https://docs.swift.org/swift-book 3 + 4 + public protocol CoreATProtocolDelegate: AnyObject {} 5 + 6 + @APActor 7 + public func setup(hostURL: String?, accessJWT: String?, refreshJWT: String?, delegate: CoreATProtocolDelegate? = nil) { 8 + APEnvironment.current.host = hostURL 9 + APEnvironment.current.accessToken = accessJWT 10 + APEnvironment.current.refreshToken = refreshJWT 11 + APEnvironment.current.atProtocoldelegate = delegate 12 + } 13 + 14 + @APActor 15 + public func setDelegate(_ delegate: CoreATProtocolDelegate) { 16 + APEnvironment.current.atProtocoldelegate = delegate 17 + } 18 + 19 + @APActor 20 + public func updateTokens(access: String?, refresh: String?) { 21 + APEnvironment.current.accessToken = access 22 + APEnvironment.current.refreshToken = refresh 23 + } 24 + 25 + @APActor 26 + public func update(hostURL: String?) { 27 + APEnvironment.current.host = hostURL 28 + }
+31
Sources/CoreATProtocol/Models/ATError.swift
··· 1 + // 2 + // ATError.swift 3 + // CoreATProtocol 4 + // 5 + // Created by Thomas Rademaker on 10/8/25. 6 + // 7 + 8 + public enum AtError: Error { 9 + case message(ErrorMessage) 10 + case network(NetworkError) 11 + } 12 + 13 + public struct ErrorMessage: Codable, Sendable { 14 + #warning("Should error be type string or AtErrorType?") 15 + public let error: String 16 + public let message: String? 17 + 18 + public init(error: String, message: String?) { 19 + self.error = error 20 + self.message = message 21 + } 22 + } 23 + 24 + public enum AtErrorType: String, Codable, Sendable { 25 + case authenticationRequired = "AuthenticationRequired" 26 + case expiredToken = "ExpiredToken" 27 + case invalidRequest = "InvalidRequest" 28 + case methodNotImplemented = "MethodNotImplemented" 29 + case rateLimitExceeded = "RateLimitExceeded" 30 + case authMissing = "AuthMissing" 31 + }
+71
Sources/CoreATProtocol/Networking.swift
··· 1 + // 2 + // Networking.swift 3 + // CoreATProtocol 4 + // 5 + // Created by Thomas Rademaker on 10/10/25. 6 + // 7 + 8 + import Foundation 9 + 10 + extension JSONDecoder { 11 + public static var atDecoder: JSONDecoder { 12 + let dateFormatter = DateFormatter() 13 + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX" 14 + dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) 15 + dateFormatter.locale = Locale(identifier: "en_US") 16 + 17 + let decoder = JSONDecoder() 18 + decoder.keyDecodingStrategy = .convertFromSnakeCase 19 + decoder.dateDecodingStrategy = .formatted(dateFormatter) 20 + 21 + return decoder 22 + } 23 + } 24 + 25 + func shouldPerformRequest(lastFetched: Double, timeLimit: Int = 3600) -> Bool { 26 + guard lastFetched != 0 else { return true } 27 + let currentTime = Date.now 28 + let lastFetchTime = Date(timeIntervalSince1970: lastFetched) 29 + guard let differenceInMinutes = Calendar.current.dateComponents([.second], from: lastFetchTime, to: currentTime).second else { return false } 30 + return differenceInMinutes >= timeLimit 31 + } 32 + 33 + @APActor 34 + public class APRouterDelegate: NetworkRouterDelegate { 35 + private var shouldRefreshToken = false 36 + 37 + public func intercept(_ request: inout URLRequest) async { 38 + if let refreshToken = APEnvironment.current.refreshToken, shouldRefreshToken { 39 + shouldRefreshToken = false 40 + request.setValue("Bearer \(refreshToken)", forHTTPHeaderField: "Authorization") 41 + } else if let accessToken = APEnvironment.current.accessToken { 42 + request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization") 43 + } 44 + } 45 + 46 + public func shouldRetry(error: Error, attempts: Int) async throws -> Bool { 47 + func getNewToken() async throws -> Bool { 48 + // shouldRefreshToken = true 49 + // let newSession = try await AtProtoLexicons().refresh(attempts: attempts + 1) 50 + // APEnvironment.current.accessToken = newSession.accessJwt 51 + // APEnvironment.current.refreshToken = newSession.refreshJwt 52 + // await delegate?.sessionUpdated(newSession) 53 + // 54 + // return true 55 + false 56 + } 57 + 58 + // TODO: verify this works! 59 + if case .network(let networkError) = error as? AtError, 60 + case .statusCode(let statusCode, _) = networkError, 61 + let statusCode = statusCode?.rawValue, (400..<500).contains(statusCode), 62 + attempts == 1 { 63 + return try await getNewToken() 64 + } else if case .message(let message) = error as? AtError, 65 + message.error == AtErrorType.expiredToken.rawValue { 66 + return try await getNewToken() 67 + } 68 + 69 + return false 70 + } 71 + }
+29
Sources/CoreATProtocol/Networking/Encoding/JSONParameterEncoder.swift
··· 1 + import Foundation 2 + 3 + struct JSONParameterEncoder: ParameterEncoder { 4 + func encode(urlRequest: inout URLRequest, with parameters: Parameters) throws { 5 + do { 6 + let jsonAsData = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) 7 + encode(urlRequest: &urlRequest, with: jsonAsData) 8 + } catch { 9 + throw NetworkError.encodingFailed 10 + } 11 + } 12 + 13 + func encode(urlRequest: inout URLRequest, with encodable: Encodable) throws { 14 + do { 15 + let data = try encodable.toJSONData() 16 + encode(urlRequest: &urlRequest, with: data) 17 + } catch { 18 + throw NetworkError.encodingFailed 19 + } 20 + } 21 + 22 + func encode(urlRequest: inout URLRequest, with data: Data) { 23 + urlRequest.httpBody = data 24 + 25 + if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { 26 + urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") 27 + } 28 + } 29 + }
+37
Sources/CoreATProtocol/Networking/Encoding/ParameterEncoding.swift
··· 1 + import Foundation 2 + 3 + public typealias Parameters = [String : Any] 4 + 5 + protocol ParameterEncoder { 6 + func encode(urlRequest: inout URLRequest, with parameters: Parameters) throws 7 + } 8 + 9 + @APActor 10 + public enum ParameterEncoding: Sendable { 11 + 12 + case urlEncoding(parameters: Parameters) 13 + case jsonEncoding(parameters: Parameters) 14 + case jsonDataEncoding(data: Data?) 15 + case jsonEncodableEncoding(encodable: Encodable) 16 + case urlAndJsonEncoding(urlParameters: Parameters, bodyParameters: Parameters) 17 + 18 + func encode(urlRequest: inout URLRequest) throws { 19 + do { 20 + switch self { 21 + case .urlEncoding(let parameters): 22 + try URLParameterEncoder().encode(urlRequest: &urlRequest, with: parameters) 23 + case .jsonEncoding(let parameters): 24 + try JSONParameterEncoder().encode(urlRequest: &urlRequest, with: parameters) 25 + case .jsonDataEncoding(let data): 26 + try JSONParameterEncoder().encode(urlRequest: &urlRequest, with: data) 27 + case .jsonEncodableEncoding(let encodable): 28 + try JSONParameterEncoder().encode(urlRequest: &urlRequest, with: encodable) 29 + case .urlAndJsonEncoding(let urlParameters, let bodyParameters): 30 + try URLParameterEncoder().encode(urlRequest: &urlRequest, with: urlParameters) 31 + try JSONParameterEncoder().encode(urlRequest: &urlRequest, with: bodyParameters) 32 + } 33 + } catch { 34 + throw NetworkError.encodingFailed 35 + } 36 + } 37 + }
+130
Sources/CoreATProtocol/Networking/Encoding/URLParameterEncoder.swift
··· 1 + import Foundation 2 + 3 + struct URLParameterEncoder: ParameterEncoder { 4 + /// Configures how `Array` parameters are encoded. 5 + enum ArrayEncoding { 6 + /// An empty set of square brackets is appended to the key for every value. This is the default behavior. 7 + case brackets 8 + /// No brackets are appended. The key is encoded as is. 9 + case noBrackets 10 + /// Brackets containing the item index are appended. This matches the jQuery and Node.js behavior. 11 + case indexInBrackets 12 + 13 + func encode(key: String, atIndex index: Int) -> String { 14 + switch self { 15 + case .brackets: 16 + return "\(key)[]" 17 + case .noBrackets: 18 + return key 19 + case .indexInBrackets: 20 + return "\(key)[\(index)]" 21 + } 22 + } 23 + } 24 + 25 + /// Configures how `Bool` parameters are encoded. 26 + enum BoolEncoding { 27 + /// Encode `true` as `1` and `false` as `0`. This is the default behavior. 28 + case numeric 29 + /// Encode `true` and `false` as string literals. 30 + case literal 31 + 32 + func encode(value: Bool) -> String { 33 + switch self { 34 + case .numeric: 35 + return value ? "1" : "0" 36 + case .literal: 37 + return value ? "true" : "false" 38 + } 39 + } 40 + } 41 + 42 + /// The encoding to use for `Array` parameters. 43 + let arrayEncoding: ArrayEncoding 44 + 45 + /// The encoding to use for `Bool` parameters. 46 + let boolEncoding: BoolEncoding 47 + 48 + /// The character set tp use for escaping 49 + let characterSet: CharacterSet 50 + 51 + init(arrayEncoding: ArrayEncoding = .brackets, boolEncoding: BoolEncoding = .numeric, characterSet: CharacterSet = .apURLQueryAllowed) { 52 + self.arrayEncoding = arrayEncoding 53 + self.boolEncoding = boolEncoding 54 + self.characterSet = characterSet 55 + } 56 + 57 + func encode(urlRequest: inout URLRequest, with parameters: Parameters) throws { 58 + 59 + guard let url = urlRequest.url else { throw NetworkError.missingURL } 60 + 61 + if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty { 62 + let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters) 63 + urlComponents.percentEncodedQuery = percentEncodedQuery 64 + urlRequest.url = urlComponents.url 65 + } 66 + 67 + if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { 68 + urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type") 69 + } 70 + } 71 + 72 + private func query(_ parameters: [String: Any]) -> String { 73 + var components: [(String, String)] = [] 74 + 75 + for key in parameters.keys.sorted(by: <) { 76 + let value = parameters[key]! 77 + components += queryComponents(fromKey: key, value: value) 78 + } 79 + return components.map { "\($0)=\($1)" }.joined(separator: "&") 80 + } 81 + 82 + /// Creates a percent-escaped, URL encoded query string components from the given key-value pair recursively. 83 + /// 84 + /// - Parameters: 85 + /// - key: Key of the query component. 86 + /// - value: Value of the query component. 87 + /// 88 + /// - Returns: The percent-escaped, URL encoded query string components. 89 + func queryComponents(fromKey key: String, value: Any) -> [(String, String)] { 90 + var components: [(String, String)] = [] 91 + switch value { 92 + case let dictionary as [String: Any]: 93 + for (nestedKey, value) in dictionary { 94 + components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value) 95 + } 96 + case let array as [Any]: 97 + for (index, value) in array.enumerated() { 98 + components += queryComponents(fromKey: arrayEncoding.encode(key: key, atIndex: index), value: value) 99 + } 100 + case let number as NSNumber: 101 + if number.isBool { 102 + components.append((escape(key), escape(boolEncoding.encode(value: number.boolValue)))) 103 + } else { 104 + components.append((escape(key), escape("\(number)"))) 105 + } 106 + case let bool as Bool: 107 + components.append((escape(key), escape(boolEncoding.encode(value: bool)))) 108 + default: 109 + components.append((escape(key), escape("\(value)"))) 110 + } 111 + return components 112 + } 113 + 114 + /// Creates a percent-escaped string following RFC 3986 for a query string key or value. 115 + /// 116 + /// - Parameter string: `String` to be percent-escaped. 117 + /// 118 + /// - Returns: The percent-escaped `String`. 119 + func escape(_ string: String) -> String { 120 + string.addingPercentEncoding(withAllowedCharacters: characterSet) ?? string 121 + } 122 + } 123 + 124 + extension NSNumber { 125 + fileprivate var isBool: Bool { 126 + // Use Obj-C type encoding to check whether the underlying type is a `Bool`, as it's guaranteed as part of 127 + // swift-corelibs-foundation, per [this discussion on the Swift forums](https://forums.swift.org/t/alamofire-on-linux-possible-but-not-release-ready/34553/22). 128 + String(cString: objCType) == "c" 129 + } 130 + }
+21
Sources/CoreATProtocol/Networking/Extensions/CharacterSet.swift
··· 1 + import Foundation 2 + 3 + extension CharacterSet { 4 + /// Creates a CharacterSet from RFC 3986 allowed characters. 5 + /// 6 + /// RFC 3986 states that the following characters are "reserved" characters. 7 + /// 8 + /// - General Delimiters: ":", "#", "[", "]", "@", "?", "/" 9 + /// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=" 10 + /// 11 + /// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow 12 + /// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/" 13 + /// should be percent-escaped in the query string. 14 + static let apURLQueryAllowed: CharacterSet = { 15 + let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 16 + let subDelimitersToEncode = "!$&'()*+,;=" 17 + let encodableDelimiters = CharacterSet(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") 18 + 19 + return CharacterSet.urlQueryAllowed.subtracting(encodableDelimiters) 20 + }() 21 + }
+7
Sources/CoreATProtocol/Networking/Extensions/Encodable.swift
··· 1 + import Foundation 2 + 3 + extension Encodable { 4 + func toJSONData() throws -> Data { 5 + try JSONEncoder().encode(self) 6 + } 7 + }
+9
Sources/CoreATProtocol/Networking/Services/EndpointType.swift
··· 1 + import Foundation 2 + 3 + public protocol EndpointType: Sendable { 4 + var baseURL: URL { get async } 5 + var path: String { get } 6 + var httpMethod: HTTPMethod { get } 7 + var task: HTTPTask { get async } 8 + var headers: HTTPHeaders? { get async } 9 + }
+7
Sources/CoreATProtocol/Networking/Services/HTTPMethod.swift
··· 1 + public enum HTTPMethod : String { 2 + case get = "GET" 3 + case post = "POST" 4 + case put = "PUT" 5 + case patch = "PATCH" 6 + case delete = "DELETE" 7 + }
+7
Sources/CoreATProtocol/Networking/Services/HTTPTask.swift
··· 1 + public enum HTTPTask: Sendable { 2 + case request 3 + 4 + case requestParameters(encoding: ParameterEncoding) 5 + 6 + // case download, upload...etc 7 + }
+121
Sources/CoreATProtocol/Networking/Services/NetworkRouter.swift
··· 1 + import Foundation 2 + 3 + @APActor 4 + public protocol NetworkRouterDelegate: AnyObject { 5 + func intercept(_ request: inout URLRequest) async 6 + func shouldRetry(error: Error, attempts: Int) async throws -> Bool 7 + } 8 + 9 + /// Describes the implementation details of a NetworkRouter 10 + /// 11 + /// ``NetworkRouter`` is the only implementation of this protocol available to the end user, but they can create their own 12 + /// implementations that can be used for testing for instance. 13 + @APActor 14 + public protocol NetworkRouterProtocol: AnyObject { 15 + associatedtype Endpoint: EndpointType 16 + var delegate: NetworkRouterDelegate? { get set } 17 + func execute<T: Decodable>(_ route: Endpoint, attempts: Int) async throws -> T 18 + } 19 + 20 + public enum NetworkError : Error, Sendable { 21 + case encodingFailed 22 + case missingURL 23 + case statusCode(_ statusCode: StatusCode?, data: Data) 24 + case noStatusCode 25 + case noData 26 + case tokenRefresh 27 + } 28 + 29 + public typealias HTTPHeaders = [String:String] 30 + 31 + /// The NetworkRouter is a generic class that has an ``EndpointType`` and it conforms to ``NetworkRouterProtocol` 32 + @APActor 33 + public class NetworkRouter<Endpoint: EndpointType>: NetworkRouterProtocol { 34 + 35 + public weak var delegate: NetworkRouterDelegate? 36 + let networking: Networking 37 + let urlSessionTaskDelegate: URLSessionTaskDelegate? 38 + var decoder: JSONDecoder 39 + 40 + public init(networking: Networking? = nil, urlSessionDelegate: URLSessionDelegate? = nil, urlSessionTaskDelegate: URLSessionTaskDelegate? = nil, decoder: JSONDecoder? = nil) { 41 + if let networking = networking { 42 + self.networking = networking 43 + } else { 44 + self.networking = URLSession(configuration: URLSessionConfiguration.default, delegate: urlSessionDelegate, delegateQueue: nil) 45 + } 46 + 47 + self.urlSessionTaskDelegate = urlSessionTaskDelegate 48 + 49 + if let decoder = decoder { 50 + self.decoder = decoder 51 + } else { 52 + self.decoder = JSONDecoder() 53 + self.decoder.keyDecodingStrategy = .convertFromSnakeCase 54 + } 55 + } 56 + 57 + /// This generic method will take a route and return the desired type via a network call 58 + /// This method is async and it can throw errors 59 + /// - Returns: The generic type is returned 60 + public func execute<T: Decodable>(_ route: Endpoint, attempts: Int = 1) async throws -> T { 61 + guard var request = try? await buildRequest(from: route) else { throw NetworkError.encodingFailed } 62 + await delegate?.intercept(&request) 63 + 64 + let (data, response) = try await networking.data(for: request, delegate: urlSessionTaskDelegate) 65 + guard let httpResponse = response as? HTTPURLResponse else { throw NetworkError.noStatusCode } 66 + switch httpResponse.statusCode { 67 + case 200...299: 68 + return try decoder.decode(T.self, from: data) 69 + default: 70 + let statusCode = StatusCode(rawValue: httpResponse.statusCode) 71 + let statusNetworkError = AtError.network(NetworkError.statusCode(statusCode, data: data)) 72 + guard let delegate else { throw statusNetworkError } 73 + 74 + let decoder = JSONDecoder() 75 + decoder.keyDecodingStrategy = .convertFromSnakeCase 76 + 77 + let errorToThrow: AtError 78 + if let errorMessage = try? decoder.decode(ErrorMessage.self, from: data) { 79 + errorToThrow = AtError.message(errorMessage) 80 + } else { 81 + errorToThrow = statusNetworkError 82 + } 83 + 84 + guard try await delegate.shouldRetry(error: errorToThrow, attempts: attempts) else { throw errorToThrow } 85 + return try await execute(route, attempts: attempts + 1) 86 + } 87 + } 88 + 89 + func buildRequest(from route: Endpoint) async throws -> URLRequest { 90 + 91 + var request = await URLRequest(url: route.baseURL.appendingPathComponent(route.path), 92 + cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, 93 + timeoutInterval: 10.0) 94 + 95 + request.httpMethod = route.httpMethod.rawValue 96 + do { 97 + switch await route.task { 98 + case .request: 99 + request.setValue("application/json", forHTTPHeaderField: "Content-Type") 100 + await addAdditionalHeaders(route.headers, request: &request) 101 + case .requestParameters(let parameterEncoding): 102 + await addAdditionalHeaders(route.headers, request: &request) 103 + try configureParameters(parameterEncoding: parameterEncoding, request: &request) 104 + } 105 + return request 106 + } catch { 107 + throw error 108 + } 109 + } 110 + 111 + private func configureParameters(parameterEncoding: ParameterEncoding, request: inout URLRequest) throws { 112 + try parameterEncoding.encode(urlRequest: &request) 113 + } 114 + 115 + private func addAdditionalHeaders(_ additionalHeaders: HTTPHeaders?, request: inout URLRequest) { 116 + guard let headers = additionalHeaders else { return } 117 + for (key, value) in headers { 118 + request.setValue(value, forHTTPHeaderField: key) 119 + } 120 + } 121 + }
+8
Sources/CoreATProtocol/Networking/Services/NetworkingProtocol.swift
··· 1 + @preconcurrency import Foundation 2 + 3 + @APActor 4 + public protocol Networking { 5 + func data(for request: URLRequest, delegate: URLSessionTaskDelegate?) async throws -> (Data, URLResponse) 6 + } 7 + 8 + extension URLSession: Networking { }
+76
Sources/CoreATProtocol/Networking/Services/StatusCode.swift
··· 1 + import Foundation 2 + 3 + public enum StatusCode: Int, Sendable { 4 + // 1xx 5 + case continueCode = 100 6 + case switchingProtocols = 101 7 + case processing = 102 8 + case earlyHints = 103 9 + 10 + // 2xx 11 + case ok = 200 12 + case created = 201 13 + case accepted = 202 14 + case nonAuthoritativeInformation = 203 15 + case noContent = 204 16 + case resetContent = 205 17 + case partialContent = 206 18 + case mutliStatus = 207 19 + case alreadyReported = 208 20 + case IMUsed = 226 21 + 22 + // 3xx 23 + case multipleChoices = 300 24 + case movedPermanently = 301 25 + case found = 302 26 + case seeOthers = 303 27 + case notModified = 304 28 + case useProxy = 305 29 + case switchProxy = 306 30 + case temporaryRedirect = 307 31 + case permanentRedirect = 308 32 + 33 + // 4xx 34 + case badRequest = 400 35 + case unauthorized = 401 36 + case paymentRequired = 402 37 + case forbidden = 403 38 + case notFound = 404 39 + case methodNotAllowed = 405 40 + case notAcceptable = 406 41 + case proxyAuthenticationRequired = 407 42 + case requestTimeout = 408 43 + case conflict = 409 44 + case gone = 410 45 + case lengthRequired = 411 46 + case preconditionFailed = 412 47 + case payloadTooLarge = 413 48 + case uriTooLong = 414 49 + case unsupportedMediaType = 415 50 + case rangeNotSatisfiable = 416 51 + case expectationFailed = 417 52 + case imATeapot = 418 53 + case misdirectedRequest = 421 54 + case unprocessableEntity = 422 55 + case locked = 423 56 + case failedDependency = 424 57 + case tooEarly = 425 58 + case upgradeRequire = 426 59 + case preconditionRequire = 428 60 + case tooManyRequests = 429 61 + case requestHeaderFieldsTooLarge = 431 62 + case unavailableForLegalResons = 451 63 + 64 + // 5xx 65 + case internalServerError = 500 66 + case notImplemented = 501 67 + case badGateway = 502 68 + case serviceUnavailable = 503 69 + case gatewayTimeout = 504 70 + case httpVersionNotSupported = 505 71 + case variantAlsoNegatiates = 506 72 + case insufficientStorage = 507 73 + case loopDetected = 508 74 + case notExtended = 510 75 + case networkAuthenticationRequired = 511 76 + }