From 263e2c88d57e0be863d45c8302b117363902eb61 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Wed, 29 Apr 2026 11:42:20 +0500 Subject: [PATCH 01/40] Bootstrap Swift SDK generation --- .gitmodules | 4 ++++ Makefile | 4 ++++ codegen/config-swift.json | 9 +++++++++ codegen/generate-swift.bash | 34 ++++++++++++++++++++++++++++++++++ scripts/generate-all.bash | 15 +++++++++++++-- submodules/swift | 1 + 6 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 codegen/config-swift.json create mode 100755 codegen/generate-swift.bash create mode 160000 submodules/swift diff --git a/.gitmodules b/.gitmodules index 03371243..15180335 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,7 @@ [submodule "submodules/dart"] path = submodules/dart url = ../Aspose.BarCode-Cloud-SDK-for-Dart +[submodule "submodules/swift"] + path = submodules/swift + url = ../Aspose.BarCode-Cloud-SDK-for-Swift + branch = main diff --git a/Makefile b/Makefile index fe359b7f..6f0d7bd2 100644 --- a/Makefile +++ b/Makefile @@ -62,3 +62,7 @@ php: .PHONY: python python: cd codegen && ./generate-python.bash + +.PHONY: swift +swift: + cd codegen && ./generate-swift.bash diff --git a/codegen/config-swift.json b/codegen/config-swift.json new file mode 100644 index 00000000..3f63a021 --- /dev/null +++ b/codegen/config-swift.json @@ -0,0 +1,9 @@ +{ + "allowUnicodeIdentifiers": true, + "enumUnknownDefaultCase": true, + "hideGenerationTimestamp": true, + "mapFileBinaryToData": true, + "projectName": "AsposeBarcodeCloud", + "swiftPackagePath": "Sources/AsposeBarcodeCloud", + "useSPMFileStructure": true +} diff --git a/codegen/generate-swift.bash b/codegen/generate-swift.bash new file mode 100755 index 00000000..7747db0c --- /dev/null +++ b/codegen/generate-swift.bash @@ -0,0 +1,34 @@ +#!/bin/bash +set -euo pipefail + +specSource="../spec/aspose-barcode-cloud.json" +tempDir=".generated/swift" +targetDir="../submodules/swift" + +if [ -d "$tempDir" ]; +then + rm -rf "$tempDir" +fi + +# java -jar Tools/openapi-generator-cli.jar config-help -g swift5 ; exit 1 +# java -DdebugModels -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift5 -o "$tempDir" -c config-swift.json > debugModels.swift.json ; exit +# java -DdebugOperations -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift5 -o "$tempDir" -c config-swift.json > debugOperations.swift.json ; exit +java -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift5 -o "$tempDir" -c config-swift.json + +mkdir -p "$targetDir/Sources" +rm -rf "$targetDir/Sources/AsposeBarcodeCloud" +mv "$tempDir/Sources/AsposeBarcodeCloud" "$targetDir/Sources/AsposeBarcodeCloud" + +rm -rf "$targetDir/docs" +mv "$tempDir/docs" "$targetDir/docs" + +mv "$tempDir/.openapi-generator-ignore" "$targetDir/" +mv "$tempDir/.swiftformat" "$targetDir/" + +mkdir -p "$targetDir/.openapi-generator" +mv "$tempDir/.openapi-generator/VERSION" "$targetDir/.openapi-generator/" +mv "$tempDir/.openapi-generator/FILES" "$targetDir/.openapi-generator/" + +cp ../LICENSE "$targetDir/" + +rm -rf "$tempDir" diff --git a/scripts/generate-all.bash b/scripts/generate-all.bash index d1145bf3..138c0cc4 100755 --- a/scripts/generate-all.bash +++ b/scripts/generate-all.bash @@ -4,9 +4,20 @@ set -euo pipefail pushd "$( dirname "${BASH_SOURCE[0]}" )/../codegen" -for gen in generate-*.bash +generators=( + android + dart + dotnet + go + java + node + php + python +) + +for gen in "${generators[@]}" do - "./${gen}" + "./generate-${gen}.bash" done popd >/dev/null diff --git a/submodules/swift b/submodules/swift new file mode 160000 index 00000000..8c69ac43 --- /dev/null +++ b/submodules/swift @@ -0,0 +1 @@ +Subproject commit 8c69ac439620ab135cca852455ee0e32395b8c24 From e3d052d55b2173a6ef1524a8ef1af628e0af4cfe Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Wed, 29 Apr 2026 13:13:13 +0500 Subject: [PATCH 02/40] Make Swift generation buildable on Linux --- codegen/config-swift.json | 1 + codegen/generate-swift.bash | 6 ++++++ submodules/swift | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/codegen/config-swift.json b/codegen/config-swift.json index 3f63a021..95022b5b 100644 --- a/codegen/config-swift.json +++ b/codegen/config-swift.json @@ -5,5 +5,6 @@ "mapFileBinaryToData": true, "projectName": "AsposeBarcodeCloud", "swiftPackagePath": "Sources/AsposeBarcodeCloud", + "useClasses": true, "useSPMFileStructure": true } diff --git a/codegen/generate-swift.bash b/codegen/generate-swift.bash index 7747db0c..29b63002 100755 --- a/codegen/generate-swift.bash +++ b/codegen/generate-swift.bash @@ -19,6 +19,12 @@ mkdir -p "$targetDir/Sources" rm -rf "$targetDir/Sources/AsposeBarcodeCloud" mv "$tempDir/Sources/AsposeBarcodeCloud" "$targetDir/Sources/AsposeBarcodeCloud" +# OpenAPI Generator 7.8.0 emits Apple-only imports for the URLSession client +# when compiling on Linux. Keep the generated client SwiftPM-compatible in WSL. +perl -0pi -e 's/import Foundation\n#if !os\(macOS\)\nimport MobileCoreServices\n#endif/import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n#if canImport(MobileCoreServices)\nimport MobileCoreServices\n#endif/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" +perl -0pi -e 's/ } else {\n if let uti = UTTypeCreatePreferredIdentifierForTag/ } else {\n #if canImport(MobileCoreServices)\n if let uti = UTTypeCreatePreferredIdentifierForTag/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" +perl -0pi -e 's/ return mimetype as String\n }\n return "application\/octet-stream"/ return mimetype as String\n }\n #endif\n return "application\/octet-stream"/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" + rm -rf "$targetDir/docs" mv "$tempDir/docs" "$targetDir/docs" diff --git a/submodules/swift b/submodules/swift index 8c69ac43..8381da79 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 8c69ac439620ab135cca852455ee0e32395b8c24 +Subproject commit 8381da79371e5eace648936f427d9d34a4e260ac From 243d1dd265a80eeedb7e6b83f5b04a2e9019d713 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Wed, 29 Apr 2026 13:31:47 +0500 Subject: [PATCH 03/40] Add Swift OAuth runtime support --- .../swift/AsposeBarcodeCloudClient.swift | 243 ++++++++++++++++++ codegen/generate-swift.bash | 1 + submodules/swift | 2 +- 3 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 codegen/Support/swift/AsposeBarcodeCloudClient.swift diff --git a/codegen/Support/swift/AsposeBarcodeCloudClient.swift b/codegen/Support/swift/AsposeBarcodeCloudClient.swift new file mode 100644 index 00000000..a815b040 --- /dev/null +++ b/codegen/Support/swift/AsposeBarcodeCloudClient.swift @@ -0,0 +1,243 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +public enum AsposeBarcodeCloudClientError: Error, CustomStringConvertible { + case missingCredentials + case invalidTokenURL(String) + case invalidTokenResponse + case tokenRequestFailed(statusCode: Int, body: String?) + case transportError(Error) + + public var description: String { + switch self { + case .missingCredentials: + return "Access token or clientId/clientSecret are required" + case let .invalidTokenURL(url): + return "Invalid token URL: \(url)" + case .invalidTokenResponse: + return "Token response does not contain access_token" + case let .tokenRequestFailed(statusCode, body): + if let body = body, !body.isEmpty { + return "Token request failed with status \(statusCode): \(body)" + } + return "Token request failed with status \(statusCode)" + case let .transportError(error): + return error.localizedDescription + } + } +} + +public final class AsposeBarcodeCloudConfiguration { + public static let defaultHost = "https://api.aspose.cloud/v4.0" + public static let defaultTokenURL = "https://id.aspose.cloud/connect/token" + public static let defaultSdkName = "swift sdk" + public static let defaultSdkVersion = "26.4.0" + + public var host: String + public var tokenURL: String + public var accessToken: String? + public var clientId: String? + public var clientSecret: String? + public var sdkName: String + public var sdkVersion: String + public var timeoutInterval: TimeInterval + + public init( + accessToken: String? = nil, + clientId: String? = nil, + clientSecret: String? = nil, + host: String = AsposeBarcodeCloudConfiguration.defaultHost, + tokenURL: String = AsposeBarcodeCloudConfiguration.defaultTokenURL, + sdkName: String = AsposeBarcodeCloudConfiguration.defaultSdkName, + sdkVersion: String = AsposeBarcodeCloudConfiguration.defaultSdkVersion, + timeoutInterval: TimeInterval = 300 + ) { + self.accessToken = accessToken + self.clientId = clientId + self.clientSecret = clientSecret + self.host = host + self.tokenURL = tokenURL + self.sdkName = sdkName + self.sdkVersion = sdkVersion + self.timeoutInterval = timeoutInterval + } + + public func makeTokenRequest() throws -> URLRequest { + guard let clientId = clientId, !clientId.isEmpty, + let clientSecret = clientSecret, !clientSecret.isEmpty else { + throw AsposeBarcodeCloudClientError.missingCredentials + } + + guard let url = URL(string: tokenURL) else { + throw AsposeBarcodeCloudClientError.invalidTokenURL(tokenURL) + } + + var components = URLComponents() + components.queryItems = [ + URLQueryItem(name: "grant_type", value: "client_credentials"), + URLQueryItem(name: "client_id", value: clientId), + URLQueryItem(name: "client_secret", value: clientSecret), + ] + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.timeoutInterval = timeoutInterval + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + request.httpBody = components.percentEncodedQuery?.data(using: .utf8) + return request + } +} + +public typealias AsposeBarcodeCloudTokenFetcher = ( + AsposeBarcodeCloudConfiguration, + @escaping (Result) -> Void +) -> Void + +public final class AsposeBarcodeCloudClient { + public let configuration: AsposeBarcodeCloudConfiguration + private let tokenFetcher: AsposeBarcodeCloudTokenFetcher + + public init( + configuration: AsposeBarcodeCloudConfiguration, + tokenFetcher: AsposeBarcodeCloudTokenFetcher? = nil + ) { + self.configuration = configuration + self.tokenFetcher = tokenFetcher ?? AsposeBarcodeCloudClient.defaultTokenFetcher + } + + public convenience init( + clientId: String, + clientSecret: String, + host: String = AsposeBarcodeCloudConfiguration.defaultHost, + tokenURL: String = AsposeBarcodeCloudConfiguration.defaultTokenURL + ) { + self.init(configuration: AsposeBarcodeCloudConfiguration( + clientId: clientId, + clientSecret: clientSecret, + host: host, + tokenURL: tokenURL + )) + } + + public convenience init( + accessToken: String, + host: String = AsposeBarcodeCloudConfiguration.defaultHost + ) { + self.init(configuration: AsposeBarcodeCloudConfiguration( + accessToken: accessToken, + host: host + )) + } + + public func apply() { + AsposeBarcodeCloudAPI.basePath = configuration.host + AsposeBarcodeCloudAPI.customHeaders["x-aspose-client"] = configuration.sdkName + AsposeBarcodeCloudAPI.customHeaders["x-aspose-client-version"] = configuration.sdkVersion + + if let accessToken = configuration.accessToken, !accessToken.isEmpty { + AsposeBarcodeCloudAPI.customHeaders["Authorization"] = "Bearer \(accessToken)" + } else { + AsposeBarcodeCloudAPI.customHeaders.removeValue(forKey: "Authorization") + } + } + + public func authorize(completion: @escaping (Result) -> Void) { + if let accessToken = configuration.accessToken, !accessToken.isEmpty { + apply() + completion(.success(accessToken)) + return + } + + tokenFetcher(configuration) { result in + switch result { + case let .success(accessToken): + self.configuration.accessToken = accessToken + self.apply() + completion(.success(accessToken)) + case let .failure(error): + completion(.failure(error)) + } + } + } + + @discardableResult + public func authorize() throws -> String { + let semaphore = DispatchSemaphore(value: 0) + var tokenResult: Result? + + authorize { result in + tokenResult = result + semaphore.signal() + } + + semaphore.wait() + + switch tokenResult { + case let .success(accessToken): + return accessToken + case let .failure(error): + throw error + case .none: + throw AsposeBarcodeCloudClientError.invalidTokenResponse + } + } + + public static func resetGlobalConfiguration() { + AsposeBarcodeCloudAPI.basePath = AsposeBarcodeCloudConfiguration.defaultHost + AsposeBarcodeCloudAPI.customHeaders.removeAll() + } + + private struct TokenResponse: Decodable { + let accessToken: String? + + enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + } + } + + private static func defaultTokenFetcher( + configuration: AsposeBarcodeCloudConfiguration, + completion: @escaping (Result) -> Void + ) { + let request: URLRequest + do { + request = try configuration.makeTokenRequest() + } catch let error as AsposeBarcodeCloudClientError { + completion(.failure(error)) + return + } catch { + completion(.failure(.transportError(error))) + return + } + + URLSession.shared.dataTask(with: request) { data, response, error in + if let error = error { + completion(.failure(.transportError(error))) + return + } + + guard let httpResponse = response as? HTTPURLResponse else { + completion(.failure(.invalidTokenResponse)) + return + } + + guard 200..<300 ~= httpResponse.statusCode else { + let body = data.flatMap { String(data: $0, encoding: .utf8) } + completion(.failure(.tokenRequestFailed(statusCode: httpResponse.statusCode, body: body))) + return + } + + guard let data = data, + let tokenResponse = try? JSONDecoder().decode(TokenResponse.self, from: data), + let accessToken = tokenResponse.accessToken, + !accessToken.isEmpty else { + completion(.failure(.invalidTokenResponse)) + return + } + + completion(.success(accessToken)) + }.resume() + } +} diff --git a/codegen/generate-swift.bash b/codegen/generate-swift.bash index 29b63002..f8476701 100755 --- a/codegen/generate-swift.bash +++ b/codegen/generate-swift.bash @@ -24,6 +24,7 @@ mv "$tempDir/Sources/AsposeBarcodeCloud" "$targetDir/Sources/AsposeBarcodeCloud" perl -0pi -e 's/import Foundation\n#if !os\(macOS\)\nimport MobileCoreServices\n#endif/import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n#if canImport(MobileCoreServices)\nimport MobileCoreServices\n#endif/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" perl -0pi -e 's/ } else {\n if let uti = UTTypeCreatePreferredIdentifierForTag/ } else {\n #if canImport(MobileCoreServices)\n if let uti = UTTypeCreatePreferredIdentifierForTag/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" perl -0pi -e 's/ return mimetype as String\n }\n return "application\/octet-stream"/ return mimetype as String\n }\n #endif\n return "application\/octet-stream"/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" +cp Support/swift/*.swift "$targetDir/Sources/AsposeBarcodeCloud/" rm -rf "$targetDir/docs" mv "$tempDir/docs" "$targetDir/docs" diff --git a/submodules/swift b/submodules/swift index 8381da79..df79ab07 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 8381da79371e5eace648936f427d9d34a4e260ac +Subproject commit df79ab070ec16a17b0d4747c5fa650bf62328a7c From b448e8458d92dd7d9533eb41c8531a258aa6dfaf Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Wed, 29 Apr 2026 14:04:24 +0500 Subject: [PATCH 04/40] Update Swift request shape coverage --- submodules/swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/swift b/submodules/swift index df79ab07..067fcd68 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit df79ab070ec16a17b0d4747c5fa650bf62328a7c +Subproject commit 067fcd682a28635f4911480c3d6f3a9a935f604c From fbd0cf7b1250823d44a242623d19333942ca9f20 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Thu, 30 Apr 2026 14:28:13 +0500 Subject: [PATCH 05/40] Update Swift live smoke coverage --- submodules/swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/swift b/submodules/swift index 067fcd68..130b9f4d 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 067fcd682a28635f4911480c3d6f3a9a935f604c +Subproject commit 130b9f4d326591d64c5523b79bc3c9b691e94ed0 From 58ff5f3acdf4df1b17847f71fd67552173c1383d Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Thu, 30 Apr 2026 14:34:12 +0500 Subject: [PATCH 06/40] Update Swift integration test docs --- submodules/swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/swift b/submodules/swift index 130b9f4d..13235caf 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 130b9f4d326591d64c5523b79bc3c9b691e94ed0 +Subproject commit 13235caf3e3f93576d945f48d60d03e37ca29e9b From ecb1442dd68e31dd7b30359e8f4b99a0f7714879 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Thu, 30 Apr 2026 14:41:25 +0500 Subject: [PATCH 07/40] Update Swift SDK repository structure --- submodules/swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/swift b/submodules/swift index 13235caf..e2b3287b 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 13235caf3e3f93576d945f48d60d03e37ca29e9b +Subproject commit e2b3287b324a50acdbdd0f6f274ca4cc2f4bf164 From cecac3fe57408dcf473eccaedec1b29770083900 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Thu, 30 Apr 2026 14:42:52 +0500 Subject: [PATCH 08/40] Update Swift live test wording --- submodules/swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/swift b/submodules/swift index e2b3287b..645b8be8 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit e2b3287b324a50acdbdd0f6f274ca4cc2f4bf164 +Subproject commit 645b8be890996a37ab08b9ebd2ff37cd03e143db From b5a74f1d55a4860b217dbcc62701316f4547e4ee Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Thu, 30 Apr 2026 15:01:28 +0500 Subject: [PATCH 09/40] Update Swift SDK changelog --- submodules/swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/swift b/submodules/swift index 645b8be8..7d005272 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 645b8be890996a37ab08b9ebd2ff37cd03e143db +Subproject commit 7d005272f7b8f19f9ff2b6ae572d86475621fc3a From c3b8011d13f139d5b6877743a4e52409523e46cb Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Tue, 12 May 2026 12:29:29 +0500 Subject: [PATCH 10/40] Point Swift submodule to GitHub-backed commit --- submodules/swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/swift b/submodules/swift index 7d005272..b89722f4 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 7d005272f7b8f19f9ff2b6ae572d86475621fc3a +Subproject commit b89722f4953f3e4c6a4b7ae41e97ae4ee8bcfb17 From 10c0e8c7dfecfc34cad10a66100116347250a3cc Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Wed, 13 May 2026 12:51:43 +0500 Subject: [PATCH 11/40] Point Swift submodule to test config loader --- submodules/swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/swift b/submodules/swift index b89722f4..5e90e012 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit b89722f4953f3e4c6a4b7ae41e97ae4ee8bcfb17 +Subproject commit 5e90e012c9b7e464a4e74d70acbffbde44bf990f From 6cd23364df4b2ecaa157caae4a0d029f8f6f194b Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Wed, 13 May 2026 14:02:26 +0500 Subject: [PATCH 12/40] Make Swift warning fixes reproducible --- codegen/generate-swift.bash | 6 ++++++ submodules/swift | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/codegen/generate-swift.bash b/codegen/generate-swift.bash index f8476701..ecc97e94 100755 --- a/codegen/generate-swift.bash +++ b/codegen/generate-swift.bash @@ -24,6 +24,12 @@ mv "$tempDir/Sources/AsposeBarcodeCloud" "$targetDir/Sources/AsposeBarcodeCloud" perl -0pi -e 's/import Foundation\n#if !os\(macOS\)\nimport MobileCoreServices\n#endif/import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n#if canImport(MobileCoreServices)\nimport MobileCoreServices\n#endif/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" perl -0pi -e 's/ } else {\n if let uti = UTTypeCreatePreferredIdentifierForTag/ } else {\n #if canImport(MobileCoreServices)\n if let uti = UTTypeCreatePreferredIdentifierForTag/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" perl -0pi -e 's/ return mimetype as String\n }\n return "application\/octet-stream"/ return mimetype as String\n }\n #endif\n return "application\/octet-stream"/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" + +# Keep the generated runtime warning-free under current Swift toolchains. +perl -0pi -e 's/extension String: CodingKey \{/extension Swift.String: Swift.CodingKey {/' "$targetDir/Sources/AsposeBarcodeCloud/Extensions.swift" +perl -0pi -e 's/}\s*\z/}\n\n#if compiler(>=5.5)\nextension OpenISO8601DateFormatter: \@unchecked Sendable {}\n#endif\n/' "$targetDir/Sources/AsposeBarcodeCloud/OpenISO8601DateFormatter.swift" +perl -0pi -e 's/\n #else\n return "application\/octet-stream"\s*\n #endif/\n #endif/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" +perl -0pi -e 's/private class SessionDelegate/private final class SessionDelegate/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" cp Support/swift/*.swift "$targetDir/Sources/AsposeBarcodeCloud/" rm -rf "$targetDir/docs" diff --git a/submodules/swift b/submodules/swift index 5e90e012..eb0dd8f3 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 5e90e012c9b7e464a4e74d70acbffbde44bf990f +Subproject commit eb0dd8f3eb1a5c44921119d3a93c3cf94fa05cb8 From 5866b773ac49d0cb2ad56d4aa751572fb90c3f68 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Wed, 13 May 2026 14:16:50 +0500 Subject: [PATCH 13/40] Point Swift submodule to release tag docs --- submodules/swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/swift b/submodules/swift index eb0dd8f3..568ba1f3 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit eb0dd8f3eb1a5c44921119d3a93c3cf94fa05cb8 +Subproject commit 568ba1f3142d36365c2bbc194c54b537b7526fec From fd9c158c82153c2430fe91a7a4e273ef76701dd6 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Wed, 13 May 2026 14:46:11 +0500 Subject: [PATCH 14/40] Add Swift codegen PR checks --- .github/workflows/check-codegen.yml | 31 +++++++++++++++++++++++++++++ submodules/swift | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-codegen.yml b/.github/workflows/check-codegen.yml index 10ee73b4..ba1fbdf0 100644 --- a/.github/workflows/check-codegen.yml +++ b/.github/workflows/check-codegen.yml @@ -30,3 +30,34 @@ jobs: - uses: actions/checkout@v6 - name: Check existing swagger specification is up-to-date run: curl https://api.aspose.cloud/v4.0/barcode/swagger/spec | diff --strip-trailing-cr -y --suppress-common-lines spec/aspose-barcode-cloud.json - + + check-swift-codegen: + runs-on: macos-latest + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + + - uses: actions/setup-java@v5 + with: + distribution: corretto + java-version: 21 + + - name: Show Swift version + run: swift --version + + - name: Regenerate Swift SDK + run: make swift + + - name: Check Swift SDK generation is reproducible + run: git diff --exit-code + + - name: Run Swift tests + working-directory: submodules/swift + run: make test + + - name: Run Swift live integration tests + working-directory: submodules/swift + run: make integration-test + env: + TEST_CONFIGURATION_ACCESS_TOKEN: ${{ secrets.TEST_CONFIGURATION_ACCESS_TOKEN }} diff --git a/submodules/swift b/submodules/swift index 568ba1f3..216a79ac 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 568ba1f3142d36365c2bbc194c54b537b7526fec +Subproject commit 216a79ace0cf54e1c42dc02701b590231bdfd93a From 41cc9cd194b01076ddf105b893eb6bd26b791ac6 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Thu, 14 May 2026 15:55:13 +0500 Subject: [PATCH 15/40] Point Swift submodule to ignored test URLs --- submodules/swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/swift b/submodules/swift index 216a79ac..e326ac26 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 216a79ace0cf54e1c42dc02701b590231bdfd93a +Subproject commit e326ac26959e49edd65846cd0901a704f29caa4d From 9d90b395e21bae3d1af783c357e023531b68776b Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Thu, 14 May 2026 16:10:54 +0500 Subject: [PATCH 16/40] Include Swift in full SDK generation --- .github/workflows/check-codegen.yml | 12 ++++++++---- .github/workflows/check-urls.yml | 7 +++++-- codegen/generate-swift.bash | 2 ++ scripts/check-urls.py | 2 ++ scripts/checkout-submodules.bash | 23 +++++++++++++++++++++++ scripts/generate-all.bash | 15 ++------------- submodules/swift | 2 +- 7 files changed, 43 insertions(+), 20 deletions(-) create mode 100755 scripts/checkout-submodules.bash diff --git a/.github/workflows/check-codegen.yml b/.github/workflows/check-codegen.yml index ba1fbdf0..ff6dafe1 100644 --- a/.github/workflows/check-codegen.yml +++ b/.github/workflows/check-codegen.yml @@ -15,12 +15,13 @@ jobs: steps: - uses: actions/checkout@v6 with: - submodules: recursive persist-credentials: true - name: Check all submodules up-to-date + env: + SWIFT_SUBMODULE_DEPLOY_KEY: ${{ secrets.SWIFT_SUBMODULE_DEPLOY_KEY }} run: | - git submodule update --recursive --remote + ./scripts/checkout-submodules.bash --remote git diff --exit-code check-swagger: @@ -35,8 +36,11 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v6 - with: - submodules: recursive + + - name: Checkout submodules + env: + SWIFT_SUBMODULE_DEPLOY_KEY: ${{ secrets.SWIFT_SUBMODULE_DEPLOY_KEY }} + run: ./scripts/checkout-submodules.bash - uses: actions/setup-java@v5 with: diff --git a/.github/workflows/check-urls.yml b/.github/workflows/check-urls.yml index 11df3b20..bb91708b 100644 --- a/.github/workflows/check-urls.yml +++ b/.github/workflows/check-urls.yml @@ -17,8 +17,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - with: - submodules: recursive + + - name: Checkout submodules + env: + SWIFT_SUBMODULE_DEPLOY_KEY: ${{ secrets.SWIFT_SUBMODULE_DEPLOY_KEY }} + run: ./scripts/checkout-submodules.bash - name: Check all URLs run: ./scripts/check_all_urls.sh diff --git a/codegen/generate-swift.bash b/codegen/generate-swift.bash index ecc97e94..fc5b61c2 100755 --- a/codegen/generate-swift.bash +++ b/codegen/generate-swift.bash @@ -45,3 +45,5 @@ mv "$tempDir/.openapi-generator/FILES" "$targetDir/.openapi-generator/" cp ../LICENSE "$targetDir/" rm -rf "$tempDir" + +pushd "$targetDir" && make after-gen && popd >/dev/null diff --git a/scripts/check-urls.py b/scripts/check-urls.py index 8c4ca36a..100d4790 100644 --- a/scripts/check-urls.py +++ b/scripts/check-urls.py @@ -37,6 +37,8 @@ URLS_TO_IGNORE = frozenset( [ "https://api.aspose.cloud", + # Temporary while the Swift SDK repository is private during bootstrap. + "https://github.com/aspose-barcode-cloud/Aspose.BarCode-Cloud-SDK-for-Swift.git", "https://www.aspose.cloud/404", "https://www.aspose.cloud/404/", ] diff --git a/scripts/checkout-submodules.bash b/scripts/checkout-submodules.bash new file mode 100755 index 00000000..b8a71229 --- /dev/null +++ b/scripts/checkout-submodules.bash @@ -0,0 +1,23 @@ +#!/bin/bash +set -euo pipefail + +submodule_args=(--init --recursive) +if [ "$#" -gt 0 ]; then + submodule_args+=("$@") +fi + +if [ -n "${SWIFT_SUBMODULE_DEPLOY_KEY:-}" ]; then + ssh_dir="$HOME/.ssh" + key_file="$ssh_dir/aspose_barcode_cloud_swift_submodule" + + mkdir -p "$ssh_dir" + chmod 700 "$ssh_dir" + printf '%s\n' "$SWIFT_SUBMODULE_DEPLOY_KEY" > "$key_file" + chmod 600 "$key_file" + ssh-keyscan github.com >> "$ssh_dir/known_hosts" 2>/dev/null + + git config submodule.submodules/swift.url git@github.com:aspose-barcode-cloud/Aspose.BarCode-Cloud-SDK-for-Swift.git + export GIT_SSH_COMMAND="ssh -i $key_file -o IdentitiesOnly=yes -o UserKnownHostsFile=$ssh_dir/known_hosts" +fi + +git submodule update "${submodule_args[@]}" diff --git a/scripts/generate-all.bash b/scripts/generate-all.bash index 138c0cc4..d1145bf3 100755 --- a/scripts/generate-all.bash +++ b/scripts/generate-all.bash @@ -4,20 +4,9 @@ set -euo pipefail pushd "$( dirname "${BASH_SOURCE[0]}" )/../codegen" -generators=( - android - dart - dotnet - go - java - node - php - python -) - -for gen in "${generators[@]}" +for gen in generate-*.bash do - "./generate-${gen}.bash" + "./${gen}" done popd >/dev/null diff --git a/submodules/swift b/submodules/swift index e326ac26..06580d67 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit e326ac26959e49edd65846cd0901a704f29caa4d +Subproject commit 06580d6753b5ed774a764ba96c763cd9c4fe55c4 From 8f7dd90138dbcccacafb4f8d4c3780025c395316 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Mon, 18 May 2026 15:21:59 +0500 Subject: [PATCH 17/40] Wire Swift versioning and Linux-first CI --- .github/workflows/check-codegen.yml | 40 +++++++- codegen/Tools/patch-swift-generated.py | 121 +++++++++++++++++++++++++ codegen/config-swift.json | 1 + codegen/generate-swift.bash | 12 +-- scripts/new-version.py | 7 ++ submodules/swift | 2 +- 6 files changed, 167 insertions(+), 16 deletions(-) create mode 100644 codegen/Tools/patch-swift-generated.py diff --git a/.github/workflows/check-codegen.yml b/.github/workflows/check-codegen.yml index ff6dafe1..ce9e9e40 100644 --- a/.github/workflows/check-codegen.yml +++ b/.github/workflows/check-codegen.yml @@ -32,11 +32,17 @@ jobs: - name: Check existing swagger specification is up-to-date run: curl https://api.aspose.cloud/v4.0/barcode/swagger/spec | diff --strip-trailing-cr -y --suppress-common-lines spec/aspose-barcode-cloud.json - - check-swift-codegen: - runs-on: macos-latest + check-swift-codegen-linux: + runs-on: ubuntu-latest + needs: [check-submodules] + container: + image: swift:6.0 steps: - uses: actions/checkout@v6 + - name: Install Linux CI tools + run: apt-get update && apt-get install -y make openssh-client python3 + - name: Checkout submodules env: SWIFT_SUBMODULE_DEPLOY_KEY: ${{ secrets.SWIFT_SUBMODULE_DEPLOY_KEY }} @@ -56,12 +62,38 @@ jobs: - name: Check Swift SDK generation is reproducible run: git diff --exit-code - - name: Run Swift tests + - name: Build Swift example working-directory: submodules/swift - run: make test + run: swift build --product GenerateAndScanExample - name: Run Swift live integration tests working-directory: submodules/swift run: make integration-test env: TEST_CONFIGURATION_ACCESS_TOKEN: ${{ secrets.TEST_CONFIGURATION_ACCESS_TOKEN }} + + check-swift-macos: + runs-on: macos-latest + needs: [check-swift-codegen-linux] + steps: + - uses: actions/checkout@v6 + + - name: Checkout submodules + env: + SWIFT_SUBMODULE_DEPLOY_KEY: ${{ secrets.SWIFT_SUBMODULE_DEPLOY_KEY }} + run: ./scripts/checkout-submodules.bash + + - name: Show Swift version + run: swift --version + + - name: Build Swift SDK + working-directory: submodules/swift + run: make build + + - name: Run Swift tests + working-directory: submodules/swift + run: make test + + - name: Build Swift example + working-directory: submodules/swift + run: swift build --product GenerateAndScanExample diff --git a/codegen/Tools/patch-swift-generated.py b/codegen/Tools/patch-swift-generated.py new file mode 100644 index 00000000..82cd7c08 --- /dev/null +++ b/codegen/Tools/patch-swift-generated.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 + +from __future__ import print_function + +import json +import re +import sys +from pathlib import Path + + +def replace_once(path, old, new): + text = path.read_text(encoding="utf-8") + count = text.count(old) + if count != 1: + raise RuntimeError("Expected exactly one match in {0}, found {1}".format(path, count)) + path.write_text(text.replace(old, new), encoding="utf-8") + + +def replace_regex_once(path, pattern, replacement): + text = path.read_text(encoding="utf-8") + text, count = re.subn(pattern, replacement, text, count=1) + if count != 1: + raise RuntimeError("Expected exactly one regex match in {0}, found {1}".format(path, count)) + path.write_text(text, encoding="utf-8") + + +def append_once(path, marker, block): + text = path.read_text(encoding="utf-8") + if marker in text: + return + path.write_text(text.rstrip() + "\n\n" + block + "\n", encoding="utf-8") + + +def patch_url_session(sources_dir): + url_session = sources_dir / "URLSessionImplementations.swift" + + replace_once( + url_session, + "import Foundation\n#if !os(macOS)\nimport MobileCoreServices\n#endif", + "import Foundation\n" + "#if canImport(FoundationNetworking)\n" + "import FoundationNetworking\n" + "#endif\n" + "#if canImport(MobileCoreServices)\n" + "import MobileCoreServices\n" + "#endif", + ) + + replace_once( + url_session, + " } else {\n" + " if let uti = UTTypeCreatePreferredIdentifierForTag", + " } else {\n" + " #if canImport(MobileCoreServices)\n" + " if let uti = UTTypeCreatePreferredIdentifierForTag", + ) + + replace_once( + url_session, + " return mimetype as String\n" + " }\n" + " return \"application/octet-stream\"", + " return mimetype as String\n" + " }\n" + " #endif\n" + " return \"application/octet-stream\"", + ) + + replace_regex_once( + url_session, + r"\n #else\n return \"application/octet-stream\"\s*\n #endif", + "\n #endif", + ) + + replace_once(url_session, "private class SessionDelegate", "private final class SessionDelegate") + + +def patch_extensions(sources_dir): + replace_once( + sources_dir / "Extensions.swift", + "extension String: CodingKey {", + "extension Swift.String: Swift.CodingKey {", + ) + + +def patch_date_formatter(sources_dir): + append_once( + sources_dir / "OpenISO8601DateFormatter.swift", + "extension OpenISO8601DateFormatter: @unchecked Sendable", + "#if compiler(>=5.5)\n" + "extension OpenISO8601DateFormatter: @unchecked Sendable {}\n" + "#endif", + ) + + +def patch_client_version(sources_dir, package_version): + replace_regex_once( + sources_dir / "AsposeBarcodeCloudClient.swift", + r'public static let defaultSdkVersion = "[^"]+"', + 'public static let defaultSdkVersion = "{0}"'.format(package_version), + ) + + +def main(target_dir, config_path): + config = json.loads(Path(config_path).read_text(encoding="utf-8")) + package_version = config.get("packageVersion") + if not package_version: + raise RuntimeError("config-swift.json must define packageVersion") + + sources_dir = Path(target_dir) / "Sources" / "AsposeBarcodeCloud" + patch_url_session(sources_dir) + patch_extensions(sources_dir) + patch_date_formatter(sources_dir) + patch_client_version(sources_dir, package_version) + + +if __name__ == "__main__": + if len(sys.argv) != 3: + print("Usage: patch-swift-generated.py ", file=sys.stderr) + sys.exit(2) + main(sys.argv[1], sys.argv[2]) diff --git a/codegen/config-swift.json b/codegen/config-swift.json index 95022b5b..6c2762b8 100644 --- a/codegen/config-swift.json +++ b/codegen/config-swift.json @@ -3,6 +3,7 @@ "enumUnknownDefaultCase": true, "hideGenerationTimestamp": true, "mapFileBinaryToData": true, + "packageVersion": "26.4.0", "projectName": "AsposeBarcodeCloud", "swiftPackagePath": "Sources/AsposeBarcodeCloud", "useClasses": true, diff --git a/codegen/generate-swift.bash b/codegen/generate-swift.bash index fc5b61c2..e290e785 100755 --- a/codegen/generate-swift.bash +++ b/codegen/generate-swift.bash @@ -19,18 +19,8 @@ mkdir -p "$targetDir/Sources" rm -rf "$targetDir/Sources/AsposeBarcodeCloud" mv "$tempDir/Sources/AsposeBarcodeCloud" "$targetDir/Sources/AsposeBarcodeCloud" -# OpenAPI Generator 7.8.0 emits Apple-only imports for the URLSession client -# when compiling on Linux. Keep the generated client SwiftPM-compatible in WSL. -perl -0pi -e 's/import Foundation\n#if !os\(macOS\)\nimport MobileCoreServices\n#endif/import Foundation\n#if canImport(FoundationNetworking)\nimport FoundationNetworking\n#endif\n#if canImport(MobileCoreServices)\nimport MobileCoreServices\n#endif/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" -perl -0pi -e 's/ } else {\n if let uti = UTTypeCreatePreferredIdentifierForTag/ } else {\n #if canImport(MobileCoreServices)\n if let uti = UTTypeCreatePreferredIdentifierForTag/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" -perl -0pi -e 's/ return mimetype as String\n }\n return "application\/octet-stream"/ return mimetype as String\n }\n #endif\n return "application\/octet-stream"/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" - -# Keep the generated runtime warning-free under current Swift toolchains. -perl -0pi -e 's/extension String: CodingKey \{/extension Swift.String: Swift.CodingKey {/' "$targetDir/Sources/AsposeBarcodeCloud/Extensions.swift" -perl -0pi -e 's/}\s*\z/}\n\n#if compiler(>=5.5)\nextension OpenISO8601DateFormatter: \@unchecked Sendable {}\n#endif\n/' "$targetDir/Sources/AsposeBarcodeCloud/OpenISO8601DateFormatter.swift" -perl -0pi -e 's/\n #else\n return "application\/octet-stream"\s*\n #endif/\n #endif/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" -perl -0pi -e 's/private class SessionDelegate/private final class SessionDelegate/' "$targetDir/Sources/AsposeBarcodeCloud/URLSessionImplementations.swift" cp Support/swift/*.swift "$targetDir/Sources/AsposeBarcodeCloud/" +python3 Tools/patch-swift-generated.py "$targetDir" config-swift.json rm -rf "$targetDir/docs" mv "$tempDir/docs" "$targetDir/docs" diff --git a/scripts/new-version.py b/scripts/new-version.py index 394f9ca7..09705672 100755 --- a/scripts/new-version.py +++ b/scripts/new-version.py @@ -66,6 +66,12 @@ def set_python_version(new_version, filename=os.path.join(BASE_CONFIG_DIR, "conf save_config(config, filename) +def set_swift_version(new_version, filename=os.path.join(BASE_CONFIG_DIR, "config-swift.json")): + config = read_config(filename) + config["packageVersion"] = str.join(".", map(str, new_version)) + save_config(config, filename) + + def read_config(filename): with open(filename, "rb") as rf: config = json.load(rf) @@ -90,6 +96,7 @@ def main(new_versions): set_node_version(new_version) set_php_version(new_version) set_python_version(new_version) + set_swift_version(new_version) def parse_args(): diff --git a/submodules/swift b/submodules/swift index 06580d67..88b1bc13 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 06580d6753b5ed774a764ba96c763cd9c4fe55c4 +Subproject commit 88b1bc1309635c3532dfe647d1d26084e378f2ab From 6302b6f682d8a7c05f9f5b8309e349f853668c60 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Mon, 18 May 2026 15:24:37 +0500 Subject: [PATCH 18/40] Make submodule checkout helper cwd-safe --- scripts/checkout-submodules.bash | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/checkout-submodules.bash b/scripts/checkout-submodules.bash index b8a71229..a266362e 100755 --- a/scripts/checkout-submodules.bash +++ b/scripts/checkout-submodules.bash @@ -6,6 +6,9 @@ if [ "$#" -gt 0 ]; then submodule_args+=("$@") fi +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" + if [ -n "${SWIFT_SUBMODULE_DEPLOY_KEY:-}" ]; then ssh_dir="$HOME/.ssh" key_file="$ssh_dir/aspose_barcode_cloud_swift_submodule" From 43c1ce99143038ceb094b843c16f222de0a0fabb Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Mon, 18 May 2026 15:26:56 +0500 Subject: [PATCH 19/40] Use explicit repo path for submodule checkout --- scripts/checkout-submodules.bash | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/checkout-submodules.bash b/scripts/checkout-submodules.bash index a266362e..81f53490 100755 --- a/scripts/checkout-submodules.bash +++ b/scripts/checkout-submodules.bash @@ -7,7 +7,6 @@ if [ "$#" -gt 0 ]; then fi repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -cd "$repo_root" if [ -n "${SWIFT_SUBMODULE_DEPLOY_KEY:-}" ]; then ssh_dir="$HOME/.ssh" @@ -19,8 +18,8 @@ if [ -n "${SWIFT_SUBMODULE_DEPLOY_KEY:-}" ]; then chmod 600 "$key_file" ssh-keyscan github.com >> "$ssh_dir/known_hosts" 2>/dev/null - git config submodule.submodules/swift.url git@github.com:aspose-barcode-cloud/Aspose.BarCode-Cloud-SDK-for-Swift.git + git -C "$repo_root" config --local submodule.submodules/swift.url git@github.com:aspose-barcode-cloud/Aspose.BarCode-Cloud-SDK-for-Swift.git export GIT_SSH_COMMAND="ssh -i $key_file -o IdentitiesOnly=yes -o UserKnownHostsFile=$ssh_dir/known_hosts" fi -git submodule update "${submodule_args[@]}" +git -C "$repo_root" submodule update "${submodule_args[@]}" From 4aa5328c4d934132624d15458505a283f5a030f5 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Mon, 18 May 2026 15:30:58 +0500 Subject: [PATCH 20/40] Harden submodule checkout in container jobs --- .github/workflows/check-codegen.yml | 9 ++++++--- .github/workflows/check-urls.yml | 3 ++- scripts/checkout-submodules.bash | 11 ++++++++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-codegen.yml b/.github/workflows/check-codegen.yml index ce9e9e40..123ff8f4 100644 --- a/.github/workflows/check-codegen.yml +++ b/.github/workflows/check-codegen.yml @@ -18,10 +18,11 @@ jobs: persist-credentials: true - name: Check all submodules up-to-date + working-directory: ${{ github.workspace }} env: SWIFT_SUBMODULE_DEPLOY_KEY: ${{ secrets.SWIFT_SUBMODULE_DEPLOY_KEY }} run: | - ./scripts/checkout-submodules.bash --remote + bash scripts/checkout-submodules.bash --remote git diff --exit-code check-swagger: @@ -44,9 +45,10 @@ jobs: run: apt-get update && apt-get install -y make openssh-client python3 - name: Checkout submodules + working-directory: ${{ github.workspace }} env: SWIFT_SUBMODULE_DEPLOY_KEY: ${{ secrets.SWIFT_SUBMODULE_DEPLOY_KEY }} - run: ./scripts/checkout-submodules.bash + run: bash scripts/checkout-submodules.bash - uses: actions/setup-java@v5 with: @@ -79,9 +81,10 @@ jobs: - uses: actions/checkout@v6 - name: Checkout submodules + working-directory: ${{ github.workspace }} env: SWIFT_SUBMODULE_DEPLOY_KEY: ${{ secrets.SWIFT_SUBMODULE_DEPLOY_KEY }} - run: ./scripts/checkout-submodules.bash + run: bash scripts/checkout-submodules.bash - name: Show Swift version run: swift --version diff --git a/.github/workflows/check-urls.yml b/.github/workflows/check-urls.yml index bb91708b..bd8298ef 100644 --- a/.github/workflows/check-urls.yml +++ b/.github/workflows/check-urls.yml @@ -19,9 +19,10 @@ jobs: - uses: actions/checkout@v6 - name: Checkout submodules + working-directory: ${{ github.workspace }} env: SWIFT_SUBMODULE_DEPLOY_KEY: ${{ secrets.SWIFT_SUBMODULE_DEPLOY_KEY }} - run: ./scripts/checkout-submodules.bash + run: bash scripts/checkout-submodules.bash - name: Check all URLs run: ./scripts/check_all_urls.sh diff --git a/scripts/checkout-submodules.bash b/scripts/checkout-submodules.bash index 81f53490..889b0f3d 100755 --- a/scripts/checkout-submodules.bash +++ b/scripts/checkout-submodules.bash @@ -6,7 +6,16 @@ if [ "$#" -gt 0 ]; then submodule_args+=("$@") fi -repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +script_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +repo_root="${GITHUB_WORKSPACE:-$script_root}" +if ! git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + repo_root="$script_root" +fi + +if ! git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "Cannot locate git repository root for submodule checkout." >&2 + exit 1 +fi if [ -n "${SWIFT_SUBMODULE_DEPLOY_KEY:-}" ]; then ssh_dir="$HOME/.ssh" From 22956edfd814c9d21dc6c43584533db564176777 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Mon, 18 May 2026 15:34:26 +0500 Subject: [PATCH 21/40] Use hosted Ubuntu Swift toolchain in codegen CI --- .github/workflows/check-codegen.yml | 5 ----- submodules/swift | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/check-codegen.yml b/.github/workflows/check-codegen.yml index 123ff8f4..846d10dd 100644 --- a/.github/workflows/check-codegen.yml +++ b/.github/workflows/check-codegen.yml @@ -36,14 +36,9 @@ jobs: check-swift-codegen-linux: runs-on: ubuntu-latest needs: [check-submodules] - container: - image: swift:6.0 steps: - uses: actions/checkout@v6 - - name: Install Linux CI tools - run: apt-get update && apt-get install -y make openssh-client python3 - - name: Checkout submodules working-directory: ${{ github.workspace }} env: diff --git a/submodules/swift b/submodules/swift index 88b1bc13..98dd349a 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 88b1bc1309635c3532dfe647d1d26084e378f2ab +Subproject commit 98dd349a38a376ab0c3419bce316c82a5a2cc48f From 05b79300fa92d0c58d52bee0357eab5bb5c46191 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Mon, 18 May 2026 16:34:54 +0500 Subject: [PATCH 22/40] Generate Swift SDK from templates --- codegen/Templates/swift/APIHelper.mustache | 122 ++++ codegen/Templates/swift/APIs.mustache | 104 +++ .../swift/AsposeBarcodeCloudClient.mustache} | 2 +- codegen/Templates/swift/Cartfile.mustache | 4 + .../Templates/swift/CodableHelper.mustache | 49 ++ .../Templates/swift/Configuration.mustache | 55 ++ codegen/Templates/swift/Extensions.mustache | 283 ++++++++ .../Templates/swift/JSONDataEncoding.mustache | 56 ++ .../swift/JSONEncodingHelper.mustache | 45 ++ codegen/Templates/swift/Models.mustache | 148 ++++ .../swift/OpenAPIDateWithoutTime.mustache | 98 +++ .../swift/OpenISO8601DateFormatter.mustache | 60 ++ .../Templates/swift/Package.swift.mustache | 50 ++ codegen/Templates/swift/Podspec.mustache | 39 + codegen/Templates/swift/README.mustache | 136 ++++ .../swift/SynchronizedDictionary.mustache | 36 + codegen/Templates/swift/Validation.mustache | 161 +++++ codegen/Templates/swift/XcodeGen.mustache | 18 + codegen/Templates/swift/_param.mustache | 1 + codegen/Templates/swift/api.mustache | 458 ++++++++++++ codegen/Templates/swift/api_doc.mustache | 141 ++++ codegen/Templates/swift/git_push.sh.mustache | 57 ++ codegen/Templates/swift/gitignore.mustache | 100 +++ .../AlamofireImplementations.mustache | 413 +++++++++++ .../URLSessionImplementations.mustache | 673 ++++++++++++++++++ codegen/Templates/swift/model.mustache | 30 + codegen/Templates/swift/modelArray.mustache | 1 + codegen/Templates/swift/modelEnum.mustache | 7 + .../swift/modelInlineEnumDeclaration.mustache | 7 + codegen/Templates/swift/modelObject.mustache | 137 ++++ codegen/Templates/swift/modelOneOf.mustache | 31 + codegen/Templates/swift/model_doc.mustache | 11 + codegen/Templates/swift/swiftformat.mustache | 45 ++ codegen/Tools/patch-swift-generated.py | 121 ---- codegen/config-swift.json | 6 + codegen/generate-swift.bash | 10 +- submodules/swift | 2 +- 37 files changed, 3589 insertions(+), 128 deletions(-) create mode 100644 codegen/Templates/swift/APIHelper.mustache create mode 100644 codegen/Templates/swift/APIs.mustache rename codegen/{Support/swift/AsposeBarcodeCloudClient.swift => Templates/swift/AsposeBarcodeCloudClient.mustache} (99%) create mode 100644 codegen/Templates/swift/Cartfile.mustache create mode 100644 codegen/Templates/swift/CodableHelper.mustache create mode 100644 codegen/Templates/swift/Configuration.mustache create mode 100644 codegen/Templates/swift/Extensions.mustache create mode 100644 codegen/Templates/swift/JSONDataEncoding.mustache create mode 100644 codegen/Templates/swift/JSONEncodingHelper.mustache create mode 100644 codegen/Templates/swift/Models.mustache create mode 100644 codegen/Templates/swift/OpenAPIDateWithoutTime.mustache create mode 100644 codegen/Templates/swift/OpenISO8601DateFormatter.mustache create mode 100644 codegen/Templates/swift/Package.swift.mustache create mode 100644 codegen/Templates/swift/Podspec.mustache create mode 100644 codegen/Templates/swift/README.mustache create mode 100644 codegen/Templates/swift/SynchronizedDictionary.mustache create mode 100644 codegen/Templates/swift/Validation.mustache create mode 100644 codegen/Templates/swift/XcodeGen.mustache create mode 100644 codegen/Templates/swift/_param.mustache create mode 100644 codegen/Templates/swift/api.mustache create mode 100644 codegen/Templates/swift/api_doc.mustache create mode 100644 codegen/Templates/swift/git_push.sh.mustache create mode 100644 codegen/Templates/swift/gitignore.mustache create mode 100644 codegen/Templates/swift/libraries/alamofire/AlamofireImplementations.mustache create mode 100644 codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache create mode 100644 codegen/Templates/swift/model.mustache create mode 100644 codegen/Templates/swift/modelArray.mustache create mode 100644 codegen/Templates/swift/modelEnum.mustache create mode 100644 codegen/Templates/swift/modelInlineEnumDeclaration.mustache create mode 100644 codegen/Templates/swift/modelObject.mustache create mode 100644 codegen/Templates/swift/modelOneOf.mustache create mode 100644 codegen/Templates/swift/model_doc.mustache create mode 100644 codegen/Templates/swift/swiftformat.mustache delete mode 100644 codegen/Tools/patch-swift-generated.py diff --git a/codegen/Templates/swift/APIHelper.mustache b/codegen/Templates/swift/APIHelper.mustache new file mode 100644 index 00000000..d4583787 --- /dev/null +++ b/codegen/Templates/swift/APIHelper.mustache @@ -0,0 +1,122 @@ +// APIHelper.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation{{#useVapor}} +import Vapor{{/useVapor}} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct APIHelper { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func rejectNil(_ source: [String: Any?]) -> [String: Any]? { + let destination = source.reduce(into: [String: Any]()) { result, item in + if let value = item.value { + result[item.key] = value + } + } + + if destination.isEmpty { + return nil + } + return destination + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func rejectNilHeaders(_ source: [String: Any?]) -> [String: String] { + return source.reduce(into: [String: String]()) { result, item in + if let collection = item.value as? [Any?] { + result[item.key] = collection + .compactMap { value in convertAnyToString(value) } + .joined(separator: ",") + } else if let value: Any = item.value { + result[item.key] = convertAnyToString(value) + } + } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func convertBoolToString(_ source: [String: Any]?) -> [String: Any]? { + guard let source = source else { + return nil + } + + return source.reduce(into: [String: Any]()) { result, item in + switch item.value { + case let x as Bool: + result[item.key] = x.description + default: + result[item.key] = item.value + } + } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func convertAnyToString(_ value: Any?) -> String? { + guard let value = value else { return nil } + if let value = value as? any RawRepresentable { + return "\(value.rawValue)" + } else { + return "\(value)" + } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func mapValueToPathItem(_ source: Any) -> Any { + if let collection = source as? [Any?] { + return collection + .compactMap { value in convertAnyToString(value) } + .joined(separator: ",") + } else if let value = source as? any RawRepresentable { + return "\(value.rawValue)" + } + return source + } + + /// maps all values from source to query parameters + /// + /// explode attribute is respected: collection values might be either joined or split up into separate key value pairs + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func mapValuesToQueryItems(_ source: [String: (wrappedValue: Any?, isExplode: Bool)]) -> [URLQueryItem]? { + let destination = source.filter { $0.value.wrappedValue != nil }.reduce(into: [URLQueryItem]()) { result, item in + if let collection = item.value.wrappedValue as? [Any?] { + + let collectionValues: [String] = collection.compactMap { value in convertAnyToString(value) } + + if !item.value.isExplode { + result.append(URLQueryItem(name: item.key, value: collectionValues.joined(separator: ","))) + } else { + collectionValues + .forEach { value in + result.append(URLQueryItem(name: item.key, value: value)) + } + } + + } else if let value = item.value.wrappedValue { + result.append(URLQueryItem(name: item.key, value: convertAnyToString(value))) + } + } + + if destination.isEmpty { + return nil + } + return destination.sorted { $0.name < $1.name } + } + + /// maps all values from source to query parameters + /// + /// collection values are always exploded + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func mapValuesToQueryItems(_ source: [String: Any?]) -> [URLQueryItem]? { + let destination = source.filter { $0.value != nil }.reduce(into: [URLQueryItem]()) { result, item in + if let collection = item.value as? [Any?] { + collection + .compactMap { value in convertAnyToString(value) } + .forEach { value in + result.append(URLQueryItem(name: item.key, value: value)) + } + + } else if let value = item.value { + result.append(URLQueryItem(name: item.key, value: convertAnyToString(value))) + } + } + + if destination.isEmpty { + return nil + } + return destination.sorted { $0.name < $1.name } + } +} diff --git a/codegen/Templates/swift/APIs.mustache b/codegen/Templates/swift/APIs.mustache new file mode 100644 index 00000000..a1d2216f --- /dev/null +++ b/codegen/Templates/swift/APIs.mustache @@ -0,0 +1,104 @@ +// APIs.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif{{#useVapor}} +import Vapor +{{/useVapor}} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class {{projectName}}API { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var basePath = "{{{basePath}}}" + {{#useVapor}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var customHeaders: HTTPHeaders = [:] + {{/useVapor}} + {{^useVapor}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var customHeaders: [String: String] = [:] + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var credential: URLCredential?{{#useAlamofire}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var requestBuilderFactory: RequestBuilderFactory = AlamofireRequestBuilderFactory(){{/useAlamofire}}{{#useURLSession}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(){{/useURLSession}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var apiResponseQueue: DispatchQueue = .main + {{/useVapor}} +}{{^useVapor}} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class RequestBuilder { + var credential: URLCredential? + var headers: [String: String] + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let parameters: [String: Any]? + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let method: String + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let URLString: String + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let requestTask: RequestTask = RequestTask() + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let requiresAuthentication: Bool + + /// Optional block to obtain a reference to the request's progress instance when available. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var onProgressReady: ((Progress) -> Void)? + + required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String: Any]?, headers: [String: String] = [:], requiresAuthentication: Bool) { + self.method = method + self.URLString = URLString + self.parameters = parameters + self.headers = headers + self.requiresAuthentication = requiresAuthentication + + addHeaders({{projectName}}API.customHeaders) + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func addHeaders(_ aHeaders: [String: String]) { + for (header, value) in aHeaders { + headers[header] = value + } + } + + @discardableResult + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute(_ apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, _ completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) -> RequestTask { + return requestTask + } + + {{#useAsyncAwait}} + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + @discardableResult + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute() async throws -> Response { + return try await withTaskCancellationHandler { + try Task.checkCancellation() + return try await withCheckedThrowingContinuation { continuation in + guard !Task.isCancelled else { + continuation.resume(throwing: CancellationError()) + return + } + + self.execute { result in + switch result { + case let .success(response): + continuation.resume(returning: response) + case let .failure(error): + continuation.resume(throwing: error) + } + } + } + } onCancel: { + self.requestTask.cancel() + } + } + + {{/useAsyncAwait}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func addHeader(name: String, value: String) -> Self { + if !value.isEmpty { + headers[name] = value + } + return self + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func addCredential() -> Self { + credential = {{projectName}}API.credential + return self + } +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol RequestBuilderFactory { + func getNonDecodableBuilder() -> RequestBuilder.Type + func getBuilder() -> RequestBuilder.Type +}{{/useVapor}} diff --git a/codegen/Support/swift/AsposeBarcodeCloudClient.swift b/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache similarity index 99% rename from codegen/Support/swift/AsposeBarcodeCloudClient.swift rename to codegen/Templates/swift/AsposeBarcodeCloudClient.mustache index a815b040..550c2f2a 100644 --- a/codegen/Support/swift/AsposeBarcodeCloudClient.swift +++ b/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache @@ -33,7 +33,7 @@ public final class AsposeBarcodeCloudConfiguration { public static let defaultHost = "https://api.aspose.cloud/v4.0" public static let defaultTokenURL = "https://id.aspose.cloud/connect/token" public static let defaultSdkName = "swift sdk" - public static let defaultSdkVersion = "26.4.0" + public static let defaultSdkVersion = "{{packageVersion}}" public var host: String public var tokenURL: String diff --git a/codegen/Templates/swift/Cartfile.mustache b/codegen/Templates/swift/Cartfile.mustache new file mode 100644 index 00000000..db77b5e6 --- /dev/null +++ b/codegen/Templates/swift/Cartfile.mustache @@ -0,0 +1,4 @@ +github "Flight-School/AnyCodable" ~> 0.6{{#useAlamofire}} +github "Alamofire/Alamofire" ~> 5.7{{/useAlamofire}}{{#usePromiseKit}} +github "mxcl/PromiseKit" ~> 6.15{{/usePromiseKit}}{{#useRxSwift}} +github "ReactiveX/RxSwift" ~> 6.2{{/useRxSwift}} diff --git a/codegen/Templates/swift/CodableHelper.mustache b/codegen/Templates/swift/CodableHelper.mustache new file mode 100644 index 00000000..554418a5 --- /dev/null +++ b/codegen/Templates/swift/CodableHelper.mustache @@ -0,0 +1,49 @@ +// +// CodableHelper.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class CodableHelper { + private static var customDateFormatter: DateFormatter? + private static var defaultDateFormatter: DateFormatter = OpenISO8601DateFormatter() + + private static var customJSONDecoder: JSONDecoder? + private static var defaultJSONDecoder: JSONDecoder = { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .formatted(CodableHelper.dateFormatter) + return decoder + }() + + private static var customJSONEncoder: JSONEncoder? + private static var defaultJSONEncoder: JSONEncoder = { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .formatted(CodableHelper.dateFormatter) + encoder.outputFormatting = .prettyPrinted + return encoder + }() + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var dateFormatter: DateFormatter { + get { return customDateFormatter ?? defaultDateFormatter } + set { customDateFormatter = newValue } + } + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var jsonDecoder: JSONDecoder { + get { return customJSONDecoder ?? defaultJSONDecoder } + set { customJSONDecoder = newValue } + } + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var jsonEncoder: JSONEncoder { + get { return customJSONEncoder ?? defaultJSONEncoder } + set { customJSONEncoder = newValue } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func decode(_ type: T.Type, from data: Data) -> Swift.Result where T: Decodable { + return Swift.Result { try jsonDecoder.decode(type, from: data) } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func encode(_ value: T) -> Swift.Result where T: Encodable { + return Swift.Result { try jsonEncoder.encode(value) } + } +} diff --git a/codegen/Templates/swift/Configuration.mustache b/codegen/Templates/swift/Configuration.mustache new file mode 100644 index 00000000..a642dbcd --- /dev/null +++ b/codegen/Templates/swift/Configuration.mustache @@ -0,0 +1,55 @@ +// Configuration.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif{{#useVapor}} +import Vapor{{/useVapor}}{{#useAlamofire}} +import Alamofire{{/useAlamofire}} + +{{#swiftUseApiNamespace}} +@available(*, deprecated, renamed: "{{projectName}}API.Configuration") +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} typealias Configuration = {{projectName}}API.Configuration + +extension {{projectName}}API { +{{/swiftUseApiNamespace}} +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class Configuration { + {{#useVapor}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var apiClient: Vapor.Client? = nil + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var apiWrapper: (inout Vapor.ClientRequest) throws -> () = { _ in } + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var contentConfiguration = ContentConfiguration.default(){{/useVapor}}{{^useVapor}} + /// Configures the range of HTTP status codes that will result in a successful response + /// + /// If a HTTP status code is outside of this range the response will be interpreted as failed. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var successfulStatusCodeRange: Range = 200..<300{{/useVapor}}{{#useAlamofire}} + /// ResponseSerializer that will be used by the generator for `Data` responses + /// + /// If unchanged, Alamofires default `DataResponseSerializer` will be used. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var dataResponseSerializer: AnyResponseSerializer = AnyResponseSerializer(DataResponseSerializer()) + /// ResponseSerializer that will be used by the generator for `String` responses + /// + /// If unchanged, Alamofires default `StringResponseSerializer` will be used. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var stringResponseSerializer: AnyResponseSerializer = AnyResponseSerializer(StringResponseSerializer()){{/useAlamofire}} +} +{{#useAlamofire}} + +/// Type-erased ResponseSerializer +/// +/// This is needed in order to use `ResponseSerializer` as a Type in `Configuration`. Obsolete with `any` keyword in Swift >= 5.7 +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct AnyResponseSerializer: ResponseSerializer { + + let _serialize: (URLRequest?, HTTPURLResponse?, Data?, Error?) throws -> T + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(_ delegatee: V) where V.SerializedObject == T { + _serialize = delegatee.serialize + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> T { + try _serialize(request, response, data, error) + } +}{{/useAlamofire}}{{#swiftUseApiNamespace}}} + +{{/swiftUseApiNamespace}} \ No newline at end of file diff --git a/codegen/Templates/swift/Extensions.mustache b/codegen/Templates/swift/Extensions.mustache new file mode 100644 index 00000000..45e11921 --- /dev/null +++ b/codegen/Templates/swift/Extensions.mustache @@ -0,0 +1,283 @@ +// Extensions.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif +#if canImport(AnyCodable) +import AnyCodable +#endif{{#usePromiseKit}} +import PromiseKit{{/usePromiseKit}}{{#useVapor}} +import Vapor{{/useVapor}}{{^useVapor}} + +extension Bool: JSONEncodable { + func encodeToJSON() -> Any { self } +} + +extension Float: JSONEncodable { + func encodeToJSON() -> Any { self } +} + +extension Int: JSONEncodable { + func encodeToJSON() -> Any { self } +} + +extension Int32: JSONEncodable { + func encodeToJSON() -> Any { self } +} + +extension Int64: JSONEncodable { + func encodeToJSON() -> Any { self } +} + +extension Double: JSONEncodable { + func encodeToJSON() -> Any { self } +} + +extension Decimal: JSONEncodable { + func encodeToJSON() -> Any { self } +} + +extension String: JSONEncodable { + func encodeToJSON() -> Any { self } +} + +extension URL: JSONEncodable { + func encodeToJSON() -> Any { self } +} + +extension UUID: JSONEncodable { + func encodeToJSON() -> Any { self } +} + +extension RawRepresentable where RawValue: JSONEncodable { + func encodeToJSON() -> Any { return self.rawValue } +} + +private func encodeIfPossible(_ object: T) -> Any { + if let encodableObject = object as? JSONEncodable { + return encodableObject.encodeToJSON() + } else { + return object + } +} + +extension Array: JSONEncodable { + func encodeToJSON() -> Any { + return self.map(encodeIfPossible) + } +} + +extension Set: JSONEncodable { + func encodeToJSON() -> Any { + return Array(self).encodeToJSON() + } +} + +extension Dictionary: JSONEncodable { + func encodeToJSON() -> Any { + var dictionary = [AnyHashable: Any]() + for (key, value) in self { + dictionary[key] = encodeIfPossible(value) + } + return dictionary + } +} + +extension Data: JSONEncodable { + func encodeToJSON() -> Any { + return self.base64EncodedString(options: Data.Base64EncodingOptions()) + } +} + +extension Date: JSONEncodable { + func encodeToJSON() -> Any { + return CodableHelper.dateFormatter.string(from: self) + } +} + +extension JSONEncodable where Self: Encodable { + func encodeToJSON() -> Any { + guard let data = try? CodableHelper.jsonEncoder.encode(self) else { + fatalError("Could not encode to json: \(self)") + } + return data.encodeToJSON() + } +}{{/useVapor}}{{#generateModelAdditionalProperties}} + +extension Swift.String: Swift.CodingKey { + + public var stringValue: String { + return self + } + + public init?(stringValue: String) { + self.init(stringLiteral: stringValue) + } + + public var intValue: Int? { + return nil + } + + public init?(intValue: Int) { + return nil + } + +} + +extension KeyedEncodingContainerProtocol { + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeArray(_ values: [T], forKey key: Self.Key) throws where T: Encodable { + var arrayContainer = nestedUnkeyedContainer(forKey: key) + try arrayContainer.encode(contentsOf: values) + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeArrayIfPresent(_ values: [T]?, forKey key: Self.Key) throws where T: Encodable { + if let values = values { + try encodeArray(values, forKey: key) + } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeMap(_ pairs: [Self.Key: T]) throws where T: Encodable { + for (key, value) in pairs { + try encode(value, forKey: key) + } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeMapIfPresent(_ pairs: [Self.Key: T]?) throws where T: Encodable { + if let pairs = pairs { + try encodeMap(pairs) + } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encode(_ value: Decimal, forKey key: Self.Key) throws { + var mutableValue = value + let stringValue = NSDecimalString(&mutableValue, Locale(identifier: "en_US")) + try encode(stringValue, forKey: key) + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeIfPresent(_ value: Decimal?, forKey key: Self.Key) throws { + if let value = value { + try encode(value, forKey: key) + } + } +} + +extension KeyedDecodingContainerProtocol { + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeArray(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T: Decodable { + var tmpArray = [T]() + + var nestedContainer = try nestedUnkeyedContainer(forKey: key) + while !nestedContainer.isAtEnd { + let arrayValue = try nestedContainer.decode(T.self) + tmpArray.append(arrayValue) + } + + return tmpArray + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeArrayIfPresent(_ type: T.Type, forKey key: Self.Key) throws -> [T]? where T: Decodable { + var tmpArray: [T]? + + if contains(key) { + tmpArray = try decodeArray(T.self, forKey: key) + } + + return tmpArray + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeMap(_ type: T.Type, excludedKeys: Set) throws -> [Self.Key: T] where T: Decodable { + var map: [Self.Key: T] = [:] + + for key in allKeys { + if !excludedKeys.contains(key) { + let value = try decode(T.self, forKey: key) + map[key] = value + } + } + + return map + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decode(_ type: Decimal.Type, forKey key: Self.Key) throws -> Decimal { + let stringValue = try decode(String.self, forKey: key) + guard let decimalValue = Decimal(string: stringValue) else { + let context = DecodingError.Context(codingPath: [key], debugDescription: "The key \(key) couldn't be converted to a Decimal value") + throw DecodingError.typeMismatch(type, context) + } + + return decimalValue + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeIfPresent(_ type: Decimal.Type, forKey key: Self.Key) throws -> Decimal? { + guard let stringValue = try decodeIfPresent(String.self, forKey: key) else { + return nil + } + guard let decimalValue = Decimal(string: stringValue) else { + let context = DecodingError.Context(codingPath: [key], debugDescription: "The key \(key) couldn't be converted to a Decimal value") + throw DecodingError.typeMismatch(type, context) + } + + return decimalValue + } + +}{{/generateModelAdditionalProperties}}{{^useVapor}} + +extension HTTPURLResponse { + var isStatusCodeSuccessful: Bool { + return Configuration.successfulStatusCodeRange.contains(statusCode) + } +}{{/useVapor}}{{#usePromiseKit}} + +extension RequestBuilder { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func execute() -> Promise> { + let deferred = Promise>.pending() + self.execute { result in + switch result { + case let .success(response): + deferred.resolver.fulfill(response) + case let .failure(error): + deferred.resolver.reject(error) + } + } + return deferred.promise + } +}{{/usePromiseKit}}{{#useVapor}} + +extension UUID: Content { } + +extension URL: Content { } + +extension Bool: Content { } + +extension Set: ResponseEncodable where Element: Content { + public func encodeResponse(for request: Vapor.Request) -> EventLoopFuture { + let response = Vapor.Response() + do { + try response.content.encode(Array(self)) + } catch { + return request.eventLoop.makeFailedFuture(error) + } + return request.eventLoop.makeSucceededFuture(response) + } +} + +extension Set: RequestDecodable where Element: Content { + public static func decodeRequest(_ request: Vapor.Request) -> EventLoopFuture { + do { + let content = try request.content.decode([Element].self) + return request.eventLoop.makeSucceededFuture(Set(content)) + } catch { + return request.eventLoop.makeFailedFuture(error) + } + } +} + +extension Set: Content where Element: Content { } + +extension AnyCodable: Content {}{{/useVapor}} diff --git a/codegen/Templates/swift/JSONDataEncoding.mustache b/codegen/Templates/swift/JSONDataEncoding.mustache new file mode 100644 index 00000000..bfc76612 --- /dev/null +++ b/codegen/Templates/swift/JSONDataEncoding.mustache @@ -0,0 +1,56 @@ +// +// JSONDataEncoding.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct JSONDataEncoding { + + // MARK: Properties + + private static let jsonDataKey = "jsonData" + + // MARK: Encoding + + /// Creates a URL request by encoding parameters and applying them onto an existing request. + /// + /// - parameter urlRequest: The request to have parameters applied. + /// - parameter parameters: The parameters to apply. This should have a single key/value + /// pair with "jsonData" as the key and a Data object as the value. + /// + /// - throws: An `Error` if the encoding process encounters an error. + /// + /// - returns: The encoded request. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) -> URLRequest { + var urlRequest = urlRequest + + guard let jsonData = parameters?[JSONDataEncoding.jsonDataKey] as? Data, !jsonData.isEmpty else { + return urlRequest + } + + if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { + urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") + } + + urlRequest.httpBody = jsonData + + return urlRequest + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func encodingParameters(jsonData: Data?) -> [String: Any]? { + var returnedParams: [String: Any]? + if let jsonData = jsonData, !jsonData.isEmpty { + var params: [String: Any] = [:] + params[jsonDataKey] = jsonData + returnedParams = params + } + return returnedParams + } + +} diff --git a/codegen/Templates/swift/JSONEncodingHelper.mustache b/codegen/Templates/swift/JSONEncodingHelper.mustache new file mode 100644 index 00000000..0eae73e1 --- /dev/null +++ b/codegen/Templates/swift/JSONEncodingHelper.mustache @@ -0,0 +1,45 @@ +// +// JSONEncodingHelper.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class JSONEncodingHelper { + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func encodingParameters(forEncodableObject encodableObj: T?) -> [String: Any]? { + var params: [String: Any]? + + // Encode the Encodable object + if let encodableObj = encodableObj { + let encodeResult = CodableHelper.encode(encodableObj) + do { + let data = try encodeResult.get() + params = JSONDataEncoding.encodingParameters(jsonData: data) + } catch { + print(error.localizedDescription) + } + } + + return params + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func encodingParameters(forEncodableObject encodableObj: Any?) -> [String: Any]? { + var params: [String: Any]? + + if let encodableObj = encodableObj { + do { + let data = try JSONSerialization.data(withJSONObject: encodableObj, options: .prettyPrinted) + params = JSONDataEncoding.encodingParameters(jsonData: data) + } catch { + print(error.localizedDescription) + return nil + } + } + + return params + } + +} diff --git a/codegen/Templates/swift/Models.mustache b/codegen/Templates/swift/Models.mustache new file mode 100644 index 00000000..3bd5e1c4 --- /dev/null +++ b/codegen/Templates/swift/Models.mustache @@ -0,0 +1,148 @@ +// Models.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif{{#useAlamofire}} +import Alamofire{{/useAlamofire}} + +protocol JSONEncodable { + func encodeToJSON() -> Any +} + +/// An enum where the last case value can be used as a default catch-all. +protocol CaseIterableDefaultsLast: Decodable & CaseIterable & RawRepresentable +where RawValue: Decodable, AllCases: BidirectionalCollection {} + +extension CaseIterableDefaultsLast { + /// Initializes an enum such that if a known raw value is found, then it is decoded. + /// Otherwise the last case is used. + /// - Parameter decoder: A decoder. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(from decoder: Decoder) throws { + if let value = try Self(rawValue: decoder.singleValueContainer().decode(RawValue.self)) { + self = value + } else if let lastValue = Self.allCases.last { + self = lastValue + } else { + throw DecodingError.valueNotFound( + Self.Type.self, + .init(codingPath: decoder.codingPath, debugDescription: "CaseIterableDefaultsLast") + ) + } + } +} + +/// A flexible type that can be encoded (`.encodeNull` or `.encodeValue`) +/// or not encoded (`.encodeNothing`). Intended for request payloads. +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NullEncodable: Hashable { + case encodeNothing + case encodeNull + case encodeValue(Wrapped) +} + +extension NullEncodable: Codable where Wrapped: Codable { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if let value = try? container.decode(Wrapped.self) { + self = .encodeValue(value) + } else if container.decodeNil() { + self = .encodeNull + } else { + self = .encodeNothing + } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .encodeNothing: return + case .encodeNull: try container.encodeNil() + case .encodeValue(let wrapped): try container.encode(wrapped) + } + } +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum ErrorResponse: Error { + case error(Int, Data?, URLResponse?, Error) +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum DownloadException: Error { + case responseDataMissing + case responseFailed + case requestMissing + case requestMissingPath + case requestMissingURL +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum DecodableRequestBuilderError: Error { + case emptyDataResponse + case nilHTTPResponse + case unsuccessfulHTTPStatusCode + case jsonDecoding(DecodingError) + case generalError(Error) +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class Response { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let statusCode: Int + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let header: [String: String] + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let body: T + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let bodyData: Data? + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(statusCode: Int, header: [String: String], body: T, bodyData: Data?) { + self.statusCode = statusCode + self.header = header + self.body = body + self.bodyData = bodyData + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} convenience init(response: HTTPURLResponse, body: T, bodyData: Data?) { + let rawHeader = response.allHeaderFields + var responseHeader = [String: String]() + for (key, value) in rawHeader { + if let key = key.base as? String, let value = value as? String { + responseHeader[key] = value + } + } + self.init(statusCode: response.statusCode, header: responseHeader, body: body, bodyData: bodyData) + } +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} final class RequestTask{{#useAsyncAwait}}: @unchecked Sendable{{/useAsyncAwait}} { + private var lock = NSRecursiveLock() +{{#useAlamofire}} + private var request: Request? + + internal func set(request: Request) { + lock.lock() + defer { lock.unlock() } + self.request = request + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func cancel() { + lock.lock() + defer { lock.unlock() } + request?.cancel() + request = nil + } +{{/useAlamofire}} +{{^useAlamofire}} + private var task: URLSessionDataTaskProtocol? + + internal func set(task: URLSessionDataTaskProtocol) { + lock.lock() + defer { lock.unlock() } + self.task = task + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func cancel() { + lock.lock() + defer { lock.unlock() } + task?.cancel() + task = nil + } +{{/useAlamofire}} +} diff --git a/codegen/Templates/swift/OpenAPIDateWithoutTime.mustache b/codegen/Templates/swift/OpenAPIDateWithoutTime.mustache new file mode 100644 index 00000000..53789ce5 --- /dev/null +++ b/codegen/Templates/swift/OpenAPIDateWithoutTime.mustache @@ -0,0 +1,98 @@ +// OpenAPIDateWithoutTime.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +/// Represents a date without time information (e.g. a birthday) for transmission from and to a REST API +/// +/// This type is used as a representation for openapi specs `date` format which does not contain +/// time information as opposed to the `date-time` format. Although it internally uses `Date` for +/// (de-)serialization as well the generator needs to be able to distinguish between the two formats. +/// - note: As `Date` is agnostic to timezones (and calendars), timezone information is needed to be able to add +/// an appropriate padding in order to transform to GMT+0 which is the assumed timezone in ISO 8601. +/// When decoding, GMT+0 can be assumed (again: ISO8601) so there is no padding necessary and wrappedDate +/// can be used safely. +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct OpenAPIDateWithoutTime: Codable, Hashable, Equatable { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let wrappedDate: Date + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let timezone: TimeZone + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum CodingKeys: CodingKey, CaseIterable { + case wrappedDate + case timezone + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum DecodingError: Error { + case notADateString + } + + /// On decoding ISO8601 timezone is assumed + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + + let dateString = try container.decode(String.self) + guard let date = OpenISO8601DateFormatter.withoutTime.date(from: dateString) else { + throw DecodingError.notADateString + } + self.wrappedDate = date + + self.timezone = OpenISO8601DateFormatter.withoutTime.timeZone + } + + /// Convenience Initializer which is useful when dealing with optionals a lot like e.g. in API mappers + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init?(wrappedDate: Date?, timezone: TimeZone = .current) { + guard let wrappedDate = wrappedDate else { + return nil + } + + self.init(wrappedDate: wrappedDate, timezone: timezone) + } + + /// Designated Initializer for `OpenAPIDateWithoutTime` + /// + /// Since usually `Date`s without time components - as e.g. birthdays - are created Calendar- and timezone-aware + // it is important to also provide a timezone so that the generator is able to normalize the supplied Date to ISO8601 (GMT+0) + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(wrappedDate: Date, timezone: TimeZone) { + self.wrappedDate = wrappedDate + self.timezone = timezone + } + + /// Only the wrappedDate is encoded normalized to GMT+0 with an offset derived from the supplied Timezone + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(OpenISO8601DateFormatter.withoutTime.string(from: normalizedWrappedDate())) + } + + /// Normalizes the wrappedDate to GMT+0 according to the supplied timezone + fileprivate func normalizedWrappedDate() -> Date { + return wrappedDate.addingTimeInterval( + Double(timezone.secondsFromGMT(for: wrappedDate))) + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func == (lhs: Self, rhs: Self) -> Bool { + Calendar.current.compare(lhs.wrappedDate, to: rhs.wrappedDate, toGranularity: .day) == .orderedSame + } +} + +extension OpenAPIDateWithoutTime: JSONEncodable { + func encodeToJSON() -> Any { + return OpenISO8601DateFormatter.withoutTime.string(from: self.normalizedWrappedDate()) + } +} + +extension OpenAPIDateWithoutTime: RawRepresentable { + public typealias RawValue = String + public init?(rawValue: String) { + if let date = OpenISO8601DateFormatter.withoutTime.date(from: rawValue) { + self.init(wrappedDate: date) + } else { + return nil + } + } + + public var rawValue: String { + OpenISO8601DateFormatter.withoutTime.string(from: normalizedWrappedDate()) + } +} \ No newline at end of file diff --git a/codegen/Templates/swift/OpenISO8601DateFormatter.mustache b/codegen/Templates/swift/OpenISO8601DateFormatter.mustache new file mode 100644 index 00000000..9685a5be --- /dev/null +++ b/codegen/Templates/swift/OpenISO8601DateFormatter.mustache @@ -0,0 +1,60 @@ +// +// OpenISO8601DateFormatter.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +// https://stackoverflow.com/a/50281094/976628 +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} class OpenISO8601DateFormatter: DateFormatter { + static let withoutSeconds: DateFormatter = { + let formatter = DateFormatter() + formatter.calendar = Calendar(identifier: .iso8601) + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" + return formatter + }() + + static let withoutTime: DateFormatter = { + let formatter = DateFormatter() + formatter.calendar = Calendar(identifier: .iso8601) + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.dateFormat = "yyyy-MM-dd" + return formatter + }() + + private func setup() { + calendar = Calendar(identifier: .iso8601) + locale = Locale(identifier: "en_US_POSIX") + timeZone = TimeZone(secondsFromGMT: 0) + dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" + } + + override init() { + super.init() + setup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setup() + } + + override {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func date(from string: String) -> Date? { + if let result = super.date(from: string) { + return result + } else if let result = OpenISO8601DateFormatter.withoutSeconds.date(from: string) { + return result + } + + return OpenISO8601DateFormatter.withoutTime.date(from: string) + } +} + +#if compiler(>=5.5) +extension OpenISO8601DateFormatter: @unchecked Sendable {} +#endif diff --git a/codegen/Templates/swift/Package.swift.mustache b/codegen/Templates/swift/Package.swift.mustache new file mode 100644 index 00000000..7a261f93 --- /dev/null +++ b/codegen/Templates/swift/Package.swift.mustache @@ -0,0 +1,50 @@ +// swift-tools-version:5.1 + +import PackageDescription + +let package = Package( + name: "{{projectName}}", + platforms: [ + {{#useVapor}} + .macOS(.v10_15), + {{/useVapor}} + {{^useVapor}} + .iOS(.v11), + .macOS(.v10_13), + .tvOS(.v11), + .watchOS(.v4), + {{/useVapor}} + ], + products: [ + // Products define the executables and libraries produced by a package, and make them visible to other packages. + .library( + name: "{{projectName}}", + targets: ["{{projectName}}"] + ), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + .package(url: "https://github.com/Flight-School/AnyCodable", .upToNextMajor(from: "0.6.1")), + {{#useAlamofire}} + .package(url: "https://github.com/Alamofire/Alamofire", .upToNextMajor(from: "5.7.0")), + {{/useAlamofire}} + {{#usePromiseKit}} + .package(url: "https://github.com/mxcl/PromiseKit", .upToNextMajor(from: "6.15.3")), + {{/usePromiseKit}} + {{#useRxSwift}} + .package(url: "https://github.com/ReactiveX/RxSwift", .upToNextMajor(from: "6.2.0")), + {{/useRxSwift}} + {{#useVapor}} + .package(url: "https://github.com/vapor/vapor", from: "4.0.0") + {{/useVapor}} + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages which this package depends on. + .target( + name: "{{projectName}}", + dependencies: ["AnyCodable", {{#useVapor}}"Vapor", {{/useVapor}}{{#useAlamofire}}"Alamofire", {{/useAlamofire}}{{#usePromiseKit}}"PromiseKit", {{/usePromiseKit}}{{#useRxSwift}}"RxSwift"{{/useRxSwift}}], + path: "{{swiftPackagePath}}{{^swiftPackagePath}}{{#useSPMFileStructure}}Sources/{{projectName}}{{/useSPMFileStructure}}{{^useSPMFileStructure}}{{projectName}}/Classes{{/useSPMFileStructure}}{{/swiftPackagePath}}" + ), + ] +) diff --git a/codegen/Templates/swift/Podspec.mustache b/codegen/Templates/swift/Podspec.mustache new file mode 100644 index 00000000..4fd2bbe3 --- /dev/null +++ b/codegen/Templates/swift/Podspec.mustache @@ -0,0 +1,39 @@ +Pod::Spec.new do |s| + s.name = '{{projectName}}'{{#projectDescription}} + s.summary = '{{.}}'{{/projectDescription}} + s.ios.deployment_target = '11.0' + s.osx.deployment_target = '10.13' + s.tvos.deployment_target = '11.0' + s.watchos.deployment_target = '4.0' + s.version = '{{podVersion}}{{^podVersion}}{{#apiInfo}}{{version}}{{/apiInfo}}{{^apiInfo}}}0.0.1{{/apiInfo}}{{/podVersion}}' + s.source = {{#podSource}}{{& podSource}}{{/podSource}}{{^podSource}}{ :git => 'git@github.com:OpenAPITools/openapi-generator.git', :tag => 'v{{#apiInfo}}{{version}}{{/apiInfo}}{{^apiInfo}}}0.0.1{{/apiInfo}}' }{{/podSource}} + {{#podAuthors}} + s.authors = '{{.}}' + {{/podAuthors}} + {{#podSocialMediaURL}} + s.social_media_url = '{{.}}' + {{/podSocialMediaURL}} + s.license = {{#podLicense}}{{& podLicense}}{{/podLicense}}{{^podLicense}}'Proprietary'{{/podLicense}} + s.homepage = '{{podHomepage}}{{^podHomepage}}https://github.com/OpenAPITools/openapi-generator{{/podHomepage}}' + s.summary = '{{podSummary}}{{^podSummary}}{{projectName}} Swift SDK{{/podSummary}}' + {{#podDescription}} + s.description = '{{.}}' + {{/podDescription}} + {{#podScreenshots}} + s.screenshots = {{& podScreenshots}} + {{/podScreenshots}} + {{#podDocumentationURL}} + s.documentation_url = '{{.}}' + {{/podDocumentationURL}} + s.source_files = '{{swiftPackagePath}}{{^swiftPackagePath}}{{#useSPMFileStructure}}Sources/{{projectName}}{{/useSPMFileStructure}}{{^useSPMFileStructure}}{{projectName}}/Classes{{/useSPMFileStructure}}{{/swiftPackagePath}}/**/*.swift' + s.dependency 'AnyCodable-FlightSchool', '~> 0.6' + {{#useAlamofire}} + s.dependency 'Alamofire', '~> 5.7' + {{/useAlamofire}} + {{#usePromiseKit}} + s.dependency 'PromiseKit/CorePromise', '~> 6.15' + {{/usePromiseKit}} + {{#useRxSwift}} + s.dependency 'RxSwift', '~> 6.2' + {{/useRxSwift}} +end diff --git a/codegen/Templates/swift/README.mustache b/codegen/Templates/swift/README.mustache new file mode 100644 index 00000000..64897978 --- /dev/null +++ b/codegen/Templates/swift/README.mustache @@ -0,0 +1,136 @@ +# Aspose.BarCode Cloud SDK for Swift + +This repository contains the Swift SDK for Aspose.BarCode Cloud. + +## Requirements + +- Swift Package Manager +- iOS 11.0 or later +- macOS 10.13 or later + +## Usage + +Add the package to your SwiftPM dependencies after the repository is published: + +```swift +.package(url: "https://github.com/aspose-barcode-cloud/Aspose.BarCode-Cloud-SDK-for-Swift.git", from: "{{packageVersion}}") +``` + +Releases use the BarCode SDK tag style, for example `v{{packageVersion}}`. SwiftPM version requirements still use the semantic version value without the `v` prefix. + +Then import the module: + +```swift +import AsposeBarcodeCloud +``` + +The first generated API surface includes `GenerateAPI`, `RecognizeAPI`, `ScanAPI`, and the corresponding models. + +### Authentication + +```swift +let client = AsposeBarcodeCloudClient( + clientId: "your-client-id", + clientSecret: "your-client-secret" +) + +try client.authorize() +``` + +If you already have an access token, configure the SDK directly: + +```swift +let client = AsposeBarcodeCloudClient(accessToken: "your-access-token") +client.apply() +``` + +`AsposeBarcodeCloudClient` sets `Authorization`, `x-aspose-client`, and `x-aspose-client-version` headers for generated requests. + +### Generate + +```swift +GenerateAPI.generate( + barcodeType: .qr, + data: "Aspose.BarCode Cloud", + imageFormat: .png +) { data, error in + if let error = error { + print(error) + return + } + + print("Generated bytes:", data?.count ?? 0) +} +``` + +### Scan and Recognize + +Use `scanBase64` when the SDK should detect barcode types automatically, or `recognizeBase64` when you know which barcode types to look for. + +```swift +let imageBase64 = generatedPngData.base64EncodedString() + +ScanAPI.scanBase64( + scanBase64Request: ScanBase64Request(fileBase64: imageBase64) +) { response, error in + if let error = error { + print(error) + return + } + + print(response?.barcodes?.first?.barcodeValue ?? "") +} + +let request = RecognizeBase64Request( + barcodeTypes: [.qr], + fileBase64: imageBase64 +) + +RecognizeAPI.recognizeBase64(recognizeBase64Request: request) { response, error in + if let error = error { + print(error) + return + } + + print(response?.barcodes?.first?.barcodeValue ?? "") +} +``` + +## Development + +The generated source is maintained from the `aspose-barcode-cloud-codegen` repository: + +```bash +cd ../aspose-barcode-cloud-codegen +make swift +``` + +Run the default local checks from this package directory: + +```bash +make test +``` + +Run live integration smoke tests with Aspose Cloud credentials: + +```bash +cp Tests/configuration.example.json Tests/configuration.json +# Fill clientId and clientSecret. +make integration-test +``` + +Alternatively, keep credentials in local environment variables: + +```bash +cp .env.integration.example .env.integration +# Fill TEST_CONFIGURATION_CLIENT_ID and TEST_CONFIGURATION_CLIENT_SECRET, or TEST_CONFIGURATION_ACCESS_TOKEN. +make integration-test +``` + +Run the sample program: + +```bash +make example +``` + +The sample writes `QR.png` locally. diff --git a/codegen/Templates/swift/SynchronizedDictionary.mustache b/codegen/Templates/swift/SynchronizedDictionary.mustache new file mode 100644 index 00000000..acf7ff40 --- /dev/null +++ b/codegen/Templates/swift/SynchronizedDictionary.mustache @@ -0,0 +1,36 @@ +// SynchronizedDictionary.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +internal struct SynchronizedDictionary { + + private var dictionary = [K: V]() + private let queue = DispatchQueue( + label: "SynchronizedDictionary", + qos: DispatchQoS.userInitiated, + attributes: [DispatchQueue.Attributes.concurrent], + autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit, + target: nil + ) + + internal subscript(key: K) -> V? { + get { + var value: V? + + queue.sync { + value = self.dictionary[key] + } + + return value + } + set { + queue.sync(flags: DispatchWorkItemFlags.barrier) { + self.dictionary[key] = newValue + } + } + } +} diff --git a/codegen/Templates/swift/Validation.mustache b/codegen/Templates/swift/Validation.mustache new file mode 100644 index 00000000..70e3abfb --- /dev/null +++ b/codegen/Templates/swift/Validation.mustache @@ -0,0 +1,161 @@ +// Validation.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct StringRule { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var minLength: Int? + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var maxLength: Int? + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var pattern: String? +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct NumericRule { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var minimum: T? + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var exclusiveMinimum = false + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var maximum: T? + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var exclusiveMaximum = false + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var multipleOf: T? +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct ArrayRule { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var minItems: Int? + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var maxItems: Int? + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var uniqueItems: Bool +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum StringValidationErrorKind: Error { + case minLength, maxLength, pattern +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NumericValidationErrorKind: Error { + case minimum, maximum, multipleOf +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum ArrayValidationErrorKind: Error { + case minItems, maxItems, uniqueItems +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct ValidationError: Error { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} fileprivate(set) var kinds: Set +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct Validator { + /// Validate a string against a rule. + /// - Parameter string: The String you wish to validate. + /// - Parameter rule: The StringRule you wish to use for validation. + /// - Returns: A validated string. + /// - Throws: `ValidationError` if the string is invalid against the rule, + /// `NSError` if the rule.pattern is invalid. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ string: String, against rule: StringRule) throws -> String { + var error = ValidationError(kinds: []) + if let minLength = rule.minLength, !(minLength <= string.count) { + error.kinds.insert(.minLength) + } + if let maxLength = rule.maxLength, !(string.count <= maxLength) { + error.kinds.insert(.maxLength) + } + if let pattern = rule.pattern { + let matches = try NSRegularExpression(pattern: pattern, options: .caseInsensitive) + .matches(in: string, range: .init(location: 0, length: string.utf16.count)) + if matches.isEmpty { + error.kinds.insert(.pattern) + } + } + guard error.kinds.isEmpty else { + throw error + } + return string + } + + /// Validate a integer against a rule. + /// - Parameter numeric: The integer you wish to validate. + /// - Parameter rule: The NumericRule you wish to use for validation. + /// - Returns: A validated integer. + /// - Throws: `ValidationError` if the numeric is invalid against the rule. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ numeric: T, against rule: NumericRule) throws -> T { + var error = ValidationError(kinds: []) + if let minium = rule.minimum { + if !rule.exclusiveMinimum, minium > numeric { + error.kinds.insert(.minimum) + } + if rule.exclusiveMinimum, minium >= numeric { + error.kinds.insert(.minimum) + } + } + if let maximum = rule.maximum { + if !rule.exclusiveMaximum, numeric > maximum { + error.kinds.insert(.maximum) + } + if rule.exclusiveMaximum, numeric >= maximum { + error.kinds.insert(.maximum) + } + } + if let multipleOf = rule.multipleOf, !numeric.isMultiple(of: multipleOf) { + error.kinds.insert(.multipleOf) + } + guard error.kinds.isEmpty else { + throw error + } + return numeric + } + + /// Validate a fractional number against a rule. + /// - Parameter numeric: The fractional number you wish to validate. + /// - Parameter rule: The NumericRule you wish to use for validation. + /// - Returns: A validated fractional number. + /// - Throws: `ValidationError` if the numeric is invalid against the rule. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ numeric: T, against rule: NumericRule) throws -> T { + var error = ValidationError(kinds: []) + if let minium = rule.minimum { + if !rule.exclusiveMinimum, minium > numeric { + error.kinds.insert(.minimum) + } + if rule.exclusiveMinimum, minium >= numeric { + error.kinds.insert(.minimum) + } + } + if let maximum = rule.maximum { + if !rule.exclusiveMaximum, numeric > maximum { + error.kinds.insert(.maximum) + } + if rule.exclusiveMaximum, numeric >= maximum { + error.kinds.insert(.maximum) + } + } + if let multipleOf = rule.multipleOf, numeric.remainder(dividingBy: multipleOf) != 0 { + error.kinds.insert(.multipleOf) + } + guard error.kinds.isEmpty else { + throw error + } + return numeric + } + + /// Validate a array against a rule. + /// - Parameter array: The Array you wish to validate. + /// - Parameter rule: The ArrayRule you wish to use for validation. + /// - Returns: A validated array. + /// - Throws: `ValidationError` if the string is invalid against the rule. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ array: Array, against rule: ArrayRule) throws -> Array { + var error = ValidationError(kinds: []) + if let minItems = rule.minItems, !(minItems <= array.count) { + error.kinds.insert(.minItems) + } + if let maxItems = rule.maxItems, !(array.count <= maxItems) { + error.kinds.insert(.maxItems) + } + if rule.uniqueItems { + let unique = Set(array) + if unique.count != array.count { + error.kinds.insert(.uniqueItems) + } + } + guard error.kinds.isEmpty else { + throw error + } + return array + } +} diff --git a/codegen/Templates/swift/XcodeGen.mustache b/codegen/Templates/swift/XcodeGen.mustache new file mode 100644 index 00000000..d4a36d58 --- /dev/null +++ b/codegen/Templates/swift/XcodeGen.mustache @@ -0,0 +1,18 @@ +name: {{projectName}} +targets: + {{projectName}}: + type: framework + platform: iOS + deploymentTarget: "11.0" + sources: [{{swiftPackagePath}}{{^swiftPackagePath}}{{#useSPMFileStructure}}Sources{{/useSPMFileStructure}}{{^useSPMFileStructure}}{{projectName}}{{/useSPMFileStructure}}{{/swiftPackagePath}}] + info: + path: ./Info.plist + version: {{podVersion}}{{^podVersion}}{{#apiInfo}}{{version}}{{/apiInfo}}{{^apiInfo}}}0.0.1{{/apiInfo}}{{/podVersion}} + settings: + APPLICATION_EXTENSION_API_ONLY: true + scheme: {} + dependencies: + - carthage: AnyCodable{{#useAlamofire}} + - carthage: Alamofire{{/useAlamofire}}{{#usePromiseKit}} + - carthage: PromiseKit{{/usePromiseKit}}{{#useRxSwift}} + - carthage: RxSwift{{/useRxSwift}} diff --git a/codegen/Templates/swift/_param.mustache b/codegen/Templates/swift/_param.mustache new file mode 100644 index 00000000..00b0c399 --- /dev/null +++ b/codegen/Templates/swift/_param.mustache @@ -0,0 +1 @@ +"{{baseName}}": {{#isQueryParam}}(wrappedValue: {{/isQueryParam}}{{paramName}}{{^required}}?{{/required}}.encodeToJSON(){{#isQueryParam}}, isExplode: {{isExplode}}){{/isQueryParam}} \ No newline at end of file diff --git a/codegen/Templates/swift/api.mustache b/codegen/Templates/swift/api.mustache new file mode 100644 index 00000000..d25148f4 --- /dev/null +++ b/codegen/Templates/swift/api.mustache @@ -0,0 +1,458 @@ +{{#operations}}// +// {{classname}}.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation{{#usePromiseKit}} +import PromiseKit{{/usePromiseKit}}{{#useRxSwift}} +import RxSwift{{/useRxSwift}}{{#useCombine}} +#if canImport(Combine) +import Combine +#endif{{/useCombine}}{{#useVapor}} +import Vapor{{/useVapor}} +#if canImport(AnyCodable) +import AnyCodable +#endif{{#swiftUseApiNamespace}} + +extension {{projectName}}API { +{{/swiftUseApiNamespace}} + +{{#description}} +/** {{{.}}} */{{/description}} +{{#objcCompatible}}@objcMembers {{/objcCompatible}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class {{classname}}{{#objcCompatible}} : NSObject{{/objcCompatible}} { +{{#operation}} + {{#allParams}} + {{#isEnum}} + + /** + * enum for parameter {{paramName}} + */ + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{enumName}}_{{operationId}}: {{^isContainer}}{{{dataType}}}{{/isContainer}}{{#isContainer}}String{{/isContainer}}, CaseIterable{{#useVapor}}, Content{{/useVapor}} { + {{^enumUnknownDefaultCase}} + {{#allowableValues}} + {{#enumVars}} + case {{name}} = {{{value}}} + {{/enumVars}} + {{/allowableValues}} + {{/enumUnknownDefaultCase}} + {{#enumUnknownDefaultCase}} + {{#allowableValues}} + {{#enumVars}} + {{^-last}} + case {{name}} = {{{value}}} + {{/-last}} + {{/enumVars}} + {{/allowableValues}} + {{/enumUnknownDefaultCase}} + } + {{/isEnum}} + {{/allParams}} +{{^useVapor}} +{{^usePromiseKit}} +{{^useRxSwift}} +{{^useResult}} +{{^useCombine}} +{{^useAsyncAwait}} + + /** + {{#summary}} + {{{.}}} + {{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} + - parameter apiResponseQueue: The queue on which api response is dispatched. + - parameter completion: completion handler to receive the data and the error objects + */ + {{#isDeprecated}} + @available(*, deprecated, message: "This operation is deprecated.") + {{/isDeprecated}} + @discardableResult + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, completion: @escaping ((_ data: {{{returnType}}}{{^returnType}}Void{{/returnType}}?, _ error: Error?) -> Void)) -> RequestTask { + return {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).execute(apiResponseQueue) { result in + switch result { + {{#returnType}} + case let .success(response): + completion(response.body, nil) + {{/returnType}} + {{^returnType}} + case .success: + completion((), nil) + {{/returnType}} + case let .failure(error): + completion(nil, error) + } + } + } +{{/useAsyncAwait}} +{{/useCombine}} +{{/useResult}} +{{/useRxSwift}} +{{/usePromiseKit}} +{{/useVapor}} +{{#usePromiseKit}} + + /** + {{#summary}} + {{{.}}} + {{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} + - returns: Promise<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> + */ + {{#isDeprecated}} + @available(*, deprecated, message: "This operation is deprecated.") + {{/isDeprecated}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}} {{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Promise<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> { + let deferred = Promise<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}>.pending() + {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).execute { result in + switch result { + {{#returnType}} + case let .success(response): + deferred.resolver.fulfill(response.body) + {{/returnType}} + {{^returnType}} + case .success: + deferred.resolver.fulfill(()) + {{/returnType}} + case let .failure(error): + deferred.resolver.reject(error) + } + } + return deferred.promise + } +{{/usePromiseKit}} +{{#useRxSwift}} + + /** + {{#summary}} + {{{.}}} + {{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} + - parameter apiResponseQueue: The queue on which api response is dispatched. + - returns: Observable<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> + */ + {{#isDeprecated}} + @available(*, deprecated, message: "This operation is deprecated.") + {{/isDeprecated}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue) -> Observable<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> { + return Observable.create { observer -> Disposable in + let requestTask = {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).execute(apiResponseQueue) { result in + switch result { + {{#returnType}} + case let .success(response): + observer.onNext(response.body) + {{/returnType}} + {{^returnType}} + case .success: + observer.onNext(()) + {{/returnType}} + case let .failure(error): + observer.onError(error) + } + observer.onCompleted() + } + + return Disposables.create { + requestTask.cancel() + } + } + } +{{/useRxSwift}} +{{#useCombine}} + + /** + {{#summary}} + {{{.}}} + {{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} + - returns: AnyPublisher<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}, Error> + */ + #if canImport(Combine) + {{#isDeprecated}} + @available(*, deprecated, message: "This operation is deprecated.") + {{/isDeprecated}} + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> AnyPublisher<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}, Error> { + let requestBuilder = {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) + let requestTask = requestBuilder.requestTask + return Future<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}, Error> { promise in + requestBuilder.execute { result in + switch result { + {{#returnType}} + case let .success(response): + promise(.success(response.body)) + {{/returnType}} + {{^returnType}} + case .success: + promise(.success(())) + {{/returnType}} + case let .failure(error): + promise(.failure(error)) + } + } + } + .handleEvents(receiveCancel: { + requestTask.cancel() + }) + .eraseToAnyPublisher() + } + #endif +{{/useCombine}} +{{#useAsyncAwait}} + + /** + {{#summary}} + {{{.}}} + {{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} + - returns: {{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}} + */ + {{#isDeprecated}} + @available(*, deprecated, message: "This operation is deprecated.") + {{/isDeprecated}} + @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) async throws{{#returnType}} -> {{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{/returnType}} { + return try await {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).execute().body + } +{{/useAsyncAwait}} +{{#useResult}} + + /** + {{#summary}} + {{{.}}} + {{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} + - parameter apiResponseQueue: The queue on which api response is dispatched. + - parameter completion: completion handler to receive the result + */ + {{#isDeprecated}} + @available(*, deprecated, message: "This operation is deprecated.") + {{/isDeprecated}} + @discardableResult + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, completion: @escaping ((_ result: Swift.Result<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}, ErrorResponse>) -> Void)) -> RequestTask { + return {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).execute(apiResponseQueue) { result in + switch result { + {{#returnType}} + case let .success(response): + completion(.success(response.body)) + {{/returnType}} + {{^returnType}} + case .success: + completion(.success(())) + {{/returnType}} + case let .failure(error): + completion(.failure(error)) + } + } + } +{{/useResult}} +{{#useVapor}} + + /** + {{#summary}} + {{{.}}} + {{/summary}} + {{httpMethod}} {{{path}}}{{#notes}} + {{{.}}}{{/notes}}{{#subresourceOperation}} + subresourceOperation: {{.}}{{/subresourceOperation}}{{#defaultResponse}} + defaultResponse: {{.}}{{/defaultResponse}} + {{#authMethods}} + - {{#isBasicBasic}}BASIC{{/isBasicBasic}}{{#isBasicBearer}}Bearer Token{{/isBasicBearer}}{{#isOAuth}}OAuth{{/isOAuth}}{{#isApiKey}}API Key{{/isApiKey}}: + - type: {{type}}{{#keyParamName}} {{keyParamName}} {{#isKeyInQuery}}(QUERY){{/isKeyInQuery}}{{#isKeyInHeader}}(HEADER){{/isKeyInHeader}}{{/keyParamName}} + - name: {{name}} + {{/authMethods}} + {{#hasResponseHeaders}} + - responseHeaders: [{{#responseHeaders}}{{{baseName}}}({{{dataType}}}){{^-last}}, {{/-last}}{{/responseHeaders}}] + {{/hasResponseHeaders}} + {{#externalDocs}} + - externalDocs: {{.}} + {{/externalDocs}} + {{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{{description}}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + {{/allParams}} + - returns: `EventLoopFuture` of `ClientResponse` {{{description}}} + */ + {{#isDeprecated}} + @available(*, deprecated, message: "This operation is deprecated.") + {{/isDeprecated}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}Raw({{#allParams}}{{paramName}}: {{#isEnum}}{{#isArray}}[{{enumName}}_{{operationId}}]{{/isArray}}{{^isArray}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers: HTTPHeaders = {{projectName}}API.customHeaders, beforeSend: (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture { + {{^pathParams}}let{{/pathParams}}{{#pathParams}}{{#-first}}var{{/-first}}{{/pathParams}} localVariablePath = "{{{path}}}"{{#pathParams}} + let {{paramName}}PreEscape = String(describing: {{#isEnum}}{{paramName}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}.rawValue{{/isContainer}}{{/isEnum}}{{^isEnum}}{{paramName}}{{/isEnum}}) + let {{paramName}}PostEscape = {{paramName}}PreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{{=<% %>=}}{<%baseName%>}<%={{ }}=%>", with: {{paramName}}PostEscape, options: .literal, range: nil){{/pathParams}} + let localVariableURLString = {{projectName}}API.basePath + localVariablePath + + guard let localVariableApiClient = {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}Configuration.apiClient else { + fatalError("Configuration.apiClient is not set.") + } + + return localVariableApiClient.send(.{{httpMethod}}, headers: headers, to: URI(string: localVariableURLString)) { localVariableRequest in + try {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}Configuration.apiWrapper(&localVariableRequest) + {{#hasHeaderParams}}{{#headerParams}} + localVariableRequest.headers.add(name: "{{baseName}}", value: {{#isArray}}{{paramName}}{{^required}}?{{/required}}.map { $0{{#isEnum}}.rawValue{{/isEnum}}.description }.description{{/isArray}}{{^isArray}}{{#isEnum}}{{paramName}}{{^required}}?{{/required}}.rawValue.description{{/isEnum}}{{^isEnum}}{{paramName}}{{^required}}?{{/required}}.description{{/isEnum}}{{/isArray}}{{^required}} ?? ""{{/required}}) + {{/headerParams}}{{/hasHeaderParams}} + {{#hasQueryParams}}struct QueryParams: Content { + {{#queryParams}} + var {{paramName}}: {{#isEnum}}{{#isArray}}[{{enumName}}_{{operationId}}]{{/isArray}}{{^isArray}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}?{{/required}} + {{/queryParams}} + + enum CodingKeys: String, CodingKey { + {{#queryParams}} + case {{paramName}}{{#baseName}} = "{{.}}"{{/baseName}} + {{/queryParams}} + } + } + try localVariableRequest.query.encode(QueryParams({{#queryParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/queryParams}})){{/hasQueryParams}} + {{#hasBodyParam}} + {{#bodyParam}}{{#required}}{{#isBinary}}localVariableRequest.body = ByteBuffer(data: {{paramName}}){{/isBinary}}{{^isBinary}}{{#isFile}}localVariableRequest.body = ByteBuffer(data: {{paramName}}){{/isFile}}try localVariableRequest.content.encode({{paramName}}, using: Configuration.contentConfiguration.requireEncoder(for: {{{dataType}}}.defaultContentType)){{/isBinary}}{{/required}}{{^required}}if let localVariableBody = {{paramName}} { + {{#isBinary}}localVariableRequest.body = ByteBuffer(data: localVariableBody){{/isBinary}}{{^isBinary}}{{#isFile}}localVariableRequest.body = ByteBuffer(data: localVariableBody){{/isFile}}try localVariableRequest.content.encode(localVariableBody, using: Configuration.contentConfiguration.requireEncoder(for: {{{dataType}}}.defaultContentType)){{/isBinary}} + }{{/required}}{{/bodyParam}} + {{/hasBodyParam}} + {{#hasFormParams}}struct FormParams: Content { + static let defaultContentType = Vapor.HTTPMediaType.formData + {{#formParams}} + var {{paramName}}: {{#isEnum}}{{#isArray}}[{{enumName}}_{{operationId}}]{{/isArray}}{{^isArray}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}?{{/required}} + {{/formParams}} + } + try localVariableRequest.content.encode(FormParams({{#formParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/formParams}}), using: Configuration.contentConfiguration.requireEncoder(for: FormParams.defaultContentType)){{/hasFormParams}} + try beforeSend(&localVariableRequest) + } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}} { + {{#responses}} + case http{{code}}({{#dataType}}value: {{{.}}}, {{/dataType}}raw: ClientResponse) + {{/responses}} + {{^hasDefaultResponse}} + case http0(raw: ClientResponse) + {{/hasDefaultResponse}} + } + + /** + {{#summary}} + {{{.}}} + {{/summary}} + {{httpMethod}} {{{path}}}{{#notes}} + {{{.}}}{{/notes}}{{#subresourceOperation}} + subresourceOperation: {{.}}{{/subresourceOperation}}{{#defaultResponse}} + defaultResponse: {{.}}{{/defaultResponse}} + {{#authMethods}} + - {{#isBasicBasic}}BASIC{{/isBasicBasic}}{{#isBasicBearer}}Bearer Token{{/isBasicBearer}}{{#isOAuth}}OAuth{{/isOAuth}}{{#isApiKey}}API Key{{/isApiKey}}: + - type: {{type}}{{#keyParamName}} {{keyParamName}} {{#isKeyInQuery}}(QUERY){{/isKeyInQuery}}{{#isKeyInHeader}}(HEADER){{/isKeyInHeader}}{{/keyParamName}} + - name: {{name}} + {{/authMethods}} + {{#hasResponseHeaders}} + - responseHeaders: [{{#responseHeaders}}{{{baseName}}}({{{dataType}}}){{^-last}}, {{/-last}}{{/responseHeaders}}] + {{/hasResponseHeaders}} + {{#externalDocs}} + - externalDocs: {{.}} + {{/externalDocs}} + {{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{{description}}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + {{/allParams}} + - returns: `EventLoopFuture` of `{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}` {{{description}}} + */ + {{#isDeprecated}} + @available(*, deprecated, message: "This operation is deprecated.") + {{/isDeprecated}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isArray}}[{{enumName}}_{{operationId}}]{{/isArray}}{{^isArray}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers: HTTPHeaders = {{projectName}}API.customHeaders, beforeSend: (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture<{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}> { + return {{operationId}}Raw({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers: headers, beforeSend: beforeSend).flatMapThrowing { response -> {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}} in + switch response.status.code { + {{#responses}} + {{#isDefault}}default{{/isDefault}}{{^isDefault}}case {{code}}{{/isDefault}}: + return .http{{code}}({{#dataType}}value: {{#isBinary}}Data(buffer: response.body ?? ByteBuffer()){{/isBinary}}{{^isBinary}}{{#isFile}}Data(buffer: response.body ?? ByteBuffer()){{/isFile}}{{^isFile}}try response.content.decode({{{dataType}}}.self, using: Configuration.contentConfiguration.requireDecoder(for: {{{dataType}}}.defaultContentType)){{/isFile}}{{/isBinary}}, {{/dataType}}raw: response) + {{/responses}} + {{^hasDefaultResponse}} + default: + return .http0(raw: response) + {{/hasDefaultResponse}} + } + } + } +{{/useVapor}} +{{^useVapor}} + + /** + {{#summary}} + {{{.}}} + {{/summary}} + - {{httpMethod}} {{{path}}}{{#notes}} + - {{{.}}}{{/notes}}{{#subresourceOperation}} + - subresourceOperation: {{.}}{{/subresourceOperation}}{{#defaultResponse}} + - defaultResponse: {{.}}{{/defaultResponse}} + {{#authMethods}} + - {{#isBasicBasic}}BASIC{{/isBasicBasic}}{{#isBasicBearer}}Bearer Token{{/isBasicBearer}}{{#isOAuth}}OAuth{{/isOAuth}}{{#isApiKey}}API Key{{/isApiKey}}: + - type: {{type}}{{#keyParamName}} {{keyParamName}} {{#isKeyInQuery}}(QUERY){{/isKeyInQuery}}{{#isKeyInHeader}}(HEADER){{/isKeyInHeader}}{{/keyParamName}} + - name: {{name}} + {{/authMethods}} + {{#hasResponseHeaders}} + - responseHeaders: [{{#responseHeaders}}{{{baseName}}}({{{dataType}}}){{^-last}}, {{/-last}}{{/responseHeaders}}] + {{/hasResponseHeaders}} + {{#externalDocs}} + - externalDocs: {{.}} + {{/externalDocs}} + {{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + {{/allParams}} + - returns: RequestBuilder<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> {{description}} + */ + {{#isDeprecated}} + @available(*, deprecated, message: "This operation is deprecated.") + {{/isDeprecated}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> RequestBuilder<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> { + {{^pathParams}}let{{/pathParams}}{{#pathParams}}{{#-first}}var{{/-first}}{{/pathParams}} localVariablePath = "{{{path}}}"{{#pathParams}} + let {{paramName}}PreEscape = "\({{#isEnum}}{{paramName}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}.rawValue{{/isContainer}}{{/isEnum}}{{^isEnum}}APIHelper.mapValueToPathItem({{paramName}}){{/isEnum}})" + let {{paramName}}PostEscape = {{paramName}}PreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{{=<% %>=}}{<%baseName%>}<%={{ }}=%>", with: {{paramName}}PostEscape, options: .literal, range: nil){{/pathParams}} + let localVariableURLString = {{projectName}}API.basePath + localVariablePath + {{#bodyParam}} + {{#isBinary}} + let localVariableParameters = ["body": {{paramName}}] + {{/isBinary}} + {{^isBinary}} + let localVariableParameters = JSONEncodingHelper.encodingParameters(forEncodableObject: {{paramName}}) + {{/isBinary}} + {{/bodyParam}} + {{^bodyParam}} + {{#hasFormParams}} + let localVariableFormParams: [String: Any?] = [ + {{#formParams}} + {{> _param}}, + {{/formParams}} + ] + + let localVariableNonNullParameters = APIHelper.rejectNil(localVariableFormParams) + let localVariableParameters = APIHelper.convertBoolToString(localVariableNonNullParameters) + {{/hasFormParams}} + {{^hasFormParams}} + let localVariableParameters: [String: Any]? = nil + {{/hasFormParams}} +{{/bodyParam}}{{#hasQueryParams}} + var localVariableUrlComponents = URLComponents(string: localVariableURLString) + localVariableUrlComponents?.queryItems = APIHelper.mapValuesToQueryItems([{{^queryParams}}:{{/queryParams}} + {{#queryParams}} + {{> _param}}, + {{/queryParams}} + ]){{/hasQueryParams}}{{^hasQueryParams}} + let localVariableUrlComponents = URLComponents(string: localVariableURLString){{/hasQueryParams}} + + let localVariableNillableHeaders: [String: Any?] = [{{^headerParams}}{{^hasFormParams}}{{^hasConsumes}} + :{{/hasConsumes}}{{/hasFormParams}}{{/headerParams}}{{#hasFormParams}} + "Content-Type": {{^consumes}}"multipart/form-data"{{/consumes}}{{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}},{{/hasFormParams}}{{^hasFormParams}}{{#hasConsumes}} + "Content-Type": {{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}},{{/hasConsumes}}{{/hasFormParams}}{{#headerParams}} + {{> _param}},{{/headerParams}} + ] + + let localVariableHeaderParameters = APIHelper.rejectNilHeaders(localVariableNillableHeaders) + + let localVariableRequestBuilder: RequestBuilder<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}>.Type = {{projectName}}API.requestBuilderFactory.{{#returnType}}getBuilder(){{/returnType}}{{^returnType}}getNonDecodableBuilder(){{/returnType}} + + return localVariableRequestBuilder.init(method: "{{httpMethod}}", URLString: (localVariableUrlComponents?.string ?? localVariableURLString), parameters: localVariableParameters, headers: localVariableHeaderParameters, requiresAuthentication: {{#hasAuthMethods}}true{{/hasAuthMethods}}{{^hasAuthMethods}}false{{/hasAuthMethods}}) + } +{{/useVapor}} +{{/operation}} +} +{{#swiftUseApiNamespace}} +} +{{/swiftUseApiNamespace}} +{{/operations}} diff --git a/codegen/Templates/swift/api_doc.mustache b/codegen/Templates/swift/api_doc.mustache new file mode 100644 index 00000000..7faccc1a --- /dev/null +++ b/codegen/Templates/swift/api_doc.mustache @@ -0,0 +1,141 @@ +# {{classname}}{{#description}} +{{.}}{{/description}} + +All URIs are relative to *{{{basePath}}}* + +Method | HTTP request | Description +------------- | ------------- | ------------- +{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{summary}} +{{/operation}}{{/operations}} + +{{#operations}} +{{#operation}} +# **{{{operationId}}}** +```swift +{{^usePromiseKit}} +{{^useRxSwift}} +{{^useVapor}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}completion: @escaping (_ data: {{{returnType}}}{{^returnType}}Void{{/returnType}}?, _ error: Error?) -> Void) +{{/useVapor}} +{{/useRxSwift}} +{{/usePromiseKit}} +{{#usePromiseKit}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}} {{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Promise<{{{returnType}}}{{^returnType}}Void{{/returnType}}> +{{/usePromiseKit}} +{{#useRxSwift}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Observable<{{{returnType}}}{{^returnType}}Void{{/returnType}}> +{{/useRxSwift}} +{{#useVapor}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers: HTTPHeaders = {{projectName}}API.customHeaders, beforeSend: (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture<{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}> +{{/useVapor}} +``` + +{{{summary}}}{{#notes}} + +{{{.}}}{{/notes}} + +### Example +```swift +// The following code samples are still beta. For any issue, please report via http://github.com/OpenAPITools/openapi-generator/issues/new +import {{{projectName}}} + +{{#allParams}}let {{paramName}} = {{{vendorExtensions.x-swift-example}}} // {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} +{{/allParams}} + +{{^usePromiseKit}} +{{^useRxSwift}} +{{^useVapor}} +{{#summary}} +// {{{.}}} +{{/summary}} +{{classname}}.{{{operationId}}}({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) { (response, error) in + guard error == nil else { + print(error) + return + } + + if (response) { + dump(response) + } +} +{{/useVapor}} +{{/useRxSwift}} +{{/usePromiseKit}} +{{#usePromiseKit}} +{{#summary}} +// {{{.}}} +{{/summary}} +{{classname}}.{{{operationId}}}({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).then { + // when the promise is fulfilled + }.always { + // regardless of whether the promise is fulfilled, or rejected + }.catch { errorType in + // when the promise is rejected +} +{{/usePromiseKit}} +{{#useRxSwift}} +// TODO RxSwift sample code not yet implemented. To contribute, please open a ticket via http://github.com/OpenAPITools/openapi-generator/issues/new +{{/useRxSwift}} +{{#useVapor}} +{{#summary}} +// {{{.}}} +{{/summary}} +{{classname}}.{{{operationId}}}({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).whenComplete { result in + switch result { + case .failure(let error): + // process error + case .success(let response): + switch response { + // process decoded response value or raw ClientResponse + {{#responses}} + case .http{{code}}(let value, let raw): + {{/responses}} + {{^hasDefaultResponse}} + case .http0(let value, let raw): + {{/hasDefaultResponse}} + } + } +} +{{/useVapor}} +``` + +### Parameters +{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} +Name | Type | Description | Notes +------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}} +{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{baseType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#defaultValue}}[default to {{.}}]{{/defaultValue}} +{{/allParams}} + +### Return type + +{{#useVapor}} +#### {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}} + +```swift +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}} { + {{#responses}} + case http{{code}}(value: {{#dataType}}{{.}}?{{/dataType}}{{^dataType}}Void?{{/dataType}}, raw: ClientResponse) + {{/responses}} + {{^hasDefaultResponse}} + case http0(value: {{#returnType}}{{.}}?{{/returnType}}{{^returnType}}Void?{{/returnType}}, raw: ClientResponse) + {{/hasDefaultResponse}} +} +``` +{{/useVapor}} +{{^useVapor}} +{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{{returnType}}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}Void (empty response body){{/returnType}} +{{/useVapor}} + +### Authorization + +{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{{name}}}](../README.md#{{{name}}}){{^-last}}, {{/-last}}{{/authMethods}} + +### HTTP request headers + + - **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} + - **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}} + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + +{{/operation}} +{{/operations}} diff --git a/codegen/Templates/swift/git_push.sh.mustache b/codegen/Templates/swift/git_push.sh.mustache new file mode 100644 index 00000000..0e3776ae --- /dev/null +++ b/codegen/Templates/swift/git_push.sh.mustache @@ -0,0 +1,57 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="{{{gitHost}}}" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="{{{gitUserId}}}" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="{{{gitRepoId}}}" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="{{{releaseNote}}}" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=$(git remote) +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/codegen/Templates/swift/gitignore.mustache b/codegen/Templates/swift/gitignore.mustache new file mode 100644 index 00000000..316a8450 --- /dev/null +++ b/codegen/Templates/swift/gitignore.mustache @@ -0,0 +1,100 @@ +# Created by https://www.toptal.com/developers/gitignore/api/xcode,swift +# Edit at https://www.toptal.com/developers/gitignore?templates=xcode,swift + +### Swift ### +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# Pods/ +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + +### Xcode ### + +## Xcode 8 and earlier + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno +**/xcshareddata/WorkspaceSettings.xcsettings + +# End of https://www.toptal.com/developers/gitignore/api/xcode,swift diff --git a/codegen/Templates/swift/libraries/alamofire/AlamofireImplementations.mustache b/codegen/Templates/swift/libraries/alamofire/AlamofireImplementations.mustache new file mode 100644 index 00000000..9ef5937c --- /dev/null +++ b/codegen/Templates/swift/libraries/alamofire/AlamofireImplementations.mustache @@ -0,0 +1,413 @@ +// AlamofireImplementations.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +import Alamofire + +class AlamofireRequestBuilderFactory: RequestBuilderFactory { + func getNonDecodableBuilder() -> RequestBuilder.Type { + return AlamofireRequestBuilder.self + } + + func getBuilder() -> RequestBuilder.Type { + return AlamofireDecodableRequestBuilder.self + } +} + +// Store manager to retain its reference +private var managerStore = SynchronizedDictionary() + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class AlamofireRequestBuilder: RequestBuilder { + required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String: Any]?, headers: [String: String] = [:], requiresAuthentication: Bool) { + super.init(method: method, URLString: URLString, parameters: parameters, headers: headers, requiresAuthentication: requiresAuthentication) + } + + /** + May be overridden by a subclass if you want to control the session + configuration. + */ + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func createAlamofireSession(interceptor: RequestInterceptor? = nil) -> Alamofire.Session { + let configuration = URLSessionConfiguration.default + configuration.httpAdditionalHeaders = buildHeaders() + return Alamofire.Session(configuration: configuration, + interceptor: interceptor) + } + + /** + May be overridden by a subclass if you want to custom request constructor. + */ + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func createURLRequest() -> URLRequest? { + let xMethod = Alamofire.HTTPMethod(rawValue: method) + + let encoding: ParameterEncoding + + switch xMethod { + case .get, .head: + encoding = URLEncoding() + + case .options, .post, .put, .patch, .delete, .trace, .connect: + encoding = JSONDataEncoding() + + default: + fatalError("Unsupported HTTPMethod - \(xMethod.rawValue)") + } + + guard let originalRequest = try? URLRequest(url: URLString, method: xMethod, headers: HTTPHeaders(buildHeaders())) else { return nil } + return try? encoding.encode(originalRequest, with: parameters) + } + + /** + May be overridden by a subclass if you want to control the Content-Type + that is given to an uploaded form part. + + Return nil to use the default behavior (inferring the Content-Type from + the file extension). Return the desired Content-Type otherwise. + */ + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func contentTypeForFormPart(fileURL: URL) -> String? { + return nil + } + + /** + May be overridden by a subclass if you want to control the request + configuration (e.g. to override the cache policy). + */ + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func makeRequest(manager: Session, method: HTTPMethod, encoding: ParameterEncoding, headers: [String: String]) -> DataRequest { + return manager.request(URLString, method: method, parameters: parameters, encoding: encoding, headers: HTTPHeaders(headers)) + } + + @discardableResult + override {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute(_ apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, _ completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) -> RequestTask { + let managerId = UUID().uuidString + // Create a new manager for each request to customize its request header + let manager = createAlamofireSession() + managerStore[managerId] = manager + + let xMethod = Alamofire.HTTPMethod(rawValue: method) + + let encoding: ParameterEncoding? + + switch xMethod { + case .get, .head: + encoding = URLEncoding() + + case .options, .post, .put, .patch, .delete, .trace, .connect: + let contentType = headers["Content-Type"] ?? "application/json" + + if contentType.hasPrefix("application/") && contentType.contains("json") { + encoding = JSONDataEncoding() + } else if contentType.hasPrefix("multipart/form-data") { + encoding = nil + + let upload = manager.upload(multipartFormData: { mpForm in + for (k, v) in self.parameters! { + for v in (v as? Array ?? [v]) { + switch v { + case let fileURL as URL: + if let mimeType = self.contentTypeForFormPart(fileURL: fileURL) { + mpForm.append(fileURL, withName: k, fileName: fileURL.lastPathComponent, mimeType: mimeType) + } else { + mpForm.append(fileURL, withName: k) + } + case let string as String: + mpForm.append(string.data(using: String.Encoding.utf8)!, withName: k) + case let number as NSNumber: + mpForm.append(number.stringValue.data(using: String.Encoding.utf8)!, withName: k) + case let data as Data: + mpForm.append(data, withName: k) + case let uuid as UUID: + mpForm.append(uuid.uuidString.data(using: String.Encoding.utf8)!, withName: k) + default: + fatalError("Unprocessable value \(v) with key \(k)") + } + } + } + }, to: URLString, method: xMethod, headers: nil) + .uploadProgress { progress in + if let onProgressReady = self.onProgressReady { + onProgressReady(progress) + } + } + + requestTask.set(request: upload) + + self.processRequest(request: upload, managerId, apiResponseQueue, completion) + } else if contentType.hasPrefix("application/x-www-form-urlencoded") { + encoding = URLEncoding(destination: .httpBody) + } else { + fatalError("Unsupported Media Type - \(contentType)") + } + + default: + fatalError("Unsupported HTTPMethod - \(xMethod.rawValue)") + } + + if let encoding = encoding { + let request = makeRequest(manager: manager, method: xMethod, encoding: encoding, headers: headers) + if let onProgressReady = self.onProgressReady { + onProgressReady(request.uploadProgress) + } + processRequest(request: request, managerId, apiResponseQueue, completion) + requestTask.set(request: request) + } + + return requestTask + } + + fileprivate func processRequest(request: DataRequest, _ managerId: String, _ apiResponseQueue: DispatchQueue, _ completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { + if let credential = self.credential { + request.authenticate(with: credential) + } + + let cleanupRequest = { + managerStore[managerId] = nil + } + + let validatedRequest = request.validate(statusCode: Configuration.successfulStatusCodeRange) + + switch T.self { + case is Void.Type: + validatedRequest.response(queue: apiResponseQueue, + responseSerializer: Configuration.dataResponseSerializer, + completionHandler: { voidResponse in + cleanupRequest() + + switch voidResponse.result { + case .success: + completion(.success(Response(response: voidResponse.response!, body: () as! T, bodyData: voidResponse.data))) + case let .failure(error): + completion(.failure(ErrorResponse.error(voidResponse.response?.statusCode ?? 500, voidResponse.data, voidResponse.response, error))) + } + + }) + default: + fatalError("Unsupported Response Body Type - \(String(describing: T.self))") + } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func buildHeaders() -> [String: String] { + var httpHeaders = Alamofire.HTTPHeaders.default.dictionary + for (key, value) in headers { + httpHeaders[key] = value + } + return httpHeaders + } + + fileprivate func getFileName(fromContentDisposition contentDisposition: String?) -> String? { + + guard let contentDisposition = contentDisposition else { + return nil + } + + let items = contentDisposition.components(separatedBy: ";") + + var filename: String? + + for contentItem in items { + + let filenameKey = "filename=" + guard let range = contentItem.range(of: filenameKey) else { + continue + } + + filename = contentItem + return filename? + .replacingCharacters(in: range, with: "") + .replacingOccurrences(of: "\"", with: "") + .trimmingCharacters(in: .whitespacesAndNewlines) + } + + return filename + + } + + fileprivate func getPath(from url: URL) throws -> String { + + guard var path = URLComponents(url: url, resolvingAgainstBaseURL: true)?.path else { + throw DownloadException.requestMissingPath + } + + if path.hasPrefix("/") { + path.remove(at: path.startIndex) + } + + return path + + } + + fileprivate func getURL(from urlRequest: URLRequest) throws -> URL { + + guard let url = urlRequest.url else { + throw DownloadException.requestMissingURL + } + + return url + } + +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class AlamofireDecodableRequestBuilder: AlamofireRequestBuilder { + + override fileprivate func processRequest(request: DataRequest, _ managerId: String, _ apiResponseQueue: DispatchQueue, _ completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { + if let credential = self.credential { + request.authenticate(with: credential) + } + + let cleanupRequest = { + managerStore[managerId] = nil + } + + let validatedRequest = request.validate(statusCode: Configuration.successfulStatusCodeRange) + + switch T.self { + case is String.Type: + validatedRequest.response(queue: apiResponseQueue, + responseSerializer: Configuration.stringResponseSerializer, + completionHandler: { stringResponse in + cleanupRequest() + + switch stringResponse.result { + case let .success(value): + completion(.success(Response(response: stringResponse.response!, body: value as! T, bodyData: stringResponse.data))) + case let .failure(error): + completion(.failure(ErrorResponse.error(stringResponse.response?.statusCode ?? 500, stringResponse.data, stringResponse.response, error))) + } + + }) + case is URL.Type: + validatedRequest.response(queue: apiResponseQueue, + responseSerializer: Configuration.dataResponseSerializer, + completionHandler: { dataResponse in + cleanupRequest() + + do { + + guard case .success = dataResponse.result else { + throw DownloadException.responseFailed + } + + guard let data = dataResponse.data else { + throw DownloadException.responseDataMissing + } + + guard let request = request.request else { + throw DownloadException.requestMissing + } + + let fileManager = FileManager.default + let urlRequest = try request.asURLRequest() + let cachesDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0] + let requestURL = try self.getURL(from: urlRequest) + + var requestPath = try self.getPath(from: requestURL) + + if let headerFileName = self.getFileName(fromContentDisposition: dataResponse.response?.allHeaderFields["Content-Disposition"] as? String) { + requestPath = requestPath.appending("/\(headerFileName)") + } else { + requestPath = requestPath.appending("/tmp.{{projectName}}.\(UUID().uuidString)") + } + + let filePath = cachesDirectory.appendingPathComponent(requestPath) + let directoryPath = filePath.deletingLastPathComponent().path + + try fileManager.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil) + try data.write(to: filePath, options: .atomic) + + completion(.success(Response(response: dataResponse.response!, body: filePath as! T, bodyData: data))) + + } catch let requestParserError as DownloadException { + completion(.failure(ErrorResponse.error(400, dataResponse.data, dataResponse.response, requestParserError))) + } catch { + completion(.failure(ErrorResponse.error(400, dataResponse.data, dataResponse.response, error))) + } + return + }) + case is Void.Type: + validatedRequest.response(queue: apiResponseQueue, + responseSerializer: Configuration.dataResponseSerializer, + completionHandler: { voidResponse in + cleanupRequest() + + switch voidResponse.result { + case .success: + completion(.success(Response(response: voidResponse.response!, body: () as! T, bodyData: voidResponse.data))) + case let .failure(error): + completion(.failure(ErrorResponse.error(voidResponse.response?.statusCode ?? 500, voidResponse.data, voidResponse.response, error))) + } + + }) + case is Data.Type: + validatedRequest.response(queue: apiResponseQueue, + responseSerializer: Configuration.dataResponseSerializer, + completionHandler: { dataResponse in + cleanupRequest() + + switch dataResponse.result { + case .success: + completion(.success(Response(response: dataResponse.response!, body: dataResponse.data as! T, bodyData: dataResponse.data))) + case let .failure(error): + completion(.failure(ErrorResponse.error(dataResponse.response?.statusCode ?? 500, dataResponse.data, dataResponse.response, error))) + } + + }) + default: + validatedRequest.response(queue: apiResponseQueue, + responseSerializer: Configuration.dataResponseSerializer, + completionHandler: { dataResponse in + cleanupRequest() + + if case let .failure(error) = dataResponse.result { + completion(.failure(ErrorResponse.error(dataResponse.response?.statusCode ?? 500, dataResponse.data, dataResponse.response, error))) + return + } + + guard let httpResponse = dataResponse.response else { + completion(.failure(ErrorResponse.error(-2, nil, dataResponse.response, DecodableRequestBuilderError.nilHTTPResponse))) + return + } + + guard let data = dataResponse.data, !data.isEmpty else { + if T.self is ExpressibleByNilLiteral.Type { + completion(.success(Response(response: httpResponse, body: Optional.none as! T, bodyData: dataResponse.data))) + } else { + completion(.failure(ErrorResponse.error(httpResponse.statusCode, nil, httpResponse, DecodableRequestBuilderError.emptyDataResponse))) + } + return + } + + let decodeResult = CodableHelper.decode(T.self, from: data) + + switch decodeResult { + case let .success(decodableObj): + completion(.success(Response(response: httpResponse, body: decodableObj, bodyData: data))) + case let .failure(error): + completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, httpResponse, error))) + } + + }) + } + } + +} + +extension JSONDataEncoding: ParameterEncoding { + + // MARK: Encoding + + /// Creates a URL request by encoding parameters and applying them onto an existing request. + /// + /// - parameter urlRequest: The request to have parameters applied. + /// - parameter parameters: The parameters to apply. This should have a single key/value + /// pair with "jsonData" as the key and a Data object as the value. + /// + /// - throws: An `Error` if the encoding process encounters an error. + /// + /// - returns: The encoded request. + public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { + let urlRequest = try urlRequest.asURLRequest() + + return encode(urlRequest, with: parameters) + } +} diff --git a/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache b/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache new file mode 100644 index 00000000..0bdb665c --- /dev/null +++ b/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache @@ -0,0 +1,673 @@ +// URLSessionImplementations.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif +#if canImport(MobileCoreServices) +import MobileCoreServices +#endif +#if canImport(UniformTypeIdentifiers) +import UniformTypeIdentifiers +#endif + +// Protocol defined for a session data task. This allows mocking out the URLSessionProtocol below since +// you may not want to create or return a real URLSessionDataTask. +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol URLSessionDataTaskProtocol { + func resume() + + var taskIdentifier: Int { get } + + var progress: Progress { get } + + func cancel() +} + +// Protocol allowing implementations to alter what is returned or to test their implementations. +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol URLSessionProtocol { + // Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request + // is sent off when `.resume()` is called. + func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTaskProtocol +} + +extension URLSession: URLSessionProtocol { + // Passthrough to URLSession.dataTask(with:completionHandler) since URLSessionDataTask conforms to URLSessionDataTaskProtocol and fetches the network data. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> any URLSessionDataTaskProtocol { + return dataTask(with: request, completionHandler: completionHandler) + } +} + +extension URLSessionDataTask: URLSessionDataTaskProtocol {} + +class URLSessionRequestBuilderFactory: RequestBuilderFactory { + func getNonDecodableBuilder() -> RequestBuilder.Type { + return URLSessionRequestBuilder.self + } + + func getBuilder() -> RequestBuilder.Type { + return URLSessionDecodableRequestBuilder.self + } +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} typealias {{projectName}}APIChallengeHandler = ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?)) + +// Store the URLSession's delegate to retain its reference +private let sessionDelegate = SessionDelegate() + +// Store the URLSession to retain its reference +private let defaultURLSession = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil) + +// Store current taskDidReceiveChallenge for every URLSessionTask +private var challengeHandlerStore = SynchronizedDictionary() + +// Store current URLCredential for every URLSessionTask +private var credentialStore = SynchronizedDictionary() + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class URLSessionRequestBuilder: RequestBuilder { + + /** + May be assigned if you want to control the authentication challenges. + */ + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var taskDidReceiveChallenge: {{projectName}}APIChallengeHandler? + + required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String: Any]?, headers: [String: String] = [:], requiresAuthentication: Bool) { + super.init(method: method, URLString: URLString, parameters: parameters, headers: headers, requiresAuthentication: requiresAuthentication) + } + + /** + May be overridden by a subclass if you want to control the URLSession + configuration. + */ + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func createURLSession() -> URLSessionProtocol { + return defaultURLSession + } + + /** + May be overridden by a subclass if you want to control the Content-Type + that is given to an uploaded form part. + + Return nil to use the default behavior (inferring the Content-Type from + the file extension). Return the desired Content-Type otherwise. + */ + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func contentTypeForFormPart(fileURL: URL) -> String? { + return nil + } + + /** + May be overridden by a subclass if you want to control the URLRequest + configuration (e.g. to override the cache policy). + */ + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func createURLRequest(urlSession: URLSessionProtocol, method: HTTPMethod, encoding: ParameterEncoding, headers: [String: String]) throws -> URLRequest { + + guard let url = URL(string: URLString) else { + throw DownloadException.requestMissingURL + } + + var originalRequest = URLRequest(url: url) + + originalRequest.httpMethod = method.rawValue + + buildHeaders().forEach { key, value in + originalRequest.setValue(value, forHTTPHeaderField: key) + } + + let modifiedRequest = try encoding.encode(originalRequest, with: parameters) + + return modifiedRequest + } + + @discardableResult + override {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute(_ apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, _ completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) -> RequestTask { + let urlSession = createURLSession() + + guard let xMethod = HTTPMethod(rawValue: method) else { + fatalError("Unsupported Http method - \(method)") + } + + let encoding: ParameterEncoding + + switch xMethod { + case .get, .head: + encoding = URLEncoding() + + case .options, .post, .put, .patch, .delete, .trace, .connect: + let contentType = headers["Content-Type"] ?? "application/json" + + if contentType.hasPrefix("application/") && contentType.contains("json") { + encoding = JSONDataEncoding() + } else if contentType.hasPrefix("multipart/form-data") { + encoding = FormDataEncoding(contentTypeForFormPart: contentTypeForFormPart(fileURL:)) + } else if contentType.hasPrefix("application/x-www-form-urlencoded") { + encoding = FormURLEncoding() + } else if contentType.hasPrefix("application/octet-stream"){ + encoding = OctetStreamEncoding() + } else { + fatalError("Unsupported Media Type - \(contentType)") + } + } + + do { + let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers) + + var taskIdentifier: Int? + let cleanupRequest = { + if let taskIdentifier = taskIdentifier { + challengeHandlerStore[taskIdentifier] = nil + credentialStore[taskIdentifier] = nil + } + } + + let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in + apiResponseQueue.async { + self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion) + cleanupRequest() + } + } + + onProgressReady?(dataTask.progress) + + taskIdentifier = dataTask.taskIdentifier + challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge + credentialStore[dataTask.taskIdentifier] = credential + + dataTask.resume() + + requestTask.set(task: dataTask) + } catch { + apiResponseQueue.async { + completion(.failure(ErrorResponse.error(415, nil, nil, error))) + } + } + + return requestTask + } + + fileprivate func processRequestResponse(urlRequest: URLRequest, data: Data?, response: URLResponse?, error: Error?, completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { + + if let error = error { + completion(.failure(ErrorResponse.error(-1, data, response, error))) + return + } + + guard let httpResponse = response as? HTTPURLResponse else { + completion(.failure(ErrorResponse.error(-2, data, response, DecodableRequestBuilderError.nilHTTPResponse))) + return + } + + guard httpResponse.isStatusCodeSuccessful else { + completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, response, DecodableRequestBuilderError.unsuccessfulHTTPStatusCode))) + return + } + + switch T.self { + case is Void.Type: + + completion(.success(Response(response: httpResponse, body: () as! T, bodyData: data))) + + default: + fatalError("Unsupported Response Body Type - \(String(describing: T.self))") + } + + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func buildHeaders() -> [String: String] { + var httpHeaders: [String: String] = [:] + for (key, value) in {{projectName}}API.customHeaders { + httpHeaders[key] = value + } + for (key, value) in headers { + httpHeaders[key] = value + } + return httpHeaders + } + + fileprivate func getFileName(fromContentDisposition contentDisposition: String?) -> String? { + + guard let contentDisposition = contentDisposition else { + return nil + } + + let items = contentDisposition.components(separatedBy: ";") + + var filename: String? + + for contentItem in items { + + let filenameKey = "filename=" + guard let range = contentItem.range(of: filenameKey) else { + continue + } + + filename = contentItem + return filename? + .replacingCharacters(in: range, with: "") + .replacingOccurrences(of: "\"", with: "") + .trimmingCharacters(in: .whitespacesAndNewlines) + } + + return filename + + } + + fileprivate func getPath(from url: URL) throws -> String { + + guard var path = URLComponents(url: url, resolvingAgainstBaseURL: true)?.path else { + throw DownloadException.requestMissingPath + } + + if path.hasPrefix("/") { + path.remove(at: path.startIndex) + } + + return path + + } + + fileprivate func getURL(from urlRequest: URLRequest) throws -> URL { + + guard let url = urlRequest.url else { + throw DownloadException.requestMissingURL + } + + return url + } + +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class URLSessionDecodableRequestBuilder: URLSessionRequestBuilder { + override fileprivate func processRequestResponse(urlRequest: URLRequest, data: Data?, response: URLResponse?, error: Error?, completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { + + if let error = error { + completion(.failure(ErrorResponse.error(-1, data, response, error))) + return + } + + guard let httpResponse = response as? HTTPURLResponse else { + completion(.failure(ErrorResponse.error(-2, data, response, DecodableRequestBuilderError.nilHTTPResponse))) + return + } + + guard httpResponse.isStatusCodeSuccessful else { + completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, response, DecodableRequestBuilderError.unsuccessfulHTTPStatusCode))) + return + } + + switch T.self { + case is String.Type: + + let body = data.flatMap { String(data: $0, encoding: .utf8) } ?? "" + + completion(.success(Response(response: httpResponse, body: body as! T, bodyData: data))) + + case is URL.Type: + do { + + guard error == nil else { + throw DownloadException.responseFailed + } + + guard let data = data else { + throw DownloadException.responseDataMissing + } + + let fileManager = FileManager.default + let cachesDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0] + let requestURL = try getURL(from: urlRequest) + + var requestPath = try getPath(from: requestURL) + + if let headerFileName = getFileName(fromContentDisposition: httpResponse.allHeaderFields["Content-Disposition"] as? String) { + requestPath = requestPath.appending("/\(headerFileName)") + } else { + requestPath = requestPath.appending("/tmp.{{projectName}}.\(UUID().uuidString)") + } + + let filePath = cachesDirectory.appendingPathComponent(requestPath) + let directoryPath = filePath.deletingLastPathComponent().path + + try fileManager.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil) + try data.write(to: filePath, options: .atomic) + + completion(.success(Response(response: httpResponse, body: filePath as! T, bodyData: data))) + + } catch let requestParserError as DownloadException { + completion(.failure(ErrorResponse.error(400, data, response, requestParserError))) + } catch { + completion(.failure(ErrorResponse.error(400, data, response, error))) + } + + case is Void.Type: + + completion(.success(Response(response: httpResponse, body: () as! T, bodyData: data))) + + case is Data.Type: + + completion(.success(Response(response: httpResponse, body: data as! T, bodyData: data))) + + default: + + guard let unwrappedData = data, !unwrappedData.isEmpty else { + if let expressibleByNilLiteralType = T.self as? ExpressibleByNilLiteral.Type { + completion(.success(Response(response: httpResponse, body: expressibleByNilLiteralType.init(nilLiteral: ()) as! T, bodyData: data))) + } else { + completion(.failure(ErrorResponse.error(httpResponse.statusCode, nil, response, DecodableRequestBuilderError.emptyDataResponse))) + } + return + } + + let decodeResult = CodableHelper.decode(T.self, from: unwrappedData) + + switch decodeResult { + case let .success(decodableObj): + completion(.success(Response(response: httpResponse, body: decodableObj, bodyData: unwrappedData))) + case let .failure(error): + completion(.failure(ErrorResponse.error(httpResponse.statusCode, unwrappedData, response, error))) + } + } + } +} + +private final class SessionDelegate: NSObject, URLSessionTaskDelegate { + func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + + var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling + + var credential: URLCredential? + + if let taskDidReceiveChallenge = challengeHandlerStore[task.taskIdentifier] { + (disposition, credential) = taskDidReceiveChallenge(session, task, challenge) + } else { + if challenge.previousFailureCount > 0 { + disposition = .rejectProtectionSpace + } else { + credential = credentialStore[task.taskIdentifier] ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace) + + if credential != nil { + disposition = .useCredential + } + } + } + + completionHandler(disposition, credential) + } +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum HTTPMethod: String { + case options = "OPTIONS" + case get = "GET" + case head = "HEAD" + case post = "POST" + case put = "PUT" + case patch = "PATCH" + case delete = "DELETE" + case trace = "TRACE" + case connect = "CONNECT" +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol ParameterEncoding { + func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest +} + +private class URLEncoding: ParameterEncoding { + func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest { + + var urlRequest = urlRequest + + guard let parameters = parameters else { return urlRequest } + + guard let url = urlRequest.url else { + throw DownloadException.requestMissingURL + } + + if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty { + urlComponents.queryItems = APIHelper.mapValuesToQueryItems(parameters) + urlRequest.url = urlComponents.url + } + + return urlRequest + } +} + +private class FormDataEncoding: ParameterEncoding { + + let contentTypeForFormPart: (_ fileURL: URL) -> String? + + init(contentTypeForFormPart: @escaping (_ fileURL: URL) -> String?) { + self.contentTypeForFormPart = contentTypeForFormPart + } + + func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest { + + var urlRequest = urlRequest + + guard let parameters = parameters, !parameters.isEmpty else { + return urlRequest + } + + let boundary = "Boundary-\(UUID().uuidString)" + + urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") + + for (key, value) in parameters { + for value in (value as? Array ?? [value]) { + switch value { + case let fileURL as URL: + + urlRequest = try configureFileUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + fileURL: fileURL + ) + + case let string as String: + + if let data = string.data(using: .utf8) { + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) + } + + case let number as NSNumber: + + if let data = number.stringValue.data(using: .utf8) { + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) + } + + case let data as Data: + + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) + + case let uuid as UUID: + + if let data = uuid.uuidString.data(using: .utf8) { + urlRequest = configureDataUploadRequest( + urlRequest: urlRequest, + boundary: boundary, + name: key, + data: data + ) + } + + default: + fatalError("Unprocessable value \(value) with key \(key)") + } + } + } + + var body = urlRequest.httpBody.orEmpty + + body.append("\r\n--\(boundary)--\r\n") + + urlRequest.httpBody = body + + return urlRequest + } + + private func configureFileUploadRequest(urlRequest: URLRequest, boundary: String, name: String, fileURL: URL) throws -> URLRequest { + + var urlRequest = urlRequest + + var body = urlRequest.httpBody.orEmpty + + let fileData = try Data(contentsOf: fileURL) + + let mimetype = contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL) + + let fileName = fileURL.lastPathComponent + + // If we already added something then we need an additional newline. + if body.count > 0 { + body.append("\r\n") + } + + // Value boundary. + body.append("--\(boundary)\r\n") + + // Value headers. + body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(fileName)\"\r\n") + body.append("Content-Type: \(mimetype)\r\n") + + // Separate headers and body. + body.append("\r\n") + + // The value data. + body.append(fileData) + + urlRequest.httpBody = body + + return urlRequest + } + + private func configureDataUploadRequest(urlRequest: URLRequest, boundary: String, name: String, data: Data) -> URLRequest { + + var urlRequest = urlRequest + + var body = urlRequest.httpBody.orEmpty + + // If we already added something then we need an additional newline. + if body.count > 0 { + body.append("\r\n") + } + + // Value boundary. + body.append("--\(boundary)\r\n") + + // Value headers. + body.append("Content-Disposition: form-data; name=\"\(name)\"\r\n") + + // Separate headers and body. + body.append("\r\n") + + // The value data. + body.append(data) + + urlRequest.httpBody = body + + return urlRequest + + } + + func mimeType(for url: URL) -> String { + let pathExtension = url.pathExtension + + if #available(iOS 15, macOS 11, *) { + #if canImport(UniformTypeIdentifiers) + if let utType = UTType(filenameExtension: pathExtension) { + return utType.preferredMIMEType ?? "application/octet-stream" + } + #endif + } else { + #if canImport(MobileCoreServices) + if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as NSString, nil)?.takeRetainedValue(), + let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() { + return mimetype as String + } + #endif + return "application/octet-stream" + } + return "application/octet-stream" + } + +} + +private class FormURLEncoding: ParameterEncoding { + func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest { + + var urlRequest = urlRequest + + var requestBodyComponents = URLComponents() + requestBodyComponents.queryItems = APIHelper.mapValuesToQueryItems(parameters ?? [:]) + + if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { + urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + } + + urlRequest.httpBody = requestBodyComponents.query?.data(using: .utf8) + + return urlRequest + } +} + +private class OctetStreamEncoding: ParameterEncoding { + func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest { + + var urlRequest = urlRequest + + guard let body = parameters?["body"] else { return urlRequest } + + if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { + urlRequest.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type") + } + + switch body { + case let fileURL as URL: + urlRequest.httpBody = try Data(contentsOf: fileURL) + case let data as Data: + urlRequest.httpBody = data + default: + fatalError("Unprocessable body \(body)") + } + + return urlRequest + } +} + +private extension Data { + /// Append string to Data + /// + /// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to Data, and then add that data to the Data, this wraps it in a nice convenient little extension to Data. This converts using UTF-8. + /// + /// - parameter string: The string to be added to the `Data`. + + mutating func append(_ string: String) { + if let data = string.data(using: .utf8) { + append(data) + } + } +} + +private extension Optional where Wrapped == Data { + var orEmpty: Data { + self ?? Data() + } +} + +extension JSONDataEncoding: ParameterEncoding {} diff --git a/codegen/Templates/swift/model.mustache b/codegen/Templates/swift/model.mustache new file mode 100644 index 00000000..b6a725d0 --- /dev/null +++ b/codegen/Templates/swift/model.mustache @@ -0,0 +1,30 @@ +{{#models}}{{#model}}// +// {{classname}}.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +#if canImport(AnyCodable) +import AnyCodable +#endif{{#useVapor}} +import Vapor{{/useVapor}} +{{#swiftUseApiNamespace}} + +@available(*, deprecated, renamed: "{{projectName}}API.{{classname}}") +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} typealias {{classname}} = {{projectName}}API.{{classname}} + +extension {{projectName}}API { +{{/swiftUseApiNamespace}} +{{#description}} + +/** {{.}} */{{/description}}{{#isDeprecated}} +@available(*, deprecated, message: "This schema is deprecated."){{/isDeprecated}}{{#vendorExtensions.x-is-one-of-interface}} +{{> modelOneOf}}{{/vendorExtensions.x-is-one-of-interface}}{{^vendorExtensions.x-is-one-of-interface}}{{#isArray}} +{{> modelArray}}{{/isArray}}{{^isArray}}{{#isEnum}} +{{> modelEnum}}{{/isEnum}}{{^isEnum}} +{{> modelObject}}{{/isEnum}}{{/isArray}}{{/vendorExtensions.x-is-one-of-interface}}{{/model}}{{/models}} +{{#swiftUseApiNamespace}} +} +{{/swiftUseApiNamespace}} \ No newline at end of file diff --git a/codegen/Templates/swift/modelArray.mustache b/codegen/Templates/swift/modelArray.mustache new file mode 100644 index 00000000..536c5e9e --- /dev/null +++ b/codegen/Templates/swift/modelArray.mustache @@ -0,0 +1 @@ +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} typealias {{classname}} = {{parent}} \ No newline at end of file diff --git a/codegen/Templates/swift/modelEnum.mustache b/codegen/Templates/swift/modelEnum.mustache new file mode 100644 index 00000000..060a14ce --- /dev/null +++ b/codegen/Templates/swift/modelEnum.mustache @@ -0,0 +1,7 @@ +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{classname}}: {{dataType}}, {{#useVapor}}Content, Hashable{{/useVapor}}{{^useVapor}}Codable{{^isString}}{{^isInteger}}{{^isFloat}}{{^isDouble}}, JSONEncodable{{/isDouble}}{{/isFloat}}{{/isInteger}}{{/isString}}{{/useVapor}}, CaseIterable{{#enumUnknownDefaultCase}}{{#isInteger}}, CaseIterableDefaultsLast{{/isInteger}}{{#isFloat}}, CaseIterableDefaultsLast{{/isFloat}}{{#isDouble}}, CaseIterableDefaultsLast{{/isDouble}}{{#isString}}, CaseIterableDefaultsLast{{/isString}}{{/enumUnknownDefaultCase}} { +{{#allowableValues}} +{{#enumVars}} + case {{{name}}} = {{{value}}} +{{/enumVars}} +{{/allowableValues}} +} \ No newline at end of file diff --git a/codegen/Templates/swift/modelInlineEnumDeclaration.mustache b/codegen/Templates/swift/modelInlineEnumDeclaration.mustache new file mode 100644 index 00000000..27f1e51a --- /dev/null +++ b/codegen/Templates/swift/modelInlineEnumDeclaration.mustache @@ -0,0 +1,7 @@ + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{enumName}}: {{^isContainer}}{{dataType}}{{/isContainer}}{{#isContainer}}String{{/isContainer}}, {{#useVapor}}Content, Hashable{{/useVapor}}{{^useVapor}}Codable{{^isContainer}}{{^isString}}{{^isInteger}}{{^isFloat}}{{^isDouble}}, JSONEncodable{{/isDouble}}{{/isFloat}}{{/isInteger}}{{/isString}}{{/isContainer}}{{/useVapor}}, CaseIterable{{#enumUnknownDefaultCase}}{{#isInteger}}, CaseIterableDefaultsLast{{/isInteger}}{{#isFloat}}, CaseIterableDefaultsLast{{/isFloat}}{{#isDouble}}, CaseIterableDefaultsLast{{/isDouble}}{{#isString}}, CaseIterableDefaultsLast{{/isString}}{{#isContainer}}, CaseIterableDefaultsLast{{/isContainer}}{{/enumUnknownDefaultCase}} { + {{#allowableValues}} + {{#enumVars}} + case {{{name}}} = {{{value}}} + {{/enumVars}} + {{/allowableValues}} + } \ No newline at end of file diff --git a/codegen/Templates/swift/modelObject.mustache b/codegen/Templates/swift/modelObject.mustache new file mode 100644 index 00000000..71025d94 --- /dev/null +++ b/codegen/Templates/swift/modelObject.mustache @@ -0,0 +1,137 @@ +{{^objcCompatible}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#useClasses}}final class{{/useClasses}}{{^useClasses}}struct{{/useClasses}} {{{classname}}}: {{#useVapor}}Content{{/useVapor}}{{^useVapor}}Codable{{#useJsonEncodable}}, JSONEncodable{{/useJsonEncodable}}{{/useVapor}}{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}} { +{{/objcCompatible}}{{#objcCompatible}}@objcMembers {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} class {{classname}}: NSObject, Codable{{#useJsonEncodable}}, JSONEncodable{{/useJsonEncodable}} { +{{/objcCompatible}} + +{{#allVars}} +{{#isEnum}} +{{> modelInlineEnumDeclaration}} +{{/isEnum}} +{{/allVars}} +{{#allVars}} +{{#validatable}} +{{#hasValidation}} +{{#isString}} + static let {{{name}}}Rule = StringRule(minLength: {{#minLength}}{{{.}}}{{/minLength}}{{^minLength}}nil{{/minLength}}, maxLength: {{#maxLength}}{{{.}}}{{/maxLength}}{{^maxLength}}nil{{/maxLength}}, pattern: {{#pattern}}"{{{.}}}"{{/pattern}}{{^pattern}}nil{{/pattern}}) +{{/isString}} +{{#isNumeric}} + static let {{{name}}}Rule = NumericRule<{{{dataType}}}>(minimum: {{#minimum}}{{{.}}}{{/minimum}}{{^minimum}}nil{{/minimum}}, exclusiveMinimum: {{#exclusiveMinimum}}true{{/exclusiveMinimum}}{{^exclusiveMinimum}}false{{/exclusiveMinimum}}, maximum: {{#maximum}}{{{.}}}{{/maximum}}{{^maximum}}nil{{/maximum}}, exclusiveMaximum: {{#exclusiveMaximum}}true{{/exclusiveMaximum}}{{^exclusiveMaximum}}false{{/exclusiveMaximum}}, multipleOf: {{#multipleOf}}{{{.}}}{{/multipleOf}}{{^multipleOf}}nil{{/multipleOf}}) +{{/isNumeric}} +{{#isArray}} + static let {{{name}}}Rule = ArrayRule(minItems: {{#minItems}}{{{.}}}{{/minItems}}{{^minItems}}nil{{/minItems}}, maxItems: {{#maxItems}}{{{.}}}{{/maxItems}}{{^maxItems}}nil{{/maxItems}}, uniqueItems: {{#uniqueItems}}true{{/uniqueItems}}{{^uniqueItems}}false{{/uniqueItems}}) +{{/isArray}} +{{/hasValidation}} +{{/validatable}} +{{/allVars}} +{{#allVars}} +{{#isEnum}} + {{#description}}/** {{{.}}} */ + {{/description}}{{#deprecated}}@available(*, deprecated, message: "This property is deprecated.") + {{/deprecated}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#readonlyProperties}}private(set) {{/readonlyProperties}}var {{{name}}}: {{#vendorExtensions.x-null-encodable}}NullEncodable<{{{datatypeWithEnum}}}>{{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}{{{datatypeWithEnum}}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^required}}?{{/required}}{{/vendorExtensions.x-null-encodable}}{{#defaultValue}} = {{#vendorExtensions.x-null-encodable}}{{{vendorExtensions.x-null-encodable-default-value}}}{{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}{{{.}}}{{/vendorExtensions.x-null-encodable}}{{/defaultValue}} +{{/isEnum}} +{{^isEnum}} + {{#description}}/** {{{.}}} */ + {{/description}}{{#deprecated}}@available(*, deprecated, message: "This property is deprecated.") + {{/deprecated}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#readonlyProperties}}private(set) {{/readonlyProperties}}var {{{name}}}: {{#vendorExtensions.x-null-encodable}}NullEncodable<{{{datatype}}}>{{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}{{{datatype}}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^required}}?{{/required}}{{/vendorExtensions.x-null-encodable}}{{#defaultValue}} = {{#vendorExtensions.x-null-encodable}}{{{vendorExtensions.x-null-encodable-default-value}}}{{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}{{{.}}}{{/vendorExtensions.x-null-encodable}}{{/defaultValue}} + {{#objcCompatible}} + {{#vendorExtensions.x-swift-optional-scalar}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var {{{name}}}Num: NSNumber? { + get { + {{^vendorExtensions.x-null-encodable}} + return {{{name}}} as NSNumber? + {{/vendorExtensions.x-null-encodable}} + {{#vendorExtensions.x-null-encodable}} + if case .encodeValue(let value) = {{name}} { + return value as NSNumber? + } else { + return nil + } + {{/vendorExtensions.x-null-encodable}} + } + } + {{/vendorExtensions.x-swift-optional-scalar}} + {{/objcCompatible}} +{{/isEnum}} +{{/allVars}} +{{#hasVars}} + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init({{#allVars}}{{{name}}}: {{#vendorExtensions.x-null-encodable}}NullEncodable<{{{datatypeWithEnum}}}>{{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}{{{datatypeWithEnum}}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^required}}?{{/required}}{{/vendorExtensions.x-null-encodable}}{{#defaultValue}} = {{#vendorExtensions.x-null-encodable}}{{{vendorExtensions.x-null-encodable-default-value}}}{{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}{{{.}}}{{/vendorExtensions.x-null-encodable}}{{/defaultValue}}{{^defaultValue}}{{^required}} = {{#vendorExtensions.x-null-encodable}}.encodeNull{{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}nil{{/vendorExtensions.x-null-encodable}}{{/required}}{{/defaultValue}}{{^-last}}, {{/-last}}{{/allVars}}) { + {{#allVars}} + self.{{{name}}} = {{{name}}} + {{/allVars}} + } +{{/hasVars}} + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum CodingKeys: {{#hasVars}}String, {{/hasVars}}CodingKey, CaseIterable { + {{#allVars}} + case {{{name}}}{{#vendorExtensions.x-codegen-escaped-property-name}} = "{{{baseName}}}"{{/vendorExtensions.x-codegen-escaped-property-name}} + {{/allVars}} + }{{#generateModelAdditionalProperties}}{{#additionalPropertiesType}} + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#readonlyProperties}}private(set) {{/readonlyProperties}}var additionalProperties: [String: {{{additionalPropertiesType}}}] = [:] + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} subscript(key: String) -> {{{additionalPropertiesType}}}? { + get { + if let value = additionalProperties[key] { + return value + } + return nil + } + + set { + additionalProperties[key] = newValue + } + }{{/additionalPropertiesType}}{{/generateModelAdditionalProperties}} + + // Encodable protocol methods + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + {{#allVars}} + {{#vendorExtensions.x-null-encodable}} + switch {{{name}}} { + case .encodeNothing: break + case .encodeNull, .encodeValue: try container.encode({{{name}}}, forKey: .{{{name}}}) + } + {{/vendorExtensions.x-null-encodable}} + {{^vendorExtensions.x-null-encodable}} + try container.encode{{^required}}IfPresent{{/required}}({{{name}}}, forKey: .{{{name}}}) + {{/vendorExtensions.x-null-encodable}} + {{/allVars}} + {{#generateModelAdditionalProperties}} + {{#additionalPropertiesType}} + var additionalPropertiesContainer = encoder.container(keyedBy: String.self) + try additionalPropertiesContainer.encodeMap(additionalProperties) + {{/additionalPropertiesType}} + {{/generateModelAdditionalProperties}} + }{{#generateModelAdditionalProperties}}{{#additionalPropertiesType}} + + // Decodable protocol methods + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}}{{#objcCompatible}} required{{/objcCompatible}} init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + {{#allVars}} + {{{name}}} = try container.decode{{#required}}{{#isNullable}}IfPresent{{/isNullable}}{{/required}}{{^required}}IfPresent{{/required}}({{{datatypeWithEnum}}}.self, forKey: .{{{name}}}) + {{/allVars}} + var nonAdditionalPropertyKeys = Set() + {{#allVars}} + nonAdditionalPropertyKeys.insert("{{{baseName}}}") + {{/allVars}} + let additionalPropertiesContainer = try decoder.container(keyedBy: String.self) + additionalProperties = try additionalPropertiesContainer.decodeMap({{{additionalPropertiesType}}}.self, excludedKeys: nonAdditionalPropertyKeys) + }{{/additionalPropertiesType}}{{/generateModelAdditionalProperties}}{{^objcCompatible}}{{#useClasses}}{{#vendorExtensions.x-swift-hashable}} + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func == (lhs: {{classname}}, rhs: {{classname}}) -> Bool { + {{#allVars}} + lhs.{{{name}}} == rhs.{{{name}}}{{^-last}} &&{{/-last}} + {{/allVars}} + {{#generateModelAdditionalProperties}}{{#additionalPropertiesType}}{{#hasVars}}&& {{/hasVars}}lhs.additionalProperties == rhs.additionalProperties{{/additionalPropertiesType}}{{/generateModelAdditionalProperties}} + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func hash(into hasher: inout Hasher) { + {{#allVars}} + hasher.combine({{{name}}}{{^vendorExtensions.x-null-encodable}}{{^required}}?{{/required}}{{/vendorExtensions.x-null-encodable}}.hashValue) + {{/allVars}} + {{#generateModelAdditionalProperties}}{{#additionalPropertiesType}}hasher.combine(additionalProperties.hashValue){{/additionalPropertiesType}}{{/generateModelAdditionalProperties}} + }{{/vendorExtensions.x-swift-hashable}}{{/useClasses}}{{/objcCompatible}} +} diff --git a/codegen/Templates/swift/modelOneOf.mustache b/codegen/Templates/swift/modelOneOf.mustache new file mode 100644 index 00000000..629de661 --- /dev/null +++ b/codegen/Templates/swift/modelOneOf.mustache @@ -0,0 +1,31 @@ +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{classname}}: {{#useVapor}}Content{{/useVapor}}{{^useVapor}}Codable, JSONEncodable{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}}{{/useVapor}} { + {{#oneOf}} + case type{{.}}({{.}}) + {{/oneOf}} + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + {{#oneOf}} + case .type{{.}}(let value): + try container.encode(value) + {{/oneOf}} + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + {{#oneOf}} + {{#-first}} + if let value = try? container.decode({{.}}.self) { + {{/-first}} + {{^-first}} + } else if let value = try? container.decode({{.}}.self) { + {{/-first}} + self = .type{{.}}(value) + {{/oneOf}} + } else { + throw DecodingError.typeMismatch(Self.Type.self, .init(codingPath: decoder.codingPath, debugDescription: "Unable to decode instance of {{classname}}")) + } + } +} diff --git a/codegen/Templates/swift/model_doc.mustache b/codegen/Templates/swift/model_doc.mustache new file mode 100644 index 00000000..952fc7de --- /dev/null +++ b/codegen/Templates/swift/model_doc.mustache @@ -0,0 +1,11 @@ +{{#models}}{{#model}}# {{classname}} + +## Properties +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +{{#vars}}**{{{name}}}** | {{#isPrimitiveType}}**{{{dataType}}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{^isContainer}}[**{{dataType}}**]({{complexType}}.md){{/isContainer}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}[default to {{#vendorExtensions.x-null-encodable}}.encodeValue({{{.}}}){{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}{{{.}}}{{/vendorExtensions.x-null-encodable}}]{{/defaultValue}} +{{/vars}} + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) + +{{/model}}{{/models}} diff --git a/codegen/Templates/swift/swiftformat.mustache b/codegen/Templates/swift/swiftformat.mustache new file mode 100644 index 00000000..93007252 --- /dev/null +++ b/codegen/Templates/swift/swiftformat.mustache @@ -0,0 +1,45 @@ +# This file is auto-generated by OpenAPI Generator: https://openapi-generator.tech/ +# +# For rules on SwiftFormat, please refer to https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md +# +# file options + +# uncomment below to exclude files, folders +#--exclude path/to/test1.swift,Snapshots,Build + +# format options + +--allman false +--binarygrouping 4,8 +--commas always +--comments indent +--decimalgrouping 3,6 +--elseposition same-line +--empty void +--exponentcase lowercase +--exponentgrouping disabled +--fractiongrouping disabled +--header ignore +--hexgrouping 4,8 +--hexliteralcase uppercase +--ifdef indent +--indent 4 +--indentcase false +--importgrouping testable-bottom +--linebreaks lf +--maxwidth none +--octalgrouping 4,8 +--operatorfunc spaced +--patternlet hoist +--ranges spaced +--self remove +--semicolons inline +--stripunusedargs always +--swiftversion 5.4 +--trimwhitespace always +--wraparguments preserve +--wrapcollections preserve + +# rules + +--enable isEmpty diff --git a/codegen/Tools/patch-swift-generated.py b/codegen/Tools/patch-swift-generated.py deleted file mode 100644 index 82cd7c08..00000000 --- a/codegen/Tools/patch-swift-generated.py +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import print_function - -import json -import re -import sys -from pathlib import Path - - -def replace_once(path, old, new): - text = path.read_text(encoding="utf-8") - count = text.count(old) - if count != 1: - raise RuntimeError("Expected exactly one match in {0}, found {1}".format(path, count)) - path.write_text(text.replace(old, new), encoding="utf-8") - - -def replace_regex_once(path, pattern, replacement): - text = path.read_text(encoding="utf-8") - text, count = re.subn(pattern, replacement, text, count=1) - if count != 1: - raise RuntimeError("Expected exactly one regex match in {0}, found {1}".format(path, count)) - path.write_text(text, encoding="utf-8") - - -def append_once(path, marker, block): - text = path.read_text(encoding="utf-8") - if marker in text: - return - path.write_text(text.rstrip() + "\n\n" + block + "\n", encoding="utf-8") - - -def patch_url_session(sources_dir): - url_session = sources_dir / "URLSessionImplementations.swift" - - replace_once( - url_session, - "import Foundation\n#if !os(macOS)\nimport MobileCoreServices\n#endif", - "import Foundation\n" - "#if canImport(FoundationNetworking)\n" - "import FoundationNetworking\n" - "#endif\n" - "#if canImport(MobileCoreServices)\n" - "import MobileCoreServices\n" - "#endif", - ) - - replace_once( - url_session, - " } else {\n" - " if let uti = UTTypeCreatePreferredIdentifierForTag", - " } else {\n" - " #if canImport(MobileCoreServices)\n" - " if let uti = UTTypeCreatePreferredIdentifierForTag", - ) - - replace_once( - url_session, - " return mimetype as String\n" - " }\n" - " return \"application/octet-stream\"", - " return mimetype as String\n" - " }\n" - " #endif\n" - " return \"application/octet-stream\"", - ) - - replace_regex_once( - url_session, - r"\n #else\n return \"application/octet-stream\"\s*\n #endif", - "\n #endif", - ) - - replace_once(url_session, "private class SessionDelegate", "private final class SessionDelegate") - - -def patch_extensions(sources_dir): - replace_once( - sources_dir / "Extensions.swift", - "extension String: CodingKey {", - "extension Swift.String: Swift.CodingKey {", - ) - - -def patch_date_formatter(sources_dir): - append_once( - sources_dir / "OpenISO8601DateFormatter.swift", - "extension OpenISO8601DateFormatter: @unchecked Sendable", - "#if compiler(>=5.5)\n" - "extension OpenISO8601DateFormatter: @unchecked Sendable {}\n" - "#endif", - ) - - -def patch_client_version(sources_dir, package_version): - replace_regex_once( - sources_dir / "AsposeBarcodeCloudClient.swift", - r'public static let defaultSdkVersion = "[^"]+"', - 'public static let defaultSdkVersion = "{0}"'.format(package_version), - ) - - -def main(target_dir, config_path): - config = json.loads(Path(config_path).read_text(encoding="utf-8")) - package_version = config.get("packageVersion") - if not package_version: - raise RuntimeError("config-swift.json must define packageVersion") - - sources_dir = Path(target_dir) / "Sources" / "AsposeBarcodeCloud" - patch_url_session(sources_dir) - patch_extensions(sources_dir) - patch_date_formatter(sources_dir) - patch_client_version(sources_dir, package_version) - - -if __name__ == "__main__": - if len(sys.argv) != 3: - print("Usage: patch-swift-generated.py ", file=sys.stderr) - sys.exit(2) - main(sys.argv[1], sys.argv[2]) diff --git a/codegen/config-swift.json b/codegen/config-swift.json index 6c2762b8..43c738be 100644 --- a/codegen/config-swift.json +++ b/codegen/config-swift.json @@ -1,6 +1,12 @@ { "allowUnicodeIdentifiers": true, "enumUnknownDefaultCase": true, + "files": { + "AsposeBarcodeCloudClient.mustache": { + "destinationFilename": "Sources/AsposeBarcodeCloud/AsposeBarcodeCloudClient.swift", + "templateType": "SupportingFiles" + } + }, "hideGenerationTimestamp": true, "mapFileBinaryToData": true, "packageVersion": "26.4.0", diff --git a/codegen/generate-swift.bash b/codegen/generate-swift.bash index e290e785..98ba5378 100755 --- a/codegen/generate-swift.bash +++ b/codegen/generate-swift.bash @@ -10,17 +10,17 @@ then rm -rf "$tempDir" fi +# Templates src https://github.com/OpenAPITools/openapi-generator/tree/v7.8.0/modules/openapi-generator/src/main/resources/swift5 # java -jar Tools/openapi-generator-cli.jar config-help -g swift5 ; exit 1 -# java -DdebugModels -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift5 -o "$tempDir" -c config-swift.json > debugModels.swift.json ; exit -# java -DdebugOperations -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift5 -o "$tempDir" -c config-swift.json > debugOperations.swift.json ; exit -java -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift5 -o "$tempDir" -c config-swift.json +# java -DdebugModels -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift5 -t Templates/swift -o "$tempDir" -c config-swift.json > debugModels.swift.json ; exit +# java -DdebugOperations -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift5 -t Templates/swift -o "$tempDir" -c config-swift.json > debugOperations.swift.json ; exit +java -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift5 -t Templates/swift -o "$tempDir" -c config-swift.json mkdir -p "$targetDir/Sources" rm -rf "$targetDir/Sources/AsposeBarcodeCloud" mv "$tempDir/Sources/AsposeBarcodeCloud" "$targetDir/Sources/AsposeBarcodeCloud" -cp Support/swift/*.swift "$targetDir/Sources/AsposeBarcodeCloud/" -python3 Tools/patch-swift-generated.py "$targetDir" config-swift.json +mv "$tempDir/README.md" "$targetDir/README.md" rm -rf "$targetDir/docs" mv "$tempDir/docs" "$targetDir/docs" diff --git a/submodules/swift b/submodules/swift index 98dd349a..9f8cd6e9 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 98dd349a38a376ab0c3419bce316c82a5a2cc48f +Subproject commit 9f8cd6e9faa8f89175c2d634b5484036c20d3636 From 94852d9f6bdf0dfb4f83a63eeb814a8fd868d64e Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Tue, 19 May 2026 18:11:25 +0500 Subject: [PATCH 23/40] Switch Swift SDK generation to Swift 6 --- codegen/Templates/swift/APIHelper.mustache | 16 +- codegen/Templates/swift/APIs.mustache | 301 ++++++++++++--- .../swift/AsposeBarcodeCloudClient.mustache | 32 +- codegen/Templates/swift/Cartfile.mustache | 8 +- .../Templates/swift/CodableHelper.mustache | 81 ++-- .../Templates/swift/Configuration.mustache | 55 --- codegen/Templates/swift/Extensions.mustache | 145 ++++---- .../Templates/swift/JSONDataEncoding.mustache | 12 +- .../swift/JSONEncodingHelper.mustache | 22 +- codegen/Templates/swift/JSONValue.mustache | 247 +++++++++++++ codegen/Templates/swift/Models.mustache | 76 ++-- .../swift/OpenAPIDateWithoutTime.mustache | 6 +- codegen/Templates/swift/OpenAPIMutex.mustache | 28 ++ .../swift/OpenISO8601DateFormatter.mustache | 6 +- .../Templates/swift/Package.swift.mustache | 38 +- codegen/Templates/swift/Podspec.mustache | 15 +- codegen/Templates/swift/README.mustache | 4 +- .../swift/SynchronizedDictionary.mustache | 27 +- codegen/Templates/swift/Validation.mustache | 42 +-- codegen/Templates/swift/XcodeGen.mustache | 3 +- codegen/Templates/swift/_param.mustache | 2 +- codegen/Templates/swift/api.mustache | 320 ++++++++-------- codegen/Templates/swift/api_doc.mustache | 2 +- .../AlamofireImplementations.mustache | 91 ++--- .../URLSessionImplementations.mustache | 347 ++++++++++++------ codegen/Templates/swift/model.mustache | 9 +- codegen/Templates/swift/modelEnum.mustache | 5 +- .../swift/modelInlineEnumDeclaration.mustache | 5 +- codegen/Templates/swift/modelObject.mustache | 11 +- codegen/Templates/swift/modelOneOf.mustache | 51 ++- codegen/config-swift.json | 2 + codegen/generate-swift.bash | 11 +- submodules/swift | 2 +- 33 files changed, 1320 insertions(+), 702 deletions(-) delete mode 100644 codegen/Templates/swift/Configuration.mustache create mode 100644 codegen/Templates/swift/JSONValue.mustache create mode 100644 codegen/Templates/swift/OpenAPIMutex.mustache diff --git a/codegen/Templates/swift/APIHelper.mustache b/codegen/Templates/swift/APIHelper.mustache index d4583787..f25c30b2 100644 --- a/codegen/Templates/swift/APIHelper.mustache +++ b/codegen/Templates/swift/APIHelper.mustache @@ -7,9 +7,9 @@ import Foundation{{#useVapor}} import Vapor{{/useVapor}} -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct APIHelper { - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func rejectNil(_ source: [String: Any?]) -> [String: Any]? { - let destination = source.reduce(into: [String: Any]()) { result, item in +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct APIHelper: Sendable { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func rejectNil(_ source: [String: (any Sendable)?]) -> [String: any Sendable]? { + let destination = source.reduce(into: [String: any Sendable]()) { result, item in if let value = item.value { result[item.key] = value } @@ -21,7 +21,7 @@ import Vapor{{/useVapor}} return destination } - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func rejectNilHeaders(_ source: [String: Any?]) -> [String: String] { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func rejectNilHeaders(_ source: [String: (any Sendable)?]) -> [String: String] { return source.reduce(into: [String: String]()) { result, item in if let collection = item.value as? [Any?] { result[item.key] = collection @@ -33,12 +33,12 @@ import Vapor{{/useVapor}} } } - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func convertBoolToString(_ source: [String: Any]?) -> [String: Any]? { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func convertBoolToString(_ source: [String: any Sendable]?) -> [String: any Sendable]? { guard let source = source else { return nil } - return source.reduce(into: [String: Any]()) { result, item in + return source.reduce(into: [String: any Sendable]()) { result, item in switch item.value { case let x as Bool: result[item.key] = x.description @@ -71,7 +71,7 @@ import Vapor{{/useVapor}} /// maps all values from source to query parameters /// /// explode attribute is respected: collection values might be either joined or split up into separate key value pairs - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func mapValuesToQueryItems(_ source: [String: (wrappedValue: Any?, isExplode: Bool)]) -> [URLQueryItem]? { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func mapValuesToQueryItems(_ source: [String: (wrappedValue: (any Sendable)?, isExplode: Bool)]) -> [URLQueryItem]? { let destination = source.filter { $0.value.wrappedValue != nil }.reduce(into: [URLQueryItem]()) { result, item in if let collection = item.value.wrappedValue as? [Any?] { @@ -100,7 +100,7 @@ import Vapor{{/useVapor}} /// maps all values from source to query parameters /// /// collection values are always exploded - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func mapValuesToQueryItems(_ source: [String: Any?]) -> [URLQueryItem]? { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func mapValuesToQueryItems(_ source: [String: (any Sendable)?]) -> [URLQueryItem]? { let destination = source.filter { $0.value != nil }.reduce(into: [URLQueryItem]()) { result, item in if let collection = item.value as? [Any?] { collection diff --git a/codegen/Templates/swift/APIs.mustache b/codegen/Templates/swift/APIs.mustache index a1d2216f..e30c5658 100644 --- a/codegen/Templates/swift/APIs.mustache +++ b/codegen/Templates/swift/APIs.mustache @@ -8,97 +8,302 @@ import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif{{#useVapor}} -import Vapor -{{/useVapor}} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class {{projectName}}API { - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var basePath = "{{{basePath}}}" - {{#useVapor}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var customHeaders: HTTPHeaders = [:] - {{/useVapor}} - {{^useVapor}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var customHeaders: [String: String] = [:] - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var credential: URLCredential?{{#useAlamofire}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var requestBuilderFactory: RequestBuilderFactory = AlamofireRequestBuilderFactory(){{/useAlamofire}}{{#useURLSession}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(){{/useURLSession}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var apiResponseQueue: DispatchQueue = .main - {{/useVapor}} +import Vapor{{/useVapor}}{{#useAlamofire}} +import Alamofire{{/useAlamofire}} +{{#swiftUseApiNamespace}} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{projectName}}API {} +{{/swiftUseApiNamespace}} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class {{projectName}}APIConfiguration: @unchecked Sendable { + + // MARK: - Private state + + private struct State { + var basePath: String{{#useVapor}} + var customHeaders: HTTPHeaders + var apiClient: Vapor.Client? + var apiWrapper: @Sendable (inout Vapor.ClientRequest) throws -> () + var contentConfiguration: ContentConfiguration{{/useVapor}}{{^useVapor}} + var customHeaders: [String: String] + var credential: URLCredential? + var requestBuilderFactory: RequestBuilderFactory + var apiResponseQueue: DispatchQueue + var codableHelper: CodableHelper + var successfulStatusCodeRange: Range{{#useURLSession}} + var interceptor: OpenAPIInterceptor{{/useURLSession}}{{#useAlamofire}} + var interceptor: RequestInterceptor? + var dataResponseSerializer: AnyResponseSerializer + var stringResponseSerializer: AnyResponseSerializer{{/useAlamofire}}{{/useVapor}} + } + + private let _state: OpenAPIMutex + + // MARK: - Public interface + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var basePath: String { + get { _state.value.basePath } + set { _state.withValue { $0.basePath = newValue } } + }{{#useVapor}} + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var customHeaders: HTTPHeaders { + get { _state.value.customHeaders } + set { _state.withValue { $0.customHeaders = newValue } } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var apiClient: Vapor.Client? { + get { _state.value.apiClient } + set { _state.withValue { $0.apiClient = newValue } } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var apiWrapper: @Sendable (inout Vapor.ClientRequest) throws -> () { + get { _state.value.apiWrapper } + set { _state.withValue { $0.apiWrapper = newValue } } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var contentConfiguration: ContentConfiguration { + get { _state.value.contentConfiguration } + set { _state.withValue { $0.contentConfiguration = newValue } } + }{{/useVapor}}{{^useVapor}} + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var customHeaders: [String: String] { + get { _state.value.customHeaders } + set { _state.withValue { $0.customHeaders = newValue } } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var credential: URLCredential? { + get { _state.value.credential } + set { _state.withValue { $0.credential = newValue } } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var requestBuilderFactory: RequestBuilderFactory { + get { _state.value.requestBuilderFactory } + set { _state.withValue { $0.requestBuilderFactory = newValue } } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var apiResponseQueue: DispatchQueue { + get { _state.value.apiResponseQueue } + set { _state.withValue { $0.apiResponseQueue = newValue } } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var codableHelper: CodableHelper { + get { _state.value.codableHelper } + set { _state.withValue { $0.codableHelper = newValue } } + } + + /// Configures the range of HTTP status codes that will result in a successful response. + /// + /// If a HTTP status code is outside of this range the response will be interpreted as failed. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var successfulStatusCodeRange: Range { + get { _state.value.successfulStatusCodeRange } + set { _state.withValue { $0.successfulStatusCodeRange = newValue } } + }{{#useURLSession}} + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var interceptor: OpenAPIInterceptor { + get { _state.value.interceptor } + set { _state.withValue { $0.interceptor = newValue } } + }{{/useURLSession}}{{#useAlamofire}} + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var interceptor: RequestInterceptor? { + get { _state.value.interceptor } + set { _state.withValue { $0.interceptor = newValue } } + } + + /// ResponseSerializer that will be used by the generator for `Data` responses + /// + /// If unchanged, Alamofires default `DataResponseSerializer` will be used. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var dataResponseSerializer: AnyResponseSerializer { + get { _state.value.dataResponseSerializer } + set { _state.withValue { $0.dataResponseSerializer = newValue } } + } + + /// ResponseSerializer that will be used by the generator for `String` responses + /// + /// If unchanged, Alamofires default `StringResponseSerializer` will be used. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var stringResponseSerializer: AnyResponseSerializer { + get { _state.value.stringResponseSerializer } + set { _state.withValue { $0.stringResponseSerializer = newValue } } + }{{/useAlamofire}}{{/useVapor}} + + // MARK: - Init + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init( + basePath: String = "{{{basePath}}}",{{#useVapor}} + customHeaders: HTTPHeaders = [:], + apiClient: Vapor.Client? = nil, + apiWrapper: @escaping @Sendable (inout Vapor.ClientRequest) throws -> () = { _ in }, + contentConfiguration: ContentConfiguration = ContentConfiguration.default(){{/useVapor}}{{^useVapor}} + customHeaders: [String: String] = [:], + credential: URLCredential? = nil, + requestBuilderFactory: RequestBuilderFactory = {{#useAlamofire}}AlamofireRequestBuilderFactory(){{/useAlamofire}}{{#useURLSession}}URLSessionRequestBuilderFactory(){{/useURLSession}}, + apiResponseQueue: DispatchQueue = .main, + codableHelper: CodableHelper = CodableHelper(), + successfulStatusCodeRange: Range = 200..<300{{#useURLSession}}, + interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor(){{/useURLSession}}{{#useAlamofire}}, + interceptor: RequestInterceptor? = nil, + dataResponseSerializer: AnyResponseSerializer = AnyResponseSerializer(DataResponseSerializer()), + stringResponseSerializer: AnyResponseSerializer = AnyResponseSerializer(StringResponseSerializer()){{/useAlamofire}}{{/useVapor}} + ) { + _state = OpenAPIMutex(State( + basePath: basePath,{{#useVapor}} + customHeaders: customHeaders, + apiClient: apiClient, + apiWrapper: apiWrapper, + contentConfiguration: contentConfiguration{{/useVapor}}{{^useVapor}} + customHeaders: customHeaders, + credential: credential, + requestBuilderFactory: requestBuilderFactory, + apiResponseQueue: apiResponseQueue, + codableHelper: codableHelper, + successfulStatusCodeRange: successfulStatusCodeRange{{#useURLSession}}, + interceptor: interceptor{{/useURLSession}}{{#useAlamofire}}, + interceptor: interceptor, + dataResponseSerializer: dataResponseSerializer, + stringResponseSerializer: stringResponseSerializer{{/useAlamofire}}{{/useVapor}} + )) + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static let shared = {{projectName}}APIConfiguration() }{{^useVapor}} -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class RequestBuilder { - var credential: URLCredential? - var headers: [String: String] - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let parameters: [String: Any]? +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class RequestBuilder: @unchecked Sendable, Identifiable { + + // MARK: - Immutable properties + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let parameters: [String: any Sendable]? {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let method: String {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let URLString: String {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let requestTask: RequestTask = RequestTask() {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let requiresAuthentication: Bool + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let apiConfiguration: {{projectName}}APIConfiguration + + // MARK: - Private mutable state + + private struct MutableState { + var credential: URLCredential? = nil + var headers: [String: String] + var onProgressReady: ((Progress) -> Void)? = nil + } + + private let _state: OpenAPIMutex + + // MARK: - Public mutable interface + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var credential: URLCredential? { + get { _state.value.credential } + set { _state.withValue { $0.credential = newValue } } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var headers: [String: String] { + get { _state.value.headers } + set { _state.withValue { $0.headers = newValue } } + } /// Optional block to obtain a reference to the request's progress instance when available. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var onProgressReady: ((Progress) -> Void)? + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var onProgressReady: ((Progress) -> Void)? { + get { _state.value.onProgressReady } + set { _state.withValue { $0.onProgressReady = newValue } } + } - required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String: Any]?, headers: [String: String] = [:], requiresAuthentication: Bool) { + // MARK: - Init + + required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init( + method: String, + URLString: String, + parameters: [String: any Sendable]?, + headers: [String: String] = [:], + requiresAuthentication: Bool, + apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared + ) { self.method = method self.URLString = URLString self.parameters = parameters - self.headers = headers self.requiresAuthentication = requiresAuthentication + self.apiConfiguration = apiConfiguration + self._state = OpenAPIMutex(MutableState(headers: headers)) - addHeaders({{projectName}}API.customHeaders) + addHeaders(apiConfiguration.customHeaders) + addCredential() } + // MARK: - Public methods + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func addHeaders(_ aHeaders: [String: String]) { - for (header, value) in aHeaders { - headers[header] = value + _state.withValue { state in + for (header, value) in aHeaders { + state.headers[header] = value + } } } @discardableResult - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute(_ apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, _ completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) -> RequestTask { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute(completion: @Sendable @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) -> RequestTask { return requestTask } {{#useAsyncAwait}} - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) + #if compiler(>=6.2) + @concurrent @discardableResult - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute() async throws -> Response { - return try await withTaskCancellationHandler { - try Task.checkCancellation() - return try await withCheckedThrowingContinuation { continuation in - guard !Task.isCancelled else { - continuation.resume(throwing: CancellationError()) - return - } + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute() async throws(ErrorResponse) -> Response { + try await _execute() + } + #else + @discardableResult + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute() async throws(ErrorResponse) -> Response { + try await _execute() + } + #endif + + @discardableResult + private func _execute() async throws(ErrorResponse) -> Response { + do { + let requestTask = self.requestTask + return try await withTaskCancellationHandler { + try Task.checkCancellation() + return try await withCheckedThrowingContinuation { continuation in + guard !Task.isCancelled else { + continuation.resume(throwing: CancellationError()) + return + } - self.execute { result in - switch result { - case let .success(response): - continuation.resume(returning: response) - case let .failure(error): - continuation.resume(throwing: error) + self.execute { result in + switch result { + case let .success(response): + continuation.resume(returning: response) + case let .failure(error): + continuation.resume(throwing: error) + } } } + } onCancel: { + requestTask.cancel() + } + } catch { + if let errorResponse = error as? ErrorResponse { + throw errorResponse + } else { + throw ErrorResponse.error(-3, nil, nil, error) } - } onCancel: { - self.requestTask.cancel() } } {{/useAsyncAwait}} {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func addHeader(name: String, value: String) -> Self { if !value.isEmpty { - headers[name] = value + _state.withValue { $0.headers[name] = value } } return self } - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func addCredential() -> Self { - credential = {{projectName}}API.credential - return self + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func addCredential() { + _state.withValue { [apiConfiguration] state in + state.credential = apiConfiguration.credential + } } } -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol RequestBuilderFactory { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol RequestBuilderFactory: Sendable { func getNonDecodableBuilder() -> RequestBuilder.Type func getBuilder() -> RequestBuilder.Type }{{/useVapor}} diff --git a/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache b/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache index 550c2f2a..86399dd6 100644 --- a/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache +++ b/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache @@ -3,7 +3,7 @@ import Foundation import FoundationNetworking #endif -public enum AsposeBarcodeCloudClientError: Error, CustomStringConvertible { +public enum AsposeBarcodeCloudClientError: Error, CustomStringConvertible, @unchecked Sendable { case missingCredentials case invalidTokenURL(String) case invalidTokenResponse @@ -29,7 +29,19 @@ public enum AsposeBarcodeCloudClientError: Error, CustomStringConvertible { } } -public final class AsposeBarcodeCloudConfiguration { +public enum AsposeBarcodeCloudAPI { + public static var basePath: String { + get { AsposeBarcodeCloudAPIConfiguration.shared.basePath } + set { AsposeBarcodeCloudAPIConfiguration.shared.basePath = newValue } + } + + public static var customHeaders: [String: String] { + get { AsposeBarcodeCloudAPIConfiguration.shared.customHeaders } + set { AsposeBarcodeCloudAPIConfiguration.shared.customHeaders = newValue } + } +} + +public final class AsposeBarcodeCloudConfiguration: @unchecked Sendable { public static let defaultHost = "https://api.aspose.cloud/v4.0" public static let defaultTokenURL = "https://id.aspose.cloud/connect/token" public static let defaultSdkName = "swift sdk" @@ -90,12 +102,12 @@ public final class AsposeBarcodeCloudConfiguration { } } -public typealias AsposeBarcodeCloudTokenFetcher = ( +public typealias AsposeBarcodeCloudTokenFetcher = @Sendable ( AsposeBarcodeCloudConfiguration, - @escaping (Result) -> Void + @escaping @Sendable (Result) -> Void ) -> Void -public final class AsposeBarcodeCloudClient { +public final class AsposeBarcodeCloudClient: @unchecked Sendable { public let configuration: AsposeBarcodeCloudConfiguration private let tokenFetcher: AsposeBarcodeCloudTokenFetcher @@ -143,7 +155,7 @@ public final class AsposeBarcodeCloudClient { } } - public func authorize(completion: @escaping (Result) -> Void) { + public func authorize(completion: @escaping @Sendable (Result) -> Void) { if let accessToken = configuration.accessToken, !accessToken.isEmpty { apply() completion(.success(accessToken)) @@ -165,16 +177,16 @@ public final class AsposeBarcodeCloudClient { @discardableResult public func authorize() throws -> String { let semaphore = DispatchSemaphore(value: 0) - var tokenResult: Result? + let tokenResult = OpenAPIMutex?>(nil) authorize { result in - tokenResult = result + tokenResult.withValue { $0 = result } semaphore.signal() } semaphore.wait() - switch tokenResult { + switch tokenResult.value { case let .success(accessToken): return accessToken case let .failure(error): @@ -199,7 +211,7 @@ public final class AsposeBarcodeCloudClient { private static func defaultTokenFetcher( configuration: AsposeBarcodeCloudConfiguration, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { let request: URLRequest do { diff --git a/codegen/Templates/swift/Cartfile.mustache b/codegen/Templates/swift/Cartfile.mustache index db77b5e6..bbf4a742 100644 --- a/codegen/Templates/swift/Cartfile.mustache +++ b/codegen/Templates/swift/Cartfile.mustache @@ -1,4 +1,4 @@ -github "Flight-School/AnyCodable" ~> 0.6{{#useAlamofire}} -github "Alamofire/Alamofire" ~> 5.7{{/useAlamofire}}{{#usePromiseKit}} -github "mxcl/PromiseKit" ~> 6.15{{/usePromiseKit}}{{#useRxSwift}} -github "ReactiveX/RxSwift" ~> 6.2{{/useRxSwift}} +{{#useAlamofire}} +github "Alamofire/Alamofire" ~> 5.10{{/useAlamofire}}{{#usePromiseKit}} +github "mxcl/PromiseKit" ~> 8.1{{/usePromiseKit}}{{#useRxSwift}} +github "ReactiveX/RxSwift" ~> 6.8{{/useRxSwift}} diff --git a/codegen/Templates/swift/CodableHelper.mustache b/codegen/Templates/swift/CodableHelper.mustache index 554418a5..f9bb64bb 100644 --- a/codegen/Templates/swift/CodableHelper.mustache +++ b/codegen/Templates/swift/CodableHelper.mustache @@ -7,43 +7,64 @@ import Foundation -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class CodableHelper { - private static var customDateFormatter: DateFormatter? - private static var defaultDateFormatter: DateFormatter = OpenISO8601DateFormatter() - - private static var customJSONDecoder: JSONDecoder? - private static var defaultJSONDecoder: JSONDecoder = { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .formatted(CodableHelper.dateFormatter) - return decoder - }() - - private static var customJSONEncoder: JSONEncoder? - private static var defaultJSONEncoder: JSONEncoder = { - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .formatted(CodableHelper.dateFormatter) - encoder.outputFormatting = .prettyPrinted - return encoder - }() - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var dateFormatter: DateFormatter { - get { return customDateFormatter ?? defaultDateFormatter } - set { customDateFormatter = newValue } +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class CodableHelper: @unchecked Sendable { + + // MARK: - Private state + + private struct State { + var customDateFormatter: DateFormatter? + var defaultDateFormatter: DateFormatter = OpenISO8601DateFormatter() + + var customJSONDecoder: JSONDecoder? + var defaultJSONDecoder: JSONDecoder = JSONDecoder() + + var customJSONEncoder: JSONEncoder? + var defaultJSONEncoder: JSONEncoder = JSONEncoder() + + init() { + defaultJSONEncoder.outputFormatting = .prettyPrinted + rebuildDefaultCoders() + } + + mutating func rebuildDefaultCoders() { + defaultJSONDecoder.dateDecodingStrategy = .formatted(customDateFormatter ?? defaultDateFormatter) + defaultJSONEncoder.dateEncodingStrategy = .formatted(customDateFormatter ?? defaultDateFormatter) + } + } + + private let _state = OpenAPIMutex(State()) + + // MARK: - Init + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init() {} + + // MARK: - Public interface + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var dateFormatter: DateFormatter { + get { _state.withValue { $0.customDateFormatter ?? $0.defaultDateFormatter } } + set { + _state.withValue { state in + state.customDateFormatter = newValue + state.rebuildDefaultCoders() + } + } } - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var jsonDecoder: JSONDecoder { - get { return customJSONDecoder ?? defaultJSONDecoder } - set { customJSONDecoder = newValue } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var jsonDecoder: JSONDecoder { + get { _state.withValue { $0.customJSONDecoder ?? $0.defaultJSONDecoder } } + set { _state.withValue { $0.customJSONDecoder = newValue } } } - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var jsonEncoder: JSONEncoder { - get { return customJSONEncoder ?? defaultJSONEncoder } - set { customJSONEncoder = newValue } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var jsonEncoder: JSONEncoder { + get { _state.withValue { $0.customJSONEncoder ?? $0.defaultJSONEncoder } } + set { _state.withValue { $0.customJSONEncoder = newValue } } } - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func decode(_ type: T.Type, from data: Data) -> Swift.Result where T: Decodable { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func decode(_ type: T.Type, from data: Data) -> Swift.Result where T: Decodable { return Swift.Result { try jsonDecoder.decode(type, from: data) } } - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func encode(_ value: T) -> Swift.Result where T: Encodable { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func encode(_ value: T) -> Swift.Result where T: Encodable { return Swift.Result { try jsonEncoder.encode(value) } } } diff --git a/codegen/Templates/swift/Configuration.mustache b/codegen/Templates/swift/Configuration.mustache deleted file mode 100644 index a642dbcd..00000000 --- a/codegen/Templates/swift/Configuration.mustache +++ /dev/null @@ -1,55 +0,0 @@ -// Configuration.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif{{#useVapor}} -import Vapor{{/useVapor}}{{#useAlamofire}} -import Alamofire{{/useAlamofire}} - -{{#swiftUseApiNamespace}} -@available(*, deprecated, renamed: "{{projectName}}API.Configuration") -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} typealias Configuration = {{projectName}}API.Configuration - -extension {{projectName}}API { -{{/swiftUseApiNamespace}} -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class Configuration { - {{#useVapor}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var apiClient: Vapor.Client? = nil - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var apiWrapper: (inout Vapor.ClientRequest) throws -> () = { _ in } - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var contentConfiguration = ContentConfiguration.default(){{/useVapor}}{{^useVapor}} - /// Configures the range of HTTP status codes that will result in a successful response - /// - /// If a HTTP status code is outside of this range the response will be interpreted as failed. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var successfulStatusCodeRange: Range = 200..<300{{/useVapor}}{{#useAlamofire}} - /// ResponseSerializer that will be used by the generator for `Data` responses - /// - /// If unchanged, Alamofires default `DataResponseSerializer` will be used. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var dataResponseSerializer: AnyResponseSerializer = AnyResponseSerializer(DataResponseSerializer()) - /// ResponseSerializer that will be used by the generator for `String` responses - /// - /// If unchanged, Alamofires default `StringResponseSerializer` will be used. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var stringResponseSerializer: AnyResponseSerializer = AnyResponseSerializer(StringResponseSerializer()){{/useAlamofire}} -} -{{#useAlamofire}} - -/// Type-erased ResponseSerializer -/// -/// This is needed in order to use `ResponseSerializer` as a Type in `Configuration`. Obsolete with `any` keyword in Swift >= 5.7 -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct AnyResponseSerializer: ResponseSerializer { - - let _serialize: (URLRequest?, HTTPURLResponse?, Data?, Error?) throws -> T - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(_ delegatee: V) where V.SerializedObject == T { - _serialize = delegatee.serialize - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> T { - try _serialize(request, response, data, error) - } -}{{/useAlamofire}}{{#swiftUseApiNamespace}}} - -{{/swiftUseApiNamespace}} \ No newline at end of file diff --git a/codegen/Templates/swift/Extensions.mustache b/codegen/Templates/swift/Extensions.mustache index 45e11921..7145643c 100644 --- a/codegen/Templates/swift/Extensions.mustache +++ b/codegen/Templates/swift/Extensions.mustache @@ -7,109 +7,99 @@ import Foundation #if canImport(FoundationNetworking) import FoundationNetworking -#endif -#if canImport(AnyCodable) -import AnyCodable #endif{{#usePromiseKit}} -import PromiseKit{{/usePromiseKit}}{{#useVapor}} +@preconcurrency import PromiseKit{{/usePromiseKit}}{{#useVapor}} import Vapor{{/useVapor}}{{^useVapor}} -extension Bool: JSONEncodable { - func encodeToJSON() -> Any { self } +extension Bool: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { self } } -extension Float: JSONEncodable { - func encodeToJSON() -> Any { self } +extension Float: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { self } } -extension Int: JSONEncodable { - func encodeToJSON() -> Any { self } +extension Int: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { self } } -extension Int32: JSONEncodable { - func encodeToJSON() -> Any { self } +extension Int32: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { self } } -extension Int64: JSONEncodable { - func encodeToJSON() -> Any { self } +extension Int64: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { self } } -extension Double: JSONEncodable { - func encodeToJSON() -> Any { self } +extension Double: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { self } } -extension Decimal: JSONEncodable { - func encodeToJSON() -> Any { self } +extension Decimal: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { self } } -extension String: JSONEncodable { - func encodeToJSON() -> Any { self } +extension String: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { self } } -extension URL: JSONEncodable { - func encodeToJSON() -> Any { self } +extension URL: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { self } } -extension UUID: JSONEncodable { - func encodeToJSON() -> Any { self } +extension UUID: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { self } } -extension RawRepresentable where RawValue: JSONEncodable { - func encodeToJSON() -> Any { return self.rawValue } +extension RawRepresentable where RawValue: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { + rawValue.asParameter(codableHelper: codableHelper) + } } -private func encodeIfPossible(_ object: T) -> Any { - if let encodableObject = object as? JSONEncodable { - return encodableObject.encodeToJSON() +private func encodeIfPossible(_ object: T, codableHelper: CodableHelper) -> any Sendable { + if let encodableObject = object as? ParameterConvertible { + return encodableObject.asParameter(codableHelper: codableHelper) } else { return object } } -extension Array: JSONEncodable { - func encodeToJSON() -> Any { - return self.map(encodeIfPossible) +extension Array where Element: Sendable { + func asParameter(codableHelper: CodableHelper) -> any Sendable { + return self.map { encodeIfPossible($0, codableHelper: codableHelper) } } } -extension Set: JSONEncodable { - func encodeToJSON() -> Any { - return Array(self).encodeToJSON() +extension Set where Element: Sendable { + func asParameter(codableHelper: CodableHelper) -> any Sendable { + return Array(self).asParameter(codableHelper: codableHelper) } } -extension Dictionary: JSONEncodable { - func encodeToJSON() -> Any { - var dictionary = [AnyHashable: Any]() +extension Dictionary where Key: Sendable, Value: Sendable { + func asParameter(codableHelper: CodableHelper) -> any Sendable { + var dictionary = [Key: any Sendable]() for (key, value) in self { - dictionary[key] = encodeIfPossible(value) + dictionary[key] = encodeIfPossible(value, codableHelper: codableHelper) } return dictionary } } -extension Data: JSONEncodable { - func encodeToJSON() -> Any { +extension Data: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { return self.base64EncodedString(options: Data.Base64EncodingOptions()) } } -extension Date: JSONEncodable { - func encodeToJSON() -> Any { - return CodableHelper.dateFormatter.string(from: self) - } -} - -extension JSONEncodable where Self: Encodable { - func encodeToJSON() -> Any { - guard let data = try? CodableHelper.jsonEncoder.encode(self) else { - fatalError("Could not encode to json: \(self)") - } - return data.encodeToJSON() +extension Date: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { + return codableHelper.dateFormatter.string(from: self) } }{{/useVapor}}{{#generateModelAdditionalProperties}} -extension Swift.String: Swift.CodingKey { +extension String: @retroactive CodingKey { public var stringValue: String { return self @@ -155,9 +145,13 @@ extension KeyedEncodingContainerProtocol { } {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encode(_ value: Decimal, forKey key: Self.Key) throws { - var mutableValue = value - let stringValue = NSDecimalString(&mutableValue, Locale(identifier: "en_US")) - try encode(stringValue, forKey: key) + let decimalNumber = NSDecimalNumber(decimal: value) + let numberFormatter = NumberFormatter() + numberFormatter.numberStyle = .decimal + numberFormatter.locale = Locale(identifier: "en_US") + numberFormatter.usesGroupingSeparator = false + let formattedString = numberFormatter.string(from: decimalNumber) ?? "\(value)" + try encode(formattedString, forKey: key) } {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeIfPresent(_ value: Decimal?, forKey key: Self.Key) throws { @@ -226,13 +220,7 @@ extension KeyedDecodingContainerProtocol { return decimalValue } -}{{/generateModelAdditionalProperties}}{{^useVapor}} - -extension HTTPURLResponse { - var isStatusCodeSuccessful: Bool { - return Configuration.successfulStatusCodeRange.contains(statusCode) - } -}{{/useVapor}}{{#usePromiseKit}} +}{{/generateModelAdditionalProperties}}{{#usePromiseKit}} extension RequestBuilder { {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func execute() -> Promise> { @@ -249,13 +237,11 @@ extension RequestBuilder { } }{{/usePromiseKit}}{{#useVapor}} -extension UUID: Content { } - -extension URL: Content { } +extension UUID: @retroactive Content { } -extension Bool: Content { } +extension URL: @retroactive Content { } -extension Set: ResponseEncodable where Element: Content { +extension Set: @retroactive ResponseEncodable where Element: Content { public func encodeResponse(for request: Vapor.Request) -> EventLoopFuture { let response = Vapor.Response() do { @@ -267,7 +253,15 @@ extension Set: ResponseEncodable where Element: Content { } } -extension Set: RequestDecodable where Element: Content { +extension Set: @retroactive AsyncResponseEncodable where Element: Content { + public func encodeResponse(for request: Vapor.Request) async throws -> Vapor.Response { + let response = Vapor.Response() + try response.content.encode(Array(self)) + return response + } +} + +extension Set: @retroactive RequestDecodable where Element: Content { public static func decodeRequest(_ request: Vapor.Request) -> EventLoopFuture { do { let content = try request.content.decode([Element].self) @@ -278,6 +272,13 @@ extension Set: RequestDecodable where Element: Content { } } -extension Set: Content where Element: Content { } +extension Set: @retroactive AsyncRequestDecodable where Element: Content { + public static func decodeRequest(_ request: Vapor.Request) async throws -> Self { + let content = try request.content.decode([Element].self) + return Set(content) + } +} + +extension Set: @retroactive Content where Element: Content { } -extension AnyCodable: Content {}{{/useVapor}} +extension JSONValue: Content {}{{/useVapor}} diff --git a/codegen/Templates/swift/JSONDataEncoding.mustache b/codegen/Templates/swift/JSONDataEncoding.mustache index bfc76612..ef6a0611 100644 --- a/codegen/Templates/swift/JSONDataEncoding.mustache +++ b/codegen/Templates/swift/JSONDataEncoding.mustache @@ -10,7 +10,7 @@ import Foundation import FoundationNetworking #endif -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct JSONDataEncoding { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct JSONDataEncoding: Sendable { // MARK: Properties @@ -27,8 +27,8 @@ import FoundationNetworking /// - throws: An `Error` if the encoding process encounters an error. /// /// - returns: The encoded request. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) -> URLRequest { - var urlRequest = urlRequest + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func encode(request: URLRequest, with parameters: [String: any Sendable]?) -> URLRequest { + var urlRequest = request guard let jsonData = parameters?[JSONDataEncoding.jsonDataKey] as? Data, !jsonData.isEmpty else { return urlRequest @@ -43,10 +43,10 @@ import FoundationNetworking return urlRequest } - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func encodingParameters(jsonData: Data?) -> [String: Any]? { - var returnedParams: [String: Any]? + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func encodingParameters(jsonData: Data?) -> [String: any Sendable]? { + var returnedParams: [String: any Sendable]? if let jsonData = jsonData, !jsonData.isEmpty { - var params: [String: Any] = [:] + var params: [String: any Sendable] = [:] params[jsonDataKey] = jsonData returnedParams = params } diff --git a/codegen/Templates/swift/JSONEncodingHelper.mustache b/codegen/Templates/swift/JSONEncodingHelper.mustache index 0eae73e1..d6e73086 100644 --- a/codegen/Templates/swift/JSONEncodingHelper.mustache +++ b/codegen/Templates/swift/JSONEncodingHelper.mustache @@ -9,12 +9,12 @@ import Foundation {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class JSONEncodingHelper { - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func encodingParameters(forEncodableObject encodableObj: T?) -> [String: Any]? { - var params: [String: Any]? + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func encodingParameters(forEncodableObject encodableObj: T?, codableHelper: CodableHelper) -> [String: any Sendable]? { + var params: [String: any Sendable]? // Encode the Encodable object if let encodableObj = encodableObj { - let encodeResult = CodableHelper.encode(encodableObj) + let encodeResult = codableHelper.encode(encodableObj) do { let data = try encodeResult.get() params = JSONDataEncoding.encodingParameters(jsonData: data) @@ -26,20 +26,4 @@ import Foundation return params } - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func encodingParameters(forEncodableObject encodableObj: Any?) -> [String: Any]? { - var params: [String: Any]? - - if let encodableObj = encodableObj { - do { - let data = try JSONSerialization.data(withJSONObject: encodableObj, options: .prettyPrinted) - params = JSONDataEncoding.encodingParameters(jsonData: data) - } catch { - print(error.localizedDescription) - return nil - } - } - - return params - } - } diff --git a/codegen/Templates/swift/JSONValue.mustache b/codegen/Templates/swift/JSONValue.mustache new file mode 100644 index 00000000..36fd830f --- /dev/null +++ b/codegen/Templates/swift/JSONValue.mustache @@ -0,0 +1,247 @@ +// +// JSONValue.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum JSONValue: Sendable, Codable, Hashable { + case string(String) + case int(Int) + case double(Double) + case bool(Bool) + case array([JSONValue]) + case dictionary([String: JSONValue]) + case null + + // MARK: - Decoding Logic + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + + if let stringValue = try? container.decode(String.self) { + self = .string(stringValue) + } else if let intValue = try? container.decode(Int.self) { + self = .int(intValue) + } else if let doubleValue = try? container.decode(Double.self) { + self = .double(doubleValue) + } else if let boolValue = try? container.decode(Bool.self) { + self = .bool(boolValue) + } else if let arrayValue = try? container.decode([JSONValue].self) { + self = .array(arrayValue) + } else if let dictionaryValue = try? container.decode([String: JSONValue].self) { + self = .dictionary(dictionaryValue) + } else if container.decodeNil() { + self = .null + } else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unknown JSON value") + } + } + + // MARK: - Encoding Logic + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + switch self { + case .string(let value): + try container.encode(value) + case .int(let value): + try container.encode(value) + case .double(let value): + try container.encode(value) + case .bool(let value): + try container.encode(value) + case .array(let value): + try container.encode(value) + case .dictionary(let value): + try container.encode(value) + case .null: + try container.encodeNil() + } + } +} + +extension JSONValue { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(_ value: String) { + self = .string(value) + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(_ value: Int) { + self = .int(value) + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(_ value: Double) { + self = .double(value) + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(_ value: Bool) { + self = .bool(value) + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(_ value: [JSONValue]) { + self = .array(value) + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(_ value: [String: JSONValue]) { + self = .dictionary(value) + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(_ codable: T) throws { + let encoder = JSONEncoder() + let encodedData = try encoder.encode(codable) + let decoder = JSONDecoder() + + let decodedValue = try decoder.decode(JSONValue.self, from: encodedData) + self = decodedValue + } +} + +extension JSONValue { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var isString: Bool { + if case .string = self { return true } + return false + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var isInt: Bool { + if case .int = self { return true } + return false + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var isDouble: Bool { + if case .double = self { return true } + return false + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var isBool: Bool { + if case .bool = self { return true } + return false + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var isArray: Bool { + if case .array = self { return true } + return false + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var isDictionary: Bool { + if case .dictionary = self { return true } + return false + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var isNull: Bool { + return self == .null + } + +} + +extension JSONValue { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var stringValue: String? { + switch self { + case .string(let value): + return value + default: + return nil + } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var intValue: Int? { + switch self { + case .int(let value): + return value + default: + return nil + } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var doubleValue: Double? { + switch self { + case .double(let value): + return value + default: + return nil + } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var boolValue: Bool? { + switch self { + case .bool(let value): + return value + default: + return nil + } + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var arrayValue: [JSONValue]? { + if case let .array(value) = self { + return value + } + return nil + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var dictionaryValue: [String: JSONValue]? { + if case let .dictionary(value) = self { + return value + } + return nil + } +} + +extension JSONValue { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} subscript(key: String) -> JSONValue? { + return dictionaryValue?[key] + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} subscript(index: Int) -> JSONValue? { + guard case let .array(array) = self, index >= 0 && index < array.count else { + return nil + } + return array[index] + } +} + +extension JSONValue: ExpressibleByStringLiteral, ExpressibleByStringInterpolation { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(stringLiteral value: StringLiteralType) { + self = .string(value) + } +} + +extension JSONValue: ExpressibleByIntegerLiteral { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(integerLiteral value: IntegerLiteralType) { + self = .int(value) + } +} + +extension JSONValue: ExpressibleByFloatLiteral { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(floatLiteral value: FloatLiteralType) { + self = .double(value) + } +} + + +extension JSONValue: ExpressibleByBooleanLiteral { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(booleanLiteral value: BooleanLiteralType) { + self = .bool(value) + } +} + +extension JSONValue: ExpressibleByArrayLiteral { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(arrayLiteral elements: JSONValue...) { + self = .array(elements) + } +} + +extension JSONValue: ExpressibleByDictionaryLiteral { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(dictionaryLiteral elements: (String, JSONValue)...) { + var dict: [String: JSONValue] = [:] + for (key, value) in elements { + dict[key] = value + } + self = .dictionary(dict) + } +} + +extension JSONValue: ExpressibleByNilLiteral { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(nilLiteral: ()) { + self = .null + } +} diff --git a/codegen/Templates/swift/Models.mustache b/codegen/Templates/swift/Models.mustache index 3bd5e1c4..c7b7dd01 100644 --- a/codegen/Templates/swift/Models.mustache +++ b/codegen/Templates/swift/Models.mustache @@ -10,8 +10,8 @@ import FoundationNetworking #endif{{#useAlamofire}} import Alamofire{{/useAlamofire}} -protocol JSONEncodable { - func encodeToJSON() -> Any +protocol ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable } /// An enum where the last case value can be used as a default catch-all. @@ -38,12 +38,16 @@ extension CaseIterableDefaultsLast { /// A flexible type that can be encoded (`.encodeNull` or `.encodeValue`) /// or not encoded (`.encodeNothing`). Intended for request payloads. -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NullEncodable: Hashable { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NullEncodable { case encodeNothing case encodeNull case encodeValue(Wrapped) } +extension NullEncodable: Equatable where Wrapped: Equatable {} +extension NullEncodable: Hashable where Wrapped: Hashable {} +extension NullEncodable: Sendable where Wrapped: Sendable {} + extension NullEncodable: Codable where Wrapped: Codable { {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() @@ -66,11 +70,11 @@ extension NullEncodable: Codable where Wrapped: Codable { } } -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum ErrorResponse: Error { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum ErrorResponse: Error, Sendable { case error(Int, Data?, URLResponse?, Error) } -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum DownloadException: Error { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum DownloadException: Error, Sendable { case responseDataMissing case responseFailed case requestMissing @@ -78,7 +82,7 @@ extension NullEncodable: Codable where Wrapped: Codable { case requestMissingURL } -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum DecodableRequestBuilderError: Error { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum DecodableRequestBuilderError: Error, Sendable { case emptyDataResponse case nilHTTPResponse case unsuccessfulHTTPStatusCode @@ -86,7 +90,7 @@ extension NullEncodable: Codable where Wrapped: Codable { case generalError(Error) } -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class Response { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct Response { {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let statusCode: Int {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let header: [String: String] {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let body: T @@ -99,7 +103,7 @@ extension NullEncodable: Codable where Wrapped: Codable { self.bodyData = bodyData } - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} convenience init(response: HTTPURLResponse, body: T, bodyData: Data?) { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(response: HTTPURLResponse, body: T, bodyData: Data?) { let rawHeader = response.allHeaderFields var responseHeader = [String: String]() for (key, value) in rawHeader { @@ -110,39 +114,59 @@ extension NullEncodable: Codable where Wrapped: Codable { self.init(statusCode: response.statusCode, header: responseHeader, body: body, bodyData: bodyData) } } +extension Response : Sendable where T : Sendable {}{{#useAlamofire}} + +/// Type-erased ResponseSerializer +/// +/// This is needed in order to use `ResponseSerializer` as a Type in `Configuration`. Obsolete with `any` keyword in Swift >= 5.7 +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct AnyResponseSerializer: ResponseSerializer { + + let _serialize: @Sendable (URLRequest?, HTTPURLResponse?, Data?, Error?) throws -> T + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(_ delegatee: V) where V.SerializedObject == T { + _serialize = delegatee.serialize + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> T { + try _serialize(request, response, data, error) + } +}{{/useAlamofire}} -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} final class RequestTask{{#useAsyncAwait}}: @unchecked Sendable{{/useAsyncAwait}} { - private var lock = NSRecursiveLock() +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} final class RequestTask: @unchecked Sendable { {{#useAlamofire}} - private var request: Request? + private let _state = OpenAPIMutex(nil) internal func set(request: Request) { - lock.lock() - defer { lock.unlock() } - self.request = request + _state.withValue { $0 = request } + } + + internal func get() -> Request? { + _state.value } {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func cancel() { - lock.lock() - defer { lock.unlock() } - request?.cancel() - request = nil + _state.withValue { + $0?.cancel() + $0 = nil + } } {{/useAlamofire}} {{^useAlamofire}} - private var task: URLSessionDataTaskProtocol? + private let _state = OpenAPIMutex(nil) internal func set(task: URLSessionDataTaskProtocol) { - lock.lock() - defer { lock.unlock() } - self.task = task + _state.withValue { $0 = task } + } + + internal func get() -> URLSessionDataTaskProtocol? { + _state.value } {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func cancel() { - lock.lock() - defer { lock.unlock() } - task?.cancel() - task = nil + _state.withValue { + $0?.cancel() + $0 = nil + } } {{/useAlamofire}} } diff --git a/codegen/Templates/swift/OpenAPIDateWithoutTime.mustache b/codegen/Templates/swift/OpenAPIDateWithoutTime.mustache index 53789ce5..0abef8a2 100644 --- a/codegen/Templates/swift/OpenAPIDateWithoutTime.mustache +++ b/codegen/Templates/swift/OpenAPIDateWithoutTime.mustache @@ -15,7 +15,7 @@ import Foundation /// an appropriate padding in order to transform to GMT+0 which is the assumed timezone in ISO 8601. /// When decoding, GMT+0 can be assumed (again: ISO8601) so there is no padding necessary and wrappedDate /// can be used safely. -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct OpenAPIDateWithoutTime: Codable, Hashable, Equatable { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct OpenAPIDateWithoutTime: Sendable, Codable, Hashable, Equatable { {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let wrappedDate: Date {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let timezone: TimeZone @@ -76,8 +76,8 @@ import Foundation } } -extension OpenAPIDateWithoutTime: JSONEncodable { - func encodeToJSON() -> Any { +extension OpenAPIDateWithoutTime: ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable { return OpenISO8601DateFormatter.withoutTime.string(from: self.normalizedWrappedDate()) } } diff --git a/codegen/Templates/swift/OpenAPIMutex.mustache b/codegen/Templates/swift/OpenAPIMutex.mustache new file mode 100644 index 00000000..d7be0b6b --- /dev/null +++ b/codegen/Templates/swift/OpenAPIMutex.mustache @@ -0,0 +1,28 @@ +// OpenAPIMutex.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation + +internal final class OpenAPIMutex: @unchecked Sendable { + + private var _value: Value + private let lock = NSRecursiveLock() + + internal init(_ value: Value) { + self._value = value + } + + internal var value: Value { + lock.withLock { _value } + } + + @discardableResult + internal func withValue( + _ body: (inout Value) throws -> Result + ) rethrows -> Result { + try lock.withLock { try body(&_value) } + } +} diff --git a/codegen/Templates/swift/OpenISO8601DateFormatter.mustache b/codegen/Templates/swift/OpenISO8601DateFormatter.mustache index 9685a5be..fa2335d7 100644 --- a/codegen/Templates/swift/OpenISO8601DateFormatter.mustache +++ b/codegen/Templates/swift/OpenISO8601DateFormatter.mustache @@ -8,7 +8,7 @@ import Foundation // https://stackoverflow.com/a/50281094/976628 -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} class OpenISO8601DateFormatter: DateFormatter { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} class OpenISO8601DateFormatter: DateFormatter, @unchecked Sendable { static let withoutSeconds: DateFormatter = { let formatter = DateFormatter() formatter.calendar = Calendar(identifier: .iso8601) @@ -54,7 +54,3 @@ import Foundation return OpenISO8601DateFormatter.withoutTime.date(from: string) } } - -#if compiler(>=5.5) -extension OpenISO8601DateFormatter: @unchecked Sendable {} -#endif diff --git a/codegen/Templates/swift/Package.swift.mustache b/codegen/Templates/swift/Package.swift.mustache index 7a261f93..7a91fde1 100644 --- a/codegen/Templates/swift/Package.swift.mustache +++ b/codegen/Templates/swift/Package.swift.mustache @@ -1,4 +1,4 @@ -// swift-tools-version:5.1 +// swift-tools-version:6.0 import PackageDescription @@ -9,10 +9,10 @@ let package = Package( .macOS(.v10_15), {{/useVapor}} {{^useVapor}} - .iOS(.v11), - .macOS(.v10_13), - .tvOS(.v11), - .watchOS(.v4), + .iOS(.v13), + .macOS(.v10_15), + .tvOS(.v13), + .watchOS(.v6), {{/useVapor}} ], products: [ @@ -21,21 +21,24 @@ let package = Package( name: "{{projectName}}", targets: ["{{projectName}}"] ), + .executable( + name: "GenerateAndScanExample", + targets: ["GenerateAndScanExample"] + ), ], dependencies: [ // Dependencies declare other packages that this package depends on. - .package(url: "https://github.com/Flight-School/AnyCodable", .upToNextMajor(from: "0.6.1")), {{#useAlamofire}} - .package(url: "https://github.com/Alamofire/Alamofire", .upToNextMajor(from: "5.7.0")), + .package(url: "https://github.com/Alamofire/Alamofire", .upToNextMajor(from: "5.10.2")), {{/useAlamofire}} {{#usePromiseKit}} - .package(url: "https://github.com/mxcl/PromiseKit", .upToNextMajor(from: "6.15.3")), + .package(url: "https://github.com/mxcl/PromiseKit", .upToNextMajor(from: "8.1.2")), {{/usePromiseKit}} {{#useRxSwift}} - .package(url: "https://github.com/ReactiveX/RxSwift", .upToNextMajor(from: "6.2.0")), + .package(url: "https://github.com/ReactiveX/RxSwift", .upToNextMajor(from: "6.8.0")), {{/useRxSwift}} {{#useVapor}} - .package(url: "https://github.com/vapor/vapor", from: "4.0.0") + .package(url: "https://github.com/vapor/vapor", from: "4.99.0"), {{/useVapor}} ], targets: [ @@ -43,8 +46,19 @@ let package = Package( // Targets can depend on other targets in this package, and on products in packages which this package depends on. .target( name: "{{projectName}}", - dependencies: ["AnyCodable", {{#useVapor}}"Vapor", {{/useVapor}}{{#useAlamofire}}"Alamofire", {{/useAlamofire}}{{#usePromiseKit}}"PromiseKit", {{/usePromiseKit}}{{#useRxSwift}}"RxSwift"{{/useRxSwift}}], + dependencies: [{{#useVapor}}.product(name: "Vapor", package: "vapor"){{/useVapor}}{{#useAlamofire}}"Alamofire", {{/useAlamofire}}{{#usePromiseKit}}"PromiseKit", {{/usePromiseKit}}{{#useRxSwift}}"RxSwift"{{/useRxSwift}}], path: "{{swiftPackagePath}}{{^swiftPackagePath}}{{#useSPMFileStructure}}Sources/{{projectName}}{{/useSPMFileStructure}}{{^useSPMFileStructure}}{{projectName}}/Classes{{/useSPMFileStructure}}{{/swiftPackagePath}}" ), - ] + .executableTarget( + name: "GenerateAndScanExample", + dependencies: ["{{projectName}}"], + path: "Examples/GenerateAndScan" + ), + .testTarget( + name: "{{projectName}}Tests", + dependencies: ["{{projectName}}"], + path: "Tests/{{projectName}}Tests" + ), + ], + swiftLanguageModes: [.v6] ) diff --git a/codegen/Templates/swift/Podspec.mustache b/codegen/Templates/swift/Podspec.mustache index 4fd2bbe3..70b97e74 100644 --- a/codegen/Templates/swift/Podspec.mustache +++ b/codegen/Templates/swift/Podspec.mustache @@ -1,10 +1,10 @@ Pod::Spec.new do |s| s.name = '{{projectName}}'{{#projectDescription}} s.summary = '{{.}}'{{/projectDescription}} - s.ios.deployment_target = '11.0' - s.osx.deployment_target = '10.13' - s.tvos.deployment_target = '11.0' - s.watchos.deployment_target = '4.0' + s.ios.deployment_target = '13.0' + s.osx.deployment_target = '10.15' + s.tvos.deployment_target = '13.0' + s.watchos.deployment_target = '6.0' s.version = '{{podVersion}}{{^podVersion}}{{#apiInfo}}{{version}}{{/apiInfo}}{{^apiInfo}}}0.0.1{{/apiInfo}}{{/podVersion}}' s.source = {{#podSource}}{{& podSource}}{{/podSource}}{{^podSource}}{ :git => 'git@github.com:OpenAPITools/openapi-generator.git', :tag => 'v{{#apiInfo}}{{version}}{{/apiInfo}}{{^apiInfo}}}0.0.1{{/apiInfo}}' }{{/podSource}} {{#podAuthors}} @@ -26,14 +26,13 @@ Pod::Spec.new do |s| s.documentation_url = '{{.}}' {{/podDocumentationURL}} s.source_files = '{{swiftPackagePath}}{{^swiftPackagePath}}{{#useSPMFileStructure}}Sources/{{projectName}}{{/useSPMFileStructure}}{{^useSPMFileStructure}}{{projectName}}/Classes{{/useSPMFileStructure}}{{/swiftPackagePath}}/**/*.swift' - s.dependency 'AnyCodable-FlightSchool', '~> 0.6' {{#useAlamofire}} - s.dependency 'Alamofire', '~> 5.7' + s.dependency 'Alamofire', '~> 5.10' {{/useAlamofire}} {{#usePromiseKit}} - s.dependency 'PromiseKit/CorePromise', '~> 6.15' + s.dependency 'PromiseKit/CorePromise', '~> 8.1' {{/usePromiseKit}} {{#useRxSwift}} - s.dependency 'RxSwift', '~> 6.2' + s.dependency 'RxSwift', '~> 6.8' {{/useRxSwift}} end diff --git a/codegen/Templates/swift/README.mustache b/codegen/Templates/swift/README.mustache index 64897978..368b6808 100644 --- a/codegen/Templates/swift/README.mustache +++ b/codegen/Templates/swift/README.mustache @@ -5,8 +5,8 @@ This repository contains the Swift SDK for Aspose.BarCode Cloud. ## Requirements - Swift Package Manager -- iOS 11.0 or later -- macOS 10.13 or later +- iOS 13.0 or later +- macOS 10.15 or later ## Usage diff --git a/codegen/Templates/swift/SynchronizedDictionary.mustache b/codegen/Templates/swift/SynchronizedDictionary.mustache index acf7ff40..6c80862c 100644 --- a/codegen/Templates/swift/SynchronizedDictionary.mustache +++ b/codegen/Templates/swift/SynchronizedDictionary.mustache @@ -6,31 +6,12 @@ import Foundation -internal struct SynchronizedDictionary { +internal final class SynchronizedDictionary: @unchecked Sendable { - private var dictionary = [K: V]() - private let queue = DispatchQueue( - label: "SynchronizedDictionary", - qos: DispatchQoS.userInitiated, - attributes: [DispatchQueue.Attributes.concurrent], - autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit, - target: nil - ) + private let _state = OpenAPIMutex<[K: V]>([:]) internal subscript(key: K) -> V? { - get { - var value: V? - - queue.sync { - value = self.dictionary[key] - } - - return value - } - set { - queue.sync(flags: DispatchWorkItemFlags.barrier) { - self.dictionary[key] = newValue - } - } + get { _state.value[key] } + set { _state.withValue { $0[key] = newValue } } } } diff --git a/codegen/Templates/swift/Validation.mustache b/codegen/Templates/swift/Validation.mustache index 70e3abfb..adfb876f 100644 --- a/codegen/Templates/swift/Validation.mustache +++ b/codegen/Templates/swift/Validation.mustache @@ -6,7 +6,7 @@ import Foundation -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct StringRule { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct StringRule: Sendable { {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var minLength: Int? {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var maxLength: Int? {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var pattern: String? @@ -19,37 +19,37 @@ import Foundation {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var exclusiveMaximum = false {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var multipleOf: T? } +extension NumericRule: Sendable where T: Sendable {} -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct ArrayRule { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct ArrayRule: Sendable { {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var minItems: Int? {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var maxItems: Int? {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var uniqueItems: Bool } -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum StringValidationErrorKind: Error { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum StringValidationErrorKind: Error, Sendable { case minLength, maxLength, pattern } -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NumericValidationErrorKind: Error { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NumericValidationErrorKind: Error, Sendable { case minimum, maximum, multipleOf } -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum ArrayValidationErrorKind: Error { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum ArrayValidationErrorKind: Error, Sendable { case minItems, maxItems, uniqueItems } -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct ValidationError: Error { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct ValidationError: Error, Sendable { {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} fileprivate(set) var kinds: Set } -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct Validator { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct Validator: Sendable { /// Validate a string against a rule. /// - Parameter string: The String you wish to validate. /// - Parameter rule: The StringRule you wish to use for validation. /// - Returns: A validated string. - /// - Throws: `ValidationError` if the string is invalid against the rule, - /// `NSError` if the rule.pattern is invalid. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ string: String, against rule: StringRule) throws -> String { + /// - Throws: `ValidationError` if the string is invalid against the rule or if the rule.pattern is invalid. + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ string: String, against rule: StringRule) throws(ValidationError) -> String { var error = ValidationError(kinds: []) if let minLength = rule.minLength, !(minLength <= string.count) { error.kinds.insert(.minLength) @@ -58,9 +58,9 @@ import Foundation error.kinds.insert(.maxLength) } if let pattern = rule.pattern { - let matches = try NSRegularExpression(pattern: pattern, options: .caseInsensitive) + let matches = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) .matches(in: string, range: .init(location: 0, length: string.utf16.count)) - if matches.isEmpty { + if matches?.isEmpty != false { error.kinds.insert(.pattern) } } @@ -75,13 +75,13 @@ import Foundation /// - Parameter rule: The NumericRule you wish to use for validation. /// - Returns: A validated integer. /// - Throws: `ValidationError` if the numeric is invalid against the rule. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ numeric: T, against rule: NumericRule) throws -> T { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ numeric: T, against rule: NumericRule) throws(ValidationError) -> T { var error = ValidationError(kinds: []) - if let minium = rule.minimum { - if !rule.exclusiveMinimum, minium > numeric { + if let minimum = rule.minimum { + if !rule.exclusiveMinimum, minimum > numeric { error.kinds.insert(.minimum) } - if rule.exclusiveMinimum, minium >= numeric { + if rule.exclusiveMinimum, minimum >= numeric { error.kinds.insert(.minimum) } } @@ -107,13 +107,13 @@ import Foundation /// - Parameter rule: The NumericRule you wish to use for validation. /// - Returns: A validated fractional number. /// - Throws: `ValidationError` if the numeric is invalid against the rule. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ numeric: T, against rule: NumericRule) throws -> T { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ numeric: T, against rule: NumericRule) throws(ValidationError) -> T { var error = ValidationError(kinds: []) - if let minium = rule.minimum { - if !rule.exclusiveMinimum, minium > numeric { + if let minimum = rule.minimum { + if !rule.exclusiveMinimum, minimum > numeric { error.kinds.insert(.minimum) } - if rule.exclusiveMinimum, minium >= numeric { + if rule.exclusiveMinimum, minimum >= numeric { error.kinds.insert(.minimum) } } @@ -139,7 +139,7 @@ import Foundation /// - Parameter rule: The ArrayRule you wish to use for validation. /// - Returns: A validated array. /// - Throws: `ValidationError` if the string is invalid against the rule. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ array: Array, against rule: ArrayRule) throws -> Array { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ array: Array, against rule: ArrayRule) throws(ValidationError) -> Array { var error = ValidationError(kinds: []) if let minItems = rule.minItems, !(minItems <= array.count) { error.kinds.insert(.minItems) diff --git a/codegen/Templates/swift/XcodeGen.mustache b/codegen/Templates/swift/XcodeGen.mustache index d4a36d58..eec5b2b7 100644 --- a/codegen/Templates/swift/XcodeGen.mustache +++ b/codegen/Templates/swift/XcodeGen.mustache @@ -11,8 +11,7 @@ targets: settings: APPLICATION_EXTENSION_API_ONLY: true scheme: {} - dependencies: - - carthage: AnyCodable{{#useAlamofire}} + dependencies:{{#useAlamofire}} - carthage: Alamofire{{/useAlamofire}}{{#usePromiseKit}} - carthage: PromiseKit{{/usePromiseKit}}{{#useRxSwift}} - carthage: RxSwift{{/useRxSwift}} diff --git a/codegen/Templates/swift/_param.mustache b/codegen/Templates/swift/_param.mustache index 00b0c399..24e2f369 100644 --- a/codegen/Templates/swift/_param.mustache +++ b/codegen/Templates/swift/_param.mustache @@ -1 +1 @@ -"{{baseName}}": {{#isQueryParam}}(wrappedValue: {{/isQueryParam}}{{paramName}}{{^required}}?{{/required}}.encodeToJSON(){{#isQueryParam}}, isExplode: {{isExplode}}){{/isQueryParam}} \ No newline at end of file +"{{baseName}}": {{#isQueryParam}}(wrappedValue: {{/isQueryParam}}{{paramName}}{{^required}}?{{/required}}.asParameter(codableHelper: apiConfiguration.codableHelper){{#isQueryParam}}, isExplode: {{isExplode}}){{/isQueryParam}} \ No newline at end of file diff --git a/codegen/Templates/swift/api.mustache b/codegen/Templates/swift/api.mustache index d25148f4..9c1c78da 100644 --- a/codegen/Templates/swift/api.mustache +++ b/codegen/Templates/swift/api.mustache @@ -6,15 +6,10 @@ // import Foundation{{#usePromiseKit}} -import PromiseKit{{/usePromiseKit}}{{#useRxSwift}} -import RxSwift{{/useRxSwift}}{{#useCombine}} -#if canImport(Combine) -import Combine -#endif{{/useCombine}}{{#useVapor}} -import Vapor{{/useVapor}} -#if canImport(AnyCodable) -import AnyCodable -#endif{{#swiftUseApiNamespace}} +@preconcurrency import PromiseKit{{/usePromiseKit}}{{#useRxSwift}} +@preconcurrency import RxSwift{{/useRxSwift}}{{#useCombine}} +import Combine{{/useCombine}}{{#useVapor}} +import Vapor{{/useVapor}}{{#swiftUseApiNamespace}} extension {{projectName}}API { {{/swiftUseApiNamespace}} @@ -22,6 +17,12 @@ extension {{projectName}}API { {{#description}} /** {{{.}}} */{{/description}} {{#objcCompatible}}@objcMembers {{/objcCompatible}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class {{classname}}{{#objcCompatible}} : NSObject{{/objcCompatible}} { +{{^apiStaticMethod}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let apiConfiguration: {{projectName}}APIConfiguration + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared) { + self.apiConfiguration = apiConfiguration + } +{{/apiStaticMethod}} {{#operation}} {{#allParams}} {{#isEnum}} @@ -29,7 +30,7 @@ extension {{projectName}}API { /** * enum for parameter {{paramName}} */ - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{enumName}}_{{operationId}}: {{^isContainer}}{{{dataType}}}{{/isContainer}}{{#isContainer}}String{{/isContainer}}, CaseIterable{{#useVapor}}, Content{{/useVapor}} { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{enumName}}_{{operationId}}: {{^isContainer}}{{{dataType}}}{{/isContainer}}{{#isContainer}}String{{/isContainer}}, Sendable, CaseIterable{{#useVapor}}, Content{{/useVapor}} { {{^enumUnknownDefaultCase}} {{#allowableValues}} {{#enumVars}} @@ -50,26 +51,22 @@ extension {{projectName}}API { {{/isEnum}} {{/allParams}} {{^useVapor}} -{{^usePromiseKit}} -{{^useRxSwift}} -{{^useResult}} -{{^useCombine}} -{{^useAsyncAwait}} +{{#useObjcBlock}} /** - {{#summary}} +{{#summary}} {{{.}}} - {{/summary}}{{#allParams}} - - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} - - parameter apiResponseQueue: The queue on which api response is dispatched. +{{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}){{#description}} {{.}}{{/description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}}{{#apiStaticMethod}} + - parameter apiConfiguration: The configuration for the http request.{{/apiStaticMethod}} - parameter completion: completion handler to receive the data and the error objects */ {{#isDeprecated}} @available(*, deprecated, message: "This operation is deprecated.") {{/isDeprecated}} @discardableResult - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, completion: @escaping ((_ data: {{{returnType}}}{{^returnType}}Void{{/returnType}}?, _ error: Error?) -> Void)) -> RequestTask { - return {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).execute(apiResponseQueue) { result in + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} {{#apiStaticMethod}}class {{/apiStaticMethod}}func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}, {{/allParams}}{{#apiStaticMethod}}apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared, {{/apiStaticMethod}}completion: @Sendable @escaping (_ data: {{{returnType}}}{{^returnType}}Void{{/returnType}}?, _ error: Error?) -> Void) -> RequestTask { + return {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#apiStaticMethod}}{{#hasParams}}, {{/hasParams}}apiConfiguration: apiConfiguration{{/apiStaticMethod}}).execute { result in switch result { {{#returnType}} case let .success(response): @@ -84,27 +81,21 @@ extension {{projectName}}API { } } } -{{/useAsyncAwait}} -{{/useCombine}} -{{/useResult}} -{{/useRxSwift}} -{{/usePromiseKit}} -{{/useVapor}} +{{/useObjcBlock}} {{#usePromiseKit}} /** - {{#summary}} +{{#summary}} {{{.}}} - {{/summary}}{{#allParams}} - - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} +{{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}){{#description}} {{.}}{{/description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}}{{#apiStaticMethod}} + - parameter apiConfiguration: The configuration for the http request.{{/apiStaticMethod}} - returns: Promise<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> */ - {{#isDeprecated}} - @available(*, deprecated, message: "This operation is deprecated.") - {{/isDeprecated}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}} {{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Promise<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> { + @available(*, deprecated, message: "{{#isDeprecated}}This operation is deprecated. | {{/isDeprecated}}NOTICE: We are considering deprecating PromiseKit support in the Swift 6 generator. If you are still using it, please share your use case here: https://github.com/OpenAPITools/openapi-generator/issues/22791") + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} {{#apiStaticMethod}}class {{/apiStaticMethod}}func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#apiStaticMethod}}{{#hasParams}}, {{/hasParams}}apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared{{/apiStaticMethod}}) -> Promise<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> { let deferred = Promise<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}>.pending() - {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).execute { result in + {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#apiStaticMethod}}{{#hasParams}}, {{/hasParams}}apiConfiguration: apiConfiguration{{/apiStaticMethod}}).execute { result in switch result { {{#returnType}} case let .success(response): @@ -124,19 +115,19 @@ extension {{projectName}}API { {{#useRxSwift}} /** - {{#summary}} +{{#summary}} {{{.}}} - {{/summary}}{{#allParams}} - - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} - - parameter apiResponseQueue: The queue on which api response is dispatched. +{{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}){{#description}} {{.}}{{/description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}}{{#apiStaticMethod}} + - parameter apiConfiguration: The configuration for the http request.{{/apiStaticMethod}} - returns: Observable<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> */ {{#isDeprecated}} @available(*, deprecated, message: "This operation is deprecated.") {{/isDeprecated}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue) -> Observable<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} {{#apiStaticMethod}}class {{/apiStaticMethod}}func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#apiStaticMethod}}{{#hasParams}}, {{/hasParams}}apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared{{/apiStaticMethod}}) -> Observable<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> { return Observable.create { observer -> Disposable in - let requestTask = {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).execute(apiResponseQueue) { result in + let requestTask = self.{{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#apiStaticMethod}}{{#hasParams}}, {{/hasParams}}apiConfiguration: apiConfiguration{{/apiStaticMethod}}).execute { result in switch result { {{#returnType}} case let .success(response): @@ -161,21 +152,21 @@ extension {{projectName}}API { {{#useCombine}} /** - {{#summary}} +{{#summary}} {{{.}}} - {{/summary}}{{#allParams}} - - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} +{{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}){{#description}} {{.}}{{/description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}}{{#apiStaticMethod}} + - parameter apiConfiguration: The configuration for the http request.{{/apiStaticMethod}} - returns: AnyPublisher<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}, Error> */ - #if canImport(Combine) {{#isDeprecated}} @available(*, deprecated, message: "This operation is deprecated.") {{/isDeprecated}} - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> AnyPublisher<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}, Error> { - let requestBuilder = {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} {{#apiStaticMethod}}class {{/apiStaticMethod}}func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#apiStaticMethod}}{{#hasParams}}, {{/hasParams}}apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared{{/apiStaticMethod}}) -> AnyPublisher<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}, Error> { + let requestBuilder = {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#apiStaticMethod}}{{#hasParams}}, {{/hasParams}}apiConfiguration: apiConfiguration{{/apiStaticMethod}}) let requestTask = requestBuilder.requestTask - return Future<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}, Error> { promise in + return {{#combineDeferred}}Deferred { {{/combineDeferred}}Future<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}, Error> { promise in + nonisolated(unsafe) let promise = promise requestBuilder.execute { result in switch result { {{#returnType}} @@ -195,42 +186,45 @@ extension {{projectName}}API { requestTask.cancel() }) .eraseToAnyPublisher() + {{#combineDeferred}} + } + .eraseToAnyPublisher() + {{/combineDeferred}} } - #endif {{/useCombine}} {{#useAsyncAwait}} /** - {{#summary}} +{{#summary}} {{{.}}} - {{/summary}}{{#allParams}} - - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} +{{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}){{#description}} {{.}}{{/description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}}{{#apiStaticMethod}} + - parameter apiConfiguration: The configuration for the http request.{{/apiStaticMethod}} - returns: {{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}} */ {{#isDeprecated}} @available(*, deprecated, message: "This operation is deprecated.") {{/isDeprecated}} - @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) async throws{{#returnType}} -> {{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{/returnType}} { - return try await {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).execute().body + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} {{#apiStaticMethod}}class {{/apiStaticMethod}}func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#apiStaticMethod}}{{#hasParams}}, {{/hasParams}}apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared{{/apiStaticMethod}}) async throws(ErrorResponse){{#returnType}} -> {{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{/returnType}} { + return try await {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#apiStaticMethod}}{{#hasParams}}, {{/hasParams}}apiConfiguration: apiConfiguration{{/apiStaticMethod}}).execute().body } {{/useAsyncAwait}} {{#useResult}} /** - {{#summary}} +{{#summary}} {{{.}}} - {{/summary}}{{#allParams}} - - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}} - - parameter apiResponseQueue: The queue on which api response is dispatched. +{{/summary}}{{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}){{#description}} {{.}}{{/description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}{{/allParams}}{{#apiStaticMethod}} + - parameter apiConfiguration: The configuration for the http request.{{/apiStaticMethod}} - parameter completion: completion handler to receive the result */ {{#isDeprecated}} @available(*, deprecated, message: "This operation is deprecated.") {{/isDeprecated}} @discardableResult - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, completion: @escaping ((_ result: Swift.Result<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}, ErrorResponse>) -> Void)) -> RequestTask { - return {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).execute(apiResponseQueue) { result in + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} {{#apiStaticMethod}}class {{/apiStaticMethod}}func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}, {{/allParams}}{{#apiStaticMethod}}apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared, {{/apiStaticMethod}}completion: @Sendable @escaping (_ result: Swift.Result<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}, ErrorResponse>) -> Void) -> RequestTask { + return {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#apiStaticMethod}}{{#hasParams}}, {{/hasParams}}apiConfiguration: apiConfiguration{{/apiStaticMethod}}).execute { result in switch result { {{#returnType}} case let .success(response): @@ -246,12 +240,94 @@ extension {{projectName}}API { } } {{/useResult}} + + /** +{{#summary}} + {{{.}}} +{{/summary}} + - {{httpMethod}} {{{path}}}{{#notes}} + - {{{.}}}{{/notes}}{{#subresourceOperation}} + - subresourceOperation: {{.}}{{/subresourceOperation}}{{#defaultResponse}} + - defaultResponse: {{.}}{{/defaultResponse}} + {{#authMethods}} + - {{#isBasicBasic}}BASIC{{/isBasicBasic}}{{#isBasicBearer}}Bearer Token{{/isBasicBearer}}{{#isOAuth}}OAuth{{/isOAuth}}{{#isApiKey}}API Key{{/isApiKey}}: + - type: {{type}}{{#keyParamName}} {{keyParamName}} {{#isKeyInQuery}}(QUERY){{/isKeyInQuery}}{{#isKeyInHeader}}(HEADER){{/isKeyInHeader}}{{/keyParamName}} + - name: {{name}} + {{/authMethods}} + {{#hasResponseHeaders}} + - responseHeaders: [{{#responseHeaders}}{{{baseName}}}({{{dataType}}}){{^-last}}, {{/-last}}{{/responseHeaders}}] + {{/hasResponseHeaders}} + {{#externalDocs}} + - externalDocs: {{.}} + {{/externalDocs}} + {{#allParams}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}){{#description}} {{.}}{{/description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + {{/allParams}} + {{#apiStaticMethod}} + - parameter apiConfiguration: The configuration for the http request. + {{/apiStaticMethod}} + - returns: RequestBuilder<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}>{{#description}} {{.}}{{/description}} + */ + {{#isDeprecated}} + @available(*, deprecated, message: "This operation is deprecated.") + {{/isDeprecated}} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} {{#apiStaticMethod}}class {{/apiStaticMethod}}func {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#apiStaticMethod}}{{#hasParams}}, {{/hasParams}}apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared{{/apiStaticMethod}}) -> RequestBuilder<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> { + {{^pathParams}}let{{/pathParams}}{{#pathParams}}{{#-first}}var{{/-first}}{{/pathParams}} localVariablePath = "{{{path}}}"{{#pathParams}} + let {{paramName}}PreEscape = "\({{#isEnum}}{{paramName}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}.rawValue{{/isContainer}}{{/isEnum}}{{^isEnum}}APIHelper.mapValueToPathItem({{paramName}}){{/isEnum}})" + let {{paramName}}PostEscape = {{paramName}}PreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" + localVariablePath = localVariablePath.replacingOccurrences(of: "{{=<% %>=}}{<%baseName%>}<%={{ }}=%>", with: {{paramName}}PostEscape, options: .literal, range: nil){{/pathParams}} + let localVariableURLString = apiConfiguration.basePath + localVariablePath + {{#bodyParam}} + {{#isBinary}} + let localVariableParameters = ["body": {{paramName}}] + {{/isBinary}} + {{^isBinary}} + let localVariableParameters = JSONEncodingHelper.encodingParameters(forEncodableObject: {{paramName}}, codableHelper: apiConfiguration.codableHelper) + {{/isBinary}} + {{/bodyParam}} + {{^bodyParam}} + {{#hasFormParams}} + let localVariableFormParams: [String: (any Sendable)?] = [ + {{#formParams}} + {{> _param}}, + {{/formParams}} + ] + + let localVariableNonNullParameters = APIHelper.rejectNil(localVariableFormParams) + let localVariableParameters = APIHelper.convertBoolToString(localVariableNonNullParameters) + {{/hasFormParams}} + {{^hasFormParams}} + let localVariableParameters: [String: any Sendable]? = nil + {{/hasFormParams}} +{{/bodyParam}}{{#hasQueryParams}} + var localVariableUrlComponents = URLComponents(string: localVariableURLString) + localVariableUrlComponents?.queryItems = APIHelper.mapValuesToQueryItems([{{^queryParams}}:{{/queryParams}} + {{#queryParams}} + {{> _param}}, + {{/queryParams}} + ]){{/hasQueryParams}}{{^hasQueryParams}} + let localVariableUrlComponents = URLComponents(string: localVariableURLString){{/hasQueryParams}} + + let localVariableNillableHeaders: [String: (any Sendable)?] = [{{^headerParams}}{{^hasFormParams}}{{^hasConsumes}} + :{{/hasConsumes}}{{/hasFormParams}}{{/headerParams}}{{#hasFormParams}} + "Content-Type": {{^consumes}}"multipart/form-data"{{/consumes}}{{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}},{{/hasFormParams}}{{^hasFormParams}}{{#hasConsumes}} + "Content-Type": {{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}},{{/hasConsumes}}{{/hasFormParams}}{{#headerParams}} + {{> _param}},{{/headerParams}} + ] + + let localVariableHeaderParameters = APIHelper.rejectNilHeaders(localVariableNillableHeaders) + + let localVariableRequestBuilder: RequestBuilder<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}>.Type = apiConfiguration.requestBuilderFactory.{{#returnType}}getBuilder(){{/returnType}}{{^returnType}}getNonDecodableBuilder(){{/returnType}} + + return localVariableRequestBuilder.init(method: "{{httpMethod}}", URLString: (localVariableUrlComponents?.string ?? localVariableURLString), parameters: localVariableParameters, headers: localVariableHeaderParameters, requiresAuthentication: {{#hasAuthMethods}}true{{/hasAuthMethods}}{{^hasAuthMethods}}false{{/hasAuthMethods}}, apiConfiguration: apiConfiguration) + } +{{/useVapor}} {{#useVapor}} /** - {{#summary}} +{{#summary}} {{{.}}} - {{/summary}} +{{/summary}} {{httpMethod}} {{{path}}}{{#notes}} {{{.}}}{{/notes}}{{#subresourceOperation}} subresourceOperation: {{.}}{{/subresourceOperation}}{{#defaultResponse}} @@ -268,26 +344,26 @@ extension {{projectName}}API { - externalDocs: {{.}} {{/externalDocs}} {{#allParams}} - - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{{description}}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}){{#description}} {{{.}}}{{/description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} {{/allParams}} - - returns: `EventLoopFuture` of `ClientResponse` {{{description}}} + - returns: `EventLoopFuture` of `ClientResponse`{{#description}} {{{.}}}{{/description}} */ {{#isDeprecated}} @available(*, deprecated, message: "This operation is deprecated.") {{/isDeprecated}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}Raw({{#allParams}}{{paramName}}: {{#isEnum}}{{#isArray}}[{{enumName}}_{{operationId}}]{{/isArray}}{{^isArray}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers: HTTPHeaders = {{projectName}}API.customHeaders, beforeSend: (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} {{#apiStaticMethod}}class {{/apiStaticMethod}}func {{operationId}}Raw({{#allParams}}{{paramName}}: {{#isEnum}}{{#isArray}}[{{enumName}}_{{operationId}}]{{/isArray}}{{^isArray}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers: HTTPHeaders? = nil, apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared, beforeSend: @Sendable (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture { {{^pathParams}}let{{/pathParams}}{{#pathParams}}{{#-first}}var{{/-first}}{{/pathParams}} localVariablePath = "{{{path}}}"{{#pathParams}} let {{paramName}}PreEscape = String(describing: {{#isEnum}}{{paramName}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}.rawValue{{/isContainer}}{{/isEnum}}{{^isEnum}}{{paramName}}{{/isEnum}}) let {{paramName}}PostEscape = {{paramName}}PreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" localVariablePath = localVariablePath.replacingOccurrences(of: "{{=<% %>=}}{<%baseName%>}<%={{ }}=%>", with: {{paramName}}PostEscape, options: .literal, range: nil){{/pathParams}} - let localVariableURLString = {{projectName}}API.basePath + localVariablePath + let localVariableURLString = apiConfiguration.basePath + localVariablePath - guard let localVariableApiClient = {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}Configuration.apiClient else { - fatalError("Configuration.apiClient is not set.") + guard let localVariableApiClient = apiConfiguration.apiClient else { + fatalError("apiConfiguration.apiClient is not set.") } - return localVariableApiClient.send(.{{httpMethod}}, headers: headers, to: URI(string: localVariableURLString)) { localVariableRequest in - try {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}Configuration.apiWrapper(&localVariableRequest) + return localVariableApiClient.send(.{{httpMethod}}, headers: headers ?? apiConfiguration.customHeaders, to: URI(string: localVariableURLString)) { localVariableRequest in + try apiConfiguration.apiWrapper(&localVariableRequest) {{#hasHeaderParams}}{{#headerParams}} localVariableRequest.headers.add(name: "{{baseName}}", value: {{#isArray}}{{paramName}}{{^required}}?{{/required}}.map { $0{{#isEnum}}.rawValue{{/isEnum}}.description }.description{{/isArray}}{{^isArray}}{{#isEnum}}{{paramName}}{{^required}}?{{/required}}.rawValue.description{{/isEnum}}{{^isEnum}}{{paramName}}{{^required}}?{{/required}}.description{{/isEnum}}{{/isArray}}{{^required}} ?? ""{{/required}}) {{/headerParams}}{{/hasHeaderParams}} @@ -304,8 +380,8 @@ extension {{projectName}}API { } try localVariableRequest.query.encode(QueryParams({{#queryParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/queryParams}})){{/hasQueryParams}} {{#hasBodyParam}} - {{#bodyParam}}{{#required}}{{#isBinary}}localVariableRequest.body = ByteBuffer(data: {{paramName}}){{/isBinary}}{{^isBinary}}{{#isFile}}localVariableRequest.body = ByteBuffer(data: {{paramName}}){{/isFile}}try localVariableRequest.content.encode({{paramName}}, using: Configuration.contentConfiguration.requireEncoder(for: {{{dataType}}}.defaultContentType)){{/isBinary}}{{/required}}{{^required}}if let localVariableBody = {{paramName}} { - {{#isBinary}}localVariableRequest.body = ByteBuffer(data: localVariableBody){{/isBinary}}{{^isBinary}}{{#isFile}}localVariableRequest.body = ByteBuffer(data: localVariableBody){{/isFile}}try localVariableRequest.content.encode(localVariableBody, using: Configuration.contentConfiguration.requireEncoder(for: {{{dataType}}}.defaultContentType)){{/isBinary}} + {{#bodyParam}}{{#required}}{{#isBinary}}localVariableRequest.body = ByteBuffer(data: {{paramName}}){{/isBinary}}{{^isBinary}}{{#isFile}}localVariableRequest.body = ByteBuffer(data: {{paramName}}){{/isFile}}try localVariableRequest.content.encode({{paramName}}, using: apiConfiguration.contentConfiguration.requireEncoder(for: {{{dataType}}}.defaultContentType)){{/isBinary}}{{/required}}{{^required}}if let localVariableBody = {{paramName}} { + {{#isBinary}}localVariableRequest.body = ByteBuffer(data: localVariableBody){{/isBinary}}{{^isBinary}}{{#isFile}}localVariableRequest.body = ByteBuffer(data: localVariableBody){{/isFile}}try localVariableRequest.content.encode(localVariableBody, using: apiConfiguration.contentConfiguration.requireEncoder(for: {{{dataType}}}.defaultContentType)){{/isBinary}} }{{/required}}{{/bodyParam}} {{/hasBodyParam}} {{#hasFormParams}}struct FormParams: Content { @@ -314,7 +390,7 @@ extension {{projectName}}API { var {{paramName}}: {{#isEnum}}{{#isArray}}[{{enumName}}_{{operationId}}]{{/isArray}}{{^isArray}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}?{{/required}} {{/formParams}} } - try localVariableRequest.content.encode(FormParams({{#formParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/formParams}}), using: Configuration.contentConfiguration.requireEncoder(for: FormParams.defaultContentType)){{/hasFormParams}} + try localVariableRequest.content.encode(FormParams({{#formParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/formParams}}), using: apiConfiguration.contentConfiguration.requireEncoder(for: FormParams.defaultContentType)){{/hasFormParams}} try beforeSend(&localVariableRequest) } } @@ -329,9 +405,9 @@ extension {{projectName}}API { } /** - {{#summary}} +{{#summary}} {{{.}}} - {{/summary}} +{{/summary}} {{httpMethod}} {{{path}}}{{#notes}} {{{.}}}{{/notes}}{{#subresourceOperation}} subresourceOperation: {{.}}{{/subresourceOperation}}{{#defaultResponse}} @@ -348,19 +424,19 @@ extension {{projectName}}API { - externalDocs: {{.}} {{/externalDocs}} {{#allParams}} - - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{{description}}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}){{#description}} {{{.}}}{{/description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} {{/allParams}} - - returns: `EventLoopFuture` of `{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}` {{{description}}} + - returns: `EventLoopFuture` of `{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}`{{#description}} {{{.}}}{{/description}} */ {{#isDeprecated}} @available(*, deprecated, message: "This operation is deprecated.") {{/isDeprecated}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isArray}}[{{enumName}}_{{operationId}}]{{/isArray}}{{^isArray}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers: HTTPHeaders = {{projectName}}API.customHeaders, beforeSend: (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture<{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}> { - return {{operationId}}Raw({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers: headers, beforeSend: beforeSend).flatMapThrowing { response -> {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}} in + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} {{#apiStaticMethod}}class {{/apiStaticMethod}}func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isArray}}[{{enumName}}_{{operationId}}]{{/isArray}}{{^isArray}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers: HTTPHeaders? = nil, apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared, beforeSend: @Sendable (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture<{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}> { + return {{operationId}}Raw({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers: headers, apiConfiguration: apiConfiguration, beforeSend: beforeSend).flatMapThrowing { response -> {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}} in switch response.status.code { {{#responses}} {{#isDefault}}default{{/isDefault}}{{^isDefault}}case {{code}}{{/isDefault}}: - return .http{{code}}({{#dataType}}value: {{#isBinary}}Data(buffer: response.body ?? ByteBuffer()){{/isBinary}}{{^isBinary}}{{#isFile}}Data(buffer: response.body ?? ByteBuffer()){{/isFile}}{{^isFile}}try response.content.decode({{{dataType}}}.self, using: Configuration.contentConfiguration.requireDecoder(for: {{{dataType}}}.defaultContentType)){{/isFile}}{{/isBinary}}, {{/dataType}}raw: response) + return .http{{code}}({{#dataType}}value: {{#isBinary}}Data(buffer: response.body ?? ByteBuffer()){{/isBinary}}{{^isBinary}}{{#isFile}}Data(buffer: response.body ?? ByteBuffer()){{/isFile}}{{^isFile}}try response.content.decode({{{dataType}}}.self, using: apiConfiguration.contentConfiguration.requireDecoder(for: {{{dataType}}}.defaultContentType)){{/isFile}}{{/isBinary}}, {{/dataType}}raw: response) {{/responses}} {{^hasDefaultResponse}} default: @@ -370,86 +446,6 @@ extension {{projectName}}API { } } {{/useVapor}} -{{^useVapor}} - - /** - {{#summary}} - {{{.}}} - {{/summary}} - - {{httpMethod}} {{{path}}}{{#notes}} - - {{{.}}}{{/notes}}{{#subresourceOperation}} - - subresourceOperation: {{.}}{{/subresourceOperation}}{{#defaultResponse}} - - defaultResponse: {{.}}{{/defaultResponse}} - {{#authMethods}} - - {{#isBasicBasic}}BASIC{{/isBasicBasic}}{{#isBasicBearer}}Bearer Token{{/isBasicBearer}}{{#isOAuth}}OAuth{{/isOAuth}}{{#isApiKey}}API Key{{/isApiKey}}: - - type: {{type}}{{#keyParamName}} {{keyParamName}} {{#isKeyInQuery}}(QUERY){{/isKeyInQuery}}{{#isKeyInHeader}}(HEADER){{/isKeyInHeader}}{{/keyParamName}} - - name: {{name}} - {{/authMethods}} - {{#hasResponseHeaders}} - - responseHeaders: [{{#responseHeaders}}{{{baseName}}}({{{dataType}}}){{^-last}}, {{/-last}}{{/responseHeaders}}] - {{/hasResponseHeaders}} - {{#externalDocs}} - - externalDocs: {{.}} - {{/externalDocs}} - {{#allParams}} - - parameter {{paramName}}: ({{#isFormParam}}form{{/isFormParam}}{{#isQueryParam}}query{{/isQueryParam}}{{#isPathParam}}path{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}) {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} - {{/allParams}} - - returns: RequestBuilder<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> {{description}} - */ - {{#isDeprecated}} - @available(*, deprecated, message: "This operation is deprecated.") - {{/isDeprecated}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> RequestBuilder<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}> { - {{^pathParams}}let{{/pathParams}}{{#pathParams}}{{#-first}}var{{/-first}}{{/pathParams}} localVariablePath = "{{{path}}}"{{#pathParams}} - let {{paramName}}PreEscape = "\({{#isEnum}}{{paramName}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}.rawValue{{/isContainer}}{{/isEnum}}{{^isEnum}}APIHelper.mapValueToPathItem({{paramName}}){{/isEnum}})" - let {{paramName}}PostEscape = {{paramName}}PreEscape.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? "" - localVariablePath = localVariablePath.replacingOccurrences(of: "{{=<% %>=}}{<%baseName%>}<%={{ }}=%>", with: {{paramName}}PostEscape, options: .literal, range: nil){{/pathParams}} - let localVariableURLString = {{projectName}}API.basePath + localVariablePath - {{#bodyParam}} - {{#isBinary}} - let localVariableParameters = ["body": {{paramName}}] - {{/isBinary}} - {{^isBinary}} - let localVariableParameters = JSONEncodingHelper.encodingParameters(forEncodableObject: {{paramName}}) - {{/isBinary}} - {{/bodyParam}} - {{^bodyParam}} - {{#hasFormParams}} - let localVariableFormParams: [String: Any?] = [ - {{#formParams}} - {{> _param}}, - {{/formParams}} - ] - - let localVariableNonNullParameters = APIHelper.rejectNil(localVariableFormParams) - let localVariableParameters = APIHelper.convertBoolToString(localVariableNonNullParameters) - {{/hasFormParams}} - {{^hasFormParams}} - let localVariableParameters: [String: Any]? = nil - {{/hasFormParams}} -{{/bodyParam}}{{#hasQueryParams}} - var localVariableUrlComponents = URLComponents(string: localVariableURLString) - localVariableUrlComponents?.queryItems = APIHelper.mapValuesToQueryItems([{{^queryParams}}:{{/queryParams}} - {{#queryParams}} - {{> _param}}, - {{/queryParams}} - ]){{/hasQueryParams}}{{^hasQueryParams}} - let localVariableUrlComponents = URLComponents(string: localVariableURLString){{/hasQueryParams}} - - let localVariableNillableHeaders: [String: Any?] = [{{^headerParams}}{{^hasFormParams}}{{^hasConsumes}} - :{{/hasConsumes}}{{/hasFormParams}}{{/headerParams}}{{#hasFormParams}} - "Content-Type": {{^consumes}}"multipart/form-data"{{/consumes}}{{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}},{{/hasFormParams}}{{^hasFormParams}}{{#hasConsumes}} - "Content-Type": {{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}},{{/hasConsumes}}{{/hasFormParams}}{{#headerParams}} - {{> _param}},{{/headerParams}} - ] - - let localVariableHeaderParameters = APIHelper.rejectNilHeaders(localVariableNillableHeaders) - - let localVariableRequestBuilder: RequestBuilder<{{{returnType}}}{{#returnType}}{{#isResponseOptional}}?{{/isResponseOptional}}{{/returnType}}{{^returnType}}Void{{/returnType}}>.Type = {{projectName}}API.requestBuilderFactory.{{#returnType}}getBuilder(){{/returnType}}{{^returnType}}getNonDecodableBuilder(){{/returnType}} - - return localVariableRequestBuilder.init(method: "{{httpMethod}}", URLString: (localVariableUrlComponents?.string ?? localVariableURLString), parameters: localVariableParameters, headers: localVariableHeaderParameters, requiresAuthentication: {{#hasAuthMethods}}true{{/hasAuthMethods}}{{^hasAuthMethods}}false{{/hasAuthMethods}}) - } -{{/useVapor}} {{/operation}} } {{#swiftUseApiNamespace}} diff --git a/codegen/Templates/swift/api_doc.mustache b/codegen/Templates/swift/api_doc.mustache index 7faccc1a..9024673a 100644 --- a/codegen/Templates/swift/api_doc.mustache +++ b/codegen/Templates/swift/api_doc.mustache @@ -26,7 +26,7 @@ Method | HTTP request | Description {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Observable<{{{returnType}}}{{^returnType}}Void{{/returnType}}> {{/useRxSwift}} {{#useVapor}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers: HTTPHeaders = {{projectName}}API.customHeaders, beforeSend: (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture<{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}> + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers: HTTPHeaders = {{projectName}}APIConfiguration.shared.customHeaders, beforeSend: (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture<{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}> {{/useVapor}} ``` diff --git a/codegen/Templates/swift/libraries/alamofire/AlamofireImplementations.mustache b/codegen/Templates/swift/libraries/alamofire/AlamofireImplementations.mustache index 9ef5937c..7ee7cd98 100644 --- a/codegen/Templates/swift/libraries/alamofire/AlamofireImplementations.mustache +++ b/codegen/Templates/swift/libraries/alamofire/AlamofireImplementations.mustache @@ -7,33 +7,40 @@ import Foundation import Alamofire -class AlamofireRequestBuilderFactory: RequestBuilderFactory { - func getNonDecodableBuilder() -> RequestBuilder.Type { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} final class AlamofireRequestBuilderFactory: RequestBuilderFactory, Sendable { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init() {} + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func getNonDecodableBuilder() -> RequestBuilder.Type { return AlamofireRequestBuilder.self } - func getBuilder() -> RequestBuilder.Type { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func getBuilder() -> RequestBuilder.Type { return AlamofireDecodableRequestBuilder.self } } -// Store manager to retain its reference -private var managerStore = SynchronizedDictionary() +fileprivate class AlamofireRequestBuilderConfiguration: @unchecked Sendable { + private init() {} + static let shared = AlamofireRequestBuilderConfiguration() + + // Store manager to retain its reference + let managerStore = SynchronizedDictionary() +} -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class AlamofireRequestBuilder: RequestBuilder { - required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String: Any]?, headers: [String: String] = [:], requiresAuthentication: Bool) { - super.init(method: method, URLString: URLString, parameters: parameters, headers: headers, requiresAuthentication: requiresAuthentication) +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class AlamofireRequestBuilder: RequestBuilder, @unchecked Sendable { + required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String: any Sendable]?, headers: [String: String] = [:], requiresAuthentication: Bool, apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared) { + super.init(method: method, URLString: URLString, parameters: parameters, headers: headers, requiresAuthentication: requiresAuthentication, apiConfiguration: apiConfiguration) } /** May be overridden by a subclass if you want to control the session configuration. */ - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func createAlamofireSession(interceptor: RequestInterceptor? = nil) -> Alamofire.Session { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func createAlamofireSession() -> Alamofire.Session { let configuration = URLSessionConfiguration.default configuration.httpAdditionalHeaders = buildHeaders() return Alamofire.Session(configuration: configuration, - interceptor: interceptor) + interceptor: apiConfiguration.interceptor) } /** @@ -79,11 +86,11 @@ private var managerStore = SynchronizedDictionary() } @discardableResult - override {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute(_ apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, _ completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) -> RequestTask { + override {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute(completion: @Sendable @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) -> RequestTask { let managerId = UUID().uuidString // Create a new manager for each request to customize its request header let manager = createAlamofireSession() - managerStore[managerId] = manager + AlamofireRequestBuilderConfiguration.shared.managerStore[managerId] = manager let xMethod = Alamofire.HTTPMethod(rawValue: method) @@ -133,7 +140,7 @@ private var managerStore = SynchronizedDictionary() requestTask.set(request: upload) - self.processRequest(request: upload, managerId, apiResponseQueue, completion) + self.processRequest(request: upload, managerId: managerId, completion: completion) } else if contentType.hasPrefix("application/x-www-form-urlencoded") { encoding = URLEncoding(destination: .httpBody) } else { @@ -149,28 +156,28 @@ private var managerStore = SynchronizedDictionary() if let onProgressReady = self.onProgressReady { onProgressReady(request.uploadProgress) } - processRequest(request: request, managerId, apiResponseQueue, completion) + processRequest(request: request, managerId: managerId, completion: completion) requestTask.set(request: request) } return requestTask } - fileprivate func processRequest(request: DataRequest, _ managerId: String, _ apiResponseQueue: DispatchQueue, _ completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { + fileprivate func processRequest(request: DataRequest, managerId: String, completion: @Sendable @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { if let credential = self.credential { request.authenticate(with: credential) } - let cleanupRequest = { - managerStore[managerId] = nil + let cleanupRequest = { @Sendable in + AlamofireRequestBuilderConfiguration.shared.managerStore[managerId] = nil } - let validatedRequest = request.validate(statusCode: Configuration.successfulStatusCodeRange) + let validatedRequest = request.validate(statusCode: apiConfiguration.successfulStatusCodeRange) switch T.self { case is Void.Type: - validatedRequest.response(queue: apiResponseQueue, - responseSerializer: Configuration.dataResponseSerializer, + validatedRequest.response(queue: apiConfiguration.apiResponseQueue, + responseSerializer: apiConfiguration.dataResponseSerializer, completionHandler: { voidResponse in cleanupRequest() @@ -248,23 +255,23 @@ private var managerStore = SynchronizedDictionary() } -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class AlamofireDecodableRequestBuilder: AlamofireRequestBuilder { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class AlamofireDecodableRequestBuilder: AlamofireRequestBuilder, @unchecked Sendable { - override fileprivate func processRequest(request: DataRequest, _ managerId: String, _ apiResponseQueue: DispatchQueue, _ completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { + override fileprivate func processRequest(request: DataRequest, managerId: String, completion: @Sendable @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { if let credential = self.credential { request.authenticate(with: credential) } - let cleanupRequest = { - managerStore[managerId] = nil + let cleanupRequest = { @Sendable in + AlamofireRequestBuilderConfiguration.shared.managerStore[managerId] = nil } - let validatedRequest = request.validate(statusCode: Configuration.successfulStatusCodeRange) + let validatedRequest = request.validate(statusCode: apiConfiguration.successfulStatusCodeRange) switch T.self { case is String.Type: - validatedRequest.response(queue: apiResponseQueue, - responseSerializer: Configuration.stringResponseSerializer, + validatedRequest.response(queue: apiConfiguration.apiResponseQueue, + responseSerializer: apiConfiguration.stringResponseSerializer, completionHandler: { stringResponse in cleanupRequest() @@ -277,8 +284,8 @@ private var managerStore = SynchronizedDictionary() }) case is URL.Type: - validatedRequest.response(queue: apiResponseQueue, - responseSerializer: Configuration.dataResponseSerializer, + validatedRequest.response(queue: apiConfiguration.apiResponseQueue, + responseSerializer: apiConfiguration.dataResponseSerializer, completionHandler: { dataResponse in cleanupRequest() @@ -325,8 +332,8 @@ private var managerStore = SynchronizedDictionary() return }) case is Void.Type: - validatedRequest.response(queue: apiResponseQueue, - responseSerializer: Configuration.dataResponseSerializer, + validatedRequest.response(queue: apiConfiguration.apiResponseQueue, + responseSerializer: apiConfiguration.dataResponseSerializer, completionHandler: { voidResponse in cleanupRequest() @@ -339,8 +346,8 @@ private var managerStore = SynchronizedDictionary() }) case is Data.Type: - validatedRequest.response(queue: apiResponseQueue, - responseSerializer: Configuration.dataResponseSerializer, + validatedRequest.response(queue: apiConfiguration.apiResponseQueue, + responseSerializer: apiConfiguration.dataResponseSerializer, completionHandler: { dataResponse in cleanupRequest() @@ -353,8 +360,8 @@ private var managerStore = SynchronizedDictionary() }) default: - validatedRequest.response(queue: apiResponseQueue, - responseSerializer: Configuration.dataResponseSerializer, + validatedRequest.response(queue: apiConfiguration.apiResponseQueue, + responseSerializer: apiConfiguration.dataResponseSerializer, completionHandler: { dataResponse in cleanupRequest() @@ -368,22 +375,22 @@ private var managerStore = SynchronizedDictionary() return } - guard let data = dataResponse.data, !data.isEmpty else { - if T.self is ExpressibleByNilLiteral.Type { - completion(.success(Response(response: httpResponse, body: Optional.none as! T, bodyData: dataResponse.data))) + guard let unwrappedData = dataResponse.data, !unwrappedData.isEmpty else { + if let expressibleByNilLiteralType = T.self as? ExpressibleByNilLiteral.Type { + completion(.success(Response(response: httpResponse, body: expressibleByNilLiteralType.init(nilLiteral: ()) as! T, bodyData: dataResponse.data))) } else { completion(.failure(ErrorResponse.error(httpResponse.statusCode, nil, httpResponse, DecodableRequestBuilderError.emptyDataResponse))) } return } - let decodeResult = CodableHelper.decode(T.self, from: data) + let decodeResult = self.apiConfiguration.codableHelper.decode(T.self, from: unwrappedData) switch decodeResult { case let .success(decodableObj): - completion(.success(Response(response: httpResponse, body: decodableObj, bodyData: data))) + completion(.success(Response(response: httpResponse, body: decodableObj, bodyData: unwrappedData))) case let .failure(error): - completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, httpResponse, error))) + completion(.failure(ErrorResponse.error(httpResponse.statusCode, unwrappedData, httpResponse, error))) } }) @@ -408,6 +415,6 @@ extension JSONDataEncoding: ParameterEncoding { public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { let urlRequest = try urlRequest.asURLRequest() - return encode(urlRequest, with: parameters) + return encode(request: urlRequest, with: parameters) } } diff --git a/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache b/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache index 0bdb665c..66416042 100644 --- a/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache +++ b/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache @@ -28,54 +28,54 @@ import UniformTypeIdentifiers } // Protocol allowing implementations to alter what is returned or to test their implementations. -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol URLSessionProtocol { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol URLSessionProtocol: Sendable { // Task which performs the network fetch. Expected to be from URLSession.dataTask(with:completionHandler:) such that a network request // is sent off when `.resume()` is called. - func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTaskProtocol + func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol } extension URLSession: URLSessionProtocol { // Passthrough to URLSession.dataTask(with:completionHandler) since URLSessionDataTask conforms to URLSessionDataTaskProtocol and fetches the network data. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> any URLSessionDataTaskProtocol { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func dataTaskFromProtocol(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, (any Error)?) -> Void) -> URLSessionDataTaskProtocol { return dataTask(with: request, completionHandler: completionHandler) } } extension URLSessionDataTask: URLSessionDataTaskProtocol {} -class URLSessionRequestBuilderFactory: RequestBuilderFactory { - func getNonDecodableBuilder() -> RequestBuilder.Type { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} final class URLSessionRequestBuilderFactory: RequestBuilderFactory, Sendable { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init() {} + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func getNonDecodableBuilder() -> RequestBuilder.Type { return URLSessionRequestBuilder.self } - func getBuilder() -> RequestBuilder.Type { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func getBuilder() -> RequestBuilder.Type { return URLSessionDecodableRequestBuilder.self } } -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} typealias {{projectName}}APIChallengeHandler = ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?)) +fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable { + private init() { + defaultURLSession = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil) + } -// Store the URLSession's delegate to retain its reference -private let sessionDelegate = SessionDelegate() + static let shared = URLSessionRequestBuilderConfiguration() -// Store the URLSession to retain its reference -private let defaultURLSession = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil) + // Store the URLSession's delegate to retain its reference + let sessionDelegate = SessionDelegate() -// Store current taskDidReceiveChallenge for every URLSessionTask -private var challengeHandlerStore = SynchronizedDictionary() + // Store the URLSession to retain its reference + let defaultURLSession: URLSession -// Store current URLCredential for every URLSessionTask -private var credentialStore = SynchronizedDictionary() + // Store current URLCredential for every URLSessionTask + let credentialStore = SynchronizedDictionary() +} -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class URLSessionRequestBuilder: RequestBuilder { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class URLSessionRequestBuilder: RequestBuilder, @unchecked Sendable { - /** - May be assigned if you want to control the authentication challenges. - */ - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var taskDidReceiveChallenge: {{projectName}}APIChallengeHandler? - - required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String: Any]?, headers: [String: String] = [:], requiresAuthentication: Bool) { - super.init(method: method, URLString: URLString, parameters: parameters, headers: headers, requiresAuthentication: requiresAuthentication) + required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String: any Sendable]?, headers: [String: String] = [:], requiresAuthentication: Bool, apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared) { + super.init(method: method, URLString: URLString, parameters: parameters, headers: headers, requiresAuthentication: requiresAuthentication, apiConfiguration: apiConfiguration) } /** @@ -83,7 +83,7 @@ private var credentialStore = SynchronizedDictionary() configuration. */ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func createURLSession() -> URLSessionProtocol { - return defaultURLSession + return URLSessionRequestBuilderConfiguration.shared.defaultURLSession } /** @@ -115,13 +115,13 @@ private var credentialStore = SynchronizedDictionary() originalRequest.setValue(value, forHTTPHeaderField: key) } - let modifiedRequest = try encoding.encode(originalRequest, with: parameters) + let modifiedRequest = try encoding.encode(request: originalRequest, with: parameters) return modifiedRequest } @discardableResult - override {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute(_ apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, _ completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) -> RequestTask { + override {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute(completion: @Sendable @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) -> RequestTask { let urlSession = createURLSession() guard let xMethod = HTTPMethod(rawValue: method) else { @@ -143,7 +143,7 @@ private var credentialStore = SynchronizedDictionary() encoding = FormDataEncoding(contentTypeForFormPart: contentTypeForFormPart(fileURL:)) } else if contentType.hasPrefix("application/x-www-form-urlencoded") { encoding = FormURLEncoding() - } else if contentType.hasPrefix("application/octet-stream"){ + } else if contentType.hasPrefix("application/octet-stream") || contentType.hasPrefix("image/") { encoding = OctetStreamEncoding() } else { fatalError("Unsupported Media Type - \(contentType)") @@ -153,32 +153,83 @@ private var credentialStore = SynchronizedDictionary() do { let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers) - var taskIdentifier: Int? - let cleanupRequest = { - if let taskIdentifier = taskIdentifier { - challengeHandlerStore[taskIdentifier] = nil - credentialStore[taskIdentifier] = nil - } - } - - let dataTask = urlSession.dataTaskFromProtocol(with: request) { data, response, error in - apiResponseQueue.async { - self.processRequestResponse(urlRequest: request, data: data, response: response, error: error, completion: completion) - cleanupRequest() - } - } + apiConfiguration.interceptor.intercept(urlRequest: request, urlSession: urlSession, requestBuilder: self) { result in + + switch result { + case .success(let modifiedRequest): + let dataTask = urlSession.dataTaskFromProtocol(with: modifiedRequest) { data, response, error in + self.cleanupRequest() + + self.apiConfiguration.interceptor.didReceiveResponse(urlRequest: modifiedRequest, urlSession: urlSession, requestBuilder: self, data: data, response: response, error: error) + + if let error = error { + self.retryRequest( + urlRequest: modifiedRequest, + urlSession: urlSession, + statusCode: -1, + data: data, + response: response, + error: error, + completion: completion + ) + return + } + + guard let httpResponse = response as? HTTPURLResponse else { + self.retryRequest( + urlRequest: modifiedRequest, + urlSession: urlSession, + statusCode: -2, + data: data, + response: response, + error: DecodableRequestBuilderError.nilHTTPResponse, + completion: completion + ) + return + } + + guard self.apiConfiguration.successfulStatusCodeRange.contains(httpResponse.statusCode) else { + self.retryRequest( + urlRequest: modifiedRequest, + urlSession: urlSession, + statusCode: httpResponse.statusCode, + data: data, + response: httpResponse, + error: DecodableRequestBuilderError.unsuccessfulHTTPStatusCode, + completion: completion + ) + return + } + + self.processRequestResponse(urlRequest: modifiedRequest, urlSession: urlSession, data: data, httpResponse: httpResponse, error: error, completion: completion) + } - onProgressReady?(dataTask.progress) + self.onProgressReady?(dataTask.progress) - taskIdentifier = dataTask.taskIdentifier - challengeHandlerStore[dataTask.taskIdentifier] = taskDidReceiveChallenge - credentialStore[dataTask.taskIdentifier] = credential + URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential - dataTask.resume() + self.requestTask.set(task: dataTask) - requestTask.set(task: dataTask) + self.apiConfiguration.interceptor.willSendRequest(urlRequest: modifiedRequest, urlSession: urlSession, requestBuilder: self) + + dataTask.resume() + + case .failure(let error): + self.apiConfiguration.interceptor.didComplete(urlRequest: request, urlSession: urlSession, requestBuilder: self, data: nil, response: nil, result: .failure(error)) + self.apiConfiguration.apiResponseQueue.async { + completion(.failure(ErrorResponse.error(415, nil, nil, error))) + } + } + } } catch { - apiResponseQueue.async { + // Request creation failed - create a minimal request for error reporting + let failedURL = URL(string: URLString) ?? URL(string: "about:blank")! + var failedRequest = URLRequest(url: failedURL) + failedRequest.httpMethod = method + + self.apiConfiguration.interceptor.didComplete(urlRequest: failedRequest, urlSession: urlSession, requestBuilder: self, data: nil, response: nil, result: .failure(error)) + + self.apiConfiguration.apiResponseQueue.async { completion(.failure(ErrorResponse.error(415, nil, nil, error))) } } @@ -186,27 +237,34 @@ private var credentialStore = SynchronizedDictionary() return requestTask } - fileprivate func processRequestResponse(urlRequest: URLRequest, data: Data?, response: URLResponse?, error: Error?, completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { - - if let error = error { - completion(.failure(ErrorResponse.error(-1, data, response, error))) - return + private func cleanupRequest() { + if let task = requestTask.get() { + URLSessionRequestBuilderConfiguration.shared.credentialStore[task.taskIdentifier] = nil } + } - guard let httpResponse = response as? HTTPURLResponse else { - completion(.failure(ErrorResponse.error(-2, data, response, DecodableRequestBuilderError.nilHTTPResponse))) - return - } + private func retryRequest(urlRequest: URLRequest, urlSession: URLSessionProtocol, statusCode: Int, data: Data?, response: URLResponse?, error: Error, completion: @Sendable @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { + self.apiConfiguration.interceptor.retry(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: data, response: response, error: error) { retry in + switch retry { + case .retry: + self.execute(completion: completion) - guard httpResponse.isStatusCodeSuccessful else { - completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, response, DecodableRequestBuilderError.unsuccessfulHTTPStatusCode))) - return + case .dontRetry: + self.apiConfiguration.interceptor.didComplete(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: data, response: response, result: .failure(error)) + self.apiConfiguration.apiResponseQueue.async { + completion(.failure(ErrorResponse.error(statusCode, data, response, error))) + } + } } + } + + fileprivate func processRequestResponse(urlRequest: URLRequest, urlSession: URLSessionProtocol, data: Data?, httpResponse: HTTPURLResponse, error: Error?, completion: @Sendable @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { switch T.self { case is Void.Type: - - completion(.success(Response(response: httpResponse, body: () as! T, bodyData: data))) + let result = () as! T + apiConfiguration.interceptor.didComplete(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: data, response: httpResponse, result: .success(result)) + completion(.success(Response(response: httpResponse, body: result, bodyData: data))) default: fatalError("Unsupported Response Body Type - \(String(describing: T.self))") @@ -216,7 +274,7 @@ private var credentialStore = SynchronizedDictionary() {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func buildHeaders() -> [String: String] { var httpHeaders: [String: String] = [:] - for (key, value) in {{projectName}}API.customHeaders { + for (key, value) in apiConfiguration.customHeaders { httpHeaders[key] = value } for (key, value) in headers { @@ -278,34 +336,17 @@ private var credentialStore = SynchronizedDictionary() } -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class URLSessionDecodableRequestBuilder: URLSessionRequestBuilder { - override fileprivate func processRequestResponse(urlRequest: URLRequest, data: Data?, response: URLResponse?, error: Error?, completion: @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { - - if let error = error { - completion(.failure(ErrorResponse.error(-1, data, response, error))) - return - } - - guard let httpResponse = response as? HTTPURLResponse else { - completion(.failure(ErrorResponse.error(-2, data, response, DecodableRequestBuilderError.nilHTTPResponse))) - return - } - - guard httpResponse.isStatusCodeSuccessful else { - completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, response, DecodableRequestBuilderError.unsuccessfulHTTPStatusCode))) - return - } +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class URLSessionDecodableRequestBuilder: URLSessionRequestBuilder, @unchecked Sendable { + override fileprivate func processRequestResponse(urlRequest: URLRequest, urlSession: URLSessionProtocol, data: Data?, httpResponse: HTTPURLResponse, error: Error?, completion: @Sendable @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { switch T.self { case is String.Type: - let body = data.flatMap { String(data: $0, encoding: .utf8) } ?? "" - + apiConfiguration.interceptor.didComplete(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: data, response: httpResponse, result: .success(body as! T)) completion(.success(Response(response: httpResponse, body: body as! T, bodyData: data))) case is URL.Type: do { - guard error == nil else { throw DownloadException.responseFailed } @@ -332,63 +373,69 @@ private var credentialStore = SynchronizedDictionary() try fileManager.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil) try data.write(to: filePath, options: .atomic) + apiConfiguration.interceptor.didComplete(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: data, response: httpResponse, result: .success(filePath as! T)) completion(.success(Response(response: httpResponse, body: filePath as! T, bodyData: data))) } catch let requestParserError as DownloadException { - completion(.failure(ErrorResponse.error(400, data, response, requestParserError))) + apiConfiguration.interceptor.didComplete(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: data, response: httpResponse, result: .failure(requestParserError)) + completion(.failure(ErrorResponse.error(400, data, httpResponse, requestParserError))) } catch { - completion(.failure(ErrorResponse.error(400, data, response, error))) + apiConfiguration.interceptor.didComplete(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: data, response: httpResponse, result: .failure(error)) + completion(.failure(ErrorResponse.error(400, data, httpResponse, error))) } case is Void.Type: - - completion(.success(Response(response: httpResponse, body: () as! T, bodyData: data))) + let result = () as! T + apiConfiguration.interceptor.didComplete(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: data, response: httpResponse, result: .success(result)) + completion(.success(Response(response: httpResponse, body: result, bodyData: data))) case is Data.Type: - - completion(.success(Response(response: httpResponse, body: data as! T, bodyData: data))) + let result = data as! T + apiConfiguration.interceptor.didComplete(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: data, response: httpResponse, result: .success(result)) + completion(.success(Response(response: httpResponse, body: result, bodyData: data))) default: - guard let unwrappedData = data, !unwrappedData.isEmpty else { if let expressibleByNilLiteralType = T.self as? ExpressibleByNilLiteral.Type { - completion(.success(Response(response: httpResponse, body: expressibleByNilLiteralType.init(nilLiteral: ()) as! T, bodyData: data))) + let result = expressibleByNilLiteralType.init(nilLiteral: ()) as! T + apiConfiguration.interceptor.didComplete(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: data, response: httpResponse, result: .success(result)) + completion(.success(Response(response: httpResponse, body: result, bodyData: data))) } else { - completion(.failure(ErrorResponse.error(httpResponse.statusCode, nil, response, DecodableRequestBuilderError.emptyDataResponse))) + let emptyDataError = DecodableRequestBuilderError.emptyDataResponse + apiConfiguration.interceptor.didComplete(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: nil, response: httpResponse, result: .failure(emptyDataError)) + completion(.failure(ErrorResponse.error(httpResponse.statusCode, nil, httpResponse, emptyDataError))) } return } - let decodeResult = CodableHelper.decode(T.self, from: unwrappedData) + let decodeResult = apiConfiguration.codableHelper.decode(T.self, from: unwrappedData) switch decodeResult { case let .success(decodableObj): + apiConfiguration.interceptor.didComplete(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: unwrappedData, response: httpResponse, result: .success(decodableObj)) completion(.success(Response(response: httpResponse, body: decodableObj, bodyData: unwrappedData))) case let .failure(error): - completion(.failure(ErrorResponse.error(httpResponse.statusCode, unwrappedData, response, error))) + apiConfiguration.interceptor.didComplete(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: unwrappedData, response: httpResponse, result: .failure(error)) + completion(.failure(ErrorResponse.error(httpResponse.statusCode, unwrappedData, httpResponse, error))) } } } } -private final class SessionDelegate: NSObject, URLSessionTaskDelegate { - func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { +fileprivate final class SessionDelegate: NSObject, URLSessionTaskDelegate { + func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @Sendable @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling var credential: URLCredential? - if let taskDidReceiveChallenge = challengeHandlerStore[task.taskIdentifier] { - (disposition, credential) = taskDidReceiveChallenge(session, task, challenge) + if challenge.previousFailureCount > 0 { + disposition = .rejectProtectionSpace } else { - if challenge.previousFailureCount > 0 { - disposition = .rejectProtectionSpace - } else { - credential = credentialStore[task.taskIdentifier] ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace) + credential = URLSessionRequestBuilderConfiguration.shared.credentialStore[task.taskIdentifier] ?? session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace) - if credential != nil { - disposition = .useCredential - } + if credential != nil { + disposition = .useCredential } } @@ -409,13 +456,13 @@ private final class SessionDelegate: NSObject, URLSessionTaskDelegate { } {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol ParameterEncoding { - func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest + func encode(request: URLRequest, with parameters: [String: any Sendable]?) throws -> URLRequest } private class URLEncoding: ParameterEncoding { - func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest { + func encode(request: URLRequest, with parameters: [String: any Sendable]?) throws -> URLRequest { - var urlRequest = urlRequest + var urlRequest = request guard let parameters = parameters else { return urlRequest } @@ -436,13 +483,13 @@ private class FormDataEncoding: ParameterEncoding { let contentTypeForFormPart: (_ fileURL: URL) -> String? - init(contentTypeForFormPart: @escaping (_ fileURL: URL) -> String?) { + init(contentTypeForFormPart: @Sendable @escaping (_ fileURL: URL) -> String?) { self.contentTypeForFormPart = contentTypeForFormPart } - func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest { + func encode(request: URLRequest, with parameters: [String: any Sendable]?) throws -> URLRequest { - var urlRequest = urlRequest + var urlRequest = request guard let parameters = parameters, !parameters.isEmpty else { return urlRequest @@ -588,11 +635,13 @@ private class FormDataEncoding: ParameterEncoding { func mimeType(for url: URL) -> String { let pathExtension = url.pathExtension - if #available(iOS 15, macOS 11, *) { + if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) { #if canImport(UniformTypeIdentifiers) if let utType = UTType(filenameExtension: pathExtension) { return utType.preferredMIMEType ?? "application/octet-stream" } + #else + return "application/octet-stream" #endif } else { #if canImport(MobileCoreServices) @@ -603,23 +652,34 @@ private class FormDataEncoding: ParameterEncoding { #endif return "application/octet-stream" } - return "application/octet-stream" } } private class FormURLEncoding: ParameterEncoding { - func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest { + func encode(request: URLRequest, with parameters: [String: any Sendable]?) throws -> URLRequest { - var urlRequest = urlRequest + var urlRequest = request var requestBodyComponents = URLComponents() - requestBodyComponents.queryItems = APIHelper.mapValuesToQueryItems(parameters ?? [:]) + let queryItems = APIHelper.mapValuesToQueryItems(parameters ?? [:]) + + /// `httpBody` needs to be percent encoded + /// -> https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST + /// "application/x-www-form-urlencoded: [...] Non-alphanumeric characters in both keys and values are percent-encoded" + let percentEncodedQueryItems = queryItems?.compactMap { queryItem in + return URLQueryItem( + name: queryItem.name.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? queryItem.name, + value: queryItem.value?.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? queryItem.value) + } + requestBodyComponents.queryItems = percentEncodedQueryItems if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") } + /// We can't use `requestBodyComponents.percentEncodedQuery` since this does NOT percent encode the `+` sign + /// that is why we do the percent encoding manually for each key/value pair urlRequest.httpBody = requestBodyComponents.query?.data(using: .utf8) return urlRequest @@ -627,9 +687,9 @@ private class FormURLEncoding: ParameterEncoding { } private class OctetStreamEncoding: ParameterEncoding { - func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest { + func encode(request: URLRequest, with parameters: [String: any Sendable]?) throws -> URLRequest { - var urlRequest = urlRequest + var urlRequest = request guard let body = parameters?["body"] else { return urlRequest } @@ -671,3 +731,54 @@ private extension Optional where Wrapped == Data { } extension JSONDataEncoding: ParameterEncoding {} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum OpenAPIInterceptorRetry { + case retry + case dontRetry +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol OpenAPIInterceptor: Sendable { + // MARK: - Request Modification & Retry + + /// Called before the request is sent. Allows modifying the URLRequest (e.g., adding authentication headers). + func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder, completion: @Sendable @escaping (Result) -> Void) + + /// Called when a request fails. Allows the interceptor to decide whether to retry the request. + func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder, data: Data?, response: URLResponse?, error: Error, completion: @Sendable @escaping (OpenAPIInterceptorRetry) -> Void) + + // MARK: - Lifecycle Hooks + + /// Called right before the request is sent, after all modifications from `intercept()` have been applied. + /// Useful for logging the final request that will be sent. + func willSendRequest(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder) + + /// Called when the raw response is received, before any processing or decoding. + /// Useful for logging raw responses or performing custom validation. + func didReceiveResponse(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder, data: Data?, response: URLResponse?, error: Error?) + + /// Called after the request completes (either success or failure). + /// Useful for cleanup, analytics, or performance monitoring. + func didComplete(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder, data: Data?, response: URLResponse?, result: Result) +} + +// MARK: - Default Implementations (No-op) + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} extension OpenAPIInterceptor { + func willSendRequest(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder) {} + + func didReceiveResponse(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder, data: Data?, response: URLResponse?, error: Error?) {} + + func didComplete(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder, data: Data?, response: URLResponse?, result: Result) {} +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} final class DefaultOpenAPIInterceptor: OpenAPIInterceptor { + public init() {} + + public func intercept(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder, completion: @Sendable @escaping (Result) -> Void) { + completion(.success(urlRequest)) + } + + public func retry(urlRequest: URLRequest, urlSession: URLSessionProtocol, requestBuilder: RequestBuilder, data: Data?, response: URLResponse?, error: Error, completion: @Sendable @escaping (OpenAPIInterceptorRetry) -> Void) { + completion(.dontRetry) + } +} diff --git a/codegen/Templates/swift/model.mustache b/codegen/Templates/swift/model.mustache index b6a725d0..8f3127a0 100644 --- a/codegen/Templates/swift/model.mustache +++ b/codegen/Templates/swift/model.mustache @@ -5,10 +5,7 @@ // https://openapi-generator.tech // -import Foundation -#if canImport(AnyCodable) -import AnyCodable -#endif{{#useVapor}} +import Foundation{{#useVapor}} import Vapor{{/useVapor}} {{#swiftUseApiNamespace}} @@ -27,4 +24,6 @@ extension {{projectName}}API { {{> modelObject}}{{/isEnum}}{{/isArray}}{{/vendorExtensions.x-is-one-of-interface}}{{/model}}{{/models}} {{#swiftUseApiNamespace}} } -{{/swiftUseApiNamespace}} \ No newline at end of file +{{/swiftUseApiNamespace}}{{#models}}{{#model}}{{#vendorExtensions.x-swift-identifiable}} +extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: Identifiable {} +{{/vendorExtensions.x-swift-identifiable}}{{/model}}{{/models}} \ No newline at end of file diff --git a/codegen/Templates/swift/modelEnum.mustache b/codegen/Templates/swift/modelEnum.mustache index 060a14ce..6673dc2f 100644 --- a/codegen/Templates/swift/modelEnum.mustache +++ b/codegen/Templates/swift/modelEnum.mustache @@ -1,6 +1,9 @@ -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{classname}}: {{dataType}}, {{#useVapor}}Content, Hashable{{/useVapor}}{{^useVapor}}Codable{{^isString}}{{^isInteger}}{{^isFloat}}{{^isDouble}}, JSONEncodable{{/isDouble}}{{/isFloat}}{{/isInteger}}{{/isString}}{{/useVapor}}, CaseIterable{{#enumUnknownDefaultCase}}{{#isInteger}}, CaseIterableDefaultsLast{{/isInteger}}{{#isFloat}}, CaseIterableDefaultsLast{{/isFloat}}{{#isDouble}}, CaseIterableDefaultsLast{{/isDouble}}{{#isString}}, CaseIterableDefaultsLast{{/isString}}{{/enumUnknownDefaultCase}} { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{classname}}: {{dataType}}, Sendable, {{#useVapor}}Content, Hashable{{/useVapor}}{{^useVapor}}Codable{{^isString}}{{^isInteger}}{{^isFloat}}{{^isDouble}}{{#useParameterConvertible}}, ParameterConvertible{{/useParameterConvertible}}{{/isDouble}}{{/isFloat}}{{/isInteger}}{{/isString}}{{/useVapor}}, CaseIterable{{#enumUnknownDefaultCase}}{{#isInteger}}, CaseIterableDefaultsLast{{/isInteger}}{{#isFloat}}, CaseIterableDefaultsLast{{/isFloat}}{{#isDouble}}, CaseIterableDefaultsLast{{/isDouble}}{{#isString}}, CaseIterableDefaultsLast{{/isString}}{{/enumUnknownDefaultCase}} { {{#allowableValues}} {{#enumVars}} + {{#enumDescription}} + /// {{enumDescription}} + {{/enumDescription}} case {{{name}}} = {{{value}}} {{/enumVars}} {{/allowableValues}} diff --git a/codegen/Templates/swift/modelInlineEnumDeclaration.mustache b/codegen/Templates/swift/modelInlineEnumDeclaration.mustache index 27f1e51a..3c75296a 100644 --- a/codegen/Templates/swift/modelInlineEnumDeclaration.mustache +++ b/codegen/Templates/swift/modelInlineEnumDeclaration.mustache @@ -1,6 +1,9 @@ - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{enumName}}: {{^isContainer}}{{dataType}}{{/isContainer}}{{#isContainer}}String{{/isContainer}}, {{#useVapor}}Content, Hashable{{/useVapor}}{{^useVapor}}Codable{{^isContainer}}{{^isString}}{{^isInteger}}{{^isFloat}}{{^isDouble}}, JSONEncodable{{/isDouble}}{{/isFloat}}{{/isInteger}}{{/isString}}{{/isContainer}}{{/useVapor}}, CaseIterable{{#enumUnknownDefaultCase}}{{#isInteger}}, CaseIterableDefaultsLast{{/isInteger}}{{#isFloat}}, CaseIterableDefaultsLast{{/isFloat}}{{#isDouble}}, CaseIterableDefaultsLast{{/isDouble}}{{#isString}}, CaseIterableDefaultsLast{{/isString}}{{#isContainer}}, CaseIterableDefaultsLast{{/isContainer}}{{/enumUnknownDefaultCase}} { + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{enumName}}: {{^isContainer}}{{dataType}}{{/isContainer}}{{#isContainer}}String{{/isContainer}}, Sendable, {{#useVapor}}Content, Hashable{{/useVapor}}{{^useVapor}}Codable{{^isContainer}}{{^isString}}{{^isInteger}}{{^isFloat}}{{^isDouble}}{{#useParameterConvertible}}, ParameterConvertible{{/useParameterConvertible}}{{/isDouble}}{{/isFloat}}{{/isInteger}}{{/isString}}{{/isContainer}}{{/useVapor}}, CaseIterable{{#enumUnknownDefaultCase}}{{#isInteger}}, CaseIterableDefaultsLast{{/isInteger}}{{#isFloat}}, CaseIterableDefaultsLast{{/isFloat}}{{#isDouble}}, CaseIterableDefaultsLast{{/isDouble}}{{#isString}}, CaseIterableDefaultsLast{{/isString}}{{#isContainer}}, CaseIterableDefaultsLast{{/isContainer}}{{/enumUnknownDefaultCase}} { {{#allowableValues}} {{#enumVars}} + {{#enumDescription}} + /// {{enumDescription}} + {{/enumDescription}} case {{{name}}} = {{{value}}} {{/enumVars}} {{/allowableValues}} diff --git a/codegen/Templates/swift/modelObject.mustache b/codegen/Templates/swift/modelObject.mustache index 71025d94..4f6966b7 100644 --- a/codegen/Templates/swift/modelObject.mustache +++ b/codegen/Templates/swift/modelObject.mustache @@ -1,23 +1,24 @@ -{{^objcCompatible}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#useClasses}}final class{{/useClasses}}{{^useClasses}}struct{{/useClasses}} {{{classname}}}: {{#useVapor}}Content{{/useVapor}}{{^useVapor}}Codable{{#useJsonEncodable}}, JSONEncodable{{/useJsonEncodable}}{{/useVapor}}{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}} { -{{/objcCompatible}}{{#objcCompatible}}@objcMembers {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} class {{classname}}: NSObject, Codable{{#useJsonEncodable}}, JSONEncodable{{/useJsonEncodable}} { +{{^objcCompatible}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#useClasses}}final class{{/useClasses}}{{^useClasses}}struct{{/useClasses}} {{{classname}}}: {{^useClasses}}Sendable, {{/useClasses}}{{#useClasses}}@unchecked Sendable, {{/useClasses}}{{#useVapor}}Content{{/useVapor}}{{^useVapor}}Codable{{/useVapor}}{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}} { +{{/objcCompatible}}{{#objcCompatible}}@objcMembers {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} final class {{classname}}: NSObject, Codable, @unchecked Sendable { {{/objcCompatible}} {{#allVars}} {{#isEnum}} {{> modelInlineEnumDeclaration}} + {{/isEnum}} {{/allVars}} {{#allVars}} {{#validatable}} {{#hasValidation}} {{#isString}} - static let {{{name}}}Rule = StringRule(minLength: {{#minLength}}{{{.}}}{{/minLength}}{{^minLength}}nil{{/minLength}}, maxLength: {{#maxLength}}{{{.}}}{{/maxLength}}{{^maxLength}}nil{{/maxLength}}, pattern: {{#pattern}}"{{{.}}}"{{/pattern}}{{^pattern}}nil{{/pattern}}) + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static let {{{name}}}Rule = StringRule(minLength: {{#minLength}}{{{.}}}{{/minLength}}{{^minLength}}nil{{/minLength}}, maxLength: {{#maxLength}}{{{.}}}{{/maxLength}}{{^maxLength}}nil{{/maxLength}}, pattern: {{#pattern}}"{{{.}}}"{{/pattern}}{{^pattern}}nil{{/pattern}}) {{/isString}} {{#isNumeric}} - static let {{{name}}}Rule = NumericRule<{{{dataType}}}>(minimum: {{#minimum}}{{{.}}}{{/minimum}}{{^minimum}}nil{{/minimum}}, exclusiveMinimum: {{#exclusiveMinimum}}true{{/exclusiveMinimum}}{{^exclusiveMinimum}}false{{/exclusiveMinimum}}, maximum: {{#maximum}}{{{.}}}{{/maximum}}{{^maximum}}nil{{/maximum}}, exclusiveMaximum: {{#exclusiveMaximum}}true{{/exclusiveMaximum}}{{^exclusiveMaximum}}false{{/exclusiveMaximum}}, multipleOf: {{#multipleOf}}{{{.}}}{{/multipleOf}}{{^multipleOf}}nil{{/multipleOf}}) + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static let {{{name}}}Rule = NumericRule<{{{dataType}}}>(minimum: {{#minimum}}{{{.}}}{{/minimum}}{{^minimum}}nil{{/minimum}}, exclusiveMinimum: {{#exclusiveMinimum}}true{{/exclusiveMinimum}}{{^exclusiveMinimum}}false{{/exclusiveMinimum}}, maximum: {{#maximum}}{{{.}}}{{/maximum}}{{^maximum}}nil{{/maximum}}, exclusiveMaximum: {{#exclusiveMaximum}}true{{/exclusiveMaximum}}{{^exclusiveMaximum}}false{{/exclusiveMaximum}}, multipleOf: {{#multipleOf}}{{{.}}}{{/multipleOf}}{{^multipleOf}}nil{{/multipleOf}}) {{/isNumeric}} {{#isArray}} - static let {{{name}}}Rule = ArrayRule(minItems: {{#minItems}}{{{.}}}{{/minItems}}{{^minItems}}nil{{/minItems}}, maxItems: {{#maxItems}}{{{.}}}{{/maxItems}}{{^maxItems}}nil{{/maxItems}}, uniqueItems: {{#uniqueItems}}true{{/uniqueItems}}{{^uniqueItems}}false{{/uniqueItems}}) + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static let {{{name}}}Rule = ArrayRule(minItems: {{#minItems}}{{{.}}}{{/minItems}}{{^minItems}}nil{{/minItems}}, maxItems: {{#maxItems}}{{{.}}}{{/maxItems}}{{^maxItems}}nil{{/maxItems}}, uniqueItems: {{#uniqueItems}}true{{/uniqueItems}}{{^uniqueItems}}false{{/uniqueItems}}) {{/isArray}} {{/hasValidation}} {{/validatable}} diff --git a/codegen/Templates/swift/modelOneOf.mustache b/codegen/Templates/swift/modelOneOf.mustache index 629de661..b2efa05e 100644 --- a/codegen/Templates/swift/modelOneOf.mustache +++ b/codegen/Templates/swift/modelOneOf.mustache @@ -1,20 +1,54 @@ -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{classname}}: {{#useVapor}}Content{{/useVapor}}{{^useVapor}}Codable, JSONEncodable{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}}{{/useVapor}} { +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{classname}}: {{^useClasses}}Sendable, {{/useClasses}}{{#useClasses}}{{#readonlyProperties}}Sendable, {{/readonlyProperties}}{{/useClasses}}{{#useVapor}}Content{{/useVapor}}{{^useVapor}}Codable{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}}{{/useVapor}} { {{#oneOf}} - case type{{.}}({{.}}) + case type{{#transformArrayType}}{{.}}{{/transformArrayType}}({{.}}) {{/oneOf}} + {{#oneOfUnknownDefaultCase}} + case unknownDefaultOpenApi + {{/oneOfUnknownDefaultCase}} public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { {{#oneOf}} - case .type{{.}}(let value): + case .type{{#transformArrayType}}{{.}}{{/transformArrayType}}(let value): try container.encode(value) {{/oneOf}} + {{#oneOfUnknownDefaultCase}} + case .unknownDefaultOpenApi: + try container.encodeNil() + {{/oneOfUnknownDefaultCase}} } } +{{#discriminator}} + + private enum DiscriminatorCodingKey: String, CodingKey { + case {{discriminator.propertyName}} = "{{discriminator.propertyBaseName}}" + } +{{/discriminator}} public init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() +{{#discriminator}} let keyedContainer = try decoder.container(keyedBy: DiscriminatorCodingKey.self) + let discriminatorValue = try keyedContainer.decode(String.self, forKey: .{{discriminator.propertyName}}) + + switch discriminatorValue { + {{#discriminator.mappedModels}} + case "{{mappingName}}": + self = .type{{#transformArrayType}}{{modelName}}{{/transformArrayType}}(try {{modelName}}(from: decoder)) + {{/discriminator.mappedModels}} + default: + {{#oneOfUnknownDefaultCase}} + self = .unknownDefaultOpenApi + {{/oneOfUnknownDefaultCase}} + {{^oneOfUnknownDefaultCase}} + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Unknown discriminator value '\(discriminatorValue)' for {{classname}}" + ) + ) + {{/oneOfUnknownDefaultCase}} + } +{{/discriminator}}{{^discriminator}} let container = try decoder.singleValueContainer() {{#oneOf}} {{#-first}} if let value = try? container.decode({{.}}.self) { @@ -22,10 +56,15 @@ {{^-first}} } else if let value = try? container.decode({{.}}.self) { {{/-first}} - self = .type{{.}}(value) + self = .type{{#transformArrayType}}{{.}}{{/transformArrayType}}(value) {{/oneOf}} } else { + {{#oneOfUnknownDefaultCase}} + self = .unknownDefaultOpenApi + {{/oneOfUnknownDefaultCase}} + {{^oneOfUnknownDefaultCase}} throw DecodingError.typeMismatch(Self.Type.self, .init(codingPath: decoder.codingPath, debugDescription: "Unable to decode instance of {{classname}}")) + {{/oneOfUnknownDefaultCase}} } - } +{{/discriminator}} } } diff --git a/codegen/config-swift.json b/codegen/config-swift.json index 43c738be..4bebcc30 100644 --- a/codegen/config-swift.json +++ b/codegen/config-swift.json @@ -8,9 +8,11 @@ } }, "hideGenerationTimestamp": true, + "library": "urlsession", "mapFileBinaryToData": true, "packageVersion": "26.4.0", "projectName": "AsposeBarcodeCloud", + "responseAs": "AsyncAwait,ObjcBlock", "swiftPackagePath": "Sources/AsposeBarcodeCloud", "useClasses": true, "useSPMFileStructure": true diff --git a/codegen/generate-swift.bash b/codegen/generate-swift.bash index 98ba5378..bc3da6fb 100755 --- a/codegen/generate-swift.bash +++ b/codegen/generate-swift.bash @@ -10,16 +10,17 @@ then rm -rf "$tempDir" fi -# Templates src https://github.com/OpenAPITools/openapi-generator/tree/v7.8.0/modules/openapi-generator/src/main/resources/swift5 -# java -jar Tools/openapi-generator-cli.jar config-help -g swift5 ; exit 1 -# java -DdebugModels -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift5 -t Templates/swift -o "$tempDir" -c config-swift.json > debugModels.swift.json ; exit -# java -DdebugOperations -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift5 -t Templates/swift -o "$tempDir" -c config-swift.json > debugOperations.swift.json ; exit -java -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift5 -t Templates/swift -o "$tempDir" -c config-swift.json +# Templates src https://github.com/OpenAPITools/openapi-generator/tree/v7.22.0/modules/openapi-generator/src/main/resources/swift6 +# java -jar Tools/openapi-generator-cli.jar config-help -g swift6 ; exit 1 +# java -DdebugModels -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift6 -t Templates/swift -o "$tempDir" -c config-swift.json > debugModels.swift.json ; exit +# java -DdebugOperations -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift6 -t Templates/swift -o "$tempDir" -c config-swift.json > debugOperations.swift.json ; exit +java -jar Tools/openapi-generator-cli.jar generate -i "$specSource" -g swift6 -t Templates/swift -o "$tempDir" -c config-swift.json mkdir -p "$targetDir/Sources" rm -rf "$targetDir/Sources/AsposeBarcodeCloud" mv "$tempDir/Sources/AsposeBarcodeCloud" "$targetDir/Sources/AsposeBarcodeCloud" +mv "$tempDir/Package.swift" "$targetDir/Package.swift" mv "$tempDir/README.md" "$targetDir/README.md" rm -rf "$targetDir/docs" diff --git a/submodules/swift b/submodules/swift index 9f8cd6e9..b3e74766 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 9f8cd6e9faa8f89175c2d634b5484036c20d3636 +Subproject commit b3e74766baf4fcabef53162baa95c7415a4f2e49 From 31718351008543147e653d41fbce521c6e0f6799 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Tue, 19 May 2026 18:15:37 +0500 Subject: [PATCH 24/40] Stabilize Swift codegen checks --- scripts/check-urls.py | 1 + submodules/swift | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/check-urls.py b/scripts/check-urls.py index 100d4790..7ef02b74 100644 --- a/scripts/check-urls.py +++ b/scripts/check-urls.py @@ -63,6 +63,7 @@ ".mvnrepository.com", ".nodejs.org", ".npmjs.com", + ".openapi-generator.tech", ".nuget.org", ".opensource.org", ".packagist.org", diff --git a/submodules/swift b/submodules/swift index b3e74766..1b638f32 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit b3e74766baf4fcabef53162baa95c7415a4f2e49 +Subproject commit 1b638f32125c897c5e3324e7cc72556a238b3223 From c006d0b96cc30651a1ddfdbfb9bcf052625879c8 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Tue, 19 May 2026 18:21:16 +0500 Subject: [PATCH 25/40] Fix Swift URLSession MIME fallback --- .../libraries/urlsession/URLSessionImplementations.mustache | 1 + submodules/swift | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache b/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache index 66416042..41f72423 100644 --- a/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache +++ b/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache @@ -640,6 +640,7 @@ private class FormDataEncoding: ParameterEncoding { if let utType = UTType(filenameExtension: pathExtension) { return utType.preferredMIMEType ?? "application/octet-stream" } + return "application/octet-stream" #else return "application/octet-stream" #endif diff --git a/submodules/swift b/submodules/swift index 1b638f32..53b2a53e 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 1b638f32125c897c5e3324e7cc72556a238b3223 +Subproject commit 53b2a53eaa343a9bbb539a8bb01b832bb51c18b6 From f929eb8475c8e6a13c2517b8bbad5684441ce099 Mon Sep 17 00:00:00 2001 From: Denis Averin Date: Wed, 20 May 2026 18:36:25 +0700 Subject: [PATCH 26/40] Replace fatalError with throwing errors in Swift URLSession runtime Five sites in URLSessionRequestBuilder previously crashed the host process on server-driven or otherwise unexpected inputs. Surface them through completion handlers or throws so consumers can handle them. Updates the URLSession mustache template and bumps the Swift submodule pointer to the regenerated SDK. --- .../URLSessionImplementations.mustache | 42 ++++++++++++++++--- submodules/swift | 2 +- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache b/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache index 41f72423..d259624e 100644 --- a/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache +++ b/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache @@ -43,6 +43,29 @@ extension URLSession: URLSessionProtocol { extension URLSessionDataTask: URLSessionDataTaskProtocol {} +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum URLSessionRequestBuilderError: Error, CustomStringConvertible, Sendable { + case unsupportedHTTPMethod(String) + case unsupportedMediaType(String) + case unsupportedResponseBodyType(String) + case unprocessableMultipartValue(key: String, valueDescription: String) + case unprocessableBody(description: String) + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var description: String { + switch self { + case let .unsupportedHTTPMethod(method): + return "Unsupported HTTP method: \(method)" + case let .unsupportedMediaType(contentType): + return "Unsupported media type: \(contentType)" + case let .unsupportedResponseBodyType(typeName): + return "Unsupported response body type: \(typeName)" + case let .unprocessableMultipartValue(key, valueDescription): + return "Unprocessable multipart value for key \(key): \(valueDescription)" + case let .unprocessableBody(description): + return "Unprocessable request body: \(description)" + } + } +} + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} final class URLSessionRequestBuilderFactory: RequestBuilderFactory, Sendable { {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init() {} @@ -125,7 +148,10 @@ fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable { let urlSession = createURLSession() guard let xMethod = HTTPMethod(rawValue: method) else { - fatalError("Unsupported Http method - \(method)") + apiConfiguration.apiResponseQueue.async { + completion(.failure(ErrorResponse.error(-3, nil, nil, URLSessionRequestBuilderError.unsupportedHTTPMethod(self.method)))) + } + return requestTask } let encoding: ParameterEncoding @@ -146,7 +172,10 @@ fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable { } else if contentType.hasPrefix("application/octet-stream") || contentType.hasPrefix("image/") { encoding = OctetStreamEncoding() } else { - fatalError("Unsupported Media Type - \(contentType)") + apiConfiguration.apiResponseQueue.async { + completion(.failure(ErrorResponse.error(-4, nil, nil, URLSessionRequestBuilderError.unsupportedMediaType(contentType)))) + } + return requestTask } } @@ -267,7 +296,10 @@ fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable { completion(.success(Response(response: httpResponse, body: result, bodyData: data))) default: - fatalError("Unsupported Response Body Type - \(String(describing: T.self))") + let typeName = String(describing: T.self) + let error = URLSessionRequestBuilderError.unsupportedResponseBodyType(typeName) + apiConfiguration.interceptor.didComplete(urlRequest: urlRequest, urlSession: urlSession, requestBuilder: self, data: data, response: httpResponse, result: .failure(ErrorResponse.error(-5, data, httpResponse, error))) + completion(.failure(ErrorResponse.error(-5, data, httpResponse, error))) } } @@ -554,7 +586,7 @@ private class FormDataEncoding: ParameterEncoding { } default: - fatalError("Unprocessable value \(value) with key \(key)") + throw URLSessionRequestBuilderError.unprocessableMultipartValue(key: key, valueDescription: String(describing: value)) } } } @@ -704,7 +736,7 @@ private class OctetStreamEncoding: ParameterEncoding { case let data as Data: urlRequest.httpBody = data default: - fatalError("Unprocessable body \(body)") + throw URLSessionRequestBuilderError.unprocessableBody(description: String(describing: body)) } return urlRequest diff --git a/submodules/swift b/submodules/swift index 53b2a53e..5aa1cdc8 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 53b2a53eaa343a9bbb539a8bb01b832bb51c18b6 +Subproject commit 5aa1cdc885e66a510cf67eec475d09a3ed84f1d4 From 78117464c49e7fc17335cedd8a9e75a229f9abd0 Mon Sep 17 00:00:00 2001 From: Denis Averin Date: Wed, 20 May 2026 18:42:34 +0700 Subject: [PATCH 27/40] Switch Swift AsposeBarcodeCloudClient to per-instance apiConfiguration Drops the AsposeBarcodeCloudAPI global namespace and the resetGlobalConfiguration helper. The client now owns its own AsposeBarcodeCloudAPIConfiguration; consumers pass client.apiConfiguration to each generated API method, matching the per-instance pattern used by every other SDK in this repo. Updates the AsposeBarcodeCloudClient and README mustache templates and bumps the Swift submodule pointer to the regenerated SDK (including example and test fixes). --- .../swift/AsposeBarcodeCloudClient.mustache | 40 +++++++++---------- codegen/Templates/swift/README.mustache | 14 ++++--- submodules/swift | 2 +- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache b/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache index 86399dd6..31dd2e94 100644 --- a/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache +++ b/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache @@ -29,18 +29,6 @@ public enum AsposeBarcodeCloudClientError: Error, CustomStringConvertible, @unch } } -public enum AsposeBarcodeCloudAPI { - public static var basePath: String { - get { AsposeBarcodeCloudAPIConfiguration.shared.basePath } - set { AsposeBarcodeCloudAPIConfiguration.shared.basePath = newValue } - } - - public static var customHeaders: [String: String] { - get { AsposeBarcodeCloudAPIConfiguration.shared.customHeaders } - set { AsposeBarcodeCloudAPIConfiguration.shared.customHeaders = newValue } - } -} - public final class AsposeBarcodeCloudConfiguration: @unchecked Sendable { public static let defaultHost = "https://api.aspose.cloud/v4.0" public static let defaultTokenURL = "https://id.aspose.cloud/connect/token" @@ -109,6 +97,7 @@ public typealias AsposeBarcodeCloudTokenFetcher = @Sendable ( public final class AsposeBarcodeCloudClient: @unchecked Sendable { public let configuration: AsposeBarcodeCloudConfiguration + public let apiConfiguration: AsposeBarcodeCloudAPIConfiguration private let tokenFetcher: AsposeBarcodeCloudTokenFetcher public init( @@ -117,6 +106,16 @@ public final class AsposeBarcodeCloudClient: @unchecked Sendable { ) { self.configuration = configuration self.tokenFetcher = tokenFetcher ?? AsposeBarcodeCloudClient.defaultTokenFetcher + self.apiConfiguration = AsposeBarcodeCloudAPIConfiguration( + basePath: configuration.host, + customHeaders: [ + "x-aspose-client": configuration.sdkName, + "x-aspose-client-version": configuration.sdkVersion, + ] + ) + if let accessToken = configuration.accessToken, !accessToken.isEmpty { + apiConfiguration.customHeaders["Authorization"] = "Bearer \(accessToken)" + } } public convenience init( @@ -144,15 +143,17 @@ public final class AsposeBarcodeCloudClient: @unchecked Sendable { } public func apply() { - AsposeBarcodeCloudAPI.basePath = configuration.host - AsposeBarcodeCloudAPI.customHeaders["x-aspose-client"] = configuration.sdkName - AsposeBarcodeCloudAPI.customHeaders["x-aspose-client-version"] = configuration.sdkVersion + apiConfiguration.basePath = configuration.host + var headers = apiConfiguration.customHeaders + headers["x-aspose-client"] = configuration.sdkName + headers["x-aspose-client-version"] = configuration.sdkVersion if let accessToken = configuration.accessToken, !accessToken.isEmpty { - AsposeBarcodeCloudAPI.customHeaders["Authorization"] = "Bearer \(accessToken)" + headers["Authorization"] = "Bearer \(accessToken)" } else { - AsposeBarcodeCloudAPI.customHeaders.removeValue(forKey: "Authorization") + headers.removeValue(forKey: "Authorization") } + apiConfiguration.customHeaders = headers } public func authorize(completion: @escaping @Sendable (Result) -> Void) { @@ -196,11 +197,6 @@ public final class AsposeBarcodeCloudClient: @unchecked Sendable { } } - public static func resetGlobalConfiguration() { - AsposeBarcodeCloudAPI.basePath = AsposeBarcodeCloudConfiguration.defaultHost - AsposeBarcodeCloudAPI.customHeaders.removeAll() - } - private struct TokenResponse: Decodable { let accessToken: String? diff --git a/codegen/Templates/swift/README.mustache b/codegen/Templates/swift/README.mustache index 368b6808..e48c9bbf 100644 --- a/codegen/Templates/swift/README.mustache +++ b/codegen/Templates/swift/README.mustache @@ -41,10 +41,9 @@ If you already have an access token, configure the SDK directly: ```swift let client = AsposeBarcodeCloudClient(accessToken: "your-access-token") -client.apply() ``` -`AsposeBarcodeCloudClient` sets `Authorization`, `x-aspose-client`, and `x-aspose-client-version` headers for generated requests. +`AsposeBarcodeCloudClient` owns a per-instance `apiConfiguration` and sets `Authorization`, `x-aspose-client`, and `x-aspose-client-version` headers on it. Pass `client.apiConfiguration` to each API call so the headers are applied. ### Generate @@ -52,7 +51,8 @@ client.apply() GenerateAPI.generate( barcodeType: .qr, data: "Aspose.BarCode Cloud", - imageFormat: .png + imageFormat: .png, + apiConfiguration: client.apiConfiguration ) { data, error in if let error = error { print(error) @@ -71,7 +71,8 @@ Use `scanBase64` when the SDK should detect barcode types automatically, or `rec let imageBase64 = generatedPngData.base64EncodedString() ScanAPI.scanBase64( - scanBase64Request: ScanBase64Request(fileBase64: imageBase64) + scanBase64Request: ScanBase64Request(fileBase64: imageBase64), + apiConfiguration: client.apiConfiguration ) { response, error in if let error = error { print(error) @@ -86,7 +87,10 @@ let request = RecognizeBase64Request( fileBase64: imageBase64 ) -RecognizeAPI.recognizeBase64(recognizeBase64Request: request) { response, error in +RecognizeAPI.recognizeBase64( + recognizeBase64Request: request, + apiConfiguration: client.apiConfiguration +) { response, error in if let error = error { print(error) return diff --git a/submodules/swift b/submodules/swift index 5aa1cdc8..4333187f 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 5aa1cdc885e66a510cf67eec475d09a3ed84f1d4 +Subproject commit 4333187f3c0fa056773699749a94708f5664230d From 8da7d8964b437cdc0b22d6988bcaf6a720a067e2 Mon Sep 17 00:00:00 2001 From: Denis Averin Date: Wed, 20 May 2026 18:52:01 +0700 Subject: [PATCH 28/40] Lazy OAuth via interceptor in Swift client, drop sync semaphore Adds BarcodeAuthInterceptor to the AsposeBarcodeCloudClient template: the client installs it on its per-instance apiConfiguration so the URLSession pipeline lazily fetches and injects the bearer token on first use, deduplicating concurrent token fetches. Drops apply() and the blocking authorize() throws (DispatchSemaphore) variant; adds an async authorize() async throws overload for explicit warm-up. Updates the AsposeBarcodeCloudClient and README mustache templates and bumps the Swift submodule pointer to the regenerated SDK. --- .../swift/AsposeBarcodeCloudClient.mustache | 188 +++++++++++++----- codegen/Templates/swift/README.mustache | 8 +- submodules/swift | 2 +- 3 files changed, 141 insertions(+), 57 deletions(-) diff --git a/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache b/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache index 31dd2e94..83f84126 100644 --- a/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache +++ b/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache @@ -98,24 +98,30 @@ public typealias AsposeBarcodeCloudTokenFetcher = @Sendable ( public final class AsposeBarcodeCloudClient: @unchecked Sendable { public let configuration: AsposeBarcodeCloudConfiguration public let apiConfiguration: AsposeBarcodeCloudAPIConfiguration - private let tokenFetcher: AsposeBarcodeCloudTokenFetcher + private let authInterceptor: BarcodeAuthInterceptor public init( configuration: AsposeBarcodeCloudConfiguration, tokenFetcher: AsposeBarcodeCloudTokenFetcher? = nil ) { self.configuration = configuration - self.tokenFetcher = tokenFetcher ?? AsposeBarcodeCloudClient.defaultTokenFetcher - self.apiConfiguration = AsposeBarcodeCloudAPIConfiguration( + let fetcher = tokenFetcher ?? AsposeBarcodeCloudClient.defaultTokenFetcher + + let apiConfig = AsposeBarcodeCloudAPIConfiguration( basePath: configuration.host, customHeaders: [ "x-aspose-client": configuration.sdkName, "x-aspose-client-version": configuration.sdkVersion, ] ) - if let accessToken = configuration.accessToken, !accessToken.isEmpty { - apiConfiguration.customHeaders["Authorization"] = "Bearer \(accessToken)" - } + let interceptor = BarcodeAuthInterceptor( + configuration: configuration, + tokenFetcher: fetcher + ) + apiConfig.interceptor = interceptor + + self.apiConfiguration = apiConfig + self.authInterceptor = interceptor } public convenience init( @@ -142,58 +148,21 @@ public final class AsposeBarcodeCloudClient: @unchecked Sendable { )) } - public func apply() { - apiConfiguration.basePath = configuration.host - var headers = apiConfiguration.customHeaders - headers["x-aspose-client"] = configuration.sdkName - headers["x-aspose-client-version"] = configuration.sdkVersion - - if let accessToken = configuration.accessToken, !accessToken.isEmpty { - headers["Authorization"] = "Bearer \(accessToken)" - } else { - headers.removeValue(forKey: "Authorization") - } - apiConfiguration.customHeaders = headers - } - public func authorize(completion: @escaping @Sendable (Result) -> Void) { - if let accessToken = configuration.accessToken, !accessToken.isEmpty { - apply() - completion(.success(accessToken)) - return - } - - tokenFetcher(configuration) { result in - switch result { - case let .success(accessToken): - self.configuration.accessToken = accessToken - self.apply() - completion(.success(accessToken)) - case let .failure(error): - completion(.failure(error)) - } - } + authInterceptor.ensureToken(completion: completion) } @discardableResult - public func authorize() throws -> String { - let semaphore = DispatchSemaphore(value: 0) - let tokenResult = OpenAPIMutex?>(nil) - - authorize { result in - tokenResult.withValue { $0 = result } - semaphore.signal() - } - - semaphore.wait() - - switch tokenResult.value { - case let .success(accessToken): - return accessToken - case let .failure(error): - throw error - case .none: - throw AsposeBarcodeCloudClientError.invalidTokenResponse + public func authorize() async throws -> String { + try await withCheckedThrowingContinuation { continuation in + authorize { result in + switch result { + case let .success(token): + continuation.resume(returning: token) + case let .failure(error): + continuation.resume(throwing: error) + } + } } } @@ -249,3 +218,114 @@ public final class AsposeBarcodeCloudClient: @unchecked Sendable { }.resume() } } + +final class BarcodeAuthInterceptor: OpenAPIInterceptor, @unchecked Sendable { + private let configuration: AsposeBarcodeCloudConfiguration + private let tokenFetcher: AsposeBarcodeCloudTokenFetcher + private let state: OpenAPIMutex + + private struct State { + var token: String? + var inFlightFetch: Bool + var pendingWaiters: [@Sendable (Result) -> Void] + } + + init( + configuration: AsposeBarcodeCloudConfiguration, + tokenFetcher: @escaping AsposeBarcodeCloudTokenFetcher + ) { + self.configuration = configuration + self.tokenFetcher = tokenFetcher + let initialToken = configuration.accessToken.flatMap { $0.isEmpty ? nil : $0 } + self.state = OpenAPIMutex(State( + token: initialToken, + inFlightFetch: false, + pendingWaiters: [] + )) + } + + func intercept( + urlRequest: URLRequest, + urlSession: URLSessionProtocol, + requestBuilder: RequestBuilder, + completion: @Sendable @escaping (Result) -> Void + ) { + guard requestBuilder.requiresAuthentication else { + completion(.success(urlRequest)) + return + } + + ensureToken { result in + switch result { + case let .success(token): + var modified = urlRequest + modified.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + completion(.success(modified)) + case let .failure(error): + completion(.failure(error)) + } + } + } + + func retry( + urlRequest: URLRequest, + urlSession: URLSessionProtocol, + requestBuilder: RequestBuilder, + data: Data?, + response: URLResponse?, + error: any Error, + completion: @Sendable @escaping (OpenAPIInterceptorRetry) -> Void + ) { + completion(.dontRetry) + } + + func ensureToken(completion: @escaping @Sendable (Result) -> Void) { + enum Action { + case immediate(Result) + case wait + case startFetch + } + + var action: Action = .wait + state.withValue { state in + if let token = state.token, !token.isEmpty { + action = .immediate(.success(token)) + return + } + state.pendingWaiters.append(completion) + if state.inFlightFetch { + action = .wait + } else { + state.inFlightFetch = true + action = .startFetch + } + } + + switch action { + case let .immediate(result): + completion(result) + case .wait: + break + case .startFetch: + tokenFetcher(configuration) { [weak self] result in + self?.deliverToken(result) + } + } + } + + private func deliverToken(_ result: Result) { + var waiters: [@Sendable (Result) -> Void] = [] + state.withValue { state in + state.inFlightFetch = false + if case let .success(token) = result { + state.token = token + self.configuration.accessToken = token + } + waiters = state.pendingWaiters + state.pendingWaiters.removeAll() + } + for waiter in waiters { + waiter(result) + } + } +} diff --git a/codegen/Templates/swift/README.mustache b/codegen/Templates/swift/README.mustache index e48c9bbf..7a8de7c3 100644 --- a/codegen/Templates/swift/README.mustache +++ b/codegen/Templates/swift/README.mustache @@ -33,8 +33,12 @@ let client = AsposeBarcodeCloudClient( clientId: "your-client-id", clientSecret: "your-client-secret" ) +``` + +The OAuth access token is fetched lazily on the first API call. To warm it up at app start, await the explicit method: -try client.authorize() +```swift +_ = try await client.authorize() ``` If you already have an access token, configure the SDK directly: @@ -43,7 +47,7 @@ If you already have an access token, configure the SDK directly: let client = AsposeBarcodeCloudClient(accessToken: "your-access-token") ``` -`AsposeBarcodeCloudClient` owns a per-instance `apiConfiguration` and sets `Authorization`, `x-aspose-client`, and `x-aspose-client-version` headers on it. Pass `client.apiConfiguration` to each API call so the headers are applied. +`AsposeBarcodeCloudClient` owns a per-instance `apiConfiguration` and sets `x-aspose-client` and `x-aspose-client-version` headers on it. The bundled `BarcodeAuthInterceptor` injects the `Authorization` header on each authenticated request. Pass `client.apiConfiguration` to each API call so the headers are applied. ### Generate diff --git a/submodules/swift b/submodules/swift index 4333187f..c6e6539a 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 4333187f3c0fa056773699749a94708f5664230d +Subproject commit c6e6539aafd911403b3e3ca108dc5903b5d859f6 From 5a635fad3c8f221c2f2c5ef6336edd1f253ddcdb Mon Sep 17 00:00:00 2001 From: Denis Averin Date: Thu, 21 May 2026 16:14:42 +0700 Subject: [PATCH 29/40] Drop stale openapi-generator metadata files --- codegen/generate-swift.bash | 5 ----- submodules/swift | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/codegen/generate-swift.bash b/codegen/generate-swift.bash index bc3da6fb..7b73a9a5 100755 --- a/codegen/generate-swift.bash +++ b/codegen/generate-swift.bash @@ -26,13 +26,8 @@ mv "$tempDir/README.md" "$targetDir/README.md" rm -rf "$targetDir/docs" mv "$tempDir/docs" "$targetDir/docs" -mv "$tempDir/.openapi-generator-ignore" "$targetDir/" mv "$tempDir/.swiftformat" "$targetDir/" -mkdir -p "$targetDir/.openapi-generator" -mv "$tempDir/.openapi-generator/VERSION" "$targetDir/.openapi-generator/" -mv "$tempDir/.openapi-generator/FILES" "$targetDir/.openapi-generator/" - cp ../LICENSE "$targetDir/" rm -rf "$tempDir" diff --git a/submodules/swift b/submodules/swift index c6e6539a..ea6ad99d 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit c6e6539aafd911403b3e3ca108dc5903b5d859f6 +Subproject commit ea6ad99dc678b9141ea13cfb45516477722ea3ee From cc39ac63949ebc17154d1ee155288f6379deb336 Mon Sep 17 00:00:00 2001 From: Denis Averin Date: Thu, 21 May 2026 16:14:54 +0700 Subject: [PATCH 30/40] Bump SwiftFormat target to Swift 6.0 --- codegen/Templates/swift/swiftformat.mustache | 2 +- submodules/swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen/Templates/swift/swiftformat.mustache b/codegen/Templates/swift/swiftformat.mustache index 93007252..9df232bf 100644 --- a/codegen/Templates/swift/swiftformat.mustache +++ b/codegen/Templates/swift/swiftformat.mustache @@ -35,7 +35,7 @@ --self remove --semicolons inline --stripunusedargs always ---swiftversion 5.4 +--swiftversion 6.0 --trimwhitespace always --wraparguments preserve --wrapcollections preserve diff --git a/submodules/swift b/submodules/swift index ea6ad99d..2e3b310b 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit ea6ad99dc678b9141ea13cfb45516477722ea3ee +Subproject commit 2e3b310ba6d4ad9ab33b515ebfeb626c130bf57d From dac5c0ba7cc72bc5371f7686453abdff3dd9f971 Mon Sep 17 00:00:00 2001 From: Denis Averin Date: Thu, 21 May 2026 18:45:31 +0700 Subject: [PATCH 31/40] Drop GenerateAndScan executable from Swift package template --- codegen/Templates/swift/Package.swift.mustache | 4 ---- 1 file changed, 4 deletions(-) diff --git a/codegen/Templates/swift/Package.swift.mustache b/codegen/Templates/swift/Package.swift.mustache index 7a91fde1..ccf3699c 100644 --- a/codegen/Templates/swift/Package.swift.mustache +++ b/codegen/Templates/swift/Package.swift.mustache @@ -21,10 +21,6 @@ let package = Package( name: "{{projectName}}", targets: ["{{projectName}}"] ), - .executable( - name: "GenerateAndScanExample", - targets: ["GenerateAndScanExample"] - ), ], dependencies: [ // Dependencies declare other packages that this package depends on. From 25b398e3644951f60f774e1152e3a9b65e6f9aa7 Mon Sep 17 00:00:00 2001 From: Denis Averin Date: Thu, 21 May 2026 18:45:38 +0700 Subject: [PATCH 32/40] Split AsposeBarcodeCloudClient template into per-type files --- .../swift/AsposeBarcodeCloudClient.mustache | 198 ------------------ .../AsposeBarcodeCloudClientError.mustache | 27 +++ .../AsposeBarcodeCloudConfiguration.mustache | 65 ++++++ .../swift/BarcodeAuthInterceptor.mustache | 115 ++++++++++ codegen/config-swift.json | 12 ++ 5 files changed, 219 insertions(+), 198 deletions(-) create mode 100644 codegen/Templates/swift/AsposeBarcodeCloudClientError.mustache create mode 100644 codegen/Templates/swift/AsposeBarcodeCloudConfiguration.mustache create mode 100644 codegen/Templates/swift/BarcodeAuthInterceptor.mustache diff --git a/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache b/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache index 83f84126..0acd51d6 100644 --- a/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache +++ b/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache @@ -3,93 +3,6 @@ import Foundation import FoundationNetworking #endif -public enum AsposeBarcodeCloudClientError: Error, CustomStringConvertible, @unchecked Sendable { - case missingCredentials - case invalidTokenURL(String) - case invalidTokenResponse - case tokenRequestFailed(statusCode: Int, body: String?) - case transportError(Error) - - public var description: String { - switch self { - case .missingCredentials: - return "Access token or clientId/clientSecret are required" - case let .invalidTokenURL(url): - return "Invalid token URL: \(url)" - case .invalidTokenResponse: - return "Token response does not contain access_token" - case let .tokenRequestFailed(statusCode, body): - if let body = body, !body.isEmpty { - return "Token request failed with status \(statusCode): \(body)" - } - return "Token request failed with status \(statusCode)" - case let .transportError(error): - return error.localizedDescription - } - } -} - -public final class AsposeBarcodeCloudConfiguration: @unchecked Sendable { - public static let defaultHost = "https://api.aspose.cloud/v4.0" - public static let defaultTokenURL = "https://id.aspose.cloud/connect/token" - public static let defaultSdkName = "swift sdk" - public static let defaultSdkVersion = "{{packageVersion}}" - - public var host: String - public var tokenURL: String - public var accessToken: String? - public var clientId: String? - public var clientSecret: String? - public var sdkName: String - public var sdkVersion: String - public var timeoutInterval: TimeInterval - - public init( - accessToken: String? = nil, - clientId: String? = nil, - clientSecret: String? = nil, - host: String = AsposeBarcodeCloudConfiguration.defaultHost, - tokenURL: String = AsposeBarcodeCloudConfiguration.defaultTokenURL, - sdkName: String = AsposeBarcodeCloudConfiguration.defaultSdkName, - sdkVersion: String = AsposeBarcodeCloudConfiguration.defaultSdkVersion, - timeoutInterval: TimeInterval = 300 - ) { - self.accessToken = accessToken - self.clientId = clientId - self.clientSecret = clientSecret - self.host = host - self.tokenURL = tokenURL - self.sdkName = sdkName - self.sdkVersion = sdkVersion - self.timeoutInterval = timeoutInterval - } - - public func makeTokenRequest() throws -> URLRequest { - guard let clientId = clientId, !clientId.isEmpty, - let clientSecret = clientSecret, !clientSecret.isEmpty else { - throw AsposeBarcodeCloudClientError.missingCredentials - } - - guard let url = URL(string: tokenURL) else { - throw AsposeBarcodeCloudClientError.invalidTokenURL(tokenURL) - } - - var components = URLComponents() - components.queryItems = [ - URLQueryItem(name: "grant_type", value: "client_credentials"), - URLQueryItem(name: "client_id", value: clientId), - URLQueryItem(name: "client_secret", value: clientSecret), - ] - - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.timeoutInterval = timeoutInterval - request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") - request.httpBody = components.percentEncodedQuery?.data(using: .utf8) - return request - } -} - public typealias AsposeBarcodeCloudTokenFetcher = @Sendable ( AsposeBarcodeCloudConfiguration, @escaping @Sendable (Result) -> Void @@ -218,114 +131,3 @@ public final class AsposeBarcodeCloudClient: @unchecked Sendable { }.resume() } } - -final class BarcodeAuthInterceptor: OpenAPIInterceptor, @unchecked Sendable { - private let configuration: AsposeBarcodeCloudConfiguration - private let tokenFetcher: AsposeBarcodeCloudTokenFetcher - private let state: OpenAPIMutex - - private struct State { - var token: String? - var inFlightFetch: Bool - var pendingWaiters: [@Sendable (Result) -> Void] - } - - init( - configuration: AsposeBarcodeCloudConfiguration, - tokenFetcher: @escaping AsposeBarcodeCloudTokenFetcher - ) { - self.configuration = configuration - self.tokenFetcher = tokenFetcher - let initialToken = configuration.accessToken.flatMap { $0.isEmpty ? nil : $0 } - self.state = OpenAPIMutex(State( - token: initialToken, - inFlightFetch: false, - pendingWaiters: [] - )) - } - - func intercept( - urlRequest: URLRequest, - urlSession: URLSessionProtocol, - requestBuilder: RequestBuilder, - completion: @Sendable @escaping (Result) -> Void - ) { - guard requestBuilder.requiresAuthentication else { - completion(.success(urlRequest)) - return - } - - ensureToken { result in - switch result { - case let .success(token): - var modified = urlRequest - modified.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - completion(.success(modified)) - case let .failure(error): - completion(.failure(error)) - } - } - } - - func retry( - urlRequest: URLRequest, - urlSession: URLSessionProtocol, - requestBuilder: RequestBuilder, - data: Data?, - response: URLResponse?, - error: any Error, - completion: @Sendable @escaping (OpenAPIInterceptorRetry) -> Void - ) { - completion(.dontRetry) - } - - func ensureToken(completion: @escaping @Sendable (Result) -> Void) { - enum Action { - case immediate(Result) - case wait - case startFetch - } - - var action: Action = .wait - state.withValue { state in - if let token = state.token, !token.isEmpty { - action = .immediate(.success(token)) - return - } - state.pendingWaiters.append(completion) - if state.inFlightFetch { - action = .wait - } else { - state.inFlightFetch = true - action = .startFetch - } - } - - switch action { - case let .immediate(result): - completion(result) - case .wait: - break - case .startFetch: - tokenFetcher(configuration) { [weak self] result in - self?.deliverToken(result) - } - } - } - - private func deliverToken(_ result: Result) { - var waiters: [@Sendable (Result) -> Void] = [] - state.withValue { state in - state.inFlightFetch = false - if case let .success(token) = result { - state.token = token - self.configuration.accessToken = token - } - waiters = state.pendingWaiters - state.pendingWaiters.removeAll() - } - for waiter in waiters { - waiter(result) - } - } -} diff --git a/codegen/Templates/swift/AsposeBarcodeCloudClientError.mustache b/codegen/Templates/swift/AsposeBarcodeCloudClientError.mustache new file mode 100644 index 00000000..704113b7 --- /dev/null +++ b/codegen/Templates/swift/AsposeBarcodeCloudClientError.mustache @@ -0,0 +1,27 @@ +import Foundation + +public enum AsposeBarcodeCloudClientError: Error, CustomStringConvertible, @unchecked Sendable { + case missingCredentials + case invalidTokenURL(String) + case invalidTokenResponse + case tokenRequestFailed(statusCode: Int, body: String?) + case transportError(Error) + + public var description: String { + switch self { + case .missingCredentials: + return "Access token or clientId/clientSecret are required" + case let .invalidTokenURL(url): + return "Invalid token URL: \(url)" + case .invalidTokenResponse: + return "Token response does not contain access_token" + case let .tokenRequestFailed(statusCode, body): + if let body = body, !body.isEmpty { + return "Token request failed with status \(statusCode): \(body)" + } + return "Token request failed with status \(statusCode)" + case let .transportError(error): + return error.localizedDescription + } + } +} diff --git a/codegen/Templates/swift/AsposeBarcodeCloudConfiguration.mustache b/codegen/Templates/swift/AsposeBarcodeCloudConfiguration.mustache new file mode 100644 index 00000000..877fa176 --- /dev/null +++ b/codegen/Templates/swift/AsposeBarcodeCloudConfiguration.mustache @@ -0,0 +1,65 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +public final class AsposeBarcodeCloudConfiguration: @unchecked Sendable { + public static let defaultHost = "https://api.aspose.cloud/v4.0" + public static let defaultTokenURL = "https://id.aspose.cloud/connect/token" + public static let defaultSdkName = "swift sdk" + public static let defaultSdkVersion = "{{packageVersion}}" + + public var host: String + public var tokenURL: String + public var accessToken: String? + public var clientId: String? + public var clientSecret: String? + public var sdkName: String + public var sdkVersion: String + public var timeoutInterval: TimeInterval + + public init( + accessToken: String? = nil, + clientId: String? = nil, + clientSecret: String? = nil, + host: String = AsposeBarcodeCloudConfiguration.defaultHost, + tokenURL: String = AsposeBarcodeCloudConfiguration.defaultTokenURL, + sdkName: String = AsposeBarcodeCloudConfiguration.defaultSdkName, + sdkVersion: String = AsposeBarcodeCloudConfiguration.defaultSdkVersion, + timeoutInterval: TimeInterval = 300 + ) { + self.accessToken = accessToken + self.clientId = clientId + self.clientSecret = clientSecret + self.host = host + self.tokenURL = tokenURL + self.sdkName = sdkName + self.sdkVersion = sdkVersion + self.timeoutInterval = timeoutInterval + } + + public func makeTokenRequest() throws -> URLRequest { + guard let clientId = clientId, !clientId.isEmpty, + let clientSecret = clientSecret, !clientSecret.isEmpty else { + throw AsposeBarcodeCloudClientError.missingCredentials + } + + guard let url = URL(string: tokenURL) else { + throw AsposeBarcodeCloudClientError.invalidTokenURL(tokenURL) + } + + var components = URLComponents() + components.queryItems = [ + URLQueryItem(name: "grant_type", value: "client_credentials"), + URLQueryItem(name: "client_id", value: clientId), + URLQueryItem(name: "client_secret", value: clientSecret), + ] + + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.timeoutInterval = timeoutInterval + request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") + request.httpBody = components.percentEncodedQuery?.data(using: .utf8) + return request + } +} diff --git a/codegen/Templates/swift/BarcodeAuthInterceptor.mustache b/codegen/Templates/swift/BarcodeAuthInterceptor.mustache new file mode 100644 index 00000000..a390f3ed --- /dev/null +++ b/codegen/Templates/swift/BarcodeAuthInterceptor.mustache @@ -0,0 +1,115 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +final class BarcodeAuthInterceptor: OpenAPIInterceptor, @unchecked Sendable { + private let configuration: AsposeBarcodeCloudConfiguration + private let tokenFetcher: AsposeBarcodeCloudTokenFetcher + private let state: OpenAPIMutex + + private struct State { + var token: String? + var inFlightFetch: Bool + var pendingWaiters: [@Sendable (Result) -> Void] + } + + init( + configuration: AsposeBarcodeCloudConfiguration, + tokenFetcher: @escaping AsposeBarcodeCloudTokenFetcher + ) { + self.configuration = configuration + self.tokenFetcher = tokenFetcher + let initialToken = configuration.accessToken.flatMap { $0.isEmpty ? nil : $0 } + self.state = OpenAPIMutex(State( + token: initialToken, + inFlightFetch: false, + pendingWaiters: [] + )) + } + + func intercept( + urlRequest: URLRequest, + urlSession: URLSessionProtocol, + requestBuilder: RequestBuilder, + completion: @Sendable @escaping (Result) -> Void + ) { + guard requestBuilder.requiresAuthentication else { + completion(.success(urlRequest)) + return + } + + ensureToken { result in + switch result { + case let .success(token): + var modified = urlRequest + modified.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") + completion(.success(modified)) + case let .failure(error): + completion(.failure(error)) + } + } + } + + func retry( + urlRequest: URLRequest, + urlSession: URLSessionProtocol, + requestBuilder: RequestBuilder, + data: Data?, + response: URLResponse?, + error: any Error, + completion: @Sendable @escaping (OpenAPIInterceptorRetry) -> Void + ) { + completion(.dontRetry) + } + + func ensureToken(completion: @escaping @Sendable (Result) -> Void) { + enum Action { + case immediate(Result) + case wait + case startFetch + } + + var action: Action = .wait + state.withValue { state in + if let token = state.token, !token.isEmpty { + action = .immediate(.success(token)) + return + } + state.pendingWaiters.append(completion) + if state.inFlightFetch { + action = .wait + } else { + state.inFlightFetch = true + action = .startFetch + } + } + + switch action { + case let .immediate(result): + completion(result) + case .wait: + break + case .startFetch: + tokenFetcher(configuration) { [weak self] result in + self?.deliverToken(result) + } + } + } + + private func deliverToken(_ result: Result) { + var waiters: [@Sendable (Result) -> Void] = [] + state.withValue { state in + state.inFlightFetch = false + if case let .success(token) = result { + state.token = token + self.configuration.accessToken = token + } + waiters = state.pendingWaiters + state.pendingWaiters.removeAll() + } + for waiter in waiters { + waiter(result) + } + } +} diff --git a/codegen/config-swift.json b/codegen/config-swift.json index 4bebcc30..05de1c49 100644 --- a/codegen/config-swift.json +++ b/codegen/config-swift.json @@ -5,6 +5,18 @@ "AsposeBarcodeCloudClient.mustache": { "destinationFilename": "Sources/AsposeBarcodeCloud/AsposeBarcodeCloudClient.swift", "templateType": "SupportingFiles" + }, + "AsposeBarcodeCloudClientError.mustache": { + "destinationFilename": "Sources/AsposeBarcodeCloud/AsposeBarcodeCloudClientError.swift", + "templateType": "SupportingFiles" + }, + "AsposeBarcodeCloudConfiguration.mustache": { + "destinationFilename": "Sources/AsposeBarcodeCloud/AsposeBarcodeCloudConfiguration.swift", + "templateType": "SupportingFiles" + }, + "BarcodeAuthInterceptor.mustache": { + "destinationFilename": "Sources/AsposeBarcodeCloud/Infrastructure/BarcodeAuthInterceptor.swift", + "templateType": "SupportingFiles" } }, "hideGenerationTimestamp": true, From df1d2f0cea77b18ba2e58a3fe89e1a9da1c3fcce Mon Sep 17 00:00:00 2001 From: Denis Averin Date: Thu, 21 May 2026 18:45:44 +0700 Subject: [PATCH 33/40] Inject GenerateAndScan example into generated Swift README --- codegen/Templates/swift/README.mustache | 8 ++++++++ codegen/generate-swift.bash | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/codegen/Templates/swift/README.mustache b/codegen/Templates/swift/README.mustache index 7a8de7c3..265e8559 100644 --- a/codegen/Templates/swift/README.mustache +++ b/codegen/Templates/swift/README.mustache @@ -104,6 +104,14 @@ RecognizeAPI.recognizeBase64( } ``` +## Example + +Full runnable sample at `Examples/GenerateAndScan/main.swift`: + +```swift +%insert Examples/GenerateAndScan/main.swift% +``` + ## Development The generated source is maintained from the `aspose-barcode-cloud-codegen` repository: diff --git a/codegen/generate-swift.bash b/codegen/generate-swift.bash index 7b73a9a5..296ed744 100755 --- a/codegen/generate-swift.bash +++ b/codegen/generate-swift.bash @@ -21,7 +21,7 @@ rm -rf "$targetDir/Sources/AsposeBarcodeCloud" mv "$tempDir/Sources/AsposeBarcodeCloud" "$targetDir/Sources/AsposeBarcodeCloud" mv "$tempDir/Package.swift" "$targetDir/Package.swift" -mv "$tempDir/README.md" "$targetDir/README.md" +mv "$tempDir/README.md" "$targetDir/README.template" rm -rf "$targetDir/docs" mv "$tempDir/docs" "$targetDir/docs" From fcd32d1483344d4a92d5ff21bb04403c93d29a33 Mon Sep 17 00:00:00 2001 From: Denis Averin Date: Thu, 21 May 2026 18:45:47 +0700 Subject: [PATCH 34/40] Bump Swift submodule to regenerated SDK --- submodules/swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/swift b/submodules/swift index 2e3b310b..b013c104 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 2e3b310ba6d4ad9ab33b515ebfeb626c130bf57d +Subproject commit b013c104d81a6d4d43ff837f5148722222c66ccf From 49360290d88679ed8fba51b28eeb2aec39f92d14 Mon Sep 17 00:00:00 2001 From: Denis Averin Date: Thu, 21 May 2026 19:03:48 +0700 Subject: [PATCH 35/40] Check-codegen.yml cleanup --- .github/workflows/check-codegen.yml | 63 ----------------------------- 1 file changed, 63 deletions(-) diff --git a/.github/workflows/check-codegen.yml b/.github/workflows/check-codegen.yml index 846d10dd..94605785 100644 --- a/.github/workflows/check-codegen.yml +++ b/.github/workflows/check-codegen.yml @@ -32,66 +32,3 @@ jobs: - uses: actions/checkout@v6 - name: Check existing swagger specification is up-to-date run: curl https://api.aspose.cloud/v4.0/barcode/swagger/spec | diff --strip-trailing-cr -y --suppress-common-lines spec/aspose-barcode-cloud.json - - - check-swift-codegen-linux: - runs-on: ubuntu-latest - needs: [check-submodules] - steps: - - uses: actions/checkout@v6 - - - name: Checkout submodules - working-directory: ${{ github.workspace }} - env: - SWIFT_SUBMODULE_DEPLOY_KEY: ${{ secrets.SWIFT_SUBMODULE_DEPLOY_KEY }} - run: bash scripts/checkout-submodules.bash - - - uses: actions/setup-java@v5 - with: - distribution: corretto - java-version: 21 - - - name: Show Swift version - run: swift --version - - - name: Regenerate Swift SDK - run: make swift - - - name: Check Swift SDK generation is reproducible - run: git diff --exit-code - - - name: Build Swift example - working-directory: submodules/swift - run: swift build --product GenerateAndScanExample - - - name: Run Swift live integration tests - working-directory: submodules/swift - run: make integration-test - env: - TEST_CONFIGURATION_ACCESS_TOKEN: ${{ secrets.TEST_CONFIGURATION_ACCESS_TOKEN }} - - check-swift-macos: - runs-on: macos-latest - needs: [check-swift-codegen-linux] - steps: - - uses: actions/checkout@v6 - - - name: Checkout submodules - working-directory: ${{ github.workspace }} - env: - SWIFT_SUBMODULE_DEPLOY_KEY: ${{ secrets.SWIFT_SUBMODULE_DEPLOY_KEY }} - run: bash scripts/checkout-submodules.bash - - - name: Show Swift version - run: swift --version - - - name: Build Swift SDK - working-directory: submodules/swift - run: make build - - - name: Run Swift tests - working-directory: submodules/swift - run: make test - - - name: Build Swift example - working-directory: submodules/swift - run: swift build --product GenerateAndScanExample From 68ae969ec0524ad8e0b696244bd2c84e73a04928 Mon Sep 17 00:00:00 2001 From: Denis Averin Date: Thu, 21 May 2026 19:11:53 +0700 Subject: [PATCH 36/40] Templates cleanup --- codegen/Templates/swift/APIHelper.mustache | 122 ----- codegen/Templates/swift/APIs.mustache | 309 ------------- codegen/Templates/swift/Cartfile.mustache | 4 - .../Templates/swift/CodableHelper.mustache | 70 --- codegen/Templates/swift/Extensions.mustache | 284 ------------ .../Templates/swift/JSONDataEncoding.mustache | 56 --- .../swift/JSONEncodingHelper.mustache | 29 -- .../swift/OpenAPIDateWithoutTime.mustache | 98 ---- codegen/Templates/swift/OpenAPIMutex.mustache | 28 -- .../swift/OpenISO8601DateFormatter.mustache | 56 --- codegen/Templates/swift/Podspec.mustache | 38 -- .../swift/SynchronizedDictionary.mustache | 17 - codegen/Templates/swift/Validation.mustache | 161 ------- codegen/Templates/swift/XcodeGen.mustache | 17 - codegen/Templates/swift/_param.mustache | 1 - codegen/Templates/swift/api_doc.mustache | 141 ------ codegen/Templates/swift/git_push.sh.mustache | 57 --- codegen/Templates/swift/gitignore.mustache | 100 ----- .../AlamofireImplementations.mustache | 420 ------------------ codegen/Templates/swift/model.mustache | 29 -- codegen/Templates/swift/modelArray.mustache | 1 - codegen/Templates/swift/modelEnum.mustache | 10 - .../swift/modelInlineEnumDeclaration.mustache | 10 - codegen/Templates/swift/modelOneOf.mustache | 70 --- codegen/Templates/swift/model_doc.mustache | 11 - 25 files changed, 2139 deletions(-) delete mode 100644 codegen/Templates/swift/APIHelper.mustache delete mode 100644 codegen/Templates/swift/APIs.mustache delete mode 100644 codegen/Templates/swift/Cartfile.mustache delete mode 100644 codegen/Templates/swift/CodableHelper.mustache delete mode 100644 codegen/Templates/swift/Extensions.mustache delete mode 100644 codegen/Templates/swift/JSONDataEncoding.mustache delete mode 100644 codegen/Templates/swift/JSONEncodingHelper.mustache delete mode 100644 codegen/Templates/swift/OpenAPIDateWithoutTime.mustache delete mode 100644 codegen/Templates/swift/OpenAPIMutex.mustache delete mode 100644 codegen/Templates/swift/OpenISO8601DateFormatter.mustache delete mode 100644 codegen/Templates/swift/Podspec.mustache delete mode 100644 codegen/Templates/swift/SynchronizedDictionary.mustache delete mode 100644 codegen/Templates/swift/Validation.mustache delete mode 100644 codegen/Templates/swift/XcodeGen.mustache delete mode 100644 codegen/Templates/swift/_param.mustache delete mode 100644 codegen/Templates/swift/api_doc.mustache delete mode 100644 codegen/Templates/swift/git_push.sh.mustache delete mode 100644 codegen/Templates/swift/gitignore.mustache delete mode 100644 codegen/Templates/swift/libraries/alamofire/AlamofireImplementations.mustache delete mode 100644 codegen/Templates/swift/model.mustache delete mode 100644 codegen/Templates/swift/modelArray.mustache delete mode 100644 codegen/Templates/swift/modelEnum.mustache delete mode 100644 codegen/Templates/swift/modelInlineEnumDeclaration.mustache delete mode 100644 codegen/Templates/swift/modelOneOf.mustache delete mode 100644 codegen/Templates/swift/model_doc.mustache diff --git a/codegen/Templates/swift/APIHelper.mustache b/codegen/Templates/swift/APIHelper.mustache deleted file mode 100644 index f25c30b2..00000000 --- a/codegen/Templates/swift/APIHelper.mustache +++ /dev/null @@ -1,122 +0,0 @@ -// APIHelper.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation{{#useVapor}} -import Vapor{{/useVapor}} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct APIHelper: Sendable { - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func rejectNil(_ source: [String: (any Sendable)?]) -> [String: any Sendable]? { - let destination = source.reduce(into: [String: any Sendable]()) { result, item in - if let value = item.value { - result[item.key] = value - } - } - - if destination.isEmpty { - return nil - } - return destination - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func rejectNilHeaders(_ source: [String: (any Sendable)?]) -> [String: String] { - return source.reduce(into: [String: String]()) { result, item in - if let collection = item.value as? [Any?] { - result[item.key] = collection - .compactMap { value in convertAnyToString(value) } - .joined(separator: ",") - } else if let value: Any = item.value { - result[item.key] = convertAnyToString(value) - } - } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func convertBoolToString(_ source: [String: any Sendable]?) -> [String: any Sendable]? { - guard let source = source else { - return nil - } - - return source.reduce(into: [String: any Sendable]()) { result, item in - switch item.value { - case let x as Bool: - result[item.key] = x.description - default: - result[item.key] = item.value - } - } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func convertAnyToString(_ value: Any?) -> String? { - guard let value = value else { return nil } - if let value = value as? any RawRepresentable { - return "\(value.rawValue)" - } else { - return "\(value)" - } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func mapValueToPathItem(_ source: Any) -> Any { - if let collection = source as? [Any?] { - return collection - .compactMap { value in convertAnyToString(value) } - .joined(separator: ",") - } else if let value = source as? any RawRepresentable { - return "\(value.rawValue)" - } - return source - } - - /// maps all values from source to query parameters - /// - /// explode attribute is respected: collection values might be either joined or split up into separate key value pairs - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func mapValuesToQueryItems(_ source: [String: (wrappedValue: (any Sendable)?, isExplode: Bool)]) -> [URLQueryItem]? { - let destination = source.filter { $0.value.wrappedValue != nil }.reduce(into: [URLQueryItem]()) { result, item in - if let collection = item.value.wrappedValue as? [Any?] { - - let collectionValues: [String] = collection.compactMap { value in convertAnyToString(value) } - - if !item.value.isExplode { - result.append(URLQueryItem(name: item.key, value: collectionValues.joined(separator: ","))) - } else { - collectionValues - .forEach { value in - result.append(URLQueryItem(name: item.key, value: value)) - } - } - - } else if let value = item.value.wrappedValue { - result.append(URLQueryItem(name: item.key, value: convertAnyToString(value))) - } - } - - if destination.isEmpty { - return nil - } - return destination.sorted { $0.name < $1.name } - } - - /// maps all values from source to query parameters - /// - /// collection values are always exploded - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func mapValuesToQueryItems(_ source: [String: (any Sendable)?]) -> [URLQueryItem]? { - let destination = source.filter { $0.value != nil }.reduce(into: [URLQueryItem]()) { result, item in - if let collection = item.value as? [Any?] { - collection - .compactMap { value in convertAnyToString(value) } - .forEach { value in - result.append(URLQueryItem(name: item.key, value: value)) - } - - } else if let value = item.value { - result.append(URLQueryItem(name: item.key, value: convertAnyToString(value))) - } - } - - if destination.isEmpty { - return nil - } - return destination.sorted { $0.name < $1.name } - } -} diff --git a/codegen/Templates/swift/APIs.mustache b/codegen/Templates/swift/APIs.mustache deleted file mode 100644 index e30c5658..00000000 --- a/codegen/Templates/swift/APIs.mustache +++ /dev/null @@ -1,309 +0,0 @@ -// APIs.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif{{#useVapor}} -import Vapor{{/useVapor}}{{#useAlamofire}} -import Alamofire{{/useAlamofire}} -{{#swiftUseApiNamespace}} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{projectName}}API {} -{{/swiftUseApiNamespace}} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class {{projectName}}APIConfiguration: @unchecked Sendable { - - // MARK: - Private state - - private struct State { - var basePath: String{{#useVapor}} - var customHeaders: HTTPHeaders - var apiClient: Vapor.Client? - var apiWrapper: @Sendable (inout Vapor.ClientRequest) throws -> () - var contentConfiguration: ContentConfiguration{{/useVapor}}{{^useVapor}} - var customHeaders: [String: String] - var credential: URLCredential? - var requestBuilderFactory: RequestBuilderFactory - var apiResponseQueue: DispatchQueue - var codableHelper: CodableHelper - var successfulStatusCodeRange: Range{{#useURLSession}} - var interceptor: OpenAPIInterceptor{{/useURLSession}}{{#useAlamofire}} - var interceptor: RequestInterceptor? - var dataResponseSerializer: AnyResponseSerializer - var stringResponseSerializer: AnyResponseSerializer{{/useAlamofire}}{{/useVapor}} - } - - private let _state: OpenAPIMutex - - // MARK: - Public interface - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var basePath: String { - get { _state.value.basePath } - set { _state.withValue { $0.basePath = newValue } } - }{{#useVapor}} - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var customHeaders: HTTPHeaders { - get { _state.value.customHeaders } - set { _state.withValue { $0.customHeaders = newValue } } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var apiClient: Vapor.Client? { - get { _state.value.apiClient } - set { _state.withValue { $0.apiClient = newValue } } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var apiWrapper: @Sendable (inout Vapor.ClientRequest) throws -> () { - get { _state.value.apiWrapper } - set { _state.withValue { $0.apiWrapper = newValue } } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var contentConfiguration: ContentConfiguration { - get { _state.value.contentConfiguration } - set { _state.withValue { $0.contentConfiguration = newValue } } - }{{/useVapor}}{{^useVapor}} - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var customHeaders: [String: String] { - get { _state.value.customHeaders } - set { _state.withValue { $0.customHeaders = newValue } } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var credential: URLCredential? { - get { _state.value.credential } - set { _state.withValue { $0.credential = newValue } } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var requestBuilderFactory: RequestBuilderFactory { - get { _state.value.requestBuilderFactory } - set { _state.withValue { $0.requestBuilderFactory = newValue } } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var apiResponseQueue: DispatchQueue { - get { _state.value.apiResponseQueue } - set { _state.withValue { $0.apiResponseQueue = newValue } } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var codableHelper: CodableHelper { - get { _state.value.codableHelper } - set { _state.withValue { $0.codableHelper = newValue } } - } - - /// Configures the range of HTTP status codes that will result in a successful response. - /// - /// If a HTTP status code is outside of this range the response will be interpreted as failed. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var successfulStatusCodeRange: Range { - get { _state.value.successfulStatusCodeRange } - set { _state.withValue { $0.successfulStatusCodeRange = newValue } } - }{{#useURLSession}} - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var interceptor: OpenAPIInterceptor { - get { _state.value.interceptor } - set { _state.withValue { $0.interceptor = newValue } } - }{{/useURLSession}}{{#useAlamofire}} - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var interceptor: RequestInterceptor? { - get { _state.value.interceptor } - set { _state.withValue { $0.interceptor = newValue } } - } - - /// ResponseSerializer that will be used by the generator for `Data` responses - /// - /// If unchanged, Alamofires default `DataResponseSerializer` will be used. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var dataResponseSerializer: AnyResponseSerializer { - get { _state.value.dataResponseSerializer } - set { _state.withValue { $0.dataResponseSerializer = newValue } } - } - - /// ResponseSerializer that will be used by the generator for `String` responses - /// - /// If unchanged, Alamofires default `StringResponseSerializer` will be used. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var stringResponseSerializer: AnyResponseSerializer { - get { _state.value.stringResponseSerializer } - set { _state.withValue { $0.stringResponseSerializer = newValue } } - }{{/useAlamofire}}{{/useVapor}} - - // MARK: - Init - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init( - basePath: String = "{{{basePath}}}",{{#useVapor}} - customHeaders: HTTPHeaders = [:], - apiClient: Vapor.Client? = nil, - apiWrapper: @escaping @Sendable (inout Vapor.ClientRequest) throws -> () = { _ in }, - contentConfiguration: ContentConfiguration = ContentConfiguration.default(){{/useVapor}}{{^useVapor}} - customHeaders: [String: String] = [:], - credential: URLCredential? = nil, - requestBuilderFactory: RequestBuilderFactory = {{#useAlamofire}}AlamofireRequestBuilderFactory(){{/useAlamofire}}{{#useURLSession}}URLSessionRequestBuilderFactory(){{/useURLSession}}, - apiResponseQueue: DispatchQueue = .main, - codableHelper: CodableHelper = CodableHelper(), - successfulStatusCodeRange: Range = 200..<300{{#useURLSession}}, - interceptor: OpenAPIInterceptor = DefaultOpenAPIInterceptor(){{/useURLSession}}{{#useAlamofire}}, - interceptor: RequestInterceptor? = nil, - dataResponseSerializer: AnyResponseSerializer = AnyResponseSerializer(DataResponseSerializer()), - stringResponseSerializer: AnyResponseSerializer = AnyResponseSerializer(StringResponseSerializer()){{/useAlamofire}}{{/useVapor}} - ) { - _state = OpenAPIMutex(State( - basePath: basePath,{{#useVapor}} - customHeaders: customHeaders, - apiClient: apiClient, - apiWrapper: apiWrapper, - contentConfiguration: contentConfiguration{{/useVapor}}{{^useVapor}} - customHeaders: customHeaders, - credential: credential, - requestBuilderFactory: requestBuilderFactory, - apiResponseQueue: apiResponseQueue, - codableHelper: codableHelper, - successfulStatusCodeRange: successfulStatusCodeRange{{#useURLSession}}, - interceptor: interceptor{{/useURLSession}}{{#useAlamofire}}, - interceptor: interceptor, - dataResponseSerializer: dataResponseSerializer, - stringResponseSerializer: stringResponseSerializer{{/useAlamofire}}{{/useVapor}} - )) - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static let shared = {{projectName}}APIConfiguration() -}{{^useVapor}} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class RequestBuilder: @unchecked Sendable, Identifiable { - - // MARK: - Immutable properties - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let parameters: [String: any Sendable]? - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let method: String - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let URLString: String - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let requestTask: RequestTask = RequestTask() - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let requiresAuthentication: Bool - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let apiConfiguration: {{projectName}}APIConfiguration - - // MARK: - Private mutable state - - private struct MutableState { - var credential: URLCredential? = nil - var headers: [String: String] - var onProgressReady: ((Progress) -> Void)? = nil - } - - private let _state: OpenAPIMutex - - // MARK: - Public mutable interface - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var credential: URLCredential? { - get { _state.value.credential } - set { _state.withValue { $0.credential = newValue } } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var headers: [String: String] { - get { _state.value.headers } - set { _state.withValue { $0.headers = newValue } } - } - - /// Optional block to obtain a reference to the request's progress instance when available. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var onProgressReady: ((Progress) -> Void)? { - get { _state.value.onProgressReady } - set { _state.withValue { $0.onProgressReady = newValue } } - } - - // MARK: - Init - - required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init( - method: String, - URLString: String, - parameters: [String: any Sendable]?, - headers: [String: String] = [:], - requiresAuthentication: Bool, - apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared - ) { - self.method = method - self.URLString = URLString - self.parameters = parameters - self.requiresAuthentication = requiresAuthentication - self.apiConfiguration = apiConfiguration - self._state = OpenAPIMutex(MutableState(headers: headers)) - - addHeaders(apiConfiguration.customHeaders) - addCredential() - } - - // MARK: - Public methods - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func addHeaders(_ aHeaders: [String: String]) { - _state.withValue { state in - for (header, value) in aHeaders { - state.headers[header] = value - } - } - } - - @discardableResult - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute(completion: @Sendable @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) -> RequestTask { - return requestTask - } - - {{#useAsyncAwait}} - #if compiler(>=6.2) - @concurrent - @discardableResult - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute() async throws(ErrorResponse) -> Response { - try await _execute() - } - #else - @discardableResult - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute() async throws(ErrorResponse) -> Response { - try await _execute() - } - #endif - - @discardableResult - private func _execute() async throws(ErrorResponse) -> Response { - do { - let requestTask = self.requestTask - return try await withTaskCancellationHandler { - try Task.checkCancellation() - return try await withCheckedThrowingContinuation { continuation in - guard !Task.isCancelled else { - continuation.resume(throwing: CancellationError()) - return - } - - self.execute { result in - switch result { - case let .success(response): - continuation.resume(returning: response) - case let .failure(error): - continuation.resume(throwing: error) - } - } - } - } onCancel: { - requestTask.cancel() - } - } catch { - if let errorResponse = error as? ErrorResponse { - throw errorResponse - } else { - throw ErrorResponse.error(-3, nil, nil, error) - } - } - } - - {{/useAsyncAwait}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func addHeader(name: String, value: String) -> Self { - if !value.isEmpty { - _state.withValue { $0.headers[name] = value } - } - return self - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func addCredential() { - _state.withValue { [apiConfiguration] state in - state.credential = apiConfiguration.credential - } - } -} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol RequestBuilderFactory: Sendable { - func getNonDecodableBuilder() -> RequestBuilder.Type - func getBuilder() -> RequestBuilder.Type -}{{/useVapor}} diff --git a/codegen/Templates/swift/Cartfile.mustache b/codegen/Templates/swift/Cartfile.mustache deleted file mode 100644 index bbf4a742..00000000 --- a/codegen/Templates/swift/Cartfile.mustache +++ /dev/null @@ -1,4 +0,0 @@ -{{#useAlamofire}} -github "Alamofire/Alamofire" ~> 5.10{{/useAlamofire}}{{#usePromiseKit}} -github "mxcl/PromiseKit" ~> 8.1{{/usePromiseKit}}{{#useRxSwift}} -github "ReactiveX/RxSwift" ~> 6.8{{/useRxSwift}} diff --git a/codegen/Templates/swift/CodableHelper.mustache b/codegen/Templates/swift/CodableHelper.mustache deleted file mode 100644 index f9bb64bb..00000000 --- a/codegen/Templates/swift/CodableHelper.mustache +++ /dev/null @@ -1,70 +0,0 @@ -// -// CodableHelper.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class CodableHelper: @unchecked Sendable { - - // MARK: - Private state - - private struct State { - var customDateFormatter: DateFormatter? - var defaultDateFormatter: DateFormatter = OpenISO8601DateFormatter() - - var customJSONDecoder: JSONDecoder? - var defaultJSONDecoder: JSONDecoder = JSONDecoder() - - var customJSONEncoder: JSONEncoder? - var defaultJSONEncoder: JSONEncoder = JSONEncoder() - - init() { - defaultJSONEncoder.outputFormatting = .prettyPrinted - rebuildDefaultCoders() - } - - mutating func rebuildDefaultCoders() { - defaultJSONDecoder.dateDecodingStrategy = .formatted(customDateFormatter ?? defaultDateFormatter) - defaultJSONEncoder.dateEncodingStrategy = .formatted(customDateFormatter ?? defaultDateFormatter) - } - } - - private let _state = OpenAPIMutex(State()) - - // MARK: - Init - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init() {} - - // MARK: - Public interface - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var dateFormatter: DateFormatter { - get { _state.withValue { $0.customDateFormatter ?? $0.defaultDateFormatter } } - set { - _state.withValue { state in - state.customDateFormatter = newValue - state.rebuildDefaultCoders() - } - } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var jsonDecoder: JSONDecoder { - get { _state.withValue { $0.customJSONDecoder ?? $0.defaultJSONDecoder } } - set { _state.withValue { $0.customJSONDecoder = newValue } } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var jsonEncoder: JSONEncoder { - get { _state.withValue { $0.customJSONEncoder ?? $0.defaultJSONEncoder } } - set { _state.withValue { $0.customJSONEncoder = newValue } } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func decode(_ type: T.Type, from data: Data) -> Swift.Result where T: Decodable { - return Swift.Result { try jsonDecoder.decode(type, from: data) } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func encode(_ value: T) -> Swift.Result where T: Encodable { - return Swift.Result { try jsonEncoder.encode(value) } - } -} diff --git a/codegen/Templates/swift/Extensions.mustache b/codegen/Templates/swift/Extensions.mustache deleted file mode 100644 index 7145643c..00000000 --- a/codegen/Templates/swift/Extensions.mustache +++ /dev/null @@ -1,284 +0,0 @@ -// Extensions.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif{{#usePromiseKit}} -@preconcurrency import PromiseKit{{/usePromiseKit}}{{#useVapor}} -import Vapor{{/useVapor}}{{^useVapor}} - -extension Bool: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { self } -} - -extension Float: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { self } -} - -extension Int: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { self } -} - -extension Int32: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { self } -} - -extension Int64: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { self } -} - -extension Double: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { self } -} - -extension Decimal: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { self } -} - -extension String: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { self } -} - -extension URL: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { self } -} - -extension UUID: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { self } -} - -extension RawRepresentable where RawValue: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { - rawValue.asParameter(codableHelper: codableHelper) - } -} - -private func encodeIfPossible(_ object: T, codableHelper: CodableHelper) -> any Sendable { - if let encodableObject = object as? ParameterConvertible { - return encodableObject.asParameter(codableHelper: codableHelper) - } else { - return object - } -} - -extension Array where Element: Sendable { - func asParameter(codableHelper: CodableHelper) -> any Sendable { - return self.map { encodeIfPossible($0, codableHelper: codableHelper) } - } -} - -extension Set where Element: Sendable { - func asParameter(codableHelper: CodableHelper) -> any Sendable { - return Array(self).asParameter(codableHelper: codableHelper) - } -} - -extension Dictionary where Key: Sendable, Value: Sendable { - func asParameter(codableHelper: CodableHelper) -> any Sendable { - var dictionary = [Key: any Sendable]() - for (key, value) in self { - dictionary[key] = encodeIfPossible(value, codableHelper: codableHelper) - } - return dictionary - } -} - -extension Data: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { - return self.base64EncodedString(options: Data.Base64EncodingOptions()) - } -} - -extension Date: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { - return codableHelper.dateFormatter.string(from: self) - } -}{{/useVapor}}{{#generateModelAdditionalProperties}} - -extension String: @retroactive CodingKey { - - public var stringValue: String { - return self - } - - public init?(stringValue: String) { - self.init(stringLiteral: stringValue) - } - - public var intValue: Int? { - return nil - } - - public init?(intValue: Int) { - return nil - } - -} - -extension KeyedEncodingContainerProtocol { - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeArray(_ values: [T], forKey key: Self.Key) throws where T: Encodable { - var arrayContainer = nestedUnkeyedContainer(forKey: key) - try arrayContainer.encode(contentsOf: values) - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeArrayIfPresent(_ values: [T]?, forKey key: Self.Key) throws where T: Encodable { - if let values = values { - try encodeArray(values, forKey: key) - } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeMap(_ pairs: [Self.Key: T]) throws where T: Encodable { - for (key, value) in pairs { - try encode(value, forKey: key) - } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeMapIfPresent(_ pairs: [Self.Key: T]?) throws where T: Encodable { - if let pairs = pairs { - try encodeMap(pairs) - } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encode(_ value: Decimal, forKey key: Self.Key) throws { - let decimalNumber = NSDecimalNumber(decimal: value) - let numberFormatter = NumberFormatter() - numberFormatter.numberStyle = .decimal - numberFormatter.locale = Locale(identifier: "en_US") - numberFormatter.usesGroupingSeparator = false - let formattedString = numberFormatter.string(from: decimalNumber) ?? "\(value)" - try encode(formattedString, forKey: key) - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeIfPresent(_ value: Decimal?, forKey key: Self.Key) throws { - if let value = value { - try encode(value, forKey: key) - } - } -} - -extension KeyedDecodingContainerProtocol { - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeArray(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T: Decodable { - var tmpArray = [T]() - - var nestedContainer = try nestedUnkeyedContainer(forKey: key) - while !nestedContainer.isAtEnd { - let arrayValue = try nestedContainer.decode(T.self) - tmpArray.append(arrayValue) - } - - return tmpArray - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeArrayIfPresent(_ type: T.Type, forKey key: Self.Key) throws -> [T]? where T: Decodable { - var tmpArray: [T]? - - if contains(key) { - tmpArray = try decodeArray(T.self, forKey: key) - } - - return tmpArray - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeMap(_ type: T.Type, excludedKeys: Set) throws -> [Self.Key: T] where T: Decodable { - var map: [Self.Key: T] = [:] - - for key in allKeys { - if !excludedKeys.contains(key) { - let value = try decode(T.self, forKey: key) - map[key] = value - } - } - - return map - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decode(_ type: Decimal.Type, forKey key: Self.Key) throws -> Decimal { - let stringValue = try decode(String.self, forKey: key) - guard let decimalValue = Decimal(string: stringValue) else { - let context = DecodingError.Context(codingPath: [key], debugDescription: "The key \(key) couldn't be converted to a Decimal value") - throw DecodingError.typeMismatch(type, context) - } - - return decimalValue - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeIfPresent(_ type: Decimal.Type, forKey key: Self.Key) throws -> Decimal? { - guard let stringValue = try decodeIfPresent(String.self, forKey: key) else { - return nil - } - guard let decimalValue = Decimal(string: stringValue) else { - let context = DecodingError.Context(codingPath: [key], debugDescription: "The key \(key) couldn't be converted to a Decimal value") - throw DecodingError.typeMismatch(type, context) - } - - return decimalValue - } - -}{{/generateModelAdditionalProperties}}{{#usePromiseKit}} - -extension RequestBuilder { - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func execute() -> Promise> { - let deferred = Promise>.pending() - self.execute { result in - switch result { - case let .success(response): - deferred.resolver.fulfill(response) - case let .failure(error): - deferred.resolver.reject(error) - } - } - return deferred.promise - } -}{{/usePromiseKit}}{{#useVapor}} - -extension UUID: @retroactive Content { } - -extension URL: @retroactive Content { } - -extension Set: @retroactive ResponseEncodable where Element: Content { - public func encodeResponse(for request: Vapor.Request) -> EventLoopFuture { - let response = Vapor.Response() - do { - try response.content.encode(Array(self)) - } catch { - return request.eventLoop.makeFailedFuture(error) - } - return request.eventLoop.makeSucceededFuture(response) - } -} - -extension Set: @retroactive AsyncResponseEncodable where Element: Content { - public func encodeResponse(for request: Vapor.Request) async throws -> Vapor.Response { - let response = Vapor.Response() - try response.content.encode(Array(self)) - return response - } -} - -extension Set: @retroactive RequestDecodable where Element: Content { - public static func decodeRequest(_ request: Vapor.Request) -> EventLoopFuture { - do { - let content = try request.content.decode([Element].self) - return request.eventLoop.makeSucceededFuture(Set(content)) - } catch { - return request.eventLoop.makeFailedFuture(error) - } - } -} - -extension Set: @retroactive AsyncRequestDecodable where Element: Content { - public static func decodeRequest(_ request: Vapor.Request) async throws -> Self { - let content = try request.content.decode([Element].self) - return Set(content) - } -} - -extension Set: @retroactive Content where Element: Content { } - -extension JSONValue: Content {}{{/useVapor}} diff --git a/codegen/Templates/swift/JSONDataEncoding.mustache b/codegen/Templates/swift/JSONDataEncoding.mustache deleted file mode 100644 index ef6a0611..00000000 --- a/codegen/Templates/swift/JSONDataEncoding.mustache +++ /dev/null @@ -1,56 +0,0 @@ -// -// JSONDataEncoding.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation -#if canImport(FoundationNetworking) -import FoundationNetworking -#endif - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct JSONDataEncoding: Sendable { - - // MARK: Properties - - private static let jsonDataKey = "jsonData" - - // MARK: Encoding - - /// Creates a URL request by encoding parameters and applying them onto an existing request. - /// - /// - parameter urlRequest: The request to have parameters applied. - /// - parameter parameters: The parameters to apply. This should have a single key/value - /// pair with "jsonData" as the key and a Data object as the value. - /// - /// - throws: An `Error` if the encoding process encounters an error. - /// - /// - returns: The encoded request. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func encode(request: URLRequest, with parameters: [String: any Sendable]?) -> URLRequest { - var urlRequest = request - - guard let jsonData = parameters?[JSONDataEncoding.jsonDataKey] as? Data, !jsonData.isEmpty else { - return urlRequest - } - - if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { - urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") - } - - urlRequest.httpBody = jsonData - - return urlRequest - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func encodingParameters(jsonData: Data?) -> [String: any Sendable]? { - var returnedParams: [String: any Sendable]? - if let jsonData = jsonData, !jsonData.isEmpty { - var params: [String: any Sendable] = [:] - params[jsonDataKey] = jsonData - returnedParams = params - } - return returnedParams - } - -} diff --git a/codegen/Templates/swift/JSONEncodingHelper.mustache b/codegen/Templates/swift/JSONEncodingHelper.mustache deleted file mode 100644 index d6e73086..00000000 --- a/codegen/Templates/swift/JSONEncodingHelper.mustache +++ /dev/null @@ -1,29 +0,0 @@ -// -// JSONEncodingHelper.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class JSONEncodingHelper { - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func encodingParameters(forEncodableObject encodableObj: T?, codableHelper: CodableHelper) -> [String: any Sendable]? { - var params: [String: any Sendable]? - - // Encode the Encodable object - if let encodableObj = encodableObj { - let encodeResult = codableHelper.encode(encodableObj) - do { - let data = try encodeResult.get() - params = JSONDataEncoding.encodingParameters(jsonData: data) - } catch { - print(error.localizedDescription) - } - } - - return params - } - -} diff --git a/codegen/Templates/swift/OpenAPIDateWithoutTime.mustache b/codegen/Templates/swift/OpenAPIDateWithoutTime.mustache deleted file mode 100644 index 0abef8a2..00000000 --- a/codegen/Templates/swift/OpenAPIDateWithoutTime.mustache +++ /dev/null @@ -1,98 +0,0 @@ -// OpenAPIDateWithoutTime.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation - -/// Represents a date without time information (e.g. a birthday) for transmission from and to a REST API -/// -/// This type is used as a representation for openapi specs `date` format which does not contain -/// time information as opposed to the `date-time` format. Although it internally uses `Date` for -/// (de-)serialization as well the generator needs to be able to distinguish between the two formats. -/// - note: As `Date` is agnostic to timezones (and calendars), timezone information is needed to be able to add -/// an appropriate padding in order to transform to GMT+0 which is the assumed timezone in ISO 8601. -/// When decoding, GMT+0 can be assumed (again: ISO8601) so there is no padding necessary and wrappedDate -/// can be used safely. -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct OpenAPIDateWithoutTime: Sendable, Codable, Hashable, Equatable { - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let wrappedDate: Date - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let timezone: TimeZone - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum CodingKeys: CodingKey, CaseIterable { - case wrappedDate - case timezone - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum DecodingError: Error { - case notADateString - } - - /// On decoding ISO8601 timezone is assumed - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - - let dateString = try container.decode(String.self) - guard let date = OpenISO8601DateFormatter.withoutTime.date(from: dateString) else { - throw DecodingError.notADateString - } - self.wrappedDate = date - - self.timezone = OpenISO8601DateFormatter.withoutTime.timeZone - } - - /// Convenience Initializer which is useful when dealing with optionals a lot like e.g. in API mappers - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init?(wrappedDate: Date?, timezone: TimeZone = .current) { - guard let wrappedDate = wrappedDate else { - return nil - } - - self.init(wrappedDate: wrappedDate, timezone: timezone) - } - - /// Designated Initializer for `OpenAPIDateWithoutTime` - /// - /// Since usually `Date`s without time components - as e.g. birthdays - are created Calendar- and timezone-aware - // it is important to also provide a timezone so that the generator is able to normalize the supplied Date to ISO8601 (GMT+0) - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(wrappedDate: Date, timezone: TimeZone) { - self.wrappedDate = wrappedDate - self.timezone = timezone - } - - /// Only the wrappedDate is encoded normalized to GMT+0 with an offset derived from the supplied Timezone - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(OpenISO8601DateFormatter.withoutTime.string(from: normalizedWrappedDate())) - } - - /// Normalizes the wrappedDate to GMT+0 according to the supplied timezone - fileprivate func normalizedWrappedDate() -> Date { - return wrappedDate.addingTimeInterval( - Double(timezone.secondsFromGMT(for: wrappedDate))) - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func == (lhs: Self, rhs: Self) -> Bool { - Calendar.current.compare(lhs.wrappedDate, to: rhs.wrappedDate, toGranularity: .day) == .orderedSame - } -} - -extension OpenAPIDateWithoutTime: ParameterConvertible { - func asParameter(codableHelper: CodableHelper) -> any Sendable { - return OpenISO8601DateFormatter.withoutTime.string(from: self.normalizedWrappedDate()) - } -} - -extension OpenAPIDateWithoutTime: RawRepresentable { - public typealias RawValue = String - public init?(rawValue: String) { - if let date = OpenISO8601DateFormatter.withoutTime.date(from: rawValue) { - self.init(wrappedDate: date) - } else { - return nil - } - } - - public var rawValue: String { - OpenISO8601DateFormatter.withoutTime.string(from: normalizedWrappedDate()) - } -} \ No newline at end of file diff --git a/codegen/Templates/swift/OpenAPIMutex.mustache b/codegen/Templates/swift/OpenAPIMutex.mustache deleted file mode 100644 index d7be0b6b..00000000 --- a/codegen/Templates/swift/OpenAPIMutex.mustache +++ /dev/null @@ -1,28 +0,0 @@ -// OpenAPIMutex.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation - -internal final class OpenAPIMutex: @unchecked Sendable { - - private var _value: Value - private let lock = NSRecursiveLock() - - internal init(_ value: Value) { - self._value = value - } - - internal var value: Value { - lock.withLock { _value } - } - - @discardableResult - internal func withValue( - _ body: (inout Value) throws -> Result - ) rethrows -> Result { - try lock.withLock { try body(&_value) } - } -} diff --git a/codegen/Templates/swift/OpenISO8601DateFormatter.mustache b/codegen/Templates/swift/OpenISO8601DateFormatter.mustache deleted file mode 100644 index fa2335d7..00000000 --- a/codegen/Templates/swift/OpenISO8601DateFormatter.mustache +++ /dev/null @@ -1,56 +0,0 @@ -// -// OpenISO8601DateFormatter.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation - -// https://stackoverflow.com/a/50281094/976628 -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} class OpenISO8601DateFormatter: DateFormatter, @unchecked Sendable { - static let withoutSeconds: DateFormatter = { - let formatter = DateFormatter() - formatter.calendar = Calendar(identifier: .iso8601) - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(secondsFromGMT: 0) - formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" - return formatter - }() - - static let withoutTime: DateFormatter = { - let formatter = DateFormatter() - formatter.calendar = Calendar(identifier: .iso8601) - formatter.locale = Locale(identifier: "en_US_POSIX") - formatter.timeZone = TimeZone(secondsFromGMT: 0) - formatter.dateFormat = "yyyy-MM-dd" - return formatter - }() - - private func setup() { - calendar = Calendar(identifier: .iso8601) - locale = Locale(identifier: "en_US_POSIX") - timeZone = TimeZone(secondsFromGMT: 0) - dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" - } - - override init() { - super.init() - setup() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setup() - } - - override {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func date(from string: String) -> Date? { - if let result = super.date(from: string) { - return result - } else if let result = OpenISO8601DateFormatter.withoutSeconds.date(from: string) { - return result - } - - return OpenISO8601DateFormatter.withoutTime.date(from: string) - } -} diff --git a/codegen/Templates/swift/Podspec.mustache b/codegen/Templates/swift/Podspec.mustache deleted file mode 100644 index 70b97e74..00000000 --- a/codegen/Templates/swift/Podspec.mustache +++ /dev/null @@ -1,38 +0,0 @@ -Pod::Spec.new do |s| - s.name = '{{projectName}}'{{#projectDescription}} - s.summary = '{{.}}'{{/projectDescription}} - s.ios.deployment_target = '13.0' - s.osx.deployment_target = '10.15' - s.tvos.deployment_target = '13.0' - s.watchos.deployment_target = '6.0' - s.version = '{{podVersion}}{{^podVersion}}{{#apiInfo}}{{version}}{{/apiInfo}}{{^apiInfo}}}0.0.1{{/apiInfo}}{{/podVersion}}' - s.source = {{#podSource}}{{& podSource}}{{/podSource}}{{^podSource}}{ :git => 'git@github.com:OpenAPITools/openapi-generator.git', :tag => 'v{{#apiInfo}}{{version}}{{/apiInfo}}{{^apiInfo}}}0.0.1{{/apiInfo}}' }{{/podSource}} - {{#podAuthors}} - s.authors = '{{.}}' - {{/podAuthors}} - {{#podSocialMediaURL}} - s.social_media_url = '{{.}}' - {{/podSocialMediaURL}} - s.license = {{#podLicense}}{{& podLicense}}{{/podLicense}}{{^podLicense}}'Proprietary'{{/podLicense}} - s.homepage = '{{podHomepage}}{{^podHomepage}}https://github.com/OpenAPITools/openapi-generator{{/podHomepage}}' - s.summary = '{{podSummary}}{{^podSummary}}{{projectName}} Swift SDK{{/podSummary}}' - {{#podDescription}} - s.description = '{{.}}' - {{/podDescription}} - {{#podScreenshots}} - s.screenshots = {{& podScreenshots}} - {{/podScreenshots}} - {{#podDocumentationURL}} - s.documentation_url = '{{.}}' - {{/podDocumentationURL}} - s.source_files = '{{swiftPackagePath}}{{^swiftPackagePath}}{{#useSPMFileStructure}}Sources/{{projectName}}{{/useSPMFileStructure}}{{^useSPMFileStructure}}{{projectName}}/Classes{{/useSPMFileStructure}}{{/swiftPackagePath}}/**/*.swift' - {{#useAlamofire}} - s.dependency 'Alamofire', '~> 5.10' - {{/useAlamofire}} - {{#usePromiseKit}} - s.dependency 'PromiseKit/CorePromise', '~> 8.1' - {{/usePromiseKit}} - {{#useRxSwift}} - s.dependency 'RxSwift', '~> 6.8' - {{/useRxSwift}} -end diff --git a/codegen/Templates/swift/SynchronizedDictionary.mustache b/codegen/Templates/swift/SynchronizedDictionary.mustache deleted file mode 100644 index 6c80862c..00000000 --- a/codegen/Templates/swift/SynchronizedDictionary.mustache +++ /dev/null @@ -1,17 +0,0 @@ -// SynchronizedDictionary.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation - -internal final class SynchronizedDictionary: @unchecked Sendable { - - private let _state = OpenAPIMutex<[K: V]>([:]) - - internal subscript(key: K) -> V? { - get { _state.value[key] } - set { _state.withValue { $0[key] = newValue } } - } -} diff --git a/codegen/Templates/swift/Validation.mustache b/codegen/Templates/swift/Validation.mustache deleted file mode 100644 index adfb876f..00000000 --- a/codegen/Templates/swift/Validation.mustache +++ /dev/null @@ -1,161 +0,0 @@ -// Validation.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct StringRule: Sendable { - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var minLength: Int? - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var maxLength: Int? - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var pattern: String? -} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct NumericRule { - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var minimum: T? - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var exclusiveMinimum = false - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var maximum: T? - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var exclusiveMaximum = false - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var multipleOf: T? -} -extension NumericRule: Sendable where T: Sendable {} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct ArrayRule: Sendable { - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var minItems: Int? - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var maxItems: Int? - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var uniqueItems: Bool -} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum StringValidationErrorKind: Error, Sendable { - case minLength, maxLength, pattern -} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum NumericValidationErrorKind: Error, Sendable { - case minimum, maximum, multipleOf -} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum ArrayValidationErrorKind: Error, Sendable { - case minItems, maxItems, uniqueItems -} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct ValidationError: Error, Sendable { - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} fileprivate(set) var kinds: Set -} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct Validator: Sendable { - /// Validate a string against a rule. - /// - Parameter string: The String you wish to validate. - /// - Parameter rule: The StringRule you wish to use for validation. - /// - Returns: A validated string. - /// - Throws: `ValidationError` if the string is invalid against the rule or if the rule.pattern is invalid. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ string: String, against rule: StringRule) throws(ValidationError) -> String { - var error = ValidationError(kinds: []) - if let minLength = rule.minLength, !(minLength <= string.count) { - error.kinds.insert(.minLength) - } - if let maxLength = rule.maxLength, !(string.count <= maxLength) { - error.kinds.insert(.maxLength) - } - if let pattern = rule.pattern { - let matches = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) - .matches(in: string, range: .init(location: 0, length: string.utf16.count)) - if matches?.isEmpty != false { - error.kinds.insert(.pattern) - } - } - guard error.kinds.isEmpty else { - throw error - } - return string - } - - /// Validate a integer against a rule. - /// - Parameter numeric: The integer you wish to validate. - /// - Parameter rule: The NumericRule you wish to use for validation. - /// - Returns: A validated integer. - /// - Throws: `ValidationError` if the numeric is invalid against the rule. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ numeric: T, against rule: NumericRule) throws(ValidationError) -> T { - var error = ValidationError(kinds: []) - if let minimum = rule.minimum { - if !rule.exclusiveMinimum, minimum > numeric { - error.kinds.insert(.minimum) - } - if rule.exclusiveMinimum, minimum >= numeric { - error.kinds.insert(.minimum) - } - } - if let maximum = rule.maximum { - if !rule.exclusiveMaximum, numeric > maximum { - error.kinds.insert(.maximum) - } - if rule.exclusiveMaximum, numeric >= maximum { - error.kinds.insert(.maximum) - } - } - if let multipleOf = rule.multipleOf, !numeric.isMultiple(of: multipleOf) { - error.kinds.insert(.multipleOf) - } - guard error.kinds.isEmpty else { - throw error - } - return numeric - } - - /// Validate a fractional number against a rule. - /// - Parameter numeric: The fractional number you wish to validate. - /// - Parameter rule: The NumericRule you wish to use for validation. - /// - Returns: A validated fractional number. - /// - Throws: `ValidationError` if the numeric is invalid against the rule. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ numeric: T, against rule: NumericRule) throws(ValidationError) -> T { - var error = ValidationError(kinds: []) - if let minimum = rule.minimum { - if !rule.exclusiveMinimum, minimum > numeric { - error.kinds.insert(.minimum) - } - if rule.exclusiveMinimum, minimum >= numeric { - error.kinds.insert(.minimum) - } - } - if let maximum = rule.maximum { - if !rule.exclusiveMaximum, numeric > maximum { - error.kinds.insert(.maximum) - } - if rule.exclusiveMaximum, numeric >= maximum { - error.kinds.insert(.maximum) - } - } - if let multipleOf = rule.multipleOf, numeric.remainder(dividingBy: multipleOf) != 0 { - error.kinds.insert(.multipleOf) - } - guard error.kinds.isEmpty else { - throw error - } - return numeric - } - - /// Validate a array against a rule. - /// - Parameter array: The Array you wish to validate. - /// - Parameter rule: The ArrayRule you wish to use for validation. - /// - Returns: A validated array. - /// - Throws: `ValidationError` if the string is invalid against the rule. - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func validate(_ array: Array, against rule: ArrayRule) throws(ValidationError) -> Array { - var error = ValidationError(kinds: []) - if let minItems = rule.minItems, !(minItems <= array.count) { - error.kinds.insert(.minItems) - } - if let maxItems = rule.maxItems, !(array.count <= maxItems) { - error.kinds.insert(.maxItems) - } - if rule.uniqueItems { - let unique = Set(array) - if unique.count != array.count { - error.kinds.insert(.uniqueItems) - } - } - guard error.kinds.isEmpty else { - throw error - } - return array - } -} diff --git a/codegen/Templates/swift/XcodeGen.mustache b/codegen/Templates/swift/XcodeGen.mustache deleted file mode 100644 index eec5b2b7..00000000 --- a/codegen/Templates/swift/XcodeGen.mustache +++ /dev/null @@ -1,17 +0,0 @@ -name: {{projectName}} -targets: - {{projectName}}: - type: framework - platform: iOS - deploymentTarget: "11.0" - sources: [{{swiftPackagePath}}{{^swiftPackagePath}}{{#useSPMFileStructure}}Sources{{/useSPMFileStructure}}{{^useSPMFileStructure}}{{projectName}}{{/useSPMFileStructure}}{{/swiftPackagePath}}] - info: - path: ./Info.plist - version: {{podVersion}}{{^podVersion}}{{#apiInfo}}{{version}}{{/apiInfo}}{{^apiInfo}}}0.0.1{{/apiInfo}}{{/podVersion}} - settings: - APPLICATION_EXTENSION_API_ONLY: true - scheme: {} - dependencies:{{#useAlamofire}} - - carthage: Alamofire{{/useAlamofire}}{{#usePromiseKit}} - - carthage: PromiseKit{{/usePromiseKit}}{{#useRxSwift}} - - carthage: RxSwift{{/useRxSwift}} diff --git a/codegen/Templates/swift/_param.mustache b/codegen/Templates/swift/_param.mustache deleted file mode 100644 index 24e2f369..00000000 --- a/codegen/Templates/swift/_param.mustache +++ /dev/null @@ -1 +0,0 @@ -"{{baseName}}": {{#isQueryParam}}(wrappedValue: {{/isQueryParam}}{{paramName}}{{^required}}?{{/required}}.asParameter(codableHelper: apiConfiguration.codableHelper){{#isQueryParam}}, isExplode: {{isExplode}}){{/isQueryParam}} \ No newline at end of file diff --git a/codegen/Templates/swift/api_doc.mustache b/codegen/Templates/swift/api_doc.mustache deleted file mode 100644 index 9024673a..00000000 --- a/codegen/Templates/swift/api_doc.mustache +++ /dev/null @@ -1,141 +0,0 @@ -# {{classname}}{{#description}} -{{.}}{{/description}} - -All URIs are relative to *{{{basePath}}}* - -Method | HTTP request | Description -------------- | ------------- | ------------- -{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{summary}} -{{/operation}}{{/operations}} - -{{#operations}} -{{#operation}} -# **{{{operationId}}}** -```swift -{{^usePromiseKit}} -{{^useRxSwift}} -{{^useVapor}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}completion: @escaping (_ data: {{{returnType}}}{{^returnType}}Void{{/returnType}}?, _ error: Error?) -> Void) -{{/useVapor}} -{{/useRxSwift}} -{{/usePromiseKit}} -{{#usePromiseKit}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}} {{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Promise<{{{returnType}}}{{^returnType}}Void{{/returnType}}> -{{/usePromiseKit}} -{{#useRxSwift}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}) -> Observable<{{{returnType}}}{{^returnType}}Void{{/returnType}}> -{{/useRxSwift}} -{{#useVapor}} - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}[{{enumName}}_{{operationId}}]{{/isContainer}}{{^isContainer}}{{enumName}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}headers: HTTPHeaders = {{projectName}}APIConfiguration.shared.customHeaders, beforeSend: (inout ClientRequest) throws -> () = { _ in }) -> EventLoopFuture<{{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}}> -{{/useVapor}} -``` - -{{{summary}}}{{#notes}} - -{{{.}}}{{/notes}} - -### Example -```swift -// The following code samples are still beta. For any issue, please report via http://github.com/OpenAPITools/openapi-generator/issues/new -import {{{projectName}}} - -{{#allParams}}let {{paramName}} = {{{vendorExtensions.x-swift-example}}} // {{{dataType}}} | {{{description}}}{{^required}} (optional){{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}} -{{/allParams}} - -{{^usePromiseKit}} -{{^useRxSwift}} -{{^useVapor}} -{{#summary}} -// {{{.}}} -{{/summary}} -{{classname}}.{{{operationId}}}({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}) { (response, error) in - guard error == nil else { - print(error) - return - } - - if (response) { - dump(response) - } -} -{{/useVapor}} -{{/useRxSwift}} -{{/usePromiseKit}} -{{#usePromiseKit}} -{{#summary}} -// {{{.}}} -{{/summary}} -{{classname}}.{{{operationId}}}({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).then { - // when the promise is fulfilled - }.always { - // regardless of whether the promise is fulfilled, or rejected - }.catch { errorType in - // when the promise is rejected -} -{{/usePromiseKit}} -{{#useRxSwift}} -// TODO RxSwift sample code not yet implemented. To contribute, please open a ticket via http://github.com/OpenAPITools/openapi-generator/issues/new -{{/useRxSwift}} -{{#useVapor}} -{{#summary}} -// {{{.}}} -{{/summary}} -{{classname}}.{{{operationId}}}({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).whenComplete { result in - switch result { - case .failure(let error): - // process error - case .success(let response): - switch response { - // process decoded response value or raw ClientResponse - {{#responses}} - case .http{{code}}(let value, let raw): - {{/responses}} - {{^hasDefaultResponse}} - case .http0(let value, let raw): - {{/hasDefaultResponse}} - } - } -} -{{/useVapor}} -``` - -### Parameters -{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}} -Name | Type | Description | Notes -------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}} -{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{baseType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#defaultValue}}[default to {{.}}]{{/defaultValue}} -{{/allParams}} - -### Return type - -{{#useVapor}} -#### {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}} - -```swift -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{#lambda.titlecase}}{{operationId}}{{/lambda.titlecase}} { - {{#responses}} - case http{{code}}(value: {{#dataType}}{{.}}?{{/dataType}}{{^dataType}}Void?{{/dataType}}, raw: ClientResponse) - {{/responses}} - {{^hasDefaultResponse}} - case http0(value: {{#returnType}}{{.}}?{{/returnType}}{{^returnType}}Void?{{/returnType}}, raw: ClientResponse) - {{/hasDefaultResponse}} -} -``` -{{/useVapor}} -{{^useVapor}} -{{#returnType}}{{#returnTypeIsPrimitive}}**{{{returnType}}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{{returnType}}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}Void (empty response body){{/returnType}} -{{/useVapor}} - -### Authorization - -{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{{name}}}](../README.md#{{{name}}}){{^-last}}, {{/-last}}{{/authMethods}} - -### HTTP request headers - - - **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}} - - **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}} - -[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) - -{{/operation}} -{{/operations}} diff --git a/codegen/Templates/swift/git_push.sh.mustache b/codegen/Templates/swift/git_push.sh.mustache deleted file mode 100644 index 0e3776ae..00000000 --- a/codegen/Templates/swift/git_push.sh.mustache +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/sh -# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ -# -# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" - -git_user_id=$1 -git_repo_id=$2 -release_note=$3 -git_host=$4 - -if [ "$git_host" = "" ]; then - git_host="{{{gitHost}}}" - echo "[INFO] No command line input provided. Set \$git_host to $git_host" -fi - -if [ "$git_user_id" = "" ]; then - git_user_id="{{{gitUserId}}}" - echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" -fi - -if [ "$git_repo_id" = "" ]; then - git_repo_id="{{{gitRepoId}}}" - echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" -fi - -if [ "$release_note" = "" ]; then - release_note="{{{releaseNote}}}" - echo "[INFO] No command line input provided. Set \$release_note to $release_note" -fi - -# Initialize the local directory as a Git repository -git init - -# Adds the files in the local repository and stages them for commit. -git add . - -# Commits the tracked changes and prepares them to be pushed to a remote repository. -git commit -m "$release_note" - -# Sets the new remote -git_remote=$(git remote) -if [ "$git_remote" = "" ]; then # git remote not defined - - if [ "$GIT_TOKEN" = "" ]; then - echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." - git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git - else - git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git - fi - -fi - -git pull origin master - -# Pushes (Forces) the changes in the local repository up to the remote repository -echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" -git push origin master 2>&1 | grep -v 'To https' diff --git a/codegen/Templates/swift/gitignore.mustache b/codegen/Templates/swift/gitignore.mustache deleted file mode 100644 index 316a8450..00000000 --- a/codegen/Templates/swift/gitignore.mustache +++ /dev/null @@ -1,100 +0,0 @@ -# Created by https://www.toptal.com/developers/gitignore/api/xcode,swift -# Edit at https://www.toptal.com/developers/gitignore?templates=xcode,swift - -### Swift ### -# Xcode -# -# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore - -## User settings -xcuserdata/ - -## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) -*.xcscmblueprint -*.xccheckout - -## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) -build/ -DerivedData/ -*.moved-aside -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 - -## Obj-C/Swift specific -*.hmap - -## App packaging -*.ipa -*.dSYM.zip -*.dSYM - -## Playgrounds -timeline.xctimeline -playground.xcworkspace - -# Swift Package Manager -# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. -# Packages/ -# Package.pins -# Package.resolved -# *.xcodeproj -# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata -# hence it is not needed unless you have added a package configuration file to your project -# .swiftpm - -.build/ - -# CocoaPods -# We recommend against adding the Pods directory to your .gitignore. However -# you should judge for yourself, the pros and cons are mentioned at: -# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control -# Pods/ -# Add this line if you want to avoid checking in source code from the Xcode workspace -# *.xcworkspace - -# Carthage -# Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts - -Carthage/Build/ - -# Accio dependency management -Dependencies/ -.accio/ - -# fastlane -# It is recommended to not store the screenshots in the git repo. -# Instead, use fastlane to re-generate the screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/#source-control - -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots/**/*.png -fastlane/test_output - -# Code Injection -# After new code Injection tools there's a generated folder /iOSInjectionProject -# https://github.com/johnno1962/injectionforxcode - -iOSInjectionProject/ - -### Xcode ### - -## Xcode 8 and earlier - -### Xcode Patch ### -*.xcodeproj/* -!*.xcodeproj/project.pbxproj -!*.xcodeproj/xcshareddata/ -!*.xcworkspace/contents.xcworkspacedata -/*.gcno -**/xcshareddata/WorkspaceSettings.xcsettings - -# End of https://www.toptal.com/developers/gitignore/api/xcode,swift diff --git a/codegen/Templates/swift/libraries/alamofire/AlamofireImplementations.mustache b/codegen/Templates/swift/libraries/alamofire/AlamofireImplementations.mustache deleted file mode 100644 index 7ee7cd98..00000000 --- a/codegen/Templates/swift/libraries/alamofire/AlamofireImplementations.mustache +++ /dev/null @@ -1,420 +0,0 @@ -// AlamofireImplementations.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation -import Alamofire - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} final class AlamofireRequestBuilderFactory: RequestBuilderFactory, Sendable { - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init() {} - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func getNonDecodableBuilder() -> RequestBuilder.Type { - return AlamofireRequestBuilder.self - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func getBuilder() -> RequestBuilder.Type { - return AlamofireDecodableRequestBuilder.self - } -} - -fileprivate class AlamofireRequestBuilderConfiguration: @unchecked Sendable { - private init() {} - static let shared = AlamofireRequestBuilderConfiguration() - - // Store manager to retain its reference - let managerStore = SynchronizedDictionary() -} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class AlamofireRequestBuilder: RequestBuilder, @unchecked Sendable { - required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String: any Sendable]?, headers: [String: String] = [:], requiresAuthentication: Bool, apiConfiguration: {{projectName}}APIConfiguration = {{projectName}}APIConfiguration.shared) { - super.init(method: method, URLString: URLString, parameters: parameters, headers: headers, requiresAuthentication: requiresAuthentication, apiConfiguration: apiConfiguration) - } - - /** - May be overridden by a subclass if you want to control the session - configuration. - */ - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func createAlamofireSession() -> Alamofire.Session { - let configuration = URLSessionConfiguration.default - configuration.httpAdditionalHeaders = buildHeaders() - return Alamofire.Session(configuration: configuration, - interceptor: apiConfiguration.interceptor) - } - - /** - May be overridden by a subclass if you want to custom request constructor. - */ - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func createURLRequest() -> URLRequest? { - let xMethod = Alamofire.HTTPMethod(rawValue: method) - - let encoding: ParameterEncoding - - switch xMethod { - case .get, .head: - encoding = URLEncoding() - - case .options, .post, .put, .patch, .delete, .trace, .connect: - encoding = JSONDataEncoding() - - default: - fatalError("Unsupported HTTPMethod - \(xMethod.rawValue)") - } - - guard let originalRequest = try? URLRequest(url: URLString, method: xMethod, headers: HTTPHeaders(buildHeaders())) else { return nil } - return try? encoding.encode(originalRequest, with: parameters) - } - - /** - May be overridden by a subclass if you want to control the Content-Type - that is given to an uploaded form part. - - Return nil to use the default behavior (inferring the Content-Type from - the file extension). Return the desired Content-Type otherwise. - */ - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func contentTypeForFormPart(fileURL: URL) -> String? { - return nil - } - - /** - May be overridden by a subclass if you want to control the request - configuration (e.g. to override the cache policy). - */ - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func makeRequest(manager: Session, method: HTTPMethod, encoding: ParameterEncoding, headers: [String: String]) -> DataRequest { - return manager.request(URLString, method: method, parameters: parameters, encoding: encoding, headers: HTTPHeaders(headers)) - } - - @discardableResult - override {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute(completion: @Sendable @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) -> RequestTask { - let managerId = UUID().uuidString - // Create a new manager for each request to customize its request header - let manager = createAlamofireSession() - AlamofireRequestBuilderConfiguration.shared.managerStore[managerId] = manager - - let xMethod = Alamofire.HTTPMethod(rawValue: method) - - let encoding: ParameterEncoding? - - switch xMethod { - case .get, .head: - encoding = URLEncoding() - - case .options, .post, .put, .patch, .delete, .trace, .connect: - let contentType = headers["Content-Type"] ?? "application/json" - - if contentType.hasPrefix("application/") && contentType.contains("json") { - encoding = JSONDataEncoding() - } else if contentType.hasPrefix("multipart/form-data") { - encoding = nil - - let upload = manager.upload(multipartFormData: { mpForm in - for (k, v) in self.parameters! { - for v in (v as? Array ?? [v]) { - switch v { - case let fileURL as URL: - if let mimeType = self.contentTypeForFormPart(fileURL: fileURL) { - mpForm.append(fileURL, withName: k, fileName: fileURL.lastPathComponent, mimeType: mimeType) - } else { - mpForm.append(fileURL, withName: k) - } - case let string as String: - mpForm.append(string.data(using: String.Encoding.utf8)!, withName: k) - case let number as NSNumber: - mpForm.append(number.stringValue.data(using: String.Encoding.utf8)!, withName: k) - case let data as Data: - mpForm.append(data, withName: k) - case let uuid as UUID: - mpForm.append(uuid.uuidString.data(using: String.Encoding.utf8)!, withName: k) - default: - fatalError("Unprocessable value \(v) with key \(k)") - } - } - } - }, to: URLString, method: xMethod, headers: nil) - .uploadProgress { progress in - if let onProgressReady = self.onProgressReady { - onProgressReady(progress) - } - } - - requestTask.set(request: upload) - - self.processRequest(request: upload, managerId: managerId, completion: completion) - } else if contentType.hasPrefix("application/x-www-form-urlencoded") { - encoding = URLEncoding(destination: .httpBody) - } else { - fatalError("Unsupported Media Type - \(contentType)") - } - - default: - fatalError("Unsupported HTTPMethod - \(xMethod.rawValue)") - } - - if let encoding = encoding { - let request = makeRequest(manager: manager, method: xMethod, encoding: encoding, headers: headers) - if let onProgressReady = self.onProgressReady { - onProgressReady(request.uploadProgress) - } - processRequest(request: request, managerId: managerId, completion: completion) - requestTask.set(request: request) - } - - return requestTask - } - - fileprivate func processRequest(request: DataRequest, managerId: String, completion: @Sendable @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { - if let credential = self.credential { - request.authenticate(with: credential) - } - - let cleanupRequest = { @Sendable in - AlamofireRequestBuilderConfiguration.shared.managerStore[managerId] = nil - } - - let validatedRequest = request.validate(statusCode: apiConfiguration.successfulStatusCodeRange) - - switch T.self { - case is Void.Type: - validatedRequest.response(queue: apiConfiguration.apiResponseQueue, - responseSerializer: apiConfiguration.dataResponseSerializer, - completionHandler: { voidResponse in - cleanupRequest() - - switch voidResponse.result { - case .success: - completion(.success(Response(response: voidResponse.response!, body: () as! T, bodyData: voidResponse.data))) - case let .failure(error): - completion(.failure(ErrorResponse.error(voidResponse.response?.statusCode ?? 500, voidResponse.data, voidResponse.response, error))) - } - - }) - default: - fatalError("Unsupported Response Body Type - \(String(describing: T.self))") - } - } - - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func buildHeaders() -> [String: String] { - var httpHeaders = Alamofire.HTTPHeaders.default.dictionary - for (key, value) in headers { - httpHeaders[key] = value - } - return httpHeaders - } - - fileprivate func getFileName(fromContentDisposition contentDisposition: String?) -> String? { - - guard let contentDisposition = contentDisposition else { - return nil - } - - let items = contentDisposition.components(separatedBy: ";") - - var filename: String? - - for contentItem in items { - - let filenameKey = "filename=" - guard let range = contentItem.range(of: filenameKey) else { - continue - } - - filename = contentItem - return filename? - .replacingCharacters(in: range, with: "") - .replacingOccurrences(of: "\"", with: "") - .trimmingCharacters(in: .whitespacesAndNewlines) - } - - return filename - - } - - fileprivate func getPath(from url: URL) throws -> String { - - guard var path = URLComponents(url: url, resolvingAgainstBaseURL: true)?.path else { - throw DownloadException.requestMissingPath - } - - if path.hasPrefix("/") { - path.remove(at: path.startIndex) - } - - return path - - } - - fileprivate func getURL(from urlRequest: URLRequest) throws -> URL { - - guard let url = urlRequest.url else { - throw DownloadException.requestMissingURL - } - - return url - } - -} - -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class AlamofireDecodableRequestBuilder: AlamofireRequestBuilder, @unchecked Sendable { - - override fileprivate func processRequest(request: DataRequest, managerId: String, completion: @Sendable @escaping (_ result: Swift.Result, ErrorResponse>) -> Void) { - if let credential = self.credential { - request.authenticate(with: credential) - } - - let cleanupRequest = { @Sendable in - AlamofireRequestBuilderConfiguration.shared.managerStore[managerId] = nil - } - - let validatedRequest = request.validate(statusCode: apiConfiguration.successfulStatusCodeRange) - - switch T.self { - case is String.Type: - validatedRequest.response(queue: apiConfiguration.apiResponseQueue, - responseSerializer: apiConfiguration.stringResponseSerializer, - completionHandler: { stringResponse in - cleanupRequest() - - switch stringResponse.result { - case let .success(value): - completion(.success(Response(response: stringResponse.response!, body: value as! T, bodyData: stringResponse.data))) - case let .failure(error): - completion(.failure(ErrorResponse.error(stringResponse.response?.statusCode ?? 500, stringResponse.data, stringResponse.response, error))) - } - - }) - case is URL.Type: - validatedRequest.response(queue: apiConfiguration.apiResponseQueue, - responseSerializer: apiConfiguration.dataResponseSerializer, - completionHandler: { dataResponse in - cleanupRequest() - - do { - - guard case .success = dataResponse.result else { - throw DownloadException.responseFailed - } - - guard let data = dataResponse.data else { - throw DownloadException.responseDataMissing - } - - guard let request = request.request else { - throw DownloadException.requestMissing - } - - let fileManager = FileManager.default - let urlRequest = try request.asURLRequest() - let cachesDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask)[0] - let requestURL = try self.getURL(from: urlRequest) - - var requestPath = try self.getPath(from: requestURL) - - if let headerFileName = self.getFileName(fromContentDisposition: dataResponse.response?.allHeaderFields["Content-Disposition"] as? String) { - requestPath = requestPath.appending("/\(headerFileName)") - } else { - requestPath = requestPath.appending("/tmp.{{projectName}}.\(UUID().uuidString)") - } - - let filePath = cachesDirectory.appendingPathComponent(requestPath) - let directoryPath = filePath.deletingLastPathComponent().path - - try fileManager.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil) - try data.write(to: filePath, options: .atomic) - - completion(.success(Response(response: dataResponse.response!, body: filePath as! T, bodyData: data))) - - } catch let requestParserError as DownloadException { - completion(.failure(ErrorResponse.error(400, dataResponse.data, dataResponse.response, requestParserError))) - } catch { - completion(.failure(ErrorResponse.error(400, dataResponse.data, dataResponse.response, error))) - } - return - }) - case is Void.Type: - validatedRequest.response(queue: apiConfiguration.apiResponseQueue, - responseSerializer: apiConfiguration.dataResponseSerializer, - completionHandler: { voidResponse in - cleanupRequest() - - switch voidResponse.result { - case .success: - completion(.success(Response(response: voidResponse.response!, body: () as! T, bodyData: voidResponse.data))) - case let .failure(error): - completion(.failure(ErrorResponse.error(voidResponse.response?.statusCode ?? 500, voidResponse.data, voidResponse.response, error))) - } - - }) - case is Data.Type: - validatedRequest.response(queue: apiConfiguration.apiResponseQueue, - responseSerializer: apiConfiguration.dataResponseSerializer, - completionHandler: { dataResponse in - cleanupRequest() - - switch dataResponse.result { - case .success: - completion(.success(Response(response: dataResponse.response!, body: dataResponse.data as! T, bodyData: dataResponse.data))) - case let .failure(error): - completion(.failure(ErrorResponse.error(dataResponse.response?.statusCode ?? 500, dataResponse.data, dataResponse.response, error))) - } - - }) - default: - validatedRequest.response(queue: apiConfiguration.apiResponseQueue, - responseSerializer: apiConfiguration.dataResponseSerializer, - completionHandler: { dataResponse in - cleanupRequest() - - if case let .failure(error) = dataResponse.result { - completion(.failure(ErrorResponse.error(dataResponse.response?.statusCode ?? 500, dataResponse.data, dataResponse.response, error))) - return - } - - guard let httpResponse = dataResponse.response else { - completion(.failure(ErrorResponse.error(-2, nil, dataResponse.response, DecodableRequestBuilderError.nilHTTPResponse))) - return - } - - guard let unwrappedData = dataResponse.data, !unwrappedData.isEmpty else { - if let expressibleByNilLiteralType = T.self as? ExpressibleByNilLiteral.Type { - completion(.success(Response(response: httpResponse, body: expressibleByNilLiteralType.init(nilLiteral: ()) as! T, bodyData: dataResponse.data))) - } else { - completion(.failure(ErrorResponse.error(httpResponse.statusCode, nil, httpResponse, DecodableRequestBuilderError.emptyDataResponse))) - } - return - } - - let decodeResult = self.apiConfiguration.codableHelper.decode(T.self, from: unwrappedData) - - switch decodeResult { - case let .success(decodableObj): - completion(.success(Response(response: httpResponse, body: decodableObj, bodyData: unwrappedData))) - case let .failure(error): - completion(.failure(ErrorResponse.error(httpResponse.statusCode, unwrappedData, httpResponse, error))) - } - - }) - } - } - -} - -extension JSONDataEncoding: ParameterEncoding { - - // MARK: Encoding - - /// Creates a URL request by encoding parameters and applying them onto an existing request. - /// - /// - parameter urlRequest: The request to have parameters applied. - /// - parameter parameters: The parameters to apply. This should have a single key/value - /// pair with "jsonData" as the key and a Data object as the value. - /// - /// - throws: An `Error` if the encoding process encounters an error. - /// - /// - returns: The encoded request. - public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest { - let urlRequest = try urlRequest.asURLRequest() - - return encode(request: urlRequest, with: parameters) - } -} diff --git a/codegen/Templates/swift/model.mustache b/codegen/Templates/swift/model.mustache deleted file mode 100644 index 8f3127a0..00000000 --- a/codegen/Templates/swift/model.mustache +++ /dev/null @@ -1,29 +0,0 @@ -{{#models}}{{#model}}// -// {{classname}}.swift -// -// Generated by openapi-generator -// https://openapi-generator.tech -// - -import Foundation{{#useVapor}} -import Vapor{{/useVapor}} -{{#swiftUseApiNamespace}} - -@available(*, deprecated, renamed: "{{projectName}}API.{{classname}}") -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} typealias {{classname}} = {{projectName}}API.{{classname}} - -extension {{projectName}}API { -{{/swiftUseApiNamespace}} -{{#description}} - -/** {{.}} */{{/description}}{{#isDeprecated}} -@available(*, deprecated, message: "This schema is deprecated."){{/isDeprecated}}{{#vendorExtensions.x-is-one-of-interface}} -{{> modelOneOf}}{{/vendorExtensions.x-is-one-of-interface}}{{^vendorExtensions.x-is-one-of-interface}}{{#isArray}} -{{> modelArray}}{{/isArray}}{{^isArray}}{{#isEnum}} -{{> modelEnum}}{{/isEnum}}{{^isEnum}} -{{> modelObject}}{{/isEnum}}{{/isArray}}{{/vendorExtensions.x-is-one-of-interface}}{{/model}}{{/models}} -{{#swiftUseApiNamespace}} -} -{{/swiftUseApiNamespace}}{{#models}}{{#model}}{{#vendorExtensions.x-swift-identifiable}} -extension {{#swiftUseApiNamespace}}{{projectName}}API.{{/swiftUseApiNamespace}}{{{classname}}}: Identifiable {} -{{/vendorExtensions.x-swift-identifiable}}{{/model}}{{/models}} \ No newline at end of file diff --git a/codegen/Templates/swift/modelArray.mustache b/codegen/Templates/swift/modelArray.mustache deleted file mode 100644 index 536c5e9e..00000000 --- a/codegen/Templates/swift/modelArray.mustache +++ /dev/null @@ -1 +0,0 @@ -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} typealias {{classname}} = {{parent}} \ No newline at end of file diff --git a/codegen/Templates/swift/modelEnum.mustache b/codegen/Templates/swift/modelEnum.mustache deleted file mode 100644 index 6673dc2f..00000000 --- a/codegen/Templates/swift/modelEnum.mustache +++ /dev/null @@ -1,10 +0,0 @@ -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{classname}}: {{dataType}}, Sendable, {{#useVapor}}Content, Hashable{{/useVapor}}{{^useVapor}}Codable{{^isString}}{{^isInteger}}{{^isFloat}}{{^isDouble}}{{#useParameterConvertible}}, ParameterConvertible{{/useParameterConvertible}}{{/isDouble}}{{/isFloat}}{{/isInteger}}{{/isString}}{{/useVapor}}, CaseIterable{{#enumUnknownDefaultCase}}{{#isInteger}}, CaseIterableDefaultsLast{{/isInteger}}{{#isFloat}}, CaseIterableDefaultsLast{{/isFloat}}{{#isDouble}}, CaseIterableDefaultsLast{{/isDouble}}{{#isString}}, CaseIterableDefaultsLast{{/isString}}{{/enumUnknownDefaultCase}} { -{{#allowableValues}} -{{#enumVars}} - {{#enumDescription}} - /// {{enumDescription}} - {{/enumDescription}} - case {{{name}}} = {{{value}}} -{{/enumVars}} -{{/allowableValues}} -} \ No newline at end of file diff --git a/codegen/Templates/swift/modelInlineEnumDeclaration.mustache b/codegen/Templates/swift/modelInlineEnumDeclaration.mustache deleted file mode 100644 index 3c75296a..00000000 --- a/codegen/Templates/swift/modelInlineEnumDeclaration.mustache +++ /dev/null @@ -1,10 +0,0 @@ - {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{enumName}}: {{^isContainer}}{{dataType}}{{/isContainer}}{{#isContainer}}String{{/isContainer}}, Sendable, {{#useVapor}}Content, Hashable{{/useVapor}}{{^useVapor}}Codable{{^isContainer}}{{^isString}}{{^isInteger}}{{^isFloat}}{{^isDouble}}{{#useParameterConvertible}}, ParameterConvertible{{/useParameterConvertible}}{{/isDouble}}{{/isFloat}}{{/isInteger}}{{/isString}}{{/isContainer}}{{/useVapor}}, CaseIterable{{#enumUnknownDefaultCase}}{{#isInteger}}, CaseIterableDefaultsLast{{/isInteger}}{{#isFloat}}, CaseIterableDefaultsLast{{/isFloat}}{{#isDouble}}, CaseIterableDefaultsLast{{/isDouble}}{{#isString}}, CaseIterableDefaultsLast{{/isString}}{{#isContainer}}, CaseIterableDefaultsLast{{/isContainer}}{{/enumUnknownDefaultCase}} { - {{#allowableValues}} - {{#enumVars}} - {{#enumDescription}} - /// {{enumDescription}} - {{/enumDescription}} - case {{{name}}} = {{{value}}} - {{/enumVars}} - {{/allowableValues}} - } \ No newline at end of file diff --git a/codegen/Templates/swift/modelOneOf.mustache b/codegen/Templates/swift/modelOneOf.mustache deleted file mode 100644 index b2efa05e..00000000 --- a/codegen/Templates/swift/modelOneOf.mustache +++ /dev/null @@ -1,70 +0,0 @@ -{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{classname}}: {{^useClasses}}Sendable, {{/useClasses}}{{#useClasses}}{{#readonlyProperties}}Sendable, {{/readonlyProperties}}{{/useClasses}}{{#useVapor}}Content{{/useVapor}}{{^useVapor}}Codable{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}}{{/useVapor}} { - {{#oneOf}} - case type{{#transformArrayType}}{{.}}{{/transformArrayType}}({{.}}) - {{/oneOf}} - {{#oneOfUnknownDefaultCase}} - case unknownDefaultOpenApi - {{/oneOfUnknownDefaultCase}} - - public func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - {{#oneOf}} - case .type{{#transformArrayType}}{{.}}{{/transformArrayType}}(let value): - try container.encode(value) - {{/oneOf}} - {{#oneOfUnknownDefaultCase}} - case .unknownDefaultOpenApi: - try container.encodeNil() - {{/oneOfUnknownDefaultCase}} - } - } -{{#discriminator}} - - private enum DiscriminatorCodingKey: String, CodingKey { - case {{discriminator.propertyName}} = "{{discriminator.propertyBaseName}}" - } -{{/discriminator}} - - public init(from decoder: Decoder) throws { -{{#discriminator}} let keyedContainer = try decoder.container(keyedBy: DiscriminatorCodingKey.self) - let discriminatorValue = try keyedContainer.decode(String.self, forKey: .{{discriminator.propertyName}}) - - switch discriminatorValue { - {{#discriminator.mappedModels}} - case "{{mappingName}}": - self = .type{{#transformArrayType}}{{modelName}}{{/transformArrayType}}(try {{modelName}}(from: decoder)) - {{/discriminator.mappedModels}} - default: - {{#oneOfUnknownDefaultCase}} - self = .unknownDefaultOpenApi - {{/oneOfUnknownDefaultCase}} - {{^oneOfUnknownDefaultCase}} - throw DecodingError.dataCorrupted( - DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Unknown discriminator value '\(discriminatorValue)' for {{classname}}" - ) - ) - {{/oneOfUnknownDefaultCase}} - } -{{/discriminator}}{{^discriminator}} let container = try decoder.singleValueContainer() - {{#oneOf}} - {{#-first}} - if let value = try? container.decode({{.}}.self) { - {{/-first}} - {{^-first}} - } else if let value = try? container.decode({{.}}.self) { - {{/-first}} - self = .type{{#transformArrayType}}{{.}}{{/transformArrayType}}(value) - {{/oneOf}} - } else { - {{#oneOfUnknownDefaultCase}} - self = .unknownDefaultOpenApi - {{/oneOfUnknownDefaultCase}} - {{^oneOfUnknownDefaultCase}} - throw DecodingError.typeMismatch(Self.Type.self, .init(codingPath: decoder.codingPath, debugDescription: "Unable to decode instance of {{classname}}")) - {{/oneOfUnknownDefaultCase}} - } -{{/discriminator}} } -} diff --git a/codegen/Templates/swift/model_doc.mustache b/codegen/Templates/swift/model_doc.mustache deleted file mode 100644 index 952fc7de..00000000 --- a/codegen/Templates/swift/model_doc.mustache +++ /dev/null @@ -1,11 +0,0 @@ -{{#models}}{{#model}}# {{classname}} - -## Properties -Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- -{{#vars}}**{{{name}}}** | {{#isPrimitiveType}}**{{{dataType}}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{^isContainer}}[**{{dataType}}**]({{complexType}}.md){{/isContainer}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{/isPrimitiveType}} | {{description}} | {{^required}}[optional] {{/required}}{{#isReadOnly}}[readonly] {{/isReadOnly}}{{#defaultValue}}[default to {{#vendorExtensions.x-null-encodable}}.encodeValue({{{.}}}){{/vendorExtensions.x-null-encodable}}{{^vendorExtensions.x-null-encodable}}{{{.}}}{{/vendorExtensions.x-null-encodable}}]{{/defaultValue}} -{{/vars}} - -[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) - -{{/model}}{{/models}} From fe577fe7a2d1665e1ae1826f4b7f67c2aa35f68f Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Mon, 25 May 2026 11:21:17 +0500 Subject: [PATCH 37/40] Update Swift SDK for release 26.5 --- codegen/config-swift.json | 2 +- submodules/swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen/config-swift.json b/codegen/config-swift.json index 05de1c49..c7281147 100644 --- a/codegen/config-swift.json +++ b/codegen/config-swift.json @@ -22,7 +22,7 @@ "hideGenerationTimestamp": true, "library": "urlsession", "mapFileBinaryToData": true, - "packageVersion": "26.4.0", + "packageVersion": "26.5.0", "projectName": "AsposeBarcodeCloud", "responseAs": "AsyncAwait,ObjcBlock", "swiftPackagePath": "Sources/AsposeBarcodeCloud", diff --git a/submodules/swift b/submodules/swift index b013c104..3494d3f5 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit b013c104d81a6d4d43ff837f5148722222c66ccf +Subproject commit 3494d3f527bbc4c9f31f22167abb04ff23e4e61e From 8b96c963cedcddb2927d00569f9a0c7b3f16e031 Mon Sep 17 00:00:00 2001 From: Denis Averin Date: Tue, 26 May 2026 14:49:40 +0700 Subject: [PATCH 38/40] Send binary form-data params as file parts in Swift SDK --- codegen/Templates/swift/_param.mustache | 1 + .../URLSessionImplementations.mustache | 34 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 codegen/Templates/swift/_param.mustache diff --git a/codegen/Templates/swift/_param.mustache b/codegen/Templates/swift/_param.mustache new file mode 100644 index 00000000..47cdae78 --- /dev/null +++ b/codegen/Templates/swift/_param.mustache @@ -0,0 +1 @@ +"{{baseName}}": {{#isQueryParam}}(wrappedValue: {{/isQueryParam}}{{#isBinary}}{{paramName}}{{/isBinary}}{{^isBinary}}{{paramName}}{{^required}}?{{/required}}.asParameter(codableHelper: apiConfiguration.codableHelper){{/isBinary}}{{#isQueryParam}}, isExplode: {{isExplode}}){{/isQueryParam}} \ No newline at end of file diff --git a/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache b/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache index d259624e..d65200c9 100644 --- a/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache +++ b/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache @@ -567,7 +567,7 @@ private class FormDataEncoding: ParameterEncoding { case let data as Data: - urlRequest = configureDataUploadRequest( + urlRequest = configureBinaryUploadRequest( urlRequest: urlRequest, boundary: boundary, name: key, @@ -664,6 +664,38 @@ private class FormDataEncoding: ParameterEncoding { } + private func configureBinaryUploadRequest(urlRequest: URLRequest, boundary: String, name: String, data: Data) -> URLRequest { + + var urlRequest = urlRequest + + var body = urlRequest.httpBody.orEmpty + + // If we already added something then we need an additional newline. + if body.count > 0 { + body.append("\r\n") + } + + // Value boundary. + body.append("--\(boundary)\r\n") + + // Value headers. A raw binary value is sent as a file part (with a + // filename and a binary content type) so the server treats it as an + // uploaded file instead of a plain text form field. + body.append("Content-Disposition: form-data; name=\"\(name)\"; filename=\"\(name)\"\r\n") + body.append("Content-Type: application/octet-stream\r\n") + + // Separate headers and body. + body.append("\r\n") + + // The value data. + body.append(data) + + urlRequest.httpBody = body + + return urlRequest + + } + func mimeType(for url: URL) -> String { let pathExtension = url.pathExtension From bd842ebe43e4ba064a3d805db348937de140aae8 Mon Sep 17 00:00:00 2001 From: Denis Averin Date: Tue, 26 May 2026 14:49:43 +0700 Subject: [PATCH 39/40] Bump Swift submodule to regenerated SDK --- submodules/swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/swift b/submodules/swift index 3494d3f5..13125893 160000 --- a/submodules/swift +++ b/submodules/swift @@ -1 +1 @@ -Subproject commit 3494d3f527bbc4c9f31f22167abb04ff23e4e61e +Subproject commit 1312589347399c09856c517f29059110f865c841 From 98df58d60dab75a293d8edce26c2bb5428e45646 Mon Sep 17 00:00:00 2001 From: Timur Baiguskarov Date: Tue, 26 May 2026 14:09:45 +0500 Subject: [PATCH 40/40] Remove private Swift submodule checkout workaround --- .github/workflows/check-codegen.yml | 6 ++--- .github/workflows/check-urls.yml | 8 ++----- scripts/check-urls.py | 2 -- scripts/checkout-submodules.bash | 34 ----------------------------- 4 files changed, 4 insertions(+), 46 deletions(-) delete mode 100755 scripts/checkout-submodules.bash diff --git a/.github/workflows/check-codegen.yml b/.github/workflows/check-codegen.yml index 94605785..10ee73b4 100644 --- a/.github/workflows/check-codegen.yml +++ b/.github/workflows/check-codegen.yml @@ -15,14 +15,12 @@ jobs: steps: - uses: actions/checkout@v6 with: + submodules: recursive persist-credentials: true - name: Check all submodules up-to-date - working-directory: ${{ github.workspace }} - env: - SWIFT_SUBMODULE_DEPLOY_KEY: ${{ secrets.SWIFT_SUBMODULE_DEPLOY_KEY }} run: | - bash scripts/checkout-submodules.bash --remote + git submodule update --recursive --remote git diff --exit-code check-swagger: diff --git a/.github/workflows/check-urls.yml b/.github/workflows/check-urls.yml index bd8298ef..11df3b20 100644 --- a/.github/workflows/check-urls.yml +++ b/.github/workflows/check-urls.yml @@ -17,12 +17,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - - name: Checkout submodules - working-directory: ${{ github.workspace }} - env: - SWIFT_SUBMODULE_DEPLOY_KEY: ${{ secrets.SWIFT_SUBMODULE_DEPLOY_KEY }} - run: bash scripts/checkout-submodules.bash + with: + submodules: recursive - name: Check all URLs run: ./scripts/check_all_urls.sh diff --git a/scripts/check-urls.py b/scripts/check-urls.py index 7ef02b74..0cb404d7 100644 --- a/scripts/check-urls.py +++ b/scripts/check-urls.py @@ -37,8 +37,6 @@ URLS_TO_IGNORE = frozenset( [ "https://api.aspose.cloud", - # Temporary while the Swift SDK repository is private during bootstrap. - "https://github.com/aspose-barcode-cloud/Aspose.BarCode-Cloud-SDK-for-Swift.git", "https://www.aspose.cloud/404", "https://www.aspose.cloud/404/", ] diff --git a/scripts/checkout-submodules.bash b/scripts/checkout-submodules.bash deleted file mode 100755 index 889b0f3d..00000000 --- a/scripts/checkout-submodules.bash +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash -set -euo pipefail - -submodule_args=(--init --recursive) -if [ "$#" -gt 0 ]; then - submodule_args+=("$@") -fi - -script_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -repo_root="${GITHUB_WORKSPACE:-$script_root}" -if ! git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - repo_root="$script_root" -fi - -if ! git -C "$repo_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then - echo "Cannot locate git repository root for submodule checkout." >&2 - exit 1 -fi - -if [ -n "${SWIFT_SUBMODULE_DEPLOY_KEY:-}" ]; then - ssh_dir="$HOME/.ssh" - key_file="$ssh_dir/aspose_barcode_cloud_swift_submodule" - - mkdir -p "$ssh_dir" - chmod 700 "$ssh_dir" - printf '%s\n' "$SWIFT_SUBMODULE_DEPLOY_KEY" > "$key_file" - chmod 600 "$key_file" - ssh-keyscan github.com >> "$ssh_dir/known_hosts" 2>/dev/null - - git -C "$repo_root" config --local submodule.submodules/swift.url git@github.com:aspose-barcode-cloud/Aspose.BarCode-Cloud-SDK-for-Swift.git - export GIT_SSH_COMMAND="ssh -i $key_file -o IdentitiesOnly=yes -o UserKnownHostsFile=$ssh_dir/known_hosts" -fi - -git -C "$repo_root" submodule update "${submodule_args[@]}"