From e1c60f6f8d189eba13a01a07a595e6e5ff72c736 Mon Sep 17 00:00:00 2001 From: Martin Mitrevski Date: Wed, 16 Sep 2026 16:38:54 +0200 Subject: [PATCH] fix: update Swift protobuf codegen to match SDK runtime protoc-gen-swift was pinned to 1.22.0 while stream-video-swift links SwiftProtobuf 1.38.1, so every regeneration emitted code that fails Swift 6 builds and had to be hand-patched afterwards: - `static var allCases` instead of `static let` (concurrency-safety warning) - `_StorageClass` without Sendable handling (the iOS SDK patched this by hand in its Swift 6 migration) - deprecated `init(serializedData:)` instead of `init(serializedBytes:)` Pin the plugin to 1.38.1 via a new PROTO_SWIFT_VERSION in versions.sh, alongside the other pinned tool versions. 1.38.1 emits `nonisolated struct ...: @unchecked Sendable` on messages and `static nonisolated(unsafe) let defaultInstance`, so the hand-patched `_StorageClass: @unchecked Sendable` is no longer needed. The Swift twirp template had also drifted from what the iOS SDK maintains by hand on top of the generated file. Bring it in line: retry handling via HTTPConfig, `Response: ProtoModelResponse` with `hasError` checks, the X-Stream-Client header, `serializedBytes`, and 4-space indentation. Verified by regenerating video/sfu with the new plugin and template and building StreamVideo for iOS: no errors, no warnings from generated code. Co-Authored-By: Claude Opus 5 (1M context) --- install.sh | 2 +- .../generator/template.goswift | 55 +++++++++++++------ versions.sh | 6 ++ 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/install.sh b/install.sh index ac369cb2c..bec3406bd 100755 --- a/install.sh +++ b/install.sh @@ -67,7 +67,7 @@ if [[ -z $DISABLE_SWIFT ]]; then if command -v swift &> /dev/null then echo "Installing swift protoc plugin" - git clone --depth 1 --branch 1.22.0 https://github.com/apple/swift-protobuf $PROTOC_DIR/.swift-protobuf + git clone --depth 1 --branch "${PROTO_SWIFT_VERSION}" https://github.com/apple/swift-protobuf $PROTOC_DIR/.swift-protobuf (cd $PROTOC_DIR/.swift-protobuf && swift build -c release) ln -s $PROTOC_DIR/.swift-protobuf/.build/release/protoc-gen-swift $PROTOC_DIR/bin/protoc-gen-swift else diff --git a/tools/protoc-gen-swift-twirp/generator/template.goswift b/tools/protoc-gen-swift-twirp/generator/template.goswift index 96be6114b..c8833a75c 100644 --- a/tools/protoc-gen-swift-twirp/generator/template.goswift +++ b/tools/protoc-gen-swift-twirp/generator/template.goswift @@ -5,22 +5,24 @@ import {{.Path}}; {{range .Services}} class {{.ClassName}}: @unchecked Sendable { - private let httpClient: HTTPClient - let hostname: String - var token: String - let apiKey: String + private let httpClient: HTTPClient + let hostname: String + var token: String + let apiKey: String let syncQueue = DispatchQueue(label: "{{.ClassName}}", qos: .userInitiated) - let pathPrefix: String = "/{{.Package}}.{{.Name}}/" - init(httpClient: HTTPClient, apiKey: String, hostname: String, token: String) { + let pathPrefix: String = "/{{.Package}}.{{.Name}}/" + var httpConfig = HTTPConfig.default //TODO: move this + + init(httpClient: HTTPClient, apiKey: String, hostname: String, token: String) { self.httpClient = httpClient - self.hostname = hostname - self.token = token + self.hostname = hostname + self.token = token self.apiKey = apiKey - } + } {{range .Methods}} - func {{.Name}}({{.InputArg}}: {{.InputType}}) async throws -> {{.OutputType}} { + func {{.Name}}({{.InputArg}}: {{.InputType}}) async throws -> {{.OutputType}} { return try await execute(request: {{.InputArg}}, path: "{{.Path}}") - } + } {{end}} func update(userToken: String) { syncQueue.async { [weak self] in @@ -28,23 +30,42 @@ class {{.ClassName}}: @unchecked Sendable { } } - private func execute(request: Request, path: String) async throws -> Response { + private func execute(request: Request, path: String, retries: Int = 0) async throws -> Response { let requestData = try request.serializedData() - var request = try makeRequest(for: path) - request.httpBody = requestData - let responseData = try await httpClient.execute(request: request) - let response = try Response.init(serializedData: responseData) + var urlRequest = try makeRequest(for: path) + urlRequest.httpBody = requestData + let responseData = try await httpClient.execute(request: urlRequest) + let response = try Response.init(serializedBytes: responseData) + if response.hasError { + if response.error.shouldRetry && retries < httpConfig.maxRetries { + let delay = httpConfig.retryStrategy.getDelayAfterTheFailure() + let delayNanoseconds = UInt64(delay * 1_000_000_000) + log.debug("Delaying retry for \(delay) seconds") + try await Task.sleep(nanoseconds: delayNanoseconds) + log.debug("Retrying request for path \(path)") + return try await execute(request: request, path: path, retries: retries + 1) + } else { + httpConfig.retryStrategy.resetConsecutiveFailures() + throw NSError( + domain: "stream", + code: response.error.code.rawValue, + userInfo: ["message": response.error.message] + ) + } + } + httpConfig.retryStrategy.resetConsecutiveFailures() return response } private func makeRequest(for path: String) throws -> URLRequest { - let url = hostname + pathPrefix + path + "?api_key=\(apiKey)" + let url = hostname + pathPrefix + path + "?api_key=\(apiKey)" guard let url = URL(string: url) else { throw NSError(domain: "stream", code: 123) } var request = URLRequest(url: url) request.setValue("application/protobuf", forHTTPHeaderField: "Content-Type") request.setValue("Bearer \(token)", forHTTPHeaderField: "authorization") + request.setValue(SystemEnvironment.sdkIdentifier, forHTTPHeaderField: "X-Stream-Client") request.httpMethod = "POST" return request } diff --git a/versions.sh b/versions.sh index bb2e0bc97..3179e3242 100644 --- a/versions.sh +++ b/versions.sh @@ -12,3 +12,9 @@ export PROTO_TWIRP_VERSION=v8.1.2 export PROTO_GO_VERSION=v1.28.1 export PROTO_VTPROTO_VERSION=v0.3.0 export PROTO_LINT_VERSION=v0.39.0 +# Must match the SwiftProtobuf runtime version that the consuming Swift SDKs +# link against (see stream-video-swift Package.swift). Generating with an older +# protoc-gen-swift emits code that trips Swift 6 concurrency checking +# (`static var allCases`, non-Sendable `_StorageClass`) and deprecated APIs +# (`init(serializedData:)`). +export PROTO_SWIFT_VERSION=1.38.1