@@ -6,42 +6,23 @@ import ApolloAPI
6
6
/// Parses multipart response data into chunks and forwards each on to the next interceptor.
7
7
public struct MultipartResponseParsingInterceptor : ApolloInterceptor {
8
8
9
- public enum MultipartResponseParsingError : Error , LocalizedError , Equatable {
9
+ public enum ParsingError : Error , LocalizedError , Equatable {
10
10
case noResponseToParse
11
- case cannotParseResponseData
12
- case unsupportedContentType( type: String )
13
- case cannotParseChunkData
14
- case irrecoverableError( message: String ? )
15
- case cannotParsePayloadData
11
+ case cannotParseResponse
16
12
17
13
public var errorDescription : String ? {
18
14
switch self {
19
15
case . noResponseToParse:
20
16
return " There is no response to parse. Check the order of your interceptors. "
21
- case . cannotParseResponseData :
17
+ case . cannotParseResponse :
22
18
return " The response data could not be parsed. "
23
- case let . unsupportedContentType( type) :
24
- return " Unsupported content type: application/json is required but got \( type) . "
25
- case . cannotParseChunkData:
26
- return " The chunk data could not be parsed. "
27
- case let . irrecoverableError( message) :
28
- return " An irrecoverable error occured: \( message ?? " unknown " ) . "
29
- case . cannotParsePayloadData:
30
- return " The payload data could not be parsed. "
31
19
}
32
20
}
33
21
}
34
22
35
- private enum ChunkedDataLine {
36
- case heartbeat
37
- case contentHeader( type: String )
38
- case json( object: JSONObject )
39
- case unknown
40
- }
41
-
42
- private static let dataLineSeparator : StaticString = " \r \n \r \n "
43
- private static let contentTypeHeader : StaticString = " content-type: "
44
- private static let heartbeat : StaticString = " {} "
23
+ private static let responseParsers : [ String : MultipartResponseSpecificationParser . Type ] = [
24
+ MultipartResponseSubscriptionParser . protocolSpec: MultipartResponseSubscriptionParser . self
25
+ ]
45
26
46
27
public var id : String = UUID ( ) . uuidString
47
28
@@ -56,7 +37,7 @@ public struct MultipartResponseParsingInterceptor: ApolloInterceptor {
56
37
57
38
guard let response else {
58
39
chain. handleErrorAsync (
59
- MultipartResponseParsingError . noResponseToParse,
40
+ ParsingError . noResponseToParse,
60
41
request: request,
61
42
response: response,
62
43
completion: completion
@@ -74,129 +55,67 @@ public struct MultipartResponseParsingInterceptor: ApolloInterceptor {
74
55
return
75
56
}
76
57
58
+ let multipartComponents = response. httpResponse. multipartHeaderComponents
59
+
77
60
guard
78
- let boundaryString = response. httpResponse. multipartBoundary,
79
- let dataString = String ( data: response. rawData, encoding: . utf8)
61
+ let boundary = multipartComponents. boundary,
62
+ let `protocol` = multipartComponents. protocol,
63
+ let parser = Self . responseParsers [ `protocol`]
80
64
else {
81
65
chain. handleErrorAsync (
82
- MultipartResponseParsingError . cannotParseResponseData ,
66
+ ParsingError . cannotParseResponse ,
83
67
request: request,
84
68
response: response,
85
69
completion: completion
86
70
)
87
71
return
88
72
}
89
73
90
- for chunk in dataString. components ( separatedBy: " -- \( boundaryString) " ) {
91
- if chunk. isEmpty || chunk. isBoundaryPrefix { continue }
92
-
93
- for dataLine in chunk. components ( separatedBy: Self . dataLineSeparator. description) {
94
- switch ( parse ( dataLine: dataLine. trimmingCharacters ( in: . newlines) ) ) {
95
- case . heartbeat:
96
- // Periodically sent by the router - noop
97
- continue
98
-
99
- case let . contentHeader( type) :
100
- guard type == " application/json " else {
101
- chain. handleErrorAsync (
102
- MultipartResponseParsingError . unsupportedContentType ( type: type) ,
103
- request: request,
104
- response: response,
105
- completion: completion
106
- )
107
- return
108
- }
109
-
110
- case let . json( object) :
111
- if let errors = object [ " errors " ] as? [ JSONObject ] {
112
- let message = errors. first ? [ " message " ] as? String
113
-
114
- chain. handleErrorAsync (
115
- MultipartResponseParsingError . irrecoverableError ( message: message) ,
116
- request: request,
117
- response: response,
118
- completion: completion
119
- )
120
-
121
- // These are fatal-level transport errors, don't process anything else.
122
- return
123
- }
124
-
125
- guard let payload = object [ " payload " ] else {
126
- chain. handleErrorAsync (
127
- MultipartResponseParsingError . cannotParsePayloadData,
128
- request: request,
129
- response: response,
130
- completion: completion
131
- )
132
- return
133
- }
134
-
135
- if payload is NSNull {
136
- // `payload` can be null such as in the case of a transport error
137
- continue
138
- }
139
-
140
- guard
141
- let payload = payload as? JSONObject ,
142
- let data: Data = try ? JSONSerializationFormat . serialize ( value: payload)
143
- else {
144
- chain. handleErrorAsync (
145
- MultipartResponseParsingError . cannotParsePayloadData,
146
- request: request,
147
- response: response,
148
- completion: completion
149
- )
150
- return
151
- }
152
-
153
- let response = HTTPResponse < Operation > (
154
- response: response. httpResponse,
155
- rawData: data,
156
- parsedResponse: nil
157
- )
158
- chain. proceedAsync (
159
- request: request,
160
- response: response,
161
- interceptor: self ,
162
- completion: completion
163
- )
164
-
165
- case . unknown:
166
- chain. handleErrorAsync (
167
- MultipartResponseParsingError . cannotParseChunkData,
168
- request: request,
169
- response: response,
170
- completion: completion
171
- )
172
- }
173
- }
174
- }
175
- }
176
-
177
- /// Parses the data line of a multipart response chunk
178
- private func parse( dataLine: String ) -> ChunkedDataLine {
179
- if dataLine == Self . heartbeat. description {
180
- return . heartbeat
181
- }
74
+ let dataHandler : ( ( Data ) -> Void ) = { data in
75
+ let response = HTTPResponse < Operation > (
76
+ response: response. httpResponse,
77
+ rawData: data,
78
+ parsedResponse: nil
79
+ )
182
80
183
- if dataLine. starts ( with: Self . contentTypeHeader. description) {
184
- return . contentHeader( type: ( dataLine. components ( separatedBy: " : " ) . last ?? dataLine)
185
- . trimmingCharacters ( in: . whitespaces)
81
+ chain. proceedAsync (
82
+ request: request,
83
+ response: response,
84
+ interceptor: self ,
85
+ completion: completion
186
86
)
187
87
}
188
88
189
- if
190
- let data = dataLine. data ( using: . utf8) ,
191
- let jsonObject = try ? JSONSerializationFormat . deserialize ( data: data) as? JSONObject
192
- {
193
- return . json( object: jsonObject)
89
+ let errorHandler : ( ( Error ) -> Void ) = { parserError in
90
+ chain. handleErrorAsync (
91
+ parserError,
92
+ request: request,
93
+ response: response,
94
+ completion: completion
95
+ )
194
96
}
195
97
196
- return . unknown
98
+ parser. parse (
99
+ data: response. rawData,
100
+ boundary: boundary,
101
+ dataHandler: dataHandler,
102
+ errorHandler: errorHandler
103
+ )
197
104
}
198
105
}
199
106
200
- fileprivate extension String {
201
- var isBoundaryPrefix : Bool { self == " -- " }
107
+ /// A protocol that multipart response parsers must conform to in order to be added to the list of
108
+ /// available response specification parsers.
109
+ protocol MultipartResponseSpecificationParser {
110
+ /// The specification string matching what is expected to be received in the `Content-Type` header
111
+ /// in an HTTP response.
112
+ static var protocolSpec : String { get }
113
+
114
+ /// Function that will be called to process the response data.
115
+ static func parse(
116
+ data: Data ,
117
+ boundary: String ,
118
+ dataHandler: ( ( Data ) -> Void ) ,
119
+ errorHandler: ( ( Error ) -> Void )
120
+ )
202
121
}
0 commit comments