@kizunajs/swift 2.0.0-beta.19 → 2.0.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs
CHANGED
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as generateSwiftClient } from "./generator-
|
|
2
|
+
import { t as generateSwiftClient } from "./generator-2GeebFHv.mjs";
|
|
3
3
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
5
|
import { parseArgs } from "node:util";
|
|
@@ -1332,7 +1332,7 @@ const closure = (writer, opener, body) => {
|
|
|
1332
1332
|
writer.indent(body);
|
|
1333
1333
|
writer.line("}");
|
|
1334
1334
|
};
|
|
1335
|
-
const emitKizunaStreaming = (writer) => {
|
|
1335
|
+
const emitKizunaStreaming = (writer, clientName) => {
|
|
1336
1336
|
writer.blank();
|
|
1337
1337
|
writer.block("struct ServerSentEvent: Sendable", () => {
|
|
1338
1338
|
writer.line("let event: String?");
|
|
@@ -1341,22 +1341,39 @@ const emitKizunaStreaming = (writer) => {
|
|
|
1341
1341
|
writer.line("let retry: Int?");
|
|
1342
1342
|
});
|
|
1343
1343
|
writer.blank();
|
|
1344
|
-
writer.block(
|
|
1344
|
+
writer.block(`static func open<Failure: KizunaFailure>(_ request: inout URLRequest, session: URLSession, retry: ${clientName}.RetryPolicy?, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?, failure: Failure.Type) async throws(Failure) -> (URLSession.AsyncBytes, Int, HTTPURLResponse)`, () => {
|
|
1345
1345
|
writer.line("if let requestMiddleware {");
|
|
1346
1346
|
writer.line(" do { try await requestMiddleware(&request) }");
|
|
1347
1347
|
writer.line(" catch is CancellationError { throw Failure.cancelled }");
|
|
1348
1348
|
writer.line(" catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
|
|
1349
1349
|
writer.line(" catch { throw Failure.requestFailed(error) }");
|
|
1350
1350
|
writer.line("}");
|
|
1351
|
-
writer.line("let
|
|
1352
|
-
writer.line("
|
|
1353
|
-
writer.
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1351
|
+
writer.line("let policy = retryPolicy(for: request, retry)");
|
|
1352
|
+
writer.line("var attempt = 1");
|
|
1353
|
+
writer.block("while true", () => {
|
|
1354
|
+
writer.line("let bytes: URLSession.AsyncBytes");
|
|
1355
|
+
writer.line("let response: URLResponse");
|
|
1356
|
+
writer.line("do { (bytes, response) = try await session.bytes(for: request) }");
|
|
1357
|
+
writer.line("catch is CancellationError { throw Failure.cancelled }");
|
|
1358
|
+
writer.line("catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
|
|
1359
|
+
writer.line("catch {");
|
|
1360
|
+
writer.line(" if let policy, let delay = retryDelay(policy, attempt: attempt, response: nil) {");
|
|
1361
|
+
writer.line(" try await pause(delay, failure: Failure.self)");
|
|
1362
|
+
writer.line(" attempt += 1");
|
|
1363
|
+
writer.line(" continue");
|
|
1364
|
+
writer.line(" }");
|
|
1365
|
+
writer.line(" throw Failure.requestFailed(error)");
|
|
1366
|
+
writer.line("}");
|
|
1367
|
+
writer.line("if let responseMiddleware { await responseMiddleware(request, Foundation.Data(), response) }");
|
|
1368
|
+
writer.line("guard let httpResponse = response as? HTTPURLResponse else { throw Failure.invalidResponse }");
|
|
1369
|
+
writer.line("if let policy, let delay = retryDelay(policy, attempt: attempt, response: httpResponse) {");
|
|
1370
|
+
writer.line(" bytes.task.cancel()");
|
|
1371
|
+
writer.line(" try await pause(delay, failure: Failure.self)");
|
|
1372
|
+
writer.line(" attempt += 1");
|
|
1373
|
+
writer.line(" continue");
|
|
1374
|
+
writer.line("}");
|
|
1375
|
+
writer.line("return (bytes, httpResponse.statusCode, httpResponse)");
|
|
1376
|
+
});
|
|
1360
1377
|
});
|
|
1361
1378
|
writer.blank();
|
|
1362
1379
|
writer.block("static func collect<Failure: KizunaFailure>(_ bytes: URLSession.AsyncBytes, failure: Failure.Type) async throws(Failure) -> Foundation.Data", () => {
|
|
@@ -1560,22 +1577,68 @@ const emitKizunaNamespace = (writer, options) => {
|
|
|
1560
1577
|
writer.line("catch { throw Failure.requestFailed(error) }");
|
|
1561
1578
|
});
|
|
1562
1579
|
writer.blank();
|
|
1563
|
-
writer.block(
|
|
1580
|
+
writer.block(`static func send<Failure: KizunaFailure>(_ request: inout URLRequest, session: URLSession, retry: ${options.clientName}.RetryPolicy?, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?, failure: Failure.Type) async throws(Failure) -> (Foundation.Data, Int, HTTPURLResponse)`, () => {
|
|
1564
1581
|
writer.line("if let requestMiddleware {");
|
|
1565
1582
|
writer.line(" do { try await requestMiddleware(&request) }");
|
|
1566
1583
|
writer.line(" catch is CancellationError { throw Failure.cancelled }");
|
|
1567
1584
|
writer.line(" catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
|
|
1568
1585
|
writer.line(" catch { throw Failure.requestFailed(error) }");
|
|
1569
1586
|
writer.line("}");
|
|
1570
|
-
writer.line("let
|
|
1571
|
-
writer.line("
|
|
1572
|
-
writer.
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1587
|
+
writer.line("let policy = retryPolicy(for: request, retry)");
|
|
1588
|
+
writer.line("var attempt = 1");
|
|
1589
|
+
writer.block("while true", () => {
|
|
1590
|
+
writer.line("let data: Foundation.Data");
|
|
1591
|
+
writer.line("let response: URLResponse");
|
|
1592
|
+
writer.line("do { (data, response) = try await session.data(for: request) }");
|
|
1593
|
+
writer.line("catch is CancellationError { throw Failure.cancelled }");
|
|
1594
|
+
writer.line("catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
|
|
1595
|
+
writer.line("catch {");
|
|
1596
|
+
writer.line(" if let policy, let delay = retryDelay(policy, attempt: attempt, response: nil) {");
|
|
1597
|
+
writer.line(" try await pause(delay, failure: Failure.self)");
|
|
1598
|
+
writer.line(" attempt += 1");
|
|
1599
|
+
writer.line(" continue");
|
|
1600
|
+
writer.line(" }");
|
|
1601
|
+
writer.line(" throw Failure.requestFailed(error)");
|
|
1602
|
+
writer.line("}");
|
|
1603
|
+
writer.line("if let responseMiddleware { await responseMiddleware(request, data, response) }");
|
|
1604
|
+
writer.line("guard let httpResponse = response as? HTTPURLResponse else { throw Failure.invalidResponse }");
|
|
1605
|
+
writer.line("if let policy, let delay = retryDelay(policy, attempt: attempt, response: httpResponse) {");
|
|
1606
|
+
writer.line(" try await pause(delay, failure: Failure.self)");
|
|
1607
|
+
writer.line(" attempt += 1");
|
|
1608
|
+
writer.line(" continue");
|
|
1609
|
+
writer.line("}");
|
|
1610
|
+
writer.line("return (data, httpResponse.statusCode, httpResponse)");
|
|
1611
|
+
});
|
|
1612
|
+
});
|
|
1613
|
+
writer.blank();
|
|
1614
|
+
writer.block(`static func retryPolicy(for request: URLRequest, _ retry: ${options.clientName}.RetryPolicy?) -> ${options.clientName}.RetryPolicy?`, () => {
|
|
1615
|
+
writer.line("let idempotent = [\"GET\", \"HEAD\", \"OPTIONS\", \"PUT\", \"DELETE\"].contains(request.httpMethod ?? \"GET\")");
|
|
1616
|
+
writer.line("return idempotent && request.httpBodyStream == nil ? retry : nil");
|
|
1617
|
+
});
|
|
1618
|
+
writer.blank();
|
|
1619
|
+
writer.block(`static func retryDelay(_ policy: ${options.clientName}.RetryPolicy, attempt: Int, response: HTTPURLResponse?) -> TimeInterval?`, () => {
|
|
1620
|
+
writer.line("guard attempt < policy.maximumAttempts else { return nil }");
|
|
1621
|
+
writer.line("let backoff = Double.random(in: 0...min(policy.maximumDelay, policy.initialDelay * pow(2, Double(attempt - 1))))");
|
|
1622
|
+
writer.line("guard let response else { return backoff }");
|
|
1623
|
+
writer.line("guard [429, 503].contains(response.statusCode) else { return nil }");
|
|
1624
|
+
writer.line("guard let asked = retryAfter(response.value(forHTTPHeaderField: \"Retry-After\")) else { return backoff }");
|
|
1625
|
+
writer.line("return asked <= policy.maximumDelay ? asked : nil");
|
|
1626
|
+
});
|
|
1627
|
+
writer.blank();
|
|
1628
|
+
writer.block("static func retryAfter(_ header: String?) -> TimeInterval?", () => {
|
|
1629
|
+
writer.line("guard let value = header?.trimmingCharacters(in: .whitespaces) else { return nil }");
|
|
1630
|
+
writer.line("if let seconds = Int(value), seconds >= 0 { return TimeInterval(seconds) }");
|
|
1631
|
+
writer.line("let formatter = DateFormatter()");
|
|
1632
|
+
writer.line("formatter.locale = Locale(identifier: \"en_US_POSIX\")");
|
|
1633
|
+
writer.line("formatter.timeZone = TimeZone(identifier: \"GMT\")");
|
|
1634
|
+
writer.line("formatter.dateFormat = \"EEE, dd MMM yyyy HH:mm:ss zzz\"");
|
|
1635
|
+
writer.line("guard let date = formatter.date(from: value) else { return nil }");
|
|
1636
|
+
writer.line("return max(0, date.timeIntervalSinceNow)");
|
|
1637
|
+
});
|
|
1638
|
+
writer.blank();
|
|
1639
|
+
writer.block("static func pause<Failure: KizunaFailure>(_ seconds: TimeInterval, failure: Failure.Type) async throws(Failure)", () => {
|
|
1640
|
+
writer.line("do { try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) }");
|
|
1641
|
+
writer.line("catch { throw Failure.cancelled }");
|
|
1579
1642
|
});
|
|
1580
1643
|
writer.blank();
|
|
1581
1644
|
writer.block("static func decode<Value: Decodable, Failure: KizunaDecodableFailure>(_ type: Value.Type, from data: Foundation.Data, using decoder: JSONDecoder, statusCode: Int, failure: Failure.Type) throws(Failure) -> Value", () => {
|
|
@@ -1591,7 +1654,7 @@ const emitKizunaNamespace = (writer, options) => {
|
|
|
1591
1654
|
writer.line("return Failure.decoding(DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: \"No matching type for status \\(statusCode)\")), statusCode: statusCode, data: data)");
|
|
1592
1655
|
});
|
|
1593
1656
|
}
|
|
1594
|
-
if (options.streaming) emitKizunaStreaming(writer);
|
|
1657
|
+
if (options.streaming) emitKizunaStreaming(writer, options.clientName);
|
|
1595
1658
|
if (options.multipart) {
|
|
1596
1659
|
writer.blank();
|
|
1597
1660
|
writer.block("struct MultipartBuilder", () => {
|
|
@@ -1734,7 +1797,7 @@ const emitMethodBody = (writer, method, context, receiver = "") => {
|
|
|
1734
1797
|
emitStreamMethodTail(writer, method, context, receiver, responseBinding);
|
|
1735
1798
|
return;
|
|
1736
1799
|
}
|
|
1737
|
-
writer.line(`let (data, statusCode, ${responseBinding}) = try await Kizuna.send(&request, session: ${receiver}session, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
|
|
1800
|
+
writer.line(`let (data, statusCode, ${responseBinding}) = try await Kizuna.send(&request, session: ${receiver}session, retry: ${receiver}retry, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
|
|
1738
1801
|
writer.line("switch statusCode {");
|
|
1739
1802
|
for (const successResponse of method.successResponses) {
|
|
1740
1803
|
writer.line(`case ${successResponse.status}:`);
|
|
@@ -1809,7 +1872,7 @@ const emitStreamMethodTail = (writer, method, context, receiver, responseBinding
|
|
|
1809
1872
|
const qualifiedResult = `${context.clientName}.${method.operationName}.${method.resultWrapperName}`;
|
|
1810
1873
|
const hasHeaders = method.resultHeaderFields.length > 0;
|
|
1811
1874
|
const collect = `let data = try await Kizuna.collect(bytes, failure: ${failure}.self)`;
|
|
1812
|
-
writer.line(`let (bytes, statusCode, ${responseBinding}) = try await Kizuna.open(&request, session: ${receiver}session, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
|
|
1875
|
+
writer.line(`let (bytes, statusCode, ${responseBinding}) = try await Kizuna.open(&request, session: ${receiver}session, retry: ${receiver}retry, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
|
|
1813
1876
|
writer.line("switch statusCode {");
|
|
1814
1877
|
writer.line(`case ${stream.status}:`);
|
|
1815
1878
|
writer.indent(() => {
|
|
@@ -1943,6 +2006,22 @@ const emitClient = (writer, config, partition, context, typesByOperation) => {
|
|
|
1943
2006
|
});
|
|
1944
2007
|
});
|
|
1945
2008
|
}
|
|
2009
|
+
writer.blank();
|
|
2010
|
+
writer.docComment("Retries a GET, HEAD, OPTIONS, PUT or DELETE that failed on the network or was answered 429 or 503, waiting what Retry-After asks or backing off.");
|
|
2011
|
+
writer.block("public struct RetryPolicy: Sendable", () => {
|
|
2012
|
+
writer.docComment("Attempts in all, the first included.");
|
|
2013
|
+
writer.line("public var maximumAttempts: Int");
|
|
2014
|
+
writer.docComment("Seconds the first backoff waits at most.");
|
|
2015
|
+
writer.line("public var initialDelay: TimeInterval");
|
|
2016
|
+
writer.docComment("Seconds any wait may take.");
|
|
2017
|
+
writer.line("public var maximumDelay: TimeInterval");
|
|
2018
|
+
writer.blank();
|
|
2019
|
+
writer.block("public init(maximumAttempts: Int = 3, initialDelay: TimeInterval = 0.5, maximumDelay: TimeInterval = 30)", () => {
|
|
2020
|
+
writer.line("self.maximumAttempts = maximumAttempts");
|
|
2021
|
+
writer.line("self.initialDelay = initialDelay");
|
|
2022
|
+
writer.line("self.maximumDelay = maximumDelay");
|
|
2023
|
+
});
|
|
2024
|
+
});
|
|
1946
2025
|
if (usesMultipart) {
|
|
1947
2026
|
writer.blank();
|
|
1948
2027
|
writer.block("public struct MultipartFile: Sendable, Equatable", () => {
|
|
@@ -2040,14 +2119,16 @@ const emitClient = (writer, config, partition, context, typesByOperation) => {
|
|
|
2040
2119
|
if (contextFields.length > 0) writer.line("let requestContextHeaders: [String: String]");
|
|
2041
2120
|
writer.line("public let requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?");
|
|
2042
2121
|
writer.line("public let responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?");
|
|
2122
|
+
writer.line("public let retry: RetryPolicy?");
|
|
2043
2123
|
writer.line("let encoder: JSONEncoder");
|
|
2044
2124
|
writer.line("let decoder: JSONDecoder");
|
|
2045
2125
|
writer.blank();
|
|
2046
2126
|
const contextInitParam = contextFields.length > 0 ? `, requestContext: RequestContext${contextRequired ? "" : " = RequestContext()"}` : "";
|
|
2047
|
-
writer.block(`public init(baseURL: URL, session: URLSession = .shared, timeout: TimeInterval = 30${contextInitParam}, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)? = nil, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)? = nil)`, () => {
|
|
2127
|
+
writer.block(`public init(baseURL: URL, session: URLSession = .shared, timeout: TimeInterval = 30, retry: RetryPolicy? = nil${contextInitParam}, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)? = nil, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)? = nil)`, () => {
|
|
2048
2128
|
writer.line("self.baseURL = baseURL");
|
|
2049
2129
|
writer.line("self.session = session");
|
|
2050
2130
|
writer.line("self.timeout = timeout");
|
|
2131
|
+
writer.line("self.retry = retry");
|
|
2051
2132
|
if (contextFields.length > 0) writer.line("self.requestContextHeaders = requestContext.headerFields");
|
|
2052
2133
|
writer.line("self.requestMiddleware = requestMiddleware");
|
|
2053
2134
|
writer.line("self.responseMiddleware = responseMiddleware");
|
|
@@ -1332,7 +1332,7 @@ const closure = (writer, opener, body) => {
|
|
|
1332
1332
|
writer.indent(body);
|
|
1333
1333
|
writer.line("}");
|
|
1334
1334
|
};
|
|
1335
|
-
const emitKizunaStreaming = (writer) => {
|
|
1335
|
+
const emitKizunaStreaming = (writer, clientName) => {
|
|
1336
1336
|
writer.blank();
|
|
1337
1337
|
writer.block("struct ServerSentEvent: Sendable", () => {
|
|
1338
1338
|
writer.line("let event: String?");
|
|
@@ -1341,22 +1341,39 @@ const emitKizunaStreaming = (writer) => {
|
|
|
1341
1341
|
writer.line("let retry: Int?");
|
|
1342
1342
|
});
|
|
1343
1343
|
writer.blank();
|
|
1344
|
-
writer.block(
|
|
1344
|
+
writer.block(`static func open<Failure: KizunaFailure>(_ request: inout URLRequest, session: URLSession, retry: ${clientName}.RetryPolicy?, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?, failure: Failure.Type) async throws(Failure) -> (URLSession.AsyncBytes, Int, HTTPURLResponse)`, () => {
|
|
1345
1345
|
writer.line("if let requestMiddleware {");
|
|
1346
1346
|
writer.line(" do { try await requestMiddleware(&request) }");
|
|
1347
1347
|
writer.line(" catch is CancellationError { throw Failure.cancelled }");
|
|
1348
1348
|
writer.line(" catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
|
|
1349
1349
|
writer.line(" catch { throw Failure.requestFailed(error) }");
|
|
1350
1350
|
writer.line("}");
|
|
1351
|
-
writer.line("let
|
|
1352
|
-
writer.line("
|
|
1353
|
-
writer.
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1351
|
+
writer.line("let policy = retryPolicy(for: request, retry)");
|
|
1352
|
+
writer.line("var attempt = 1");
|
|
1353
|
+
writer.block("while true", () => {
|
|
1354
|
+
writer.line("let bytes: URLSession.AsyncBytes");
|
|
1355
|
+
writer.line("let response: URLResponse");
|
|
1356
|
+
writer.line("do { (bytes, response) = try await session.bytes(for: request) }");
|
|
1357
|
+
writer.line("catch is CancellationError { throw Failure.cancelled }");
|
|
1358
|
+
writer.line("catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
|
|
1359
|
+
writer.line("catch {");
|
|
1360
|
+
writer.line(" if let policy, let delay = retryDelay(policy, attempt: attempt, response: nil) {");
|
|
1361
|
+
writer.line(" try await pause(delay, failure: Failure.self)");
|
|
1362
|
+
writer.line(" attempt += 1");
|
|
1363
|
+
writer.line(" continue");
|
|
1364
|
+
writer.line(" }");
|
|
1365
|
+
writer.line(" throw Failure.requestFailed(error)");
|
|
1366
|
+
writer.line("}");
|
|
1367
|
+
writer.line("if let responseMiddleware { await responseMiddleware(request, Foundation.Data(), response) }");
|
|
1368
|
+
writer.line("guard let httpResponse = response as? HTTPURLResponse else { throw Failure.invalidResponse }");
|
|
1369
|
+
writer.line("if let policy, let delay = retryDelay(policy, attempt: attempt, response: httpResponse) {");
|
|
1370
|
+
writer.line(" bytes.task.cancel()");
|
|
1371
|
+
writer.line(" try await pause(delay, failure: Failure.self)");
|
|
1372
|
+
writer.line(" attempt += 1");
|
|
1373
|
+
writer.line(" continue");
|
|
1374
|
+
writer.line("}");
|
|
1375
|
+
writer.line("return (bytes, httpResponse.statusCode, httpResponse)");
|
|
1376
|
+
});
|
|
1360
1377
|
});
|
|
1361
1378
|
writer.blank();
|
|
1362
1379
|
writer.block("static func collect<Failure: KizunaFailure>(_ bytes: URLSession.AsyncBytes, failure: Failure.Type) async throws(Failure) -> Foundation.Data", () => {
|
|
@@ -1560,22 +1577,68 @@ const emitKizunaNamespace = (writer, options) => {
|
|
|
1560
1577
|
writer.line("catch { throw Failure.requestFailed(error) }");
|
|
1561
1578
|
});
|
|
1562
1579
|
writer.blank();
|
|
1563
|
-
writer.block(
|
|
1580
|
+
writer.block(`static func send<Failure: KizunaFailure>(_ request: inout URLRequest, session: URLSession, retry: ${options.clientName}.RetryPolicy?, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?, failure: Failure.Type) async throws(Failure) -> (Foundation.Data, Int, HTTPURLResponse)`, () => {
|
|
1564
1581
|
writer.line("if let requestMiddleware {");
|
|
1565
1582
|
writer.line(" do { try await requestMiddleware(&request) }");
|
|
1566
1583
|
writer.line(" catch is CancellationError { throw Failure.cancelled }");
|
|
1567
1584
|
writer.line(" catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
|
|
1568
1585
|
writer.line(" catch { throw Failure.requestFailed(error) }");
|
|
1569
1586
|
writer.line("}");
|
|
1570
|
-
writer.line("let
|
|
1571
|
-
writer.line("
|
|
1572
|
-
writer.
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1587
|
+
writer.line("let policy = retryPolicy(for: request, retry)");
|
|
1588
|
+
writer.line("var attempt = 1");
|
|
1589
|
+
writer.block("while true", () => {
|
|
1590
|
+
writer.line("let data: Foundation.Data");
|
|
1591
|
+
writer.line("let response: URLResponse");
|
|
1592
|
+
writer.line("do { (data, response) = try await session.data(for: request) }");
|
|
1593
|
+
writer.line("catch is CancellationError { throw Failure.cancelled }");
|
|
1594
|
+
writer.line("catch let error as URLError where error.code == .cancelled { throw Failure.cancelled }");
|
|
1595
|
+
writer.line("catch {");
|
|
1596
|
+
writer.line(" if let policy, let delay = retryDelay(policy, attempt: attempt, response: nil) {");
|
|
1597
|
+
writer.line(" try await pause(delay, failure: Failure.self)");
|
|
1598
|
+
writer.line(" attempt += 1");
|
|
1599
|
+
writer.line(" continue");
|
|
1600
|
+
writer.line(" }");
|
|
1601
|
+
writer.line(" throw Failure.requestFailed(error)");
|
|
1602
|
+
writer.line("}");
|
|
1603
|
+
writer.line("if let responseMiddleware { await responseMiddleware(request, data, response) }");
|
|
1604
|
+
writer.line("guard let httpResponse = response as? HTTPURLResponse else { throw Failure.invalidResponse }");
|
|
1605
|
+
writer.line("if let policy, let delay = retryDelay(policy, attempt: attempt, response: httpResponse) {");
|
|
1606
|
+
writer.line(" try await pause(delay, failure: Failure.self)");
|
|
1607
|
+
writer.line(" attempt += 1");
|
|
1608
|
+
writer.line(" continue");
|
|
1609
|
+
writer.line("}");
|
|
1610
|
+
writer.line("return (data, httpResponse.statusCode, httpResponse)");
|
|
1611
|
+
});
|
|
1612
|
+
});
|
|
1613
|
+
writer.blank();
|
|
1614
|
+
writer.block(`static func retryPolicy(for request: URLRequest, _ retry: ${options.clientName}.RetryPolicy?) -> ${options.clientName}.RetryPolicy?`, () => {
|
|
1615
|
+
writer.line("let idempotent = [\"GET\", \"HEAD\", \"OPTIONS\", \"PUT\", \"DELETE\"].contains(request.httpMethod ?? \"GET\")");
|
|
1616
|
+
writer.line("return idempotent && request.httpBodyStream == nil ? retry : nil");
|
|
1617
|
+
});
|
|
1618
|
+
writer.blank();
|
|
1619
|
+
writer.block(`static func retryDelay(_ policy: ${options.clientName}.RetryPolicy, attempt: Int, response: HTTPURLResponse?) -> TimeInterval?`, () => {
|
|
1620
|
+
writer.line("guard attempt < policy.maximumAttempts else { return nil }");
|
|
1621
|
+
writer.line("let backoff = Double.random(in: 0...min(policy.maximumDelay, policy.initialDelay * pow(2, Double(attempt - 1))))");
|
|
1622
|
+
writer.line("guard let response else { return backoff }");
|
|
1623
|
+
writer.line("guard [429, 503].contains(response.statusCode) else { return nil }");
|
|
1624
|
+
writer.line("guard let asked = retryAfter(response.value(forHTTPHeaderField: \"Retry-After\")) else { return backoff }");
|
|
1625
|
+
writer.line("return asked <= policy.maximumDelay ? asked : nil");
|
|
1626
|
+
});
|
|
1627
|
+
writer.blank();
|
|
1628
|
+
writer.block("static func retryAfter(_ header: String?) -> TimeInterval?", () => {
|
|
1629
|
+
writer.line("guard let value = header?.trimmingCharacters(in: .whitespaces) else { return nil }");
|
|
1630
|
+
writer.line("if let seconds = Int(value), seconds >= 0 { return TimeInterval(seconds) }");
|
|
1631
|
+
writer.line("let formatter = DateFormatter()");
|
|
1632
|
+
writer.line("formatter.locale = Locale(identifier: \"en_US_POSIX\")");
|
|
1633
|
+
writer.line("formatter.timeZone = TimeZone(identifier: \"GMT\")");
|
|
1634
|
+
writer.line("formatter.dateFormat = \"EEE, dd MMM yyyy HH:mm:ss zzz\"");
|
|
1635
|
+
writer.line("guard let date = formatter.date(from: value) else { return nil }");
|
|
1636
|
+
writer.line("return max(0, date.timeIntervalSinceNow)");
|
|
1637
|
+
});
|
|
1638
|
+
writer.blank();
|
|
1639
|
+
writer.block("static func pause<Failure: KizunaFailure>(_ seconds: TimeInterval, failure: Failure.Type) async throws(Failure)", () => {
|
|
1640
|
+
writer.line("do { try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000)) }");
|
|
1641
|
+
writer.line("catch { throw Failure.cancelled }");
|
|
1579
1642
|
});
|
|
1580
1643
|
writer.blank();
|
|
1581
1644
|
writer.block("static func decode<Value: Decodable, Failure: KizunaDecodableFailure>(_ type: Value.Type, from data: Foundation.Data, using decoder: JSONDecoder, statusCode: Int, failure: Failure.Type) throws(Failure) -> Value", () => {
|
|
@@ -1591,7 +1654,7 @@ const emitKizunaNamespace = (writer, options) => {
|
|
|
1591
1654
|
writer.line("return Failure.decoding(DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: \"No matching type for status \\(statusCode)\")), statusCode: statusCode, data: data)");
|
|
1592
1655
|
});
|
|
1593
1656
|
}
|
|
1594
|
-
if (options.streaming) emitKizunaStreaming(writer);
|
|
1657
|
+
if (options.streaming) emitKizunaStreaming(writer, options.clientName);
|
|
1595
1658
|
if (options.multipart) {
|
|
1596
1659
|
writer.blank();
|
|
1597
1660
|
writer.block("struct MultipartBuilder", () => {
|
|
@@ -1734,7 +1797,7 @@ const emitMethodBody = (writer, method, context, receiver = "") => {
|
|
|
1734
1797
|
emitStreamMethodTail(writer, method, context, receiver, responseBinding);
|
|
1735
1798
|
return;
|
|
1736
1799
|
}
|
|
1737
|
-
writer.line(`let (data, statusCode, ${responseBinding}) = try await Kizuna.send(&request, session: ${receiver}session, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
|
|
1800
|
+
writer.line(`let (data, statusCode, ${responseBinding}) = try await Kizuna.send(&request, session: ${receiver}session, retry: ${receiver}retry, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
|
|
1738
1801
|
writer.line("switch statusCode {");
|
|
1739
1802
|
for (const successResponse of method.successResponses) {
|
|
1740
1803
|
writer.line(`case ${successResponse.status}:`);
|
|
@@ -1809,7 +1872,7 @@ const emitStreamMethodTail = (writer, method, context, receiver, responseBinding
|
|
|
1809
1872
|
const qualifiedResult = `${context.clientName}.${method.operationName}.${method.resultWrapperName}`;
|
|
1810
1873
|
const hasHeaders = method.resultHeaderFields.length > 0;
|
|
1811
1874
|
const collect = `let data = try await Kizuna.collect(bytes, failure: ${failure}.self)`;
|
|
1812
|
-
writer.line(`let (bytes, statusCode, ${responseBinding}) = try await Kizuna.open(&request, session: ${receiver}session, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
|
|
1875
|
+
writer.line(`let (bytes, statusCode, ${responseBinding}) = try await Kizuna.open(&request, session: ${receiver}session, retry: ${receiver}retry, requestMiddleware: ${receiver}requestMiddleware, responseMiddleware: ${receiver}responseMiddleware, failure: ${failure}.self)`);
|
|
1813
1876
|
writer.line("switch statusCode {");
|
|
1814
1877
|
writer.line(`case ${stream.status}:`);
|
|
1815
1878
|
writer.indent(() => {
|
|
@@ -1943,6 +2006,22 @@ const emitClient = (writer, config, partition, context, typesByOperation) => {
|
|
|
1943
2006
|
});
|
|
1944
2007
|
});
|
|
1945
2008
|
}
|
|
2009
|
+
writer.blank();
|
|
2010
|
+
writer.docComment("Retries a GET, HEAD, OPTIONS, PUT or DELETE that failed on the network or was answered 429 or 503, waiting what Retry-After asks or backing off.");
|
|
2011
|
+
writer.block("public struct RetryPolicy: Sendable", () => {
|
|
2012
|
+
writer.docComment("Attempts in all, the first included.");
|
|
2013
|
+
writer.line("public var maximumAttempts: Int");
|
|
2014
|
+
writer.docComment("Seconds the first backoff waits at most.");
|
|
2015
|
+
writer.line("public var initialDelay: TimeInterval");
|
|
2016
|
+
writer.docComment("Seconds any wait may take.");
|
|
2017
|
+
writer.line("public var maximumDelay: TimeInterval");
|
|
2018
|
+
writer.blank();
|
|
2019
|
+
writer.block("public init(maximumAttempts: Int = 3, initialDelay: TimeInterval = 0.5, maximumDelay: TimeInterval = 30)", () => {
|
|
2020
|
+
writer.line("self.maximumAttempts = maximumAttempts");
|
|
2021
|
+
writer.line("self.initialDelay = initialDelay");
|
|
2022
|
+
writer.line("self.maximumDelay = maximumDelay");
|
|
2023
|
+
});
|
|
2024
|
+
});
|
|
1946
2025
|
if (usesMultipart) {
|
|
1947
2026
|
writer.blank();
|
|
1948
2027
|
writer.block("public struct MultipartFile: Sendable, Equatable", () => {
|
|
@@ -2040,14 +2119,16 @@ const emitClient = (writer, config, partition, context, typesByOperation) => {
|
|
|
2040
2119
|
if (contextFields.length > 0) writer.line("let requestContextHeaders: [String: String]");
|
|
2041
2120
|
writer.line("public let requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)?");
|
|
2042
2121
|
writer.line("public let responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)?");
|
|
2122
|
+
writer.line("public let retry: RetryPolicy?");
|
|
2043
2123
|
writer.line("let encoder: JSONEncoder");
|
|
2044
2124
|
writer.line("let decoder: JSONDecoder");
|
|
2045
2125
|
writer.blank();
|
|
2046
2126
|
const contextInitParam = contextFields.length > 0 ? `, requestContext: RequestContext${contextRequired ? "" : " = RequestContext()"}` : "";
|
|
2047
|
-
writer.block(`public init(baseURL: URL, session: URLSession = .shared, timeout: TimeInterval = 30${contextInitParam}, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)? = nil, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)? = nil)`, () => {
|
|
2127
|
+
writer.block(`public init(baseURL: URL, session: URLSession = .shared, timeout: TimeInterval = 30, retry: RetryPolicy? = nil${contextInitParam}, requestMiddleware: (@Sendable (inout URLRequest) async throws -> Void)? = nil, responseMiddleware: (@Sendable (URLRequest, Foundation.Data, URLResponse) async -> Void)? = nil)`, () => {
|
|
2048
2128
|
writer.line("self.baseURL = baseURL");
|
|
2049
2129
|
writer.line("self.session = session");
|
|
2050
2130
|
writer.line("self.timeout = timeout");
|
|
2131
|
+
writer.line("self.retry = retry");
|
|
2051
2132
|
if (contextFields.length > 0) writer.line("self.requestContextHeaders = requestContext.headerFields");
|
|
2052
2133
|
writer.line("self.requestMiddleware = requestMiddleware");
|
|
2053
2134
|
writer.line("self.responseMiddleware = responseMiddleware");
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_generator = require("./generator-
|
|
2
|
+
const require_generator = require("./generator-Bk6JRrTJ.cjs");
|
|
3
3
|
let kizunajs_generator = require("kizunajs/generator");
|
|
4
4
|
let zod = require("zod");
|
|
5
5
|
//#region src/client-target.ts
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kizunajs/swift",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.20",
|
|
4
4
|
"description": "Swift client generation for Kizuna.js APIs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kizunajs",
|
|
@@ -59,14 +59,14 @@
|
|
|
59
59
|
"access": "public"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@kizunajs/cli": "2.0.0-beta.
|
|
62
|
+
"@kizunajs/cli": "2.0.0-beta.20"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"kizunajs": "2.0.0-beta.
|
|
65
|
+
"kizunajs": "2.0.0-beta.20",
|
|
66
66
|
"zod": "^4.0.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
|
-
"kizunajs": "2.0.0-beta.
|
|
69
|
+
"kizunajs": "2.0.0-beta.20",
|
|
70
70
|
"@types/node": "^24",
|
|
71
71
|
"tsdown": "^0.21.0",
|
|
72
72
|
"typescript": "^6.0.3",
|