mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			201 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			201 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Swift
		
	
| // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | |
| 
 | |
| import Foundation
 | |
| import Combine
 | |
| 
 | |
| public extension Network {
 | |
|     // MARK: - Network.BatchResponse
 | |
| 
 | |
|     struct BatchResponse: Decodable, Collection {
 | |
|         public let data: [Any]
 | |
|         
 | |
|         // MARK: - Collection Conformance
 | |
|         
 | |
|         public var startIndex: Int { data.startIndex }
 | |
|         public var endIndex: Int { data.endIndex }
 | |
|         public var count: Int { data.count }
 | |
|         
 | |
|         public subscript(index: Int) -> Any { data[index] }
 | |
|         public func index(after i: Int) -> Int { return data.index(after: i) }
 | |
|         
 | |
|         // MARK: - Initialization
 | |
|         
 | |
|         init(data: [Any]) {
 | |
|             self.data = data
 | |
|         }
 | |
|         
 | |
|         public init(from decoder: Decoder) throws {
 | |
| #if DEBUG
 | |
|             preconditionFailure("The `HTTP.BatchResponse` type cannot be decoded directly, this is simply here to allow for `PreparedSendData<HTTP.BatchResponse>` support")
 | |
| #else
 | |
|             data = []
 | |
| #endif
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     // MARK: - BatchResponseMap<E>
 | |
|     
 | |
|     struct BatchResponseMap<E: EndpointType>: Decodable, ErasedBatchResponseMap {
 | |
|         public let data: [E: Any]
 | |
|         
 | |
|         public subscript(position: E) -> Any? {
 | |
|             get { return data[position] }
 | |
|         }
 | |
|         
 | |
|         public var count: Int { data.count }
 | |
|         public var keys: Dictionary<E, Any>.Keys { data.keys }
 | |
|         public var values: Dictionary<E, Any>.Values { data.values }
 | |
|         
 | |
|         // MARK: - Initialization
 | |
|         
 | |
|         init(data: [E: Any]) {
 | |
|             self.data = data
 | |
|         }
 | |
|         
 | |
|         public init(from decoder: Decoder) throws {
 | |
| #if DEBUG
 | |
|             preconditionFailure("The `HTTP.BatchResponseMap` type cannot be decoded directly, this is simply here to allow for `PreparedSendData<HTTP.BatchResponseMap>` support")
 | |
| #else
 | |
|             data = [:]
 | |
| #endif
 | |
|         }
 | |
|         
 | |
|         // MARK: - ErasedBatchResponseMap
 | |
|         
 | |
|         public static func from(
 | |
|             batchEndpoints: [any EndpointType],
 | |
|             response: Network.BatchResponse
 | |
|         ) throws -> Self {
 | |
|             let convertedEndpoints: [E] = batchEndpoints.compactMap { $0 as? E }
 | |
|             
 | |
|             guard convertedEndpoints.count == response.data.count else { throw NetworkError.parsingFailed }
 | |
|             
 | |
|             return BatchResponseMap(
 | |
|                 data: zip(convertedEndpoints, response.data)
 | |
|                     .reduce(into: [:]) { result, next in
 | |
|                         result[next.0] = next.1
 | |
|                     }
 | |
|             )
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     // MARK: - BatchSubResponse<T>
 | |
|     
 | |
|     struct BatchSubResponse<T>: ErasedBatchSubResponse {
 | |
|         public enum CodingKeys: String, CodingKey {
 | |
|             case code
 | |
|             case headers
 | |
|             case body
 | |
|         }
 | |
|         
 | |
|         /// The numeric http response code (e.g. 200 for success)
 | |
|         public let code: Int
 | |
|         
 | |
|         /// Any headers returned by the request
 | |
|         public let headers: [String: String]
 | |
|         
 | |
|         /// The body of the request; will be plain json if content-type is `application/json`, otherwise it will be base64 encoded data
 | |
|         public let body: T?
 | |
|         
 | |
|         var erasedBody: Any? { body }
 | |
|         
 | |
|         /// A flag to indicate that there was a body but it failed to parse
 | |
|         public let failedToParseBody: Bool
 | |
|         
 | |
|         public init(
 | |
|             code: Int,
 | |
|             headers: [String: String] = [:],
 | |
|             body: T? = nil,
 | |
|             failedToParseBody: Bool = false
 | |
|         ) {
 | |
|             self.code = code
 | |
|             self.headers = headers
 | |
|             self.body = body
 | |
|             self.failedToParseBody = failedToParseBody
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: - ErasedBatchResponseMap
 | |
| 
 | |
| public protocol ErasedBatchResponseMap {
 | |
|     static func from(
 | |
|         batchEndpoints: [any EndpointType],
 | |
|         response: Network.BatchResponse
 | |
|     ) throws -> Self
 | |
| }
 | |
| 
 | |
| // MARK: - BatchSubResponse<T> Coding
 | |
| 
 | |
| extension Network.BatchSubResponse: Encodable where T: Encodable {}
 | |
| extension Network.BatchSubResponse: Decodable {
 | |
|     public init(from decoder: Decoder) throws {
 | |
|         let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
 | |
|         let body: T? = ((try? (T.self as? Decodable.Type)?.decoded(with: container, forKey: .body)) as? T)
 | |
|         
 | |
|         self = Network.BatchSubResponse(
 | |
|             code: try container.decode(Int.self, forKey: .code),
 | |
|             headers: ((try? container.decode([String: String].self, forKey: .headers)) ?? [:]),
 | |
|             body: body,
 | |
|             failedToParseBody: (
 | |
|                 body == nil &&
 | |
|                 T.self != NoResponse.self &&
 | |
|                 !(T.self is ExpressibleByNilLiteral.Type)
 | |
|             )
 | |
|         )
 | |
|     }
 | |
| }
 | |
| 
 | |
| // MARK: - ErasedBatchSubResponse
 | |
| 
 | |
| protocol ErasedBatchSubResponse: ResponseInfoType {
 | |
|     var erasedBody: Any? { get }
 | |
| }
 | |
| 
 | |
| // MARK: - Convenience
 | |
| 
 | |
| internal extension Network.BatchResponse {
 | |
|     static func decodingResponses(
 | |
|         from data: Data?,
 | |
|         as types: [Decodable.Type],
 | |
|         requireAllResults: Bool,
 | |
|         using dependencies: Dependencies = Dependencies()
 | |
|     ) throws -> Network.BatchResponse {
 | |
|         // Need to split the data into an array of data so each item can be Decoded correctly
 | |
|         guard let data: Data = data else { throw NetworkError.parsingFailed }
 | |
|         guard let jsonObject: Any = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed]) else {
 | |
|             throw NetworkError.parsingFailed
 | |
|         }
 | |
|         
 | |
|         let dataArray: [Data]
 | |
|         
 | |
|         switch jsonObject {
 | |
|             case let anyArray as [Any]:
 | |
|                 dataArray = anyArray.compactMap { try? JSONSerialization.data(withJSONObject: $0) }
 | |
|                 
 | |
|                 guard !requireAllResults || dataArray.count == types.count else {
 | |
|                     throw NetworkError.parsingFailed
 | |
|                 }
 | |
|                 
 | |
|             case let anyDict as [String: Any]:
 | |
|                 guard
 | |
|                     let resultsArray: [Data] = (anyDict["results"] as? [Any])?   // stringlint:disable
 | |
|                         .compactMap({ try? JSONSerialization.data(withJSONObject: $0) }),
 | |
|                     (
 | |
|                         !requireAllResults ||
 | |
|                         resultsArray.count == types.count
 | |
|                     )
 | |
|                 else { throw NetworkError.parsingFailed }
 | |
|                 
 | |
|                 dataArray = resultsArray
 | |
|                 
 | |
|             default: throw NetworkError.parsingFailed
 | |
|         }
 | |
|         
 | |
|         return Network.BatchResponse(
 | |
|             data: try zip(dataArray, types)
 | |
|                 .map { data, type in try type.decoded(from: data, using: dependencies) }
 | |
|         )
 | |
|     }
 | |
| }
 |