Skip to content

Commit b2cb602

Browse files
authored
Merge pull request #409 from appwrite/feat-swift-async-await
Feat swift async await
2 parents 6c5428e + 61ff680 commit b2cb602

File tree

15 files changed

+473
-681
lines changed

15 files changed

+473
-681
lines changed

templates/swift/Package.swift.twig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ let package = Package(
1717
),
1818
],
1919
dependencies: [
20-
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.0.0"),
20+
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.9.0"),
2121
.package(url: "https://github.com/apple/swift-nio.git", from: "2.32.0")
2222
],
2323
targets: [

templates/swift/Sources/Client.swift.twig

Lines changed: 72 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ open class Client {
2121
open var headers: [String: String] = [
2222
"content-type": "",
2323
"x-sdk-version": "{{spec.title | caseDash}}:{{ language.name | caseLower }}:{{ sdk.version }}"{% if spec.global.defaultHeaders | length > 0 %},{% endif %}
24+
2425
{% for key,header in spec.global.defaultHeaders %}
2526
"{{key}}": "{{header}}"{% if not loop.last %},{% endif %}
2627
{% endfor %}
@@ -228,29 +229,17 @@ open class Client {
228229
headers: [String: String] = [:],
229230
params: [String: Any?] = [:],
230231
sink: ((ByteBuffer) -> Void)? = nil,
231-
convert: (([String: Any]) -> T)? = nil,
232-
completion: ((Result<T, {{ spec.title | caseUcfirst }}Error>) -> Void)? = nil
233-
) {
232+
convert: (([String: Any]) -> T)? = nil
233+
) async throws -> T {
234234
let validParams = params.filter { $0.value != nil }
235235

236236
let queryParameters = method == "GET" && !validParams.isEmpty
237237
? "?" + parametersToQueryString(params: validParams)
238238
: ""
239239

240-
let targetURL = URL(string: endPoint + path + queryParameters)!
240+
var request = HTTPClientRequest(url: endPoint + path + queryParameters)
241+
request.method = .RAW(value: method)
241242

242-
var request: HTTPClient.Request
243-
do {
244-
request = try HTTPClient.Request(
245-
url: targetURL,
246-
method: .RAW(value: method)
247-
)
248-
} catch {
249-
completion?(Result.failure({{ spec.title | caseUcfirst }}Error(
250-
message: error.localizedDescription
251-
)))
252-
return
253-
}
254243

255244
for (key, value) in self.headers.merging(headers, uniquingKeysWith: { $1 }) {
256245
request.headers.add(name: key, value: value)
@@ -259,24 +248,16 @@ open class Client {
259248
request.addDomainCookies()
260249

261250
if "GET" == method {
262-
execute(request, convert: convert, completion: completion)
263-
return
251+
return try await execute(request, convert: convert)
264252
}
265253

266-
do {
267-
try buildBody(for: &request, with: validParams)
268-
} catch let error {
269-
completion?(Result.failure({{ spec.title | caseUcfirst}}Error(
270-
message: error.localizedDescription
271-
)))
272-
return
273-
}
254+
try buildBody(for: &request, with: validParams)
274255

275-
execute(request, withSink: sink, convert: convert, completion: completion)
256+
return try await execute(request, withSink: sink, convert: convert)
276257
}
277258

278259
private func buildBody(
279-
for request: inout HTTPClient.Request,
260+
for request: inout HTTPClientRequest,
280261
with params: [String: Any?]
281262
) throws {
282263
if request.headers["content-type"][0] == "multipart/form-data" {
@@ -287,96 +268,62 @@ open class Client {
287268
}
288269

289270
private func execute<T>(
290-
_ request: HTTPClient.Request,
271+
_ request: HTTPClientRequest,
291272
withSink bufferSink: ((ByteBuffer) -> Void)? = nil,
292-
convert: (([String: Any]) -> T)? = nil,
293-
completion: ((Result<T, {{ spec.title | caseUcfirst}}Error>) -> Void)? = nil
294-
) {
295-
if bufferSink == nil {
296-
http.execute(
297-
request: request,
298-
delegate: ResponseAccumulator(request: request)
299-
).futureResult.whenComplete( { result in
300-
complete(with: result)
301-
})
302-
return
303-
}
304-
305-
http.execute(
306-
request: request,
307-
delegate: StreamingDelegate(request: request, sink: bufferSink)
308-
).futureResult.whenComplete { result in
309-
complete(with: result)
310-
}
311-
312-
func complete(with result: Result<HTTPClient.Response, Swift.Error>) {
313-
guard let completion = completion else {
314-
return
315-
}
316-
317-
switch result {
318-
case .failure(let error):
319-
completion(.failure({{ spec.title | caseUcfirst}}Error(
320-
message: error.localizedDescription
321-
)))
322-
case .success(var response):
323-
switch response.status.code {
324-
case 0..<400:
325-
if response.cookies.count > 0 {
326-
UserDefaults.standard.set(
327-
try! response.cookies.toJson(),
328-
forKey: "\(response.host)-cookies"
329-
)
330-
}
331-
switch T.self {
332-
case is Bool.Type:
333-
completion(.success(true as! T))
334-
case is ByteBuffer.Type:
335-
completion(.success(response.body! as! T))
336-
default:
337-
if response.body == nil {
338-
completion(.success(true as! T))
339-
return
340-
}
341-
let dict = try! JSONSerialization
342-
.jsonObject(with: response.body!) as? [String: Any]
343-
344-
completion(.success(convert?(dict!) ?? dict! as! T))
345-
}
346-
default:
347-
var message = ""
348-
var type = ""
349-
350-
if response.body == nil {
351-
completion(.failure({{ spec.title | caseUcfirst }}Error(
352-
message: "Unknown error with status code \(response.status.code)",
353-
code: Int(response.status.code)
354-
)))
355-
return
356-
}
357-
358-
do {
359-
let dict = try JSONSerialization
360-
.jsonObject(with: response.body!) as? [String: Any]
361-
362-
message = dict?["message"] as? String
363-
?? response.status.reasonPhrase
364-
365-
type = dict?["type"] as? String ?? ""
366-
} catch {
367-
message = response.body!.readString(length: response.body!.readableBytes)!
368-
}
369-
370-
let error = {{ spec.title | caseUcfirst }}Error(
371-
message: message,
372-
code: Int(response.status.code),
373-
type: type
273+
convert: (([String: Any]) -> T)? = nil
274+
) async throws -> T {
275+
func complete(with response: HTTPClientResponse) async throws -> T {
276+
switch response.status.code {
277+
case 0..<400:
278+
if response.headers["Set-Cookie"].count > 0 {
279+
UserDefaults.standard.set(
280+
response.headers["Set-Cookie"],
281+
forKey: URL(string: request.url)!.host! + "-cookies"
374282
)
283+
}
284+
switch T.self {
285+
case is Bool.Type:
286+
return true as! T
287+
case is ByteBuffer.Type:
288+
return response.body as! T
289+
default:
290+
let data = try await response.body.collect(upTo: Int.max)
291+
let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any]
375292

376-
completion(.failure(error))
293+
return convert?(dict!) ?? dict! as! T
377294
}
295+
default:
296+
var message = ""
297+
var data = try await response.body.collect(upTo: Int.max)
298+
299+
do {
300+
let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any]
301+
302+
message = dict?["message"] as? String ?? response.status.reasonPhrase
303+
} catch {
304+
message = data.readString(length: data.readableBytes)!
305+
}
306+
307+
throw {{ spec.title | caseUcfirst }}Error(
308+
message: message,
309+
code: Int(response.status.code)
310+
)
378311
}
379312
}
313+
314+
if bufferSink == nil {
315+
let response = try await http.execute(
316+
request,
317+
timeout: .seconds(30)
318+
)
319+
return try await complete(with: response)
320+
}
321+
322+
let response = try await http.execute(
323+
request,
324+
timeout: .seconds(30)
325+
)
326+
return try await complete(with: response)
380327
}
381328

382329
func chunkedUpload<T>(
@@ -385,60 +332,43 @@ open class Client {
385332
params: inout [String: Any?],
386333
paramName: String,
387334
convert: (([String: Any]) -> T)? = nil,
388-
onProgress: ((UploadProgress) -> Void)? = nil,
389-
completion: ((Result<T, {{ spec.title | caseUcfirst }}Error>) -> Void)? = nil
390-
) {
335+
onProgress: ((UploadProgress) -> Void)? = nil
336+
) async throws -> T {
391337
let file = params[paramName] as! File
392338
let size = file.buffer.readableBytes
393339

394340
if size < Client.chunkSize {
395-
call(
341+
return try await call(
396342
method: "POST",
397343
path: path,
398344
headers: headers,
399345
params: params,
400-
convert: convert,
401-
completion: completion
346+
convert: convert
402347
)
403-
return
404348
}
405349

406350
var input = file.buffer
407351
var offset = 0
408352
var result = [String:Any]()
409-
let group = DispatchGroup()
410353

411354
while offset < size {
412355
let slice = input.readSlice(length: Client.chunkSize)
413356
?? input.readSlice(length: Int(size - offset))
414-
357+
415358
params[paramName] = File(
416359
name: file.name,
417360
buffer: slice!
418361
)
419-
420-
headers["content-range"] = "bytes \(offset)-\(min((offset + Client.chunkSize) - 1, size))/\(size)"
421362

422-
group.enter()
363+
headers["content-range"] = "bytes \(offset)-\(min((offset + Client.chunkSize) - 1, size))/\(size)"
423364

424-
call(
365+
result = try await call(
425366
method: "POST",
426367
path: path,
427368
headers: headers,
428369
params: params,
429370
convert: { return $0 }
430-
) { response in
431-
switch response {
432-
case let .success(map):
433-
result = map
434-
group.leave()
435-
case let .failure(error):
436-
completion?(.failure(error))
437-
return
438-
}
439-
}
440-
441-
group.wait()
371+
)
442372

443373
offset += Client.chunkSize
444374
headers["x-{{ spec.title | caseLower }}-id"] = result["$id"] as? String
@@ -451,10 +381,9 @@ open class Client {
451381
))
452382
}
453383

454-
completion?(.success(convert!(result)))
384+
return convert!(result)
455385
}
456386

457-
458387
private static func randomBoundary() -> String {
459388
var string = ""
460389
for _ in 0..<16 {
@@ -464,16 +393,16 @@ open class Client {
464393
}
465394

466395
private func buildJSON(
467-
_ request: inout HTTPClient.Request,
396+
_ request: inout HTTPClientRequest,
468397
with params: [String: Any?] = [:]
469398
) throws {
470399
let json = try JSONSerialization.data(withJSONObject: params, options: [])
471400

472-
request.body = .data(json)
401+
request.body = .bytes(json)
473402
}
474403

475404
private func buildMultipart(
476-
_ request: inout HTTPClient.Request,
405+
_ request: inout HTTPClientRequest,
477406
with params: [String: Any?] = [:],
478407
chunked: Bool = false
479408
) {
@@ -528,7 +457,7 @@ open class Client {
528457
request.headers.add(name: "Content-Length", value: bodyBuffer.readableBytes.description)
529458
}
530459
request.headers.add(name: "Content-Type", value: "multipart/form-data;boundary=\"\(Client.boundary)\"")
531-
request.body = .byteBuffer(bodyBuffer)
460+
request.body = .bytes(bodyBuffer)
532461
}
533462

534463
private func addUserAgentHeader() {

templates/swift/Sources/Extensions/HTTPClientRequest+Cookies.swift.twig

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,19 @@ extension HTTPClient.Request {
99
}
1010
}
1111

12+
extension HTTPClientRequest {
13+
public mutating func addDomainCookies() {
14+
headers.addDomainCookies(for: URL(string: url)!.host!)
15+
}
16+
}
17+
1218
extension HTTPHeaders {
1319
public mutating func addDomainCookies(for domain: String) {
14-
let cookieJson = UserDefaults.standard.string(forKey: "\(domain)-cookies")
15-
let cookies: [HTTPClient.Cookie?]? = try? cookieJson?.fromJson(to: [HTTPClient.Cookie].self)
16-
?? [(try? cookieJson?.fromJson(to: HTTPClient.Cookie.self))]
17-
18-
var cookiesValue = ""
19-
for cookie in cookies ?? [] {
20-
if let cookie = cookie {
21-
cookiesValue += "\(cookie.name)=\(cookie.value);"
22-
}
20+
guard let cookies = UserDefaults.standard.stringArray(forKey: "\(domain)-cookies") else {
21+
return
2322
}
24-
add(name: "Cookie", value: cookiesValue)
25-
}
23+
for cookie in cookies {
24+
add(name: "Cookie", value: cookie)
25+
}
26+
}
2627
}

0 commit comments

Comments
 (0)