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/Templates/swift/AsposeBarcodeCloudClient.mustache b/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache new file mode 100644 index 00000000..0acd51d6 --- /dev/null +++ b/codegen/Templates/swift/AsposeBarcodeCloudClient.mustache @@ -0,0 +1,133 @@ +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif + +public typealias AsposeBarcodeCloudTokenFetcher = @Sendable ( + AsposeBarcodeCloudConfiguration, + @escaping @Sendable (Result) -> Void +) -> Void + +public final class AsposeBarcodeCloudClient: @unchecked Sendable { + public let configuration: AsposeBarcodeCloudConfiguration + public let apiConfiguration: AsposeBarcodeCloudAPIConfiguration + private let authInterceptor: BarcodeAuthInterceptor + + public init( + configuration: AsposeBarcodeCloudConfiguration, + tokenFetcher: AsposeBarcodeCloudTokenFetcher? = nil + ) { + self.configuration = configuration + let fetcher = tokenFetcher ?? AsposeBarcodeCloudClient.defaultTokenFetcher + + let apiConfig = AsposeBarcodeCloudAPIConfiguration( + basePath: configuration.host, + customHeaders: [ + "x-aspose-client": configuration.sdkName, + "x-aspose-client-version": configuration.sdkVersion, + ] + ) + let interceptor = BarcodeAuthInterceptor( + configuration: configuration, + tokenFetcher: fetcher + ) + apiConfig.interceptor = interceptor + + self.apiConfiguration = apiConfig + self.authInterceptor = interceptor + } + + 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 authorize(completion: @escaping @Sendable (Result) -> Void) { + authInterceptor.ensureToken(completion: completion) + } + + @discardableResult + 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) + } + } + } + } + + private struct TokenResponse: Decodable { + let accessToken: String? + + enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + } + } + + private static func defaultTokenFetcher( + configuration: AsposeBarcodeCloudConfiguration, + completion: @escaping @Sendable (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/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/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 new file mode 100644 index 00000000..c7b7dd01 --- /dev/null +++ b/codegen/Templates/swift/Models.mustache @@ -0,0 +1,172 @@ +// Models.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation +#if canImport(FoundationNetworking) +import FoundationNetworking +#endif{{#useAlamofire}} +import Alamofire{{/useAlamofire}} + +protocol ParameterConvertible { + func asParameter(codableHelper: CodableHelper) -> any Sendable +} + +/// 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 { + 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() + 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, Sendable { + case error(Int, Data?, URLResponse?, Error) +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum DownloadException: Error, Sendable { + case responseDataMissing + case responseFailed + case requestMissing + case requestMissingPath + case requestMissingURL +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum DecodableRequestBuilderError: Error, Sendable { + case emptyDataResponse + case nilHTTPResponse + case unsuccessfulHTTPStatusCode + case jsonDecoding(DecodingError) + case generalError(Error) +} + +{{#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 + {{#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}} 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) + } +} +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: @unchecked Sendable { +{{#useAlamofire}} + private let _state = OpenAPIMutex(nil) + + internal func set(request: Request) { + _state.withValue { $0 = request } + } + + internal func get() -> Request? { + _state.value + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func cancel() { + _state.withValue { + $0?.cancel() + $0 = nil + } + } +{{/useAlamofire}} +{{^useAlamofire}} + private let _state = OpenAPIMutex(nil) + + internal func set(task: URLSessionDataTaskProtocol) { + _state.withValue { $0 = task } + } + + internal func get() -> URLSessionDataTaskProtocol? { + _state.value + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func cancel() { + _state.withValue { + $0?.cancel() + $0 = nil + } + } +{{/useAlamofire}} +} diff --git a/codegen/Templates/swift/Package.swift.mustache b/codegen/Templates/swift/Package.swift.mustache new file mode 100644 index 00000000..ccf3699c --- /dev/null +++ b/codegen/Templates/swift/Package.swift.mustache @@ -0,0 +1,60 @@ +// swift-tools-version:6.0 + +import PackageDescription + +let package = Package( + name: "{{projectName}}", + platforms: [ + {{#useVapor}} + .macOS(.v10_15), + {{/useVapor}} + {{^useVapor}} + .iOS(.v13), + .macOS(.v10_15), + .tvOS(.v13), + .watchOS(.v6), + {{/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. + {{#useAlamofire}} + .package(url: "https://github.com/Alamofire/Alamofire", .upToNextMajor(from: "5.10.2")), + {{/useAlamofire}} + {{#usePromiseKit}} + .package(url: "https://github.com/mxcl/PromiseKit", .upToNextMajor(from: "8.1.2")), + {{/usePromiseKit}} + {{#useRxSwift}} + .package(url: "https://github.com/ReactiveX/RxSwift", .upToNextMajor(from: "6.8.0")), + {{/useRxSwift}} + {{#useVapor}} + .package(url: "https://github.com/vapor/vapor", from: "4.99.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: [{{#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/README.mustache b/codegen/Templates/swift/README.mustache new file mode 100644 index 00000000..265e8559 --- /dev/null +++ b/codegen/Templates/swift/README.mustache @@ -0,0 +1,152 @@ +# Aspose.BarCode Cloud SDK for Swift + +This repository contains the Swift SDK for Aspose.BarCode Cloud. + +## Requirements + +- Swift Package Manager +- iOS 13.0 or later +- macOS 10.15 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" +) +``` + +The OAuth access token is fetched lazily on the first API call. To warm it up at app start, await the explicit method: + +```swift +_ = try await client.authorize() +``` + +If you already have an access token, configure the SDK directly: + +```swift +let client = AsposeBarcodeCloudClient(accessToken: "your-access-token") +``` + +`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 + +```swift +GenerateAPI.generate( + barcodeType: .qr, + data: "Aspose.BarCode Cloud", + imageFormat: .png, + apiConfiguration: client.apiConfiguration +) { 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), + apiConfiguration: client.apiConfiguration +) { 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, + apiConfiguration: client.apiConfiguration +) { response, error in + if let error = error { + print(error) + return + } + + print(response?.barcodes?.first?.barcodeValue ?? "") +} +``` + +## 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: + +```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/_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/api.mustache b/codegen/Templates/swift/api.mustache new file mode 100644 index 00000000..9c1c78da --- /dev/null +++ b/codegen/Templates/swift/api.mustache @@ -0,0 +1,454 @@ +{{#operations}}// +// {{classname}}.swift +// +// Generated by openapi-generator +// https://openapi-generator.tech +// + +import Foundation{{#usePromiseKit}} +@preconcurrency import PromiseKit{{/usePromiseKit}}{{#useRxSwift}} +@preconcurrency import RxSwift{{/useRxSwift}}{{#useCombine}} +import Combine{{/useCombine}}{{#useVapor}} +import Vapor{{/useVapor}}{{#swiftUseApiNamespace}} + +extension {{projectName}}API { +{{/swiftUseApiNamespace}} + +{{#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}} + + /** + * enum for parameter {{paramName}} + */ + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum {{enumName}}_{{operationId}}: {{^isContainer}}{{{dataType}}}{{/isContainer}}{{#isContainer}}String{{/isContainer}}, Sendable, 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}} +{{#useObjcBlock}} + + /** +{{#summary}} + {{{.}}} +{{/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}} {{#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): + completion(response.body, nil) + {{/returnType}} + {{^returnType}} + case .success: + completion((), nil) + {{/returnType}} + case let .failure(error): + completion(nil, error) + } + } + } +{{/useObjcBlock}} +{{#usePromiseKit}} + + /** +{{#summary}} + {{{.}}} +{{/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}}> + */ + @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}}{{#apiStaticMethod}}{{#hasParams}}, {{/hasParams}}apiConfiguration: apiConfiguration{{/apiStaticMethod}}).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}} {{.}}{{/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}} {{#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 = 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): + 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}} {{.}}{{/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> + */ + {{#isDeprecated}} + @available(*, deprecated, message: "This operation is deprecated.") + {{/isDeprecated}} + {{#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 {{#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}} + 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() + {{#combineDeferred}} + } + .eraseToAnyPublisher() + {{/combineDeferred}} + } +{{/useCombine}} +{{#useAsyncAwait}} + + /** +{{#summary}} + {{{.}}} +{{/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}} + {{#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}}{{#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}} {{#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): + completion(.success(response.body)) + {{/returnType}} + {{^returnType}} + case .success: + completion(.success(())) + {{/returnType}} + case let .failure(error): + completion(.failure(error)) + } + } + } +{{/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}} + {{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}} + - returns: `EventLoopFuture` of `ClientResponse`{{#description}} {{{.}}}{{/description}} + */ + {{#isDeprecated}} + @available(*, deprecated, message: "This operation is deprecated.") + {{/isDeprecated}} + {{#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 = apiConfiguration.basePath + localVariablePath + + guard let localVariableApiClient = apiConfiguration.apiClient else { + fatalError("apiConfiguration.apiClient is not set.") + } + + 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}} + {{#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: 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 { + 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: apiConfiguration.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}} {{{.}}}{{/description}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}} + {{/allParams}} + - 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}} {{#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: apiConfiguration.contentConfiguration.requireDecoder(for: {{{dataType}}}.defaultContentType)){{/isFile}}{{/isBinary}}, {{/dataType}}raw: response) + {{/responses}} + {{^hasDefaultResponse}} + default: + return .http0(raw: response) + {{/hasDefaultResponse}} + } + } + } +{{/useVapor}} +{{/operation}} +} +{{#swiftUseApiNamespace}} +} +{{/swiftUseApiNamespace}} +{{/operations}} diff --git a/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache b/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache new file mode 100644 index 00000000..d65200c9 --- /dev/null +++ b/codegen/Templates/swift/libraries/urlsession/URLSessionImplementations.mustache @@ -0,0 +1,849 @@ +// 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: 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?, (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) -> URLSessionDataTaskProtocol { + return dataTask(with: request, completionHandler: completionHandler) + } +} + +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() {} + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func getNonDecodableBuilder() -> RequestBuilder.Type { + return URLSessionRequestBuilder.self + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func getBuilder() -> RequestBuilder.Type { + return URLSessionDecodableRequestBuilder.self + } +} + +fileprivate class URLSessionRequestBuilderConfiguration: @unchecked Sendable { + private init() { + defaultURLSession = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil) + } + + static let shared = URLSessionRequestBuilderConfiguration() + + // Store the URLSession's delegate to retain its reference + let sessionDelegate = SessionDelegate() + + // Store the URLSession to retain its reference + let defaultURLSession: URLSession + + // Store current URLCredential for every URLSessionTask + let credentialStore = SynchronizedDictionary() +} + +{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class URLSessionRequestBuilder: 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 URLSession + configuration. + */ + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func createURLSession() -> URLSessionProtocol { + return URLSessionRequestBuilderConfiguration.shared.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(request: originalRequest, with: parameters) + + return modifiedRequest + } + + @discardableResult + 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 { + apiConfiguration.apiResponseQueue.async { + completion(.failure(ErrorResponse.error(-3, nil, nil, URLSessionRequestBuilderError.unsupportedHTTPMethod(self.method)))) + } + return requestTask + } + + 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") || contentType.hasPrefix("image/") { + encoding = OctetStreamEncoding() + } else { + apiConfiguration.apiResponseQueue.async { + completion(.failure(ErrorResponse.error(-4, nil, nil, URLSessionRequestBuilderError.unsupportedMediaType(contentType)))) + } + return requestTask + } + } + + do { + let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers) + + 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) + } + + self.onProgressReady?(dataTask.progress) + + URLSessionRequestBuilderConfiguration.shared.credentialStore[dataTask.taskIdentifier] = self.credential + + self.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 { + // 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))) + } + } + + return requestTask + } + + private func cleanupRequest() { + if let task = requestTask.get() { + URLSessionRequestBuilderConfiguration.shared.credentialStore[task.taskIdentifier] = nil + } + } + + 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) + + 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: + 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: + 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))) + } + + } + + {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func buildHeaders() -> [String: String] { + var httpHeaders: [String: String] = [:] + for (key, value) in apiConfiguration.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, @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 + } + + 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) + + 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 { + 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 { + 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: + 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: + 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 { + 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 { + 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 = 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): + 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))) + } + } + } +} + +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 challenge.previousFailureCount > 0 { + disposition = .rejectProtectionSpace + } else { + credential = URLSessionRequestBuilderConfiguration.shared.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(request: URLRequest, with parameters: [String: any Sendable]?) throws -> URLRequest +} + +private class URLEncoding: ParameterEncoding { + func encode(request: URLRequest, with parameters: [String: any Sendable]?) throws -> URLRequest { + + var urlRequest = request + + 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: @Sendable @escaping (_ fileURL: URL) -> String?) { + self.contentTypeForFormPart = contentTypeForFormPart + } + + func encode(request: URLRequest, with parameters: [String: any Sendable]?) throws -> URLRequest { + + var urlRequest = request + + 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 = configureBinaryUploadRequest( + 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: + throw URLSessionRequestBuilderError.unprocessableMultipartValue(key: key, valueDescription: String(describing: value)) + } + } + } + + 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 + + } + + 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 + + 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" + } + return "application/octet-stream" + #else + return "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" + } + } + +} + +private class FormURLEncoding: ParameterEncoding { + func encode(request: URLRequest, with parameters: [String: any Sendable]?) throws -> URLRequest { + + var urlRequest = request + + var requestBodyComponents = URLComponents() + 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 + } +} + +private class OctetStreamEncoding: ParameterEncoding { + func encode(request: URLRequest, with parameters: [String: any Sendable]?) throws -> URLRequest { + + var urlRequest = request + + 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: + throw URLSessionRequestBuilderError.unprocessableBody(description: String(describing: 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 {} + +{{#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/modelObject.mustache b/codegen/Templates/swift/modelObject.mustache new file mode 100644 index 00000000..4f6966b7 --- /dev/null +++ b/codegen/Templates/swift/modelObject.mustache @@ -0,0 +1,138 @@ +{{^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}} + {{#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}} + {{#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}} + {{#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}} +{{/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/swiftformat.mustache b/codegen/Templates/swift/swiftformat.mustache new file mode 100644 index 00000000..9df232bf --- /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 6.0 +--trimwhitespace always +--wraparguments preserve +--wrapcollections preserve + +# rules + +--enable isEmpty diff --git a/codegen/config-swift.json b/codegen/config-swift.json new file mode 100644 index 00000000..c7281147 --- /dev/null +++ b/codegen/config-swift.json @@ -0,0 +1,31 @@ +{ + "allowUnicodeIdentifiers": true, + "enumUnknownDefaultCase": true, + "files": { + "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, + "library": "urlsession", + "mapFileBinaryToData": true, + "packageVersion": "26.5.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 new file mode 100755 index 00000000..296ed744 --- /dev/null +++ b/codegen/generate-swift.bash @@ -0,0 +1,35 @@ +#!/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 + +# 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.template" + +rm -rf "$targetDir/docs" +mv "$tempDir/docs" "$targetDir/docs" + +mv "$tempDir/.swiftformat" "$targetDir/" + +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..0cb404d7 100644 --- a/scripts/check-urls.py +++ b/scripts/check-urls.py @@ -61,6 +61,7 @@ ".mvnrepository.com", ".nodejs.org", ".npmjs.com", + ".openapi-generator.tech", ".nuget.org", ".opensource.org", ".packagist.org", diff --git a/scripts/new-version.py b/scripts/new-version.py index 8cc8b3da..89bb4205 100755 --- a/scripts/new-version.py +++ b/scripts/new-version.py @@ -94,6 +94,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) @@ -119,6 +125,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 new file mode 160000 index 00000000..13125893 --- /dev/null +++ b/submodules/swift @@ -0,0 +1 @@ +Subproject commit 1312589347399c09856c517f29059110f865c841