├── Libraries
├── Connect
│ ├── proto
│ │ ├── README.md
│ │ ├── buf.yaml
│ │ └── grpc
│ │ │ └── status
│ │ │ └── v1
│ │ │ └── status.proto
│ ├── buf.gen.yaml
│ ├── README.md
│ ├── Public
│ │ ├── Interfaces
│ │ │ ├── ProtobufMessage.swift
│ │ │ ├── HTTPMethod.swift
│ │ │ ├── Cancelable.swift
│ │ │ ├── HTTPMetrics.swift
│ │ │ ├── Headers.swift
│ │ │ ├── Trailers.swift
│ │ │ ├── Streaming
│ │ │ │ ├── Callbacks
│ │ │ │ │ ├── ServerOnlyStreamInterface.swift
│ │ │ │ │ ├── RequestCallbacks.swift
│ │ │ │ │ ├── BidirectionalStreamInterface.swift
│ │ │ │ │ ├── ClientOnlyStreamInterface.swift
│ │ │ │ │ └── ResponseCallbacks.swift
│ │ │ │ ├── AsyncAwait
│ │ │ │ │ ├── ServerOnlyAsyncStreamInterface.swift
│ │ │ │ │ ├── BidirectionalAsyncStreamInterface.swift
│ │ │ │ │ └── ClientOnlyAsyncStreamInterface.swift
│ │ │ │ └── StreamResult.swift
│ │ │ ├── NetworkProtocol.swift
│ │ │ ├── HeaderConstants.swift
│ │ │ ├── HTTPRequest.swift
│ │ │ ├── MethodSpec.swift
│ │ │ ├── CompressionPool.swift
│ │ │ ├── ResponseMessage.swift
│ │ │ ├── HTTPClientInterface.swift
│ │ │ ├── Interceptors
│ │ │ │ ├── InterceptorFactory.swift
│ │ │ │ ├── Interceptor.swift
│ │ │ │ └── UnaryInterceptor.swift
│ │ │ ├── IdempotencyLevel.swift
│ │ │ ├── HTTPResponse.swift
│ │ │ ├── Codec.swift
│ │ │ └── Code.swift
│ │ └── Implementation
│ │ │ └── Codecs
│ │ │ ├── ProtoCodec.swift
│ │ │ └── JSONCodec.swift
│ ├── Internal
│ │ ├── Streaming
│ │ │ ├── ServerOnlyStream.swift
│ │ │ ├── BidirectionalStream.swift
│ │ │ ├── ServerOnlyAsyncStream.swift
│ │ │ ├── ConnectEndStreamResponse.swift
│ │ │ ├── ClientOnlyAsyncStream.swift
│ │ │ ├── ClientOnlyStreamValidation.swift
│ │ │ ├── ClientOnlyStream.swift
│ │ │ ├── BidirectionalAsyncStream.swift
│ │ │ └── URLSessionStream.swift
│ │ ├── Utilities
│ │ │ ├── Lock.swift
│ │ │ ├── Locked.swift
│ │ │ └── TimeoutTimer.swift
│ │ ├── Unary
│ │ │ └── UnaryAsyncWrapper.swift
│ │ └── GeneratedSources
│ │ │ └── proto
│ │ │ └── grpc
│ │ │ └── status
│ │ │ └── v1
│ │ │ └── status.pb.swift
│ └── PackageInternal
│ │ ├── Trailers+gRPC.swift
│ │ └── Headers+GRPC.swift
├── ConnectNIO
│ ├── README.md
│ ├── Internal
│ │ └── Extensions
│ │ │ ├── ConnectError+Extensions.swift
│ │ │ ├── HTTPRequestHead+Extensions.swift
│ │ │ └── Headers+Extensions.swift
│ └── Public
│ │ └── NetworkProtocol+Extensions.swift
└── ConnectMocks
│ ├── README.md
│ ├── MockClientOnlyStream.swift
│ ├── MockClientOnlyAsyncStream.swift
│ ├── MockServerOnlyStream.swift
│ ├── MockBidirectionalStream.swift
│ ├── MockServerOnlyAsyncStream.swift
│ └── MockBidirectionalAsyncStream.swift
├── Examples
├── ElizaCocoaPodsApp
│ ├── ElizaCocoaPodsApp
│ │ └── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Podfile
│ ├── ElizaCocoaPodsApp.xcworkspace
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── ElizaCocoaPodsApp.xcodeproj
│ │ └── project.xcworkspace
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── Podfile.lock
│ └── README.md
├── ElizaSwiftPackageApp
│ ├── ElizaSwiftPackageApp
│ │ ├── Assets.xcassets
│ │ │ ├── Contents.json
│ │ │ ├── AccentColor.colorset
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ └── Info.plist
│ ├── ElizaSwiftPackageApp.xcodeproj
│ │ └── project.xcworkspace
│ │ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── swiftpm
│ │ │ └── Package.resolved
│ └── README.md
├── ElizaSharedSources
│ ├── README.md
│ ├── AppSources
│ │ ├── ElizaApp.swift
│ │ ├── Message.swift
│ │ ├── MessagingView.swift
│ │ └── MessagingViewModel.swift
│ └── GeneratedSources
│ │ └── connectrpc
│ │ └── eliza
│ │ └── v1
│ │ └── eliza.connect.swift
├── buf.gen.yaml
└── README.md
├── .github
├── dependabot.yml
├── CODE_OF_CONDUCT.md
├── pull_request_template.md
├── release.yml
└── workflows
│ ├── add-to-project.yml
│ ├── pr-title.yml
│ ├── release.yml
│ └── ci.yml
├── Tests
├── UnitTests
│ ├── README.md
│ ├── ConnectLibraryTests
│ │ ├── TestResources
│ │ │ └── gzip-test.txt.gz
│ │ ├── buf.gen.yaml
│ │ └── ConnectTests
│ │ │ ├── ProtoCodecTests.swift
│ │ │ ├── ConnectEndStreamResponseTests.swift
│ │ │ ├── InterceptorFactoryTests.swift
│ │ │ ├── GzipCompressionPoolTests.swift
│ │ │ ├── ServiceMetadataTests.swift
│ │ │ ├── JSONCodecTests.swift
│ │ │ └── EnvelopeTests.swift
│ └── ConnectPluginUtilitiesTests
│ │ └── FilePathComponentsTests.swift
└── ConformanceClient
│ ├── buf.gen.yaml
│ ├── README.md
│ ├── InvocationConfigs
│ ├── urlsession.yaml
│ └── nio.yaml
│ └── Sources
│ └── CommandLineArgument.swift
├── SECURITY.md
├── .gitignore
├── MAINTAINERS.md
├── Connect-Swift.podspec
├── Connect-Swift-Mocks.podspec
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── xcshareddata
│ └── IDETemplateMacros.plist
├── Plugins
└── ConnectPluginUtilities
│ ├── ServiceDescriptor+Extensions.swift
│ ├── FilePathComponents.swift
│ └── MethodDescriptor+Extensions.swift
├── Package.resolved
├── .swiftlint.yml
└── Makefile
/Libraries/Connect/proto/README.md:
--------------------------------------------------------------------------------
1 | This directory contains `.proto` files whose outputs are compiled into the
2 | Connect library.
3 |
--------------------------------------------------------------------------------
/Examples/ElizaCocoaPodsApp/ElizaCocoaPodsApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 |
--------------------------------------------------------------------------------
/Examples/ElizaSwiftPackageApp/ElizaSwiftPackageApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tests/UnitTests/README.md:
--------------------------------------------------------------------------------
1 | # UnitTests
2 |
3 | This directory contains categorizations of `XCTestCase` classes to validate
4 | various library and plugin behaviors.
5 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | ## Community Code of Conduct
2 |
3 | Connect follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
4 |
--------------------------------------------------------------------------------
/Libraries/Connect/buf.gen.yaml:
--------------------------------------------------------------------------------
1 | version: v1
2 | plugins:
3 | - plugin: buf.build/apple/swift:v1.30.0
4 | opt: Visibility=Internal
5 | out: ./Internal/GeneratedSources
6 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | Security Policy
2 | ===============
3 |
4 | This project follows the [Connect security policy and reporting
5 | process](https://connectrpc.com/docs/governance/security).
6 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | **Before submitting your PR:** Please read through the contribution guide at https://github.com/connectrpc/connect-swift/blob/main/.github/CONTRIBUTING.md
2 |
--------------------------------------------------------------------------------
/Tests/UnitTests/ConnectLibraryTests/TestResources/gzip-test.txt.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connectrpc/connect-swift/HEAD/Tests/UnitTests/ConnectLibraryTests/TestResources/gzip-test.txt.gz
--------------------------------------------------------------------------------
/Libraries/Connect/proto/buf.yaml:
--------------------------------------------------------------------------------
1 | version: v1
2 | lint:
3 | use:
4 | - DEFAULT
5 | ignore:
6 | - grpc/status/v1/status.proto
7 | breaking:
8 | use:
9 | - WIRE_JSON
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.xcscheme
2 | *.xcsettings
3 | *.xcuserdatad
4 | *.xcuserstate
5 | *.xcworkspacedata
6 | .build
7 | .DS_Store
8 | /.tmp
9 | DerivedData
10 | Examples/ElizaCocoaPodsApp/ElizaCocoaPodsApp.xcworkspace
11 | Pods
12 |
--------------------------------------------------------------------------------
/Examples/ElizaSwiftPackageApp/ElizaSwiftPackageApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Examples/ElizaCocoaPodsApp/ElizaCocoaPodsApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/ElizaSwiftPackageApp/ElizaSwiftPackageApp/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Examples/ElizaCocoaPodsApp/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '15.0'
2 |
3 | target 'ElizaCocoaPodsApp' do
4 | use_frameworks!
5 |
6 | # For real projects, use a version instead of a path:
7 | # pod 'Connect-Swift', '~> x.y.z'
8 | pod 'Connect-Swift', :path => '../..'
9 | end
10 |
--------------------------------------------------------------------------------
/Examples/ElizaSharedSources/README.md:
--------------------------------------------------------------------------------
1 | # ElizaSharedSources example
2 |
3 | This directory contains sources that are shared by the Eliza example apps.
4 | See the examples for more details and usage:
5 |
6 | - [ElizaCocoaPodsApp](../ElizaCocoaPodsApp)
7 | - [ElizaSwiftPackageApp](../ElizaSwiftPackageApp)
8 |
--------------------------------------------------------------------------------
/Libraries/Connect/README.md:
--------------------------------------------------------------------------------
1 | ## Connect
2 |
3 | This module contains support for the Connect and gRPC-Web protocols.
4 | For gRPC support, you must also include the `ConnectNIO` module.
5 |
6 | For additional details and tutorials, see the
7 | [Connect-Swift documentation](https://connectrpc.com/docs/swift/getting-started/).
8 |
--------------------------------------------------------------------------------
/Examples/ElizaCocoaPodsApp/ElizaCocoaPodsApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Examples/ElizaSwiftPackageApp/ElizaSwiftPackageApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Examples/ElizaCocoaPodsApp/ElizaCocoaPodsApp.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | changelog:
2 | exclude:
3 | labels:
4 | - ignore-for-release
5 | authors:
6 | - dependabot
7 | categories:
8 | - title: Enhancements
9 | labels:
10 | - enhancement
11 | - title: Bugfixes
12 | labels:
13 | - bug
14 | - title: Other changes
15 | labels:
16 | - "*"
17 |
--------------------------------------------------------------------------------
/Examples/ElizaCocoaPodsApp/ElizaCocoaPodsApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Examples/ElizaSwiftPackageApp/ElizaSwiftPackageApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MAINTAINERS.md:
--------------------------------------------------------------------------------
1 | Maintainers
2 | ===========
3 |
4 | ## Current
5 | * [Peter Edge](https://github.com/bufdev), [Buf](https://buf.build)
6 | * [Michael Rebello](https://github.com/rebello95), [Airbnb](https://airbnb.com)
7 | * [Josh Humphries](https://github.com/jhump), [Buf](https://buf.build)
8 | * [Eddie Seay](https://github.com/eseay), [Chick-fil-A](https://www.chick-fil-a.com/)
9 |
--------------------------------------------------------------------------------
/Tests/ConformanceClient/buf.gen.yaml:
--------------------------------------------------------------------------------
1 | version: v1
2 | plugins:
3 | - plugin: buf.build/apple/swift:v1.30.0
4 | opt: Visibility=Internal
5 | out: ./GeneratedSources
6 | - name: connect-swift
7 | opt:
8 | - GenerateServiceMetadata=false
9 | - Visibility=Internal
10 | out: ./GeneratedSources
11 | path: ../../.tmp/bin/protoc-gen-connect-swift
12 |
--------------------------------------------------------------------------------
/Examples/buf.gen.yaml:
--------------------------------------------------------------------------------
1 | version: v1
2 | plugins:
3 | - plugin: buf.build/apple/swift:v1.30.0
4 | opt: Visibility=Internal
5 | out: ./ElizaSharedSources/GeneratedSources
6 | - name: connect-swift
7 | opt:
8 | - GenerateServiceMetadata=false
9 | - Visibility=Internal
10 | out: ./ElizaSharedSources/GeneratedSources
11 | path: ../.tmp/bin/protoc-gen-connect-swift
12 |
--------------------------------------------------------------------------------
/Examples/README.md:
--------------------------------------------------------------------------------
1 | # ConnectExamples
2 |
3 | This directory contains comprehensive example apps that utilize
4 | Connect-Swift and provide the ability to [chat with ELIZA][eliza-demo] over
5 | each supported protocol using either unary or streaming APIs:
6 |
7 | - [`ElizaSwiftPackageApp`](./ElizaSwiftPackageApp): Uses
8 | **Swift Package Manager**.
9 | - [`ElizaCocoaPodsApp`](./ElizaCocoaPodsApp): Uses **CocoaPods**.
10 |
11 | [eliza-demo]: https://connectrpc.com/demo
12 |
--------------------------------------------------------------------------------
/Tests/ConformanceClient/README.md:
--------------------------------------------------------------------------------
1 | # ConformanceClient
2 |
3 | This directory contains an executable which can be invoked by the
4 | [Connect conformance runner](https://github.com/connectrpc/conformance)
5 | which performs a matrix of hundreds of runtime tests against a local
6 | server in order to validate library behaviors with various permutations of
7 | protocols, codecs, etc.
8 |
9 | Refer to the [contributing guide](../../.github/CONTRIBUTING.md#running-tests)
10 | for details on how to run these tests with the runner.
11 |
--------------------------------------------------------------------------------
/.github/workflows/add-to-project.yml:
--------------------------------------------------------------------------------
1 | name: Add issues and PRs to project
2 |
3 | on:
4 | issues:
5 | types:
6 | - opened
7 | - reopened
8 | - transferred
9 | pull_request_target:
10 | types:
11 | - opened
12 | - reopened
13 | issue_comment:
14 | types:
15 | - created
16 |
17 | jobs:
18 | call-workflow-add-to-project:
19 | name: Call workflow to add issue to project
20 | uses: connectrpc/base-workflows/.github/workflows/add-to-project.yaml@main
21 | secrets: inherit
22 |
--------------------------------------------------------------------------------
/Tests/ConformanceClient/InvocationConfigs/urlsession.yaml:
--------------------------------------------------------------------------------
1 | features:
2 | codecs:
3 | - CODEC_PROTO
4 | - CODEC_JSON
5 | compressions:
6 | - COMPRESSION_IDENTITY
7 | - COMPRESSION_GZIP
8 | protocols:
9 | - PROTOCOL_CONNECT
10 | - PROTOCOL_GRPC_WEB
11 | supports_connect_get: true
12 | supports_half_duplex_bidi_over_http1: true
13 | supports_message_receive_limit: false
14 | supports_h2c: false
15 | supports_tls: false
16 | supports_tls_client_certs: false
17 | supports_trailers: false
18 | versions:
19 | - HTTP_VERSION_1
20 |
--------------------------------------------------------------------------------
/Tests/ConformanceClient/InvocationConfigs/nio.yaml:
--------------------------------------------------------------------------------
1 | features:
2 | codecs:
3 | - CODEC_PROTO
4 | - CODEC_JSON
5 | compressions:
6 | - COMPRESSION_IDENTITY
7 | - COMPRESSION_GZIP
8 | protocols:
9 | - PROTOCOL_CONNECT
10 | - PROTOCOL_GRPC_WEB
11 | - PROTOCOL_GRPC
12 | supports_connect_get: true
13 | supports_half_duplex_bidi_over_http1: true
14 | supports_message_receive_limit: false
15 | supports_h2c: true
16 | supports_tls: false
17 | supports_tls_client_certs: false
18 | supports_trailers: true
19 | versions:
20 | - HTTP_VERSION_2
21 |
--------------------------------------------------------------------------------
/Examples/ElizaCocoaPodsApp/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Connect-Swift (1.2.0):
3 | - SwiftProtobuf (~> 1.30.0)
4 | - SwiftProtobuf (1.30.0)
5 |
6 | DEPENDENCIES:
7 | - Connect-Swift (from `../..`)
8 |
9 | SPEC REPOS:
10 | trunk:
11 | - SwiftProtobuf
12 |
13 | EXTERNAL SOURCES:
14 | Connect-Swift:
15 | :path: "../.."
16 |
17 | SPEC CHECKSUMS:
18 | Connect-Swift: 82bcc0834587bd537f17a9720f62ea9fc7d9f3a5
19 | SwiftProtobuf: 3697407f0d5b23bedeba9c2eaaf3ec6fdff69349
20 |
21 | PODFILE CHECKSUM: b598f373a6ab5add976b09c2ac79029bf2200d48
22 |
23 | COCOAPODS: 1.15.2
24 |
--------------------------------------------------------------------------------
/Libraries/ConnectNIO/README.md:
--------------------------------------------------------------------------------
1 | ## ConnectNIO
2 |
3 | This module provides an `NIOHTTPClient` which conforms to the Connect
4 | library's `HTTPClientInterface` protocol and is backed by the
5 | [SwiftNIO](https://github.com/apple/swift-nio) networking stack.
6 |
7 | Additionally, since SwiftNIO supports trailers, this module provides support
8 | for using the gRPC protocol alongside the Connect and gRPC-Web protocols
9 | provided by the main Connect library.
10 |
11 | This library is unavailable through CocoaPods since
12 | [SwiftNIO does not support CocoaPods](https://github.com/apple/swift-nio/issues/2393),
13 | and it can only be consumed using Swift Package Manager.
14 |
--------------------------------------------------------------------------------
/Tests/UnitTests/ConnectLibraryTests/buf.gen.yaml:
--------------------------------------------------------------------------------
1 | version: v1
2 | plugins:
3 | - plugin: buf.build/apple/swift:v1.30.0
4 | opt: Visibility=Internal
5 | out: ./GeneratedSources
6 | - name: connect-swift
7 | opt:
8 | - GenerateAsyncMethods=true
9 | - GenerateCallbackMethods=true
10 | - Visibility=Internal
11 | out: ./GeneratedSources
12 | path: ../../../.tmp/bin/protoc-gen-connect-swift
13 | - name: connect-swift-mocks
14 | opt:
15 | - GenerateAsyncMethods=true
16 | - GenerateCallbackMethods=true
17 | - Visibility=Internal
18 | out: ./GeneratedSources
19 | path: ../../../.tmp/bin/protoc-gen-connect-swift-mocks
20 |
--------------------------------------------------------------------------------
/.github/workflows/pr-title.yml:
--------------------------------------------------------------------------------
1 | name: Lint PR Title
2 | # Prevent writing to the repository using the CI token.
3 | # Ref: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions
4 | permissions:
5 | pull-requests: read
6 | on:
7 | pull_request:
8 | # By default, a workflow only runs when a pull_request's activity type is opened,
9 | # synchronize, or reopened. We explicity override here so that PR titles are
10 | # re-linted when the PR text content is edited.
11 | types:
12 | - opened
13 | - edited
14 | - reopened
15 | - synchronize
16 | jobs:
17 | lint:
18 | uses: bufbuild/base-workflows/.github/workflows/pr-title.yaml@main
19 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/ProtobufMessage.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import SwiftProtobuf
16 |
17 | public typealias ProtobufMessage = SwiftProtobuf.Message
18 |
--------------------------------------------------------------------------------
/Connect-Swift.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = 'Connect-Swift'
3 | spec.module_name = 'Connect'
4 | spec.version = '1.2.0'
5 | spec.license = { :type => 'Apache 2.0', :file => 'LICENSE' }
6 | spec.summary = 'Idiomatic gRPC & Connect RPCs for Swift.'
7 | spec.homepage = 'https://github.com/connectrpc/connect-swift'
8 | spec.author = 'The Connect Authors'
9 | spec.source = { :git => 'https://github.com/connectrpc/connect-swift.git', :tag => spec.version }
10 |
11 | spec.ios.deployment_target = '12.0'
12 | spec.osx.deployment_target = '10.15'
13 | spec.tvos.deployment_target = '13.0'
14 | spec.watchos.deployment_target = '6.0'
15 |
16 | spec.dependency 'SwiftProtobuf', '~> 1.30.0'
17 |
18 | spec.source_files = 'Libraries/Connect/**/*.swift'
19 |
20 | spec.swift_versions = ['5.0']
21 | end
22 |
--------------------------------------------------------------------------------
/Libraries/ConnectMocks/README.md:
--------------------------------------------------------------------------------
1 | ## ConnectMocks
2 |
3 | This module contains types that are designed to make mocking/testing generated
4 | Connect clients and methods easier.
5 |
6 | The typical workflow for consuming these mocks is:
7 |
8 | 1. Invoke the `connect-swift-mocks` plugin.
9 | 2. The plugin will output corresponding `*.mock.swift` files which import `ConnectMocks` to provide default no-op implementations for each generated `service` and `rpc`.
10 | 3. Import both the `ConnectMocks` module and the generated files into your test suite.
11 | 4. Assuming your code uses the `*Interface` types (rather than their concrete implementation types), you can inject the corresponding `*Mock` types (instead of the generated `*Client` types as you would in production) and easily replace production RPC calls with mocked out test data.
12 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/HTTPMethod.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /// HTTP methods supported by the library.
16 | public enum HTTPMethod: String, Sendable {
17 | case get = "GET"
18 | case post = "POST"
19 | }
20 |
--------------------------------------------------------------------------------
/Examples/ElizaSharedSources/AppSources/ElizaApp.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import SwiftUI
16 |
17 | @main
18 | struct ElizaApp: App {
19 | var body: some Scene {
20 | WindowGroup {
21 | MenuView()
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Connect-Swift-Mocks.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |spec|
2 | spec.name = 'Connect-Swift-Mocks'
3 | spec.module_name = 'ConnectMocks'
4 | spec.version = '1.2.0'
5 | spec.license = { :type => 'Apache 2.0', :file => 'LICENSE' }
6 | spec.summary = 'Mocks for testing with Connect-Swift.'
7 | spec.homepage = 'https://github.com/connectrpc/connect-swift'
8 | spec.author = 'The Connect Authors'
9 | spec.source = { :git => 'https://github.com/connectrpc/connect-swift.git', :tag => spec.version }
10 |
11 | spec.ios.deployment_target = '12.0'
12 | spec.osx.deployment_target = '10.15'
13 | spec.tvos.deployment_target = '13.0'
14 | spec.watchos.deployment_target = '6.0'
15 |
16 | spec.dependency 'Connect-Swift', "#{spec.version.to_s}"
17 | spec.dependency 'SwiftProtobuf', '~> 1.30.0'
18 |
19 | spec.source_files = 'Libraries/ConnectMocks/**/*.swift'
20 |
21 | spec.swift_versions = ['5.0']
22 | end
23 |
--------------------------------------------------------------------------------
/Libraries/ConnectNIO/Internal/Extensions/ConnectError+Extensions.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Connect
16 |
17 | extension ConnectError {
18 | static func deadlineExceeded() -> Self {
19 | return .init(code: .deadlineExceeded, message: "timed out")
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDETemplateMacros.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FILEHEADER
6 | Copyright 2022-2025 The Connect Authors
7 | //
8 | // Licensed under the Apache License, Version 2.0 (the "License");
9 | // you may not use this file except in compliance with the License.
10 | // You may obtain a copy of the License at
11 | //
12 | // http://www.apache.org/licenses/LICENSE-2.0
13 | //
14 | // Unless required by applicable law or agreed to in writing, software
15 | // distributed under the License is distributed on an "AS IS" BASIS,
16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | // See the License for the specific language governing permissions and
18 | // limitations under the License.
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Examples/ElizaSharedSources/AppSources/Message.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Foundation
16 |
17 | struct Message {
18 | enum Author {
19 | case eliza
20 | case user
21 | }
22 |
23 | let id = UUID()
24 | let message: String
25 | let author: Author
26 | }
27 |
28 | extension Message: Identifiable {
29 | typealias ID = UUID
30 | }
31 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/Cancelable.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /// Type that wraps an action that can be canceled.
16 | public struct Cancelable: Sendable {
17 | /// Cancel the current action.
18 | public let cancel: @Sendable () -> Void
19 |
20 | public init(cancel: @escaping @Sendable () -> Void) {
21 | self.cancel = cancel
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/HTTPMetrics.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Foundation
16 |
17 | /// Contains metrics collected during the span of an HTTP request.
18 | public struct HTTPMetrics: Sendable {
19 | public let taskMetrics: URLSessionTaskMetrics?
20 |
21 | public init(taskMetrics: URLSessionTaskMetrics?) {
22 | self.taskMetrics = taskMetrics
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/Headers.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /// Request/response headers.
16 | /// All keys are expected to be lowercased.
17 | /// Comma-separated values are split into individual items in the array. For example:
18 | /// On the wire: `accept-encoding: gzip,brotli` or `accept-encoding: gzip, brotli`
19 | /// Yields: `["accept-encoding": ["gzip", "brotli"]]`
20 | public typealias Headers = [String: [String]]
21 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/Trailers.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /// Request/response trailers.
16 | /// All keys are expected to be lowercased.
17 | /// Comma-separated values are split into individual items in the array. For example:
18 | /// On the wire: `accept-encoding: gzip,brotli` or `accept-encoding: gzip, brotli`
19 | /// Yields: `["accept-encoding": ["gzip", "brotli"]]`
20 | public typealias Trailers = [String: [String]]
21 |
--------------------------------------------------------------------------------
/Examples/ElizaSwiftPackageApp/README.md:
--------------------------------------------------------------------------------
1 | # ElizaSwiftPackageApp example
2 |
3 | This example app imports the `Connect` library using Swift Package Manager,
4 | and provides an interface for
5 | [chatting with Eliza](https://buf.build/connectrpc/eliza).
6 |
7 | The app has support for chatting using a variety of protocols supported by
8 | the Connect library:
9 |
10 | - [Connect](https://connectrpc.com) + unary
11 | - [Connect](https://connectrpc.com) + streaming
12 | - [gRPC](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md) + unary (using `ConnectGRPC` + `SwiftNIO`)
13 | - [gRPC](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md) + streaming (using `ConnectGRPC` + `SwiftNIO`)
14 | - [gRPC-Web](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md) + unary
15 | - [gRPC-Web](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md) + streaming
16 |
17 | ## Try it out
18 |
19 | Simply open the `.xcodeproj` in this directory and build the app target
20 | using Xcode.
21 |
22 | Note that the project uses a local reference to the Connect package,
23 | rather than the GitHub URL.
24 |
--------------------------------------------------------------------------------
/Examples/ElizaCocoaPodsApp/README.md:
--------------------------------------------------------------------------------
1 | # ElizaSwiftPackageApp example
2 |
3 | This example app imports the `Connect` library using CocoaPods,
4 | and provides an interface for
5 | [chatting with Eliza](https://connectrpc.com/demo).
6 |
7 | The app has support for chatting using a variety of protocols supported by
8 | the Connect library:
9 |
10 | - [Connect](https://connectrpc.com) + unary
11 | - [Connect](https://connectrpc.com) + streaming
12 | - [gRPC-Web](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md) + unary
13 | - [gRPC-Web](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md) + streaming
14 |
15 | **Note that vanilla gRPC support is not available in this example because
16 | [SwiftNIO does not support CocoaPods](https://github.com/apple/swift-nio/issues/2393).**
17 |
18 | ## Try it out
19 |
20 | 1. Ensure you have CocoaPods installed (`brew install cocoapods`)
21 | 2. `cd` into this directory and install the pods (`pod install`)
22 | 3. Open the generated `.xcworkspace` file (`xed .`)
23 | 4. Build the app target using Xcode
24 |
25 | Note that the [`Podfile`](./Podfile) uses a local path reference to the
26 | Connect library in this repository, rather than the one in the CocoaPods
27 | specs repo.
28 |
--------------------------------------------------------------------------------
/Libraries/ConnectNIO/Public/NetworkProtocol+Extensions.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Connect
16 |
17 | extension NetworkProtocol {
18 | /// The gRPC protocol:
19 | /// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
20 | ///
21 | /// IMPORTANT: This protocol must be used in conjunction with an HTTP client that supports
22 | /// trailers, such as the `NIOHTTPClient` included in this library.
23 | public static var grpc: Self {
24 | return .custom(
25 | name: "gRPC",
26 | protocolInterceptor: InterceptorFactory { GRPCInterceptor(config: $0) }
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Libraries/Connect/proto/grpc/status/v1/status.proto:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | syntax = "proto3";
16 |
17 | // This package is for internal use by Connect, and provides no backward
18 | // compatibility guarantees whatsoever.
19 | package grpc.status.v1;
20 |
21 | import "google/protobuf/any.proto";
22 |
23 | // See https://cloud.google.com/apis/design/errors.
24 | //
25 | // This struct must remain binary-compatible with
26 | // https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto.
27 | message Status {
28 | int32 code = 1; // a google.rpc.Code
29 | string message = 2; // developer-facing, English (localize in details or client-side)
30 | repeated google.protobuf.Any details = 3;
31 | }
32 |
--------------------------------------------------------------------------------
/Libraries/Connect/Internal/Streaming/ServerOnlyStream.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import SwiftProtobuf
16 |
17 | /// Concrete **internal** implementation of `ServerOnlyStreamInterface`.
18 | final class ServerOnlyStream: Sendable {
19 | private let bidirectionalStream: BidirectionalStream
20 |
21 | init(bidirectionalStream: BidirectionalStream) {
22 | self.bidirectionalStream = bidirectionalStream
23 | }
24 | }
25 |
26 | extension ServerOnlyStream: ServerOnlyStreamInterface {
27 | typealias Input = Message
28 |
29 | func send(_ input: Message) {
30 | self.bidirectionalStream.send(input)
31 | self.bidirectionalStream.close()
32 | }
33 |
34 | func cancel() {
35 | self.bidirectionalStream.cancel()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Libraries/Connect/PackageInternal/Trailers+gRPC.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | extension Trailers {
16 | /// **This should not be considered part of Connect's public/stable interface, and is subject
17 | /// to change. When the compiler supports it, this should be package-internal.**
18 | ///
19 | /// Identifies the status code from gRPC and gRPC-Web trailers.
20 | ///
21 | /// - returns: The gRPC status code, if specified.
22 | @available(
23 | swift,
24 | deprecated: 100.0,
25 | message: "This is an internal-only API which will be made package-private in Swift 6."
26 | )
27 | public func _grpcStatus() -> Code? {
28 | return self[HeaderConstants.grpcStatus]?
29 | .first
30 | .flatMap(Int.init)
31 | .flatMap { Code(rawValue: $0) }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/Streaming/Callbacks/ServerOnlyStreamInterface.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import SwiftProtobuf
16 |
17 | /// Represents a server-only stream (a stream where the server streams data to the client after
18 | /// receiving an initial request) that can send request messages.
19 | public protocol ServerOnlyStreamInterface {
20 | /// The input (request) message type.
21 | associatedtype Input: ProtobufMessage
22 |
23 | /// Send a request to the server over the stream.
24 | ///
25 | /// Should be called exactly one time when starting the stream.
26 | ///
27 | /// - parameter input: The request message to send.
28 | func send(_ input: Input)
29 |
30 | /// Cancel the stream and return a canceled code.
31 | /// No calls to `send()` are valid after calling `cancel()`.
32 | func cancel()
33 | }
34 |
--------------------------------------------------------------------------------
/Libraries/ConnectMocks/MockClientOnlyStream.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Connect
16 | import SwiftProtobuf
17 |
18 | /// Mock implementation of `ClientOnlyStreamInterface` which can be used for testing.
19 | ///
20 | /// This type can be used by setting `on*` closures and observing their calls,
21 | /// by validating its instance variables such as `inputs` at the end of invocation,
22 | /// or by subclassing the type and overriding functions such as `send()`.
23 | ///
24 | /// To return data over the stream, outputs can be specified using `init(outputs: ...)`.
25 | @available(iOS 13, *)
26 | open class MockClientOnlyStream<
27 | Input: ProtobufMessage,
28 | Output: ProtobufMessage
29 | >: MockBidirectionalStream, ClientOnlyStreamInterface, @unchecked Sendable {
30 | open func closeAndReceive() {
31 | self.close()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/Streaming/Callbacks/RequestCallbacks.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /// Set of closures that are used for wiring outbound request data through to HTTP clients.
16 | public final class RequestCallbacks: Sendable {
17 | /// Closure to cancel the request from the client.
18 | public let cancel: @Sendable () -> Void
19 | /// Closure to send data through to the server.
20 | public let sendData: @Sendable (T) -> Void
21 | /// Closure to initiate a close for a stream.
22 | public let sendClose: @Sendable () -> Void
23 |
24 | public init(
25 | cancel: @escaping @Sendable () -> Void,
26 | sendData: @escaping @Sendable (T) -> Void,
27 | sendClose: @escaping @Sendable () -> Void
28 | ) {
29 | self.cancel = cancel
30 | self.sendData = sendData
31 | self.sendClose = sendClose
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Libraries/Connect/Internal/Streaming/BidirectionalStream.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import SwiftProtobuf
16 |
17 | /// Concrete **internal** implementation of `BidirectionalStreamInterface`.
18 | final class BidirectionalStream: Sendable {
19 | private let requestCallbacks: RequestCallbacks
20 |
21 | init(requestCallbacks: RequestCallbacks) {
22 | self.requestCallbacks = requestCallbacks
23 | }
24 | }
25 |
26 | extension BidirectionalStream: BidirectionalStreamInterface {
27 | typealias Input = Message
28 |
29 | @discardableResult
30 | func send(_ input: Input) -> Self {
31 | self.requestCallbacks.sendData(input)
32 | return self
33 | }
34 |
35 | func close() {
36 | self.requestCallbacks.sendClose()
37 | }
38 |
39 | func cancel() {
40 | self.requestCallbacks.cancel()
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/NetworkProtocol.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /// Protocols that are supported by the library.
16 | public enum NetworkProtocol: Sendable {
17 | /// The Connect protocol:
18 | /// https://connectrpc.com/docs/protocol
19 | case connect
20 | /// The gRPC-Web protocol:
21 | /// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md
22 | case grpcWeb
23 | /// A custom protocol that is implemented via an interceptor.
24 | case custom(name: String, protocolInterceptor: InterceptorFactory)
25 | }
26 |
27 | extension NetworkProtocol: CustomStringConvertible {
28 | public var description: String {
29 | switch self {
30 | case .connect:
31 | return "Connect"
32 | case .grpcWeb:
33 | return "gRPC-Web"
34 | case .custom(let name, _):
35 | return name
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Libraries/ConnectNIO/Internal/Extensions/HTTPRequestHead+Extensions.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Connect
16 | import Foundation
17 | import NIOHTTP1
18 |
19 | extension HTTPRequestHead {
20 | static func fromConnect(
21 | _ request: Connect.HTTPRequest, nioHeaders: NIOHTTP1.HTTPHeaders
22 | ) -> Self {
23 | switch request.method {
24 | case .get:
25 | return HTTPRequestHead(
26 | version: .http1_1,
27 | method: .GET,
28 | uri: "\(request.url.path)?\(request.url.query ?? "")",
29 | headers: nioHeaders
30 | )
31 | case .post:
32 | return HTTPRequestHead(
33 | version: .http1_1,
34 | method: .POST,
35 | uri: request.url.path,
36 | headers: nioHeaders
37 | )
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Plugins/ConnectPluginUtilities/ServiceDescriptor+Extensions.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import SwiftProtobufPluginLibrary
16 |
17 | extension ServiceDescriptor {
18 | public var servicePath: String {
19 | if self.file.package.isEmpty {
20 | return self.name
21 | } else {
22 | return "\(self.file.package).\(self.name)"
23 | }
24 | }
25 |
26 | public func implementationName(using namer: SwiftProtobufNamer) -> String {
27 | let upperCamelName = NamingUtils.toUpperCamelCase(self.name) + "Client"
28 | if self.file.package.isEmpty {
29 | return upperCamelName
30 | } else {
31 | return namer.typePrefix(forFile: self.file) + upperCamelName
32 | }
33 | }
34 |
35 | public func protocolName(using namer: SwiftProtobufNamer) -> String {
36 | return self.implementationName(using: namer) + "Interface"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/Streaming/Callbacks/BidirectionalStreamInterface.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import SwiftProtobuf
16 |
17 | /// Represents a bidirectional stream that can send request messages and initiate closes.
18 | public protocol BidirectionalStreamInterface {
19 | /// The input (request) message type.
20 | associatedtype Input: ProtobufMessage
21 |
22 | /// Send a request to the server over the stream.
23 | ///
24 | /// - parameter input: The request message to send.
25 | ///
26 | /// - returns: An instance of this stream, for syntactic sugar.
27 | @discardableResult
28 | func send(_ input: Input) -> Self
29 |
30 | /// Close the stream. No calls to `send()` are valid after calling `close()`.
31 | func close()
32 |
33 | /// Cancel the stream and return a canceled code.
34 | /// No calls to `send()` are valid after calling `cancel()`.
35 | func cancel()
36 | }
37 |
--------------------------------------------------------------------------------
/Libraries/ConnectMocks/MockClientOnlyAsyncStream.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Connect
16 | import SwiftProtobuf
17 |
18 | /// Mock implementation of `ClientOnlyAsyncStreamInterface` which can be used for testing.
19 | ///
20 | /// This type can be used by setting `on*` closures and observing their calls,
21 | /// by validating its instance variables such as `inputs` at the end of invocation,
22 | /// or by subclassing the type and overriding functions such as `send()`.
23 | ///
24 | /// To return data over the stream, outputs can be specified using `init(outputs: ...)` or by
25 | /// subclassing and overriding `results()`.
26 | @available(iOS 13, *)
27 | open class MockClientOnlyAsyncStream<
28 | Input: ProtobufMessage,
29 | Output: ProtobufMessage
30 | >: MockBidirectionalAsyncStream, ClientOnlyAsyncStreamInterface, @unchecked Sendable
31 | {
32 | open func closeAndReceive() {
33 | self.close()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Tests/UnitTests/ConnectLibraryTests/ConnectTests/ProtoCodecTests.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Connect
16 | import SwiftProtobuf
17 | import XCTest
18 |
19 | final class ProtoCodecTests: XCTestCase {
20 | private let message: Connectrpc_Conformance_V1_RawHTTPRequest = .with { proto in
21 | proto.body = .unary(Connectrpc_Conformance_V1_MessageContents.with { message in
22 | message.binary = Data([0x0, 0x1, 0x2, 0x3])
23 | })
24 | proto.uri = "foo/bar"
25 | proto.headers = [
26 | .with { header in
27 | header.name = "x-header"
28 | header.value = ["a"]
29 | },
30 | ]
31 | }
32 |
33 | func testSerializingAndDeserializing() throws {
34 | let codec = ProtoCodec()
35 | let serialized = try codec.serialize(message: self.message)
36 | XCTAssertEqual(try codec.deserialize(source: serialized), self.message)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Libraries/Connect/Internal/Streaming/ServerOnlyAsyncStream.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import SwiftProtobuf
16 |
17 | /// Concrete **internal** implementation of `ServerOnlyAsyncStreamInterface`.
18 | @available(iOS 13, *)
19 | final class ServerOnlyAsyncStream: Sendable {
20 | private let bidirectionalStream: BidirectionalAsyncStream
21 |
22 | init(bidirectionalStream: BidirectionalAsyncStream) {
23 | self.bidirectionalStream = bidirectionalStream
24 | }
25 | }
26 |
27 | @available(iOS 13, *)
28 | extension ServerOnlyAsyncStream: ServerOnlyAsyncStreamInterface {
29 | func send(_ input: Input) throws {
30 | try self.bidirectionalStream.send(input)
31 | self.bidirectionalStream.close()
32 | }
33 |
34 | func results() -> AsyncStream> {
35 | return self.bidirectionalStream.results()
36 | }
37 |
38 | func cancel() {
39 | self.bidirectionalStream.cancel()
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/HeaderConstants.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | public enum HeaderConstants {
16 | public static let acceptEncoding = "accept-encoding"
17 | public static let contentEncoding = "content-encoding"
18 | public static let contentType = "content-type"
19 |
20 | public static let connectProtocolVersion = "connect-protocol-version"
21 | public static let connectTimeoutMs = "connect-timeout-ms"
22 |
23 | public static let connectStreamingAcceptEncoding = "connect-accept-encoding"
24 | public static let connectStreamingContentEncoding = "connect-content-encoding"
25 |
26 | public static let xUserAgent = "x-user-agent"
27 |
28 | public static let grpcAcceptEncoding = "grpc-accept-encoding"
29 | public static let grpcContentEncoding = "grpc-encoding"
30 | public static let grpcMessage = "grpc-message"
31 | public static let grpcStatus = "grpc-status"
32 | public static let grpcStatusDetails = "grpc-status-details-bin"
33 | public static let grpcTimeout = "grpc-timeout"
34 | public static let grpcTE = "te"
35 | }
36 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/Streaming/Callbacks/ClientOnlyStreamInterface.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import SwiftProtobuf
16 |
17 | /// Represents a client-only stream (a stream where the client streams data to the server and
18 | /// eventually receives a response) that can send request messages and initiate closes.
19 | public protocol ClientOnlyStreamInterface {
20 | /// The input (request) message type.
21 | associatedtype Input: ProtobufMessage
22 |
23 | /// Send a request to the server over the stream.
24 | ///
25 | /// - parameter input: The request message to send.
26 | ///
27 | /// - returns: An instance of this stream, for syntactic sugar.
28 | @discardableResult
29 | func send(_ input: Input) throws -> Self
30 |
31 | /// Close the stream and await a response.
32 | /// No calls to `send()` are valid after calling `closeAndReceive()`.
33 | func closeAndReceive()
34 |
35 | /// Cancel the stream and return a canceled code.
36 | /// No calls to `send()` are valid after calling `cancel()`.
37 | func cancel()
38 | }
39 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/HTTPRequest.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Foundation
16 |
17 | /// Request used for sending data to the server.
18 | public struct HTTPRequest: Sendable {
19 | /// Target URL for the request.
20 | public let url: URL
21 | /// Additional outbound headers for the request.
22 | public let headers: Headers
23 | /// Data to send with the request.
24 | public let message: Input
25 | /// HTTP method to use for the request.
26 | public let method: HTTPMethod
27 | /// Outbound trailers for the request.
28 | public let trailers: Trailers?
29 | /// Idempotency level of the request.
30 | public let idempotencyLevel: IdempotencyLevel
31 |
32 | public init(
33 | url: URL, headers: Headers, message: Input, method: HTTPMethod,
34 | trailers: Trailers?, idempotencyLevel: IdempotencyLevel
35 | ) {
36 | self.url = url
37 | self.headers = headers
38 | self.message = message
39 | self.method = method
40 | self.trailers = trailers
41 | self.idempotencyLevel = idempotencyLevel
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/MethodSpec.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /// Contains metadata for a specific RPC method.
16 | public struct MethodSpec: Equatable, Codable, Sendable {
17 | /// The name of the method (1:1 with the `.proto` file). E.g., `Foo`.
18 | public let name: String
19 | /// The fully qualified name of the method's service. E.g., `foo.v1.FooService`.
20 | public let service: String
21 | /// The type of method (unary, bidirectional stream, etc.).
22 | public let type: MethodType
23 |
24 | /// The path of the RPC, constructed using the package, service, and method name.
25 | /// E.g., `foo.v1.FooService/Foo`.
26 | public var path: String {
27 | return "\(self.service)/\(self.name)"
28 | }
29 |
30 | public enum MethodType: Equatable, Codable, Sendable {
31 | case unary
32 | case clientStream
33 | case serverStream
34 | case bidirectionalStream
35 | }
36 |
37 | public init(name: String, service: String, type: MethodType) {
38 | self.name = name
39 | self.service = service
40 | self.type = type
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Libraries/ConnectNIO/Internal/Extensions/Headers+Extensions.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Connect
16 | import Foundation
17 | import NIOHTTP1
18 |
19 | // MARK: - NIO type extensions
20 |
21 | extension NIOHTTP1.HTTPHeaders {
22 | mutating func addNIOHeadersFromConnect(_ headers: Connect.Headers) {
23 | for (name, value) in headers {
24 | self.add(name: name, value: value.joined(separator: ","))
25 | }
26 | }
27 | }
28 |
29 | // MARK: - Connect type extensions
30 |
31 | extension Headers {
32 | static func fromNIOHeaders(_ nioHeaders: NIOHTTP1.HTTPHeaders) -> Self {
33 | return nioHeaders.reduce(into: [:]) { headers, current in
34 | let headerName = current.name.lowercased()
35 | for value in current.value.components(separatedBy: ",") {
36 | headers[headerName, default: []].append(value.trimmingCharacters(in: .whitespaces))
37 | }
38 | }
39 | }
40 | }
41 |
42 | extension Code {
43 | static func fromNIOStatus(_ nioStatus: NIOHTTP1.HTTPResponseStatus) -> Self {
44 | return .fromHTTPStatus(Int(nioStatus.code))
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Implementation/Codecs/ProtoCodec.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Foundation
16 | import SwiftProtobuf
17 |
18 | /// Codec providing functionality for serializing to/from Protobuf binary.
19 | public struct ProtoCodec {
20 | private let deterministicEncodingOptions: BinaryEncodingOptions = {
21 | var encodingOptions = BinaryEncodingOptions()
22 | encodingOptions.useDeterministicOrdering = true
23 | return encodingOptions
24 | }()
25 |
26 | public init() {}
27 | }
28 |
29 | extension ProtoCodec: Codec {
30 | public func name() -> String {
31 | return "proto"
32 | }
33 |
34 | public func serialize(message: Input) throws -> Data {
35 | return try message.serializedData()
36 | }
37 |
38 | public func deterministicallySerialize(message: Input) throws -> Data {
39 | return try message.serializedData(options: self.deterministicEncodingOptions)
40 | }
41 |
42 | public func deserialize(source: Data) throws -> Output {
43 | return try Output(serializedBytes: source)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Libraries/Connect/Internal/Utilities/Lock.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Foundation
16 |
17 | /// Internal implementation of a lock. Wraps usage of `os_unfair_lock`.
18 | final class Lock: @unchecked Sendable {
19 | private let underlyingLock: UnsafeMutablePointer
20 |
21 | init() {
22 | // Reasoning for allocating here: http://www.russbishop.net/the-law
23 | // When iOS 15 support is dropped, `OSAllocatedUnfairLock` should be used.
24 | self.underlyingLock = .allocate(capacity: 1)
25 | self.underlyingLock.initialize(to: os_unfair_lock())
26 | }
27 |
28 | deinit {
29 | self.underlyingLock.deinitialize(count: 1)
30 | self.underlyingLock.deallocate()
31 | }
32 |
33 | /// Perform an action within the context of the lock.
34 | ///
35 | /// - parameter action: Closure to be executed in the context of the lock.
36 | ///
37 | /// - returns: The result of the closure.
38 | func perform(action: @escaping () -> T) -> T {
39 | os_unfair_lock_lock(self.underlyingLock)
40 | defer { os_unfair_lock_unlock(self.underlyingLock) }
41 | return action()
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Libraries/Connect/Internal/Utilities/Locked.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Foundation
16 |
17 | /// Class containing an internal lock which can be used to ensure thread-safe access to an
18 | /// underlying value. Conforms to `Sendable`, making it accessible from `@Sendable` closures.
19 | final class Locked: @unchecked Sendable {
20 | private let lock = Lock()
21 | private var wrappedValue: Wrapped
22 |
23 | /// Thread-safe access to the underlying value.
24 | var value: Wrapped {
25 | get { self.lock.perform { self.wrappedValue } }
26 | set { self.lock.perform { self.wrappedValue = newValue } }
27 | }
28 |
29 | /// Perform an action with the underlying value, potentially updating that value.
30 | ///
31 | /// - parameter action: Closure to perform with the underlying value.
32 | ///
33 | /// - returns: The value returned by the closure.
34 | @discardableResult
35 | func perform(action: @escaping (inout Wrapped) -> Result) -> Result {
36 | return self.lock.perform {
37 | action(&self.wrappedValue)
38 | }
39 | }
40 |
41 | init(_ value: Wrapped) {
42 | self.wrappedValue = value
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Libraries/Connect/Internal/Streaming/ConnectEndStreamResponse.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | /// Structure modeling the final JSON message that is returned by Connect streams:
16 | /// https://connectrpc.com/docs/protocol#error-end-stream
17 | struct ConnectEndStreamResponse: Sendable {
18 | /// Connect error that was returned with the response.
19 | let error: ConnectError?
20 | /// Additional metadata that was passed with the response. Keys are guaranteed to be lowercased.
21 | let metadata: Trailers?
22 | }
23 |
24 | extension ConnectEndStreamResponse: Decodable {
25 | private enum CodingKeys: String, CodingKey {
26 | case error = "error"
27 | case metadata = "metadata"
28 | }
29 |
30 | init(from decoder: Decoder) throws {
31 | let container = try decoder.container(keyedBy: CodingKeys.self)
32 | let rawMetadata = try container.decodeIfPresent(Trailers.self, forKey: .metadata)
33 | self.init(
34 | error: try container.decodeIfPresent(ConnectError.self, forKey: .error),
35 | metadata: rawMetadata?.reduce(into: Trailers()) { trailers, current in
36 | trailers[current.key.lowercased()] = current.value
37 | }
38 | )
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Tests/ConformanceClient/Sources/CommandLineArgument.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // MARK: - Arguments
16 |
17 | enum ClientTypeArg: String, CaseIterable, CommandLineArgument {
18 | case swiftNIO = "nio"
19 | case urlSession = "urlsession"
20 |
21 | static let key = "httpclient"
22 | }
23 |
24 | // MARK: - Protocol interface
25 |
26 | protocol CommandLineArgument: RawRepresentable, CaseIterable {
27 | static var key: String { get }
28 |
29 | static func fromCommandLineArguments(_ arguments: [String]) throws -> Self
30 | }
31 |
32 | extension CommandLineArgument where RawValue == String {
33 | static func fromCommandLineArguments(_ arguments: [String]) throws -> Self {
34 | guard let argument = arguments.first(where: { $0.hasPrefix(self.key) }) else {
35 | throw "'\(self.key)' argument must be specified"
36 | }
37 |
38 | guard let argumentValue = self.init(
39 | rawValue: argument
40 | .replacingOccurrences(of: "\(self.key)=", with: "")
41 | .trimmingCharacters(in: .whitespaces)
42 | ) else {
43 | throw """
44 | Invalid argument passed for '\(self.key)' argument. \
45 | Expected \(self.allCases.map(\.rawValue)), got '\(argument)'
46 | """
47 | }
48 |
49 | return argumentValue
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/Streaming/AsyncAwait/ServerOnlyAsyncStreamInterface.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import SwiftProtobuf
16 |
17 | /// Represents a server-only stream (a stream where the server streams data to the client after
18 | /// receiving an initial request) that can be interacted with using async/await.
19 | @available(iOS 13, *)
20 | public protocol ServerOnlyAsyncStreamInterface: Sendable {
21 | /// The input (request) message type.
22 | associatedtype Input: ProtobufMessage
23 |
24 | /// The output (response) message type.
25 | associatedtype Output: ProtobufMessage
26 |
27 | /// Send a request to the server over the stream.
28 | ///
29 | /// Should be called exactly one time when starting the stream.
30 | ///
31 | /// - parameter input: The request message to send.
32 | func send(_ input: Input) throws
33 |
34 | /// Obtain an await-able list of results from the stream using async/await.
35 | ///
36 | /// Example usage: `for await result in stream.results() {...}`
37 | ///
38 | /// - returns: An `AsyncStream` that contains all outputs/results from the stream.
39 | func results() -> AsyncStream>
40 |
41 | /// Cancel the stream and return a canceled code.
42 | /// No calls to `send()` are valid after calling `cancel()`.
43 | func cancel()
44 | }
45 |
--------------------------------------------------------------------------------
/Libraries/Connect/Internal/Utilities/TimeoutTimer.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Foundation
16 |
17 | final class TimeoutTimer: @unchecked Sendable {
18 | private var hasTimedOut = false
19 | private var onTimeout: (() -> Void)?
20 | private let queue = DispatchQueue(label: "connectrpc.Timeout")
21 | private let timeout: TimeInterval
22 | private var workItem: DispatchWorkItem! // Force-unwrapped to allow capturing self in init
23 |
24 | var timedOut: Bool {
25 | return self.queue.sync { self.hasTimedOut }
26 | }
27 |
28 | init?(config: ProtocolClientConfig) {
29 | guard let timeout = config.timeout else {
30 | return nil
31 | }
32 |
33 | self.timeout = timeout
34 | self.workItem = DispatchWorkItem { [weak self] in
35 | self?.hasTimedOut = true
36 | self?.onTimeout?()
37 | }
38 | }
39 |
40 | deinit {
41 | self.cancel()
42 | }
43 |
44 | func start(onTimeout: @escaping () -> Void) {
45 | let milliseconds = Int(self.timeout * 1_000)
46 | self.queue.sync { self.onTimeout = onTimeout }
47 | self.queue.asyncAfter(
48 | deadline: .now() + .milliseconds(milliseconds), execute: self.workItem
49 | )
50 | }
51 |
52 | func cancel() {
53 | self.queue.sync {
54 | self.workItem.cancel()
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/CompressionPool.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Foundation
16 |
17 | /// Conforming types provide the functionality to compress/decompress data using a specific
18 | /// algorithm.
19 | ///
20 | /// `ProtocolClientInterface` implementations are expected to use the first compression pool with
21 | /// a matching `name()` for decompressing inbound responses.
22 | ///
23 | /// Outbound request compression can be specified using additional options that specify a
24 | /// `compressionName` that matches a compression pool's `name()`.
25 | public protocol CompressionPool: Sendable {
26 | /// The name of the compression pool, which corresponds to the `content-encoding` header.
27 | /// Example: `gzip`.
28 | ///
29 | /// - returns: The name of the compression pool that can be used with the `content-encoding`
30 | /// header.
31 | func name() -> String
32 |
33 | /// Compress an outbound request message.
34 | ///
35 | /// - parameter data: The uncompressed request message.
36 | ///
37 | /// - returns: The compressed request message.
38 | func compress(data: Data) throws -> Data
39 |
40 | /// Decompress an inbound response message.
41 | ///
42 | /// - parameter data: The compressed response message.
43 | ///
44 | /// - returns: The uncompressed response message.
45 | func decompress(data: Data) throws -> Data
46 | }
47 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/Streaming/AsyncAwait/BidirectionalAsyncStreamInterface.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import SwiftProtobuf
16 |
17 | /// Represents a bidirectional stream that can be interacted with using async/await.
18 | @available(iOS 13, *)
19 | public protocol BidirectionalAsyncStreamInterface: Sendable {
20 | /// The input (request) message type.
21 | associatedtype Input: ProtobufMessage
22 |
23 | /// The output (response) message type.
24 | associatedtype Output: ProtobufMessage
25 |
26 | /// Send a request to the server over the stream.
27 | ///
28 | /// - parameter input: The request message to send.
29 | ///
30 | /// - returns: An instance of this stream, for syntactic sugar.
31 | @discardableResult
32 | func send(_ input: Input) throws -> Self
33 |
34 | /// Obtain an await-able list of results from the stream using async/await.
35 | ///
36 | /// Example usage: `for await result in stream.results() {...}`
37 | ///
38 | /// - returns: An `AsyncStream` that contains all outputs/results from the stream.
39 | func results() -> AsyncStream>
40 |
41 | /// Close the stream. No calls to `send()` are valid after calling `close()`.
42 | func close()
43 |
44 | /// Cancel the stream and return a canceled code.
45 | /// No calls to `send()` are valid after calling `cancel()`.
46 | func cancel()
47 | }
48 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 | on:
3 | push:
4 | tags:
5 | - "*"
6 | workflow_dispatch: {} # support manual runs
7 | env:
8 | # Sets the Xcode version to use for the CI.
9 | # Available versions: https://github.com/actions/runner-images/blob/main/images/macos/macos-26-arm64-Readme.md#xcode
10 | # Ref: https://www.jessesquires.com/blog/2020/01/06/selecting-an-xcode-version-on-github-ci/
11 | DEVELOPER_DIR: /Applications/Xcode_26.1.1.app/Contents/Developer
12 | permissions:
13 | contents: write
14 | jobs:
15 | release:
16 | runs-on: macos-26
17 | steps:
18 | - uses: actions/checkout@v6
19 | - uses: bufbuild/buf-setup-action@v1.50.0
20 | with:
21 | github_token: ${{ github.token }}
22 | - name: Build plugins
23 | run: make buildplugins
24 | - name: Zip artifacts
25 | run: |
26 | cd ./.tmp/bin
27 | mkdir ./artifacts
28 | tar -zcvf ./artifacts/protoc-gen-connect-swift.tar.gz ./protoc-gen-connect-swift
29 | tar -zcvf ./artifacts/protoc-gen-connect-swift-mocks.tar.gz ./protoc-gen-connect-swift-mocks
30 | cd ./artifacts
31 | for file in $(find . -maxdepth 1 -type f | sed 's/^\.\///' | sort | uniq); do
32 | shasum -a 256 "${file}" >> sha256.txt
33 | done
34 | - name: Publish release
35 | uses: softprops/action-gh-release@v2
36 | with:
37 | generate_release_notes: true
38 | append_body: true
39 | files: |
40 | ./.tmp/bin/artifacts/*
41 | publish-podspecs:
42 | runs-on: macos-26
43 | steps:
44 | - uses: actions/checkout@v6
45 | - name: Publish podspecs to CocoaPods
46 | env:
47 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
48 | # Note that --synchronous is used since Mocks depends on the primary spec.
49 | run: |
50 | pod trunk push Connect-Swift.podspec --allow-warnings --synchronous
51 | pod trunk push Connect-Swift-Mocks.podspec --allow-warnings --synchronous
52 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/Streaming/Callbacks/ResponseCallbacks.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import Foundation
16 |
17 | /// Set of closures that are used for wiring inbound response data through from HTTP clients.
18 | public final class ResponseCallbacks: Sendable {
19 | /// Closure to call when response headers are available.
20 | public let receiveResponseHeaders: @Sendable (Headers) -> Void
21 | /// Closure to call when response data is available.
22 | public let receiveResponseData: @Sendable (Data) -> Void
23 | /// Closure to call when response metrics are available.
24 | public let receiveResponseMetrics: @Sendable (HTTPMetrics) -> Void
25 | /// Closure to call when the stream is closed.
26 | /// Includes the status code, trailers, and potentially an error.
27 | public let receiveClose: @Sendable (Code, Trailers, Swift.Error?) -> Void
28 |
29 | public init(
30 | receiveResponseHeaders: @escaping @Sendable (Headers) -> Void,
31 | receiveResponseData: @escaping @Sendable (Data) -> Void,
32 | receiveResponseMetrics: @escaping @Sendable (HTTPMetrics) -> Void,
33 | receiveClose: @escaping @Sendable (Code, Trailers, Swift.Error?) -> Void
34 | ) {
35 | self.receiveResponseHeaders = receiveResponseHeaders
36 | self.receiveResponseData = receiveResponseData
37 | self.receiveResponseMetrics = receiveResponseMetrics
38 | self.receiveClose = receiveClose
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Libraries/Connect/Public/Interfaces/ResponseMessage.swift:
--------------------------------------------------------------------------------
1 | // Copyright 2022-2025 The Connect Authors
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import SwiftProtobuf
16 |
17 | /// Typed unary response from an RPC.
18 | public struct ResponseMessage: Sendable {
19 | /// The status code of the response.
20 | public let code: Code
21 | /// Response headers specified by the server.
22 | public let headers: Headers
23 | /// The result of the RPC (either a message or an error).
24 | public let result: Result