Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
263e2c8
Bootstrap Swift SDK generation
TimurBaiguskarovAspose Apr 29, 2026
e3d052d
Make Swift generation buildable on Linux
TimurBaiguskarovAspose Apr 29, 2026
243d1dd
Add Swift OAuth runtime support
TimurBaiguskarovAspose Apr 29, 2026
b448e84
Update Swift request shape coverage
TimurBaiguskarovAspose Apr 29, 2026
fbd0cf7
Update Swift live smoke coverage
TimurBaiguskarovAspose Apr 30, 2026
58ff5f3
Update Swift integration test docs
TimurBaiguskarovAspose Apr 30, 2026
ecb1442
Update Swift SDK repository structure
TimurBaiguskarovAspose Apr 30, 2026
cecac3f
Update Swift live test wording
TimurBaiguskarovAspose Apr 30, 2026
b5a74f1
Update Swift SDK changelog
TimurBaiguskarovAspose Apr 30, 2026
c3b8011
Point Swift submodule to GitHub-backed commit
TimurBaiguskarovAspose May 12, 2026
10c0e8c
Point Swift submodule to test config loader
TimurBaiguskarovAspose May 13, 2026
6cd2336
Make Swift warning fixes reproducible
TimurBaiguskarovAspose May 13, 2026
5866b77
Point Swift submodule to release tag docs
TimurBaiguskarovAspose May 13, 2026
fd9c158
Add Swift codegen PR checks
TimurBaiguskarovAspose May 13, 2026
41cc9cd
Point Swift submodule to ignored test URLs
TimurBaiguskarovAspose May 14, 2026
9d90b39
Include Swift in full SDK generation
TimurBaiguskarovAspose May 14, 2026
8f7dd90
Wire Swift versioning and Linux-first CI
TimurBaiguskarovAspose May 18, 2026
6302b6f
Make submodule checkout helper cwd-safe
TimurBaiguskarovAspose May 18, 2026
43c1ce9
Use explicit repo path for submodule checkout
TimurBaiguskarovAspose May 18, 2026
4aa5328
Harden submodule checkout in container jobs
TimurBaiguskarovAspose May 18, 2026
22956ed
Use hosted Ubuntu Swift toolchain in codegen CI
TimurBaiguskarovAspose May 18, 2026
05b7930
Generate Swift SDK from templates
TimurBaiguskarovAspose May 18, 2026
94852d9
Switch Swift SDK generation to Swift 6
TimurBaiguskarovAspose May 19, 2026
3171835
Stabilize Swift codegen checks
TimurBaiguskarovAspose May 19, 2026
c006d0b
Fix Swift URLSession MIME fallback
TimurBaiguskarovAspose May 19, 2026
7811746
Switch Swift AsposeBarcodeCloudClient to per-instance apiConfiguration
Denis-Averin May 20, 2026
8da7d89
Lazy OAuth via interceptor in Swift client, drop sync semaphore
Denis-Averin May 20, 2026
0f64731
Merge branch 'main' into feature/swift-sdk-bootstrap
Denis-Averin May 20, 2026
f929eb8
Replace fatalError with throwing errors in Swift URLSession runtime
Denis-Averin May 20, 2026
5a635fa
Drop stale openapi-generator metadata files
Denis-Averin May 21, 2026
cc39ac6
Bump SwiftFormat target to Swift 6.0
Denis-Averin May 21, 2026
dac5c0b
Drop GenerateAndScan executable from Swift package template
Denis-Averin May 21, 2026
25b398e
Split AsposeBarcodeCloudClient template into per-type files
Denis-Averin May 21, 2026
df1d2f0
Inject GenerateAndScan example into generated Swift README
Denis-Averin May 21, 2026
fcd32d1
Bump Swift submodule to regenerated SDK
Denis-Averin May 21, 2026
4936029
Check-codegen.yml cleanup
Denis-Averin May 21, 2026
68ae969
Templates cleanup
Denis-Averin May 21, 2026
10df178
Merge main release 26.5 into Swift SDK branch
TimurBaiguskarovAspose May 25, 2026
fe577fe
Update Swift SDK for release 26.5
TimurBaiguskarovAspose May 25, 2026
8b96c96
Send binary form-data params as file parts in Swift SDK
Denis-Averin May 26, 2026
bd842eb
Bump Swift submodule to regenerated SDK
Denis-Averin May 26, 2026
98df58d
Remove private Swift submodule checkout workaround
TimurBaiguskarovAspose May 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,7 @@ php:
.PHONY: python
python:
cd codegen && ./generate-python.bash

.PHONY: swift
swift:
cd codegen && ./generate-swift.bash
133 changes: 133 additions & 0 deletions codegen/Templates/swift/AsposeBarcodeCloudClient.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

public typealias AsposeBarcodeCloudTokenFetcher = @Sendable (
AsposeBarcodeCloudConfiguration,
@escaping @Sendable (Result<String, AsposeBarcodeCloudClientError>) -> 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<String, AsposeBarcodeCloudClientError>) -> 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<String, AsposeBarcodeCloudClientError>) -> 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()
}
}
27 changes: 27 additions & 0 deletions codegen/Templates/swift/AsposeBarcodeCloudClientError.mustache
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Original file line number Diff line number Diff line change
@@ -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
}
}
115 changes: 115 additions & 0 deletions codegen/Templates/swift/BarcodeAuthInterceptor.mustache
Original file line number Diff line number Diff line change
@@ -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<State>

private struct State {
var token: String?
var inFlightFetch: Bool
var pendingWaiters: [@Sendable (Result<String, AsposeBarcodeCloudClientError>) -> 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<T>(
urlRequest: URLRequest,
urlSession: URLSessionProtocol,
requestBuilder: RequestBuilder<T>,
completion: @Sendable @escaping (Result<URLRequest, any Error>) -> 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<T>(
urlRequest: URLRequest,
urlSession: URLSessionProtocol,
requestBuilder: RequestBuilder<T>,
data: Data?,
response: URLResponse?,
error: any Error,
completion: @Sendable @escaping (OpenAPIInterceptorRetry) -> Void
) {
completion(.dontRetry)
}

func ensureToken(completion: @escaping @Sendable (Result<String, AsposeBarcodeCloudClientError>) -> Void) {
enum Action {
case immediate(Result<String, AsposeBarcodeCloudClientError>)
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<String, AsposeBarcodeCloudClientError>) {
var waiters: [@Sendable (Result<String, AsposeBarcodeCloudClientError>) -> 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)
}
}
}
Loading