apple_kit

Apple-native services for Radroots iOS and macOS apps
git clone https://radroots.dev/git/apple_kit.git
Log | Files | Refs | README | LICENSE

RadrootsBackgroundTransferResponse.swift (4369B)


      1 import Foundation
      2 
      3 public struct RadrootsBackgroundTransferResponsePolicy: Sendable, Equatable, Hashable, Codable {
      4     public let maximumBodyBytes: Int
      5     public let acceptedMediaTypes: [String]
      6 
      7     public init(maximumBodyBytes: Int = 0, acceptedMediaTypes: [String] = []) throws {
      8         guard (0 ... 65536).contains(maximumBodyBytes), acceptedMediaTypes.count <= 8 else {
      9             throw RadrootsBackgroundTransferError.invalidRequest
     10         }
     11         let normalized = try acceptedMediaTypes.map {
     12             try RadrootsBackgroundTransferValidation.normalizedMediaType($0)
     13         }
     14         guard Set(normalized).count == normalized.count, (maximumBodyBytes == 0) == normalized.isEmpty
     15         else {
     16             throw RadrootsBackgroundTransferError.invalidRequest
     17         }
     18         self.maximumBodyBytes = maximumBodyBytes
     19         self.acceptedMediaTypes = normalized
     20     }
     21 
     22     public static let discard = Self(maximumBodyBytes: 0, acceptedMediaTypes: [], validated: ())
     23 
     24     public static func boundedJSON(maximumBodyBytes: Int = 16384) throws -> Self {
     25         try Self(maximumBodyBytes: maximumBodyBytes, acceptedMediaTypes: ["application/json"])
     26     }
     27 
     28     private init(maximumBodyBytes: Int, acceptedMediaTypes: [String], validated _: Void) {
     29         self.maximumBodyBytes = maximumBodyBytes
     30         self.acceptedMediaTypes = acceptedMediaTypes
     31     }
     32 
     33     private enum CodingKeys: String, CodingKey {
     34         case maximumBodyBytes
     35         case acceptedMediaTypes
     36     }
     37 
     38     public init(from decoder: any Decoder) throws {
     39         let values = try decoder.container(keyedBy: CodingKeys.self)
     40         try self.init(
     41             maximumBodyBytes: values.decode(Int.self, forKey: .maximumBodyBytes),
     42             acceptedMediaTypes: values.decode([String].self, forKey: .acceptedMediaTypes)
     43         )
     44     }
     45 
     46     public func encode(to encoder: any Encoder) throws {
     47         var values = encoder.container(keyedBy: CodingKeys.self)
     48         try values.encode(maximumBodyBytes, forKey: .maximumBodyBytes)
     49         try values.encode(acceptedMediaTypes, forKey: .acceptedMediaTypes)
     50     }
     51 }
     52 
     53 public struct RadrootsBackgroundTransferResponse: Sendable, Equatable, Hashable, Codable,
     54     CustomDebugStringConvertible {
     55     public let statusCode: Int
     56     public let mediaType: String?
     57     public let contentEncoding: String?
     58     public let body: Data?
     59 
     60     public var debugDescription: String {
     61         "RadrootsBackgroundTransferResponse(statusCode: \(statusCode), "
     62             + "bodyBytes: \(body?.count ?? 0), content: <redacted>)"
     63     }
     64 
     65     public init(
     66         statusCode: Int, mediaType: String?, contentEncoding: String? = nil, body: Data?
     67     ) throws {
     68         guard (100 ... 599).contains(statusCode), body?.count ?? 0 <= 65536 else {
     69             throw RadrootsBackgroundTransferError.invalidRequest
     70         }
     71         self.statusCode = statusCode
     72         self.mediaType = try mediaType.map {
     73             try RadrootsBackgroundTransferValidation.normalizedMediaType($0)
     74         }
     75         let normalizedEncoding = contentEncoding?.trimmingCharacters(in: .whitespacesAndNewlines)
     76             .lowercased()
     77         guard normalizedEncoding == nil || normalizedEncoding == "identity" else {
     78             throw RadrootsBackgroundTransferError.invalidRequest
     79         }
     80         self.contentEncoding = normalizedEncoding
     81         self.body = body
     82     }
     83 
     84     private enum CodingKeys: String, CodingKey {
     85         case statusCode
     86         case mediaType
     87         case contentEncoding
     88         case body
     89     }
     90 
     91     public init(from decoder: any Decoder) throws {
     92         let values = try decoder.container(keyedBy: CodingKeys.self)
     93         try self.init(
     94             statusCode: values.decode(Int.self, forKey: .statusCode),
     95             mediaType: values.decodeIfPresent(String.self, forKey: .mediaType),
     96             contentEncoding: values.decodeIfPresent(String.self, forKey: .contentEncoding),
     97             body: values.decodeIfPresent(Data.self, forKey: .body)
     98         )
     99     }
    100 
    101     public func encode(to encoder: any Encoder) throws {
    102         var values = encoder.container(keyedBy: CodingKeys.self)
    103         try values.encode(statusCode, forKey: .statusCode)
    104         try values.encodeIfPresent(mediaType, forKey: .mediaType)
    105         try values.encodeIfPresent(contentEncoding, forKey: .contentEncoding)
    106         try values.encodeIfPresent(body, forKey: .body)
    107     }
    108 }