// C o p y r i g h t © 2 0 2 2 R a n g e p r o o f P t y L t d . A l l r i g h t s r e s e r v e d .
import Foundation
import Combine
import SessionUtilitiesKit
public extension Network {
// MARK: - N e t w o r k . B a t c h R e s p o n s e
struct BatchResponse : Decodable , Collection {
public let data : [ Any ]
// MARK: - C o l l e c t i o n C o n f o r m a n c e
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: - I n i t i a l i z a t i o n
init ( data : [ Any ] ) {
self . data = data
}
public init ( from decoder : Decoder ) throws {
#if DEBUG
preconditionFailure ( " The `Network.BatchResponse` type cannot be decoded directly, this is simply here to allow for `PreparedSendData<Network.BatchResponse>` support " )
#else
data = [ ]
#endif
}
}
// MARK: - B a t c h R e s p o n s e M a p < 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: - I n i t i a l i z a t i o n
init ( data : [ E : Any ] ) {
self . data = data
}
public init ( from decoder : Decoder ) throws {
#if DEBUG
preconditionFailure ( " The `Network.BatchResponseMap` type cannot be decoded directly, this is simply here to allow for `PreparedSendData<Network.BatchResponseMap>` support " )
#else
data = [ : ]
#endif
}
// MARK: - E r a s e d B a t c h R e s p o n s e M a p
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: - B a t c h S u b R e s p o n s e < T >
struct BatchSubResponse < T > : ErasedBatchSubResponse {
public enum CodingKeys : String , CodingKey {
case code
case headers
case body
}
// / T h e n u m e r i c h t t p r e s p o n s e c o d e ( e . g . 2 0 0 f o r s u c c e s s )
public let code : Int
// / A n y h e a d e r s r e t u r n e d b y t h e r e q u e s t
public let headers : [ String : String ]
// / T h e b o d y o f t h e r e q u e s t ; w i l l b e p l a i n j s o n i f c o n t e n t - t y p e i s ` a p p l i c a t i o n / j s o n ` , o t h e r w i s e i t w i l l b e b a s e 6 4 e n c o d e d d a t a
public let body : T ?
var erasedBody : Any ? { body }
// / A f l a g t o i n d i c a t e t h a t t h e r e w a s a b o d y b u t i t f a i l e d t o p a r s e
public let failedToParseBody : Bool
public init (
code : Int ,
headers : [ String : String ] = [ : ] ,
body : T ? = nil ,
failedToParseBody : Bool
) {
self . code = code
self . headers = headers
self . body = body
self . failedToParseBody = failedToParseBody
}
}
}
// MARK: - E r a s e d B a t c h R e s p o n s e M a p
public protocol ErasedBatchResponseMap {
static func from (
batchEndpoints : [ any EndpointType ] ,
response : Network . BatchResponse
) throws -> Self
}
// MARK: - B a t c h S u b R e s p o n s e < T > C o d i n g
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: - E r a s e d B a t c h S u b R e s p o n s e
protocol ErasedBatchSubResponse : ResponseInfoType {
var erasedBody : Any ? { get }
var failedToParseBody : Bool { get }
}
// MARK: - C o n v e n i e n c e
internal extension Network . BatchResponse {
// s t r i n g l i n t : i g n o r e _ c o n t e n t s
static func decodingResponses (
from data : Data ? ,
as types : [ Decodable . Type ] ,
requireAllResults : Bool ,
using dependencies : Dependencies
) throws -> Network . BatchResponse {
// N e e d t o s p l i t t h e d a t a i n t o a n a r r a y o f d a t a s o e a c h i t e m c a n b e D e c o d e d c o r r e c t l y
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 ] ) ?
. 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 ) }
)
}
}