Skip to content

Commit

Permalink
Reorganize project files & reduce public interface (#212)
Browse files Browse the repository at this point in the history
Reorganizes files in the project to be more clearly and consistently
organized by visibility (whether the types within each file should be
internal to the package or public). Also reduces the public interface of
the module by moving some types to `internal` or `package`.

Note that `package` is specific to SPM, so the keyword cannot be used in
conjunction with CocoaPods. For this reason, a few of the types are
guarded with `#if COCOAPODS`. It's also worth noting that `ConnectNIO`
cannot be consumed via CocoaPods today because NIO does not support
CocoaPods. For this reason, the shared gRPC interfaces between `Connect`
and `ConnectNIO` don't need to be exposed to CocoaPods at all.
Additionally, [`package` requires Swift
5.9](https://github.com/apple/swift-evolution/blob/main/proposals/0386-package-access-modifier.md),
so the Swift package version has been updated as well.

New directory structure:

```
.
├── Examples
│   ├── ElizaCocoaPodsApp
│   ├── ElizaSharedSources
│   │   ├── AppSources
│   │   └── GeneratedSources
│   │       └── connectrpc
│   │           └── eliza
│   │               └── v1
│   └── ElizaSwiftPackageApp
│       └── ElizaSwiftPackageApp
├── Libraries
│   ├── Connect
│   │   ├── Internal
│   │   │   ├── Generated
│   │   │   │   └── grpc
│   │   │   │       └── status
│   │   │   │           └── v1
│   │   │   ├── Interceptors
│   │   │   ├── Locks
│   │   │   ├── Streaming
│   │   │   └── Unary
│   │   ├── PackageInternal
│   │   ├── Public
│   │   │   ├── Implementation
│   │   │   │   ├── Clients
│   │   │   │   ├── Codecs
│   │   │   │   └── Compression
│   │   │   └── Interfaces
│   │   │       ├── Interceptors
│   │   │       └── Streaming
│   │   │           ├── AsyncAwait
│   │   │           └── Callbacks
│   │   └── proto
│   │       └── grpc
│   │           └── status
│   │               └── v1
│   ├── ConnectMocks
│   └── ConnectNIO
│       ├── Internal
│       │   └── Extensions
│       └── Public
├── Plugins
│   ├── ConnectMocksPlugin
│   ├── ConnectPluginUtilities
│   └── ConnectSwiftPlugin
└── Tests
    ├── ConnectLibraryTests
    │   ├── ConnectConformance
    │   ├── ConnectMocksTests
    │   ├── ConnectTests
    │   ├── Generated
    │   │   ├── connectrpc
    │   │   │   └── conformance
    │   │   │       └── v1
    │   │   └── server
    │   │       └── v1
    │   └── TestResources
    └── ConnectPluginUtilitiesTests
```
  • Loading branch information
rebello95 committed Nov 14, 2023
1 parent e19c6b3 commit c3a8716
Show file tree
Hide file tree
Showing 64 changed files with 112 additions and 47 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yaml
Expand Up @@ -44,6 +44,9 @@ jobs:
runs-on: macos-13
steps:
- uses: actions/checkout@v4
- name: Select Xcode version
# https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md#xcode
run: sudo xcode-select --switch /Applications/Xcode_15.0.app
- uses: bufbuild/buf-setup-action@v1.28.0
with:
github_token: ${{ github.token }}
Expand Down
2 changes: 1 addition & 1 deletion .swiftlint.yml
Expand Up @@ -4,7 +4,7 @@ included:
- Plugins
- Tests
excluded:
- Libraries/Connect/Implementation/Generated
- Libraries/Connect/Internal/Generated
- Tests/ConnectLibraryTests/Generated
disabled_rules:
- blanket_disable_command
Expand Down
File renamed without changes.
Expand Up @@ -16,26 +16,26 @@ import Foundation

/// Class containing an internal lock which can be used to ensure thread-safe access to an
/// underlying value. Conforms to `Sendable`, making it accessible from `@Sendable` closures.
public final class Locked<T>: @unchecked Sendable {
final class Locked<T>: @unchecked Sendable {
private let lock = Lock()
private var wrappedValue: T

/// Thread-safe access to the underlying value.
public var value: T {
var value: T {
get { self.lock.perform { self.wrappedValue } }
set { self.lock.perform { self.wrappedValue = newValue } }
}

/// Perform an action with the underlying value, potentially updating that value.
///
/// - parameter action: Closure to perform with the underlying value.
public func perform(action: @escaping (inout T) -> Void) {
func perform(action: @escaping (inout T) -> Void) {
self.lock.perform {
action(&self.wrappedValue)
}
}

public init(_ value: T) {
init(_ value: T) {
self.wrappedValue = value
}
}
Expand Up @@ -21,7 +21,17 @@ extension ConnectError {
/// - parameter code: The status code received from the server.
///
/// - returns: An error, if the status indicated an error.
public static func fromGRPCTrailers(_ trailers: Trailers, code: Code) -> Self? {
#if COCOAPODS // ConnectNIO is unavailable from CocoaPods, so this can be internal.
static func fromGRPCTrailers(_ trailers: Trailers, code: Code) -> Self? {
return self._fromGRPCTrailers(trailers, code: code)
}
#else
package static func fromGRPCTrailers(_ trailers: Trailers, code: Code) -> Self? {
return self._fromGRPCTrailers(trailers, code: code)
}
#endif

private static func _fromGRPCTrailers(_ trailers: Trailers, code: Code) -> Self? {
if code == .ok {
return nil
}
Expand Down
Expand Up @@ -16,39 +16,10 @@ import Foundation
import SwiftProtobuf

/// Provides functionality for packing and unpacking (headers and length prefixed) messages.
///
/// This API is not considered part of Connect's public interface and is subject to change.
/// TODO: Make this `package` instead of `public` if/when CocoaPods support is dropped.
public enum Envelope {
public enum Error: Swift.Error {
case missingExpectedCompressionPool
}

/// The total number of bytes that will prefix a message.
public static var prefixLength: Int {
return 5 // Header flags (1 byte) + message length (4 bytes)
}

/// Computes the length of the message contained by a packed chunk of data.
/// A packed chunk in this context refers to prefixed message data.
///
/// Compliant with Connect streams: https://connectrpc.com/docs/protocol/#streaming-request
/// And gRPC: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#responses
///
/// - parameter data: The packed data from which to determine the enveloped message's size.
///
/// - returns: The length of the next expected message in the packed data. If multiple chunks
/// are specified, this will return the length of the first. Returns -1 if there is
/// not enough prefix data to determine the message length.
public static func messageLength(forPackedData data: Data) -> Int {
guard data.count >= self.prefixLength else {
return -1
}

// Skip header flags (1 byte) and determine the message length (next 4 bytes, big-endian)
var messageLength: UInt32 = 0
(data[1...4] as NSData).getBytes(&messageLength, length: 4)
messageLength = UInt32(bigEndian: messageLength)
return Int(messageLength)
}

/// Packs a message into an "envelope", adding required header bytes and optionally
/// applying compression.
///
Expand Down Expand Up @@ -112,6 +83,40 @@ public enum Envelope {
}
}

// MARK: - Internal

enum Error: Swift.Error {
case missingExpectedCompressionPool
}

/// The total number of bytes that will prefix a message.
static var prefixLength: Int {
return 5 // Header flags (1 byte) + message length (4 bytes)
}

/// Computes the length of the message contained by a packed chunk of data.
/// A packed chunk in this context refers to prefixed message data.
///
/// Compliant with Connect streams: https://connectrpc.com/docs/protocol/#streaming-request
/// And gRPC: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#responses
///
/// - parameter data: The packed data from which to determine the enveloped message's size.
///
/// - returns: The length of the next expected message in the packed data. If multiple chunks
/// are specified, this will return the length of the first. Returns -1 if there is
/// not enough prefix data to determine the message length.
static func messageLength(forPackedData data: Data) -> Int {
guard data.count >= self.prefixLength else {
return -1
}

// Skip header flags (1 byte) and determine the message length (next 4 bytes, big-endian)
var messageLength: UInt32 = 0
(data[1...4] as NSData).getBytes(&messageLength, length: 4)
messageLength = UInt32(bigEndian: messageLength)
return Int(messageLength)
}

// MARK: - Private

private static func write(lengthOf message: Data, to buffer: inout Data) {
Expand Down
Expand Up @@ -22,7 +22,17 @@ extension Headers {
/// - parameter grpcWeb: Should be true if using gRPC-Web, false if gRPC.
///
/// - returns: A set of updated headers.
public func addingGRPCHeaders(using config: ProtocolClientConfig, grpcWeb: Bool) -> Self {
#if COCOAPODS // ConnectNIO is unavailable from CocoaPods, so this can be internal.
func addingGRPCHeaders(using config: ProtocolClientConfig, grpcWeb: Bool) -> Self {
return self._addingGRPCHeaders(using: config, grpcWeb: grpcWeb)
}
#else
package func addingGRPCHeaders(using config: ProtocolClientConfig, grpcWeb: Bool) -> Self {
return self._addingGRPCHeaders(using: config, grpcWeb: grpcWeb)
}
#endif

private func _addingGRPCHeaders(using config: ProtocolClientConfig, grpcWeb: Bool) -> Self {
var headers = self
headers[HeaderConstants.grpcAcceptEncoding] = config
.acceptCompressionPoolNames()
Expand Down
Expand Up @@ -16,7 +16,17 @@ extension Trailers {
/// Identifies the status code from gRPC and gRPC-Web trailers.
///
/// - returns: The gRPC status code, if specified.
public func grpcStatus() -> Code? {
#if COCOAPODS // ConnectNIO is unavailable from CocoaPods, so this can be internal.
func grpcStatus() -> Code? {
return self._grpcStatus()
}
#else
package func grpcStatus() -> Code? {
return self._grpcStatus()
}
#endif

private func _grpcStatus() -> Code? {
return self[HeaderConstants.grpcStatus]?
.first
.flatMap(Int.init)
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion Libraries/Connect/buf.gen.yaml
Expand Up @@ -2,4 +2,4 @@ version: v1
plugins:
- plugin: buf.build/apple/swift:v1.25.1
opt: Visibility=Internal
out: ./Implementation/Generated
out: ./Internal/Generated
16 changes: 16 additions & 0 deletions Libraries/ConnectNIO/Internal/GRPCInterceptor.swift
Expand Up @@ -14,6 +14,7 @@

import Connect
import Foundation
import NIOConcurrencyHelpers

/// Implementation of the gRPC protocol as an interceptor.
/// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md
Expand Down Expand Up @@ -186,3 +187,18 @@ extension GRPCInterceptor: StreamInterceptor {
}
}
}

private final class Locked<T>: @unchecked Sendable {
private let lock = NIOLock()
private var wrappedValue: T

/// Thread-safe access to the underlying value.
var value: T {
get { self.lock.withLock { self.wrappedValue } }
set { self.lock.withLock { self.wrappedValue = newValue } }
}

init(_ value: T) {
self.wrappedValue = value
}
}
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -14,7 +14,7 @@ LICENSE_HEADER_VERSION := v1.12.0
LICENSE_IGNORE := -e Package.swift \
-e $(BIN)\/ \
-e Examples/ElizaSharedSources/GeneratedSources\/ \
-e Libraries/Connect/Implementation/Generated\/ \
-e Libraries/Connect/Internal/Generated\/ \
-e Tests/ConnectLibraryTests/proto/grpc\/ \
-e Tests/ConnectLibraryTests/Generated\/

Expand Down
15 changes: 13 additions & 2 deletions Package.swift
@@ -1,4 +1,4 @@
// swift-tools-version:5.6
// swift-tools-version:5.9

// Copyright 2022-2023 Buf Technologies, Inc.
//
Expand All @@ -16,8 +16,10 @@

import PackageDescription

private let packageName = "Connect"

let package = Package(
name: "Connect",
name: packageName,
platforms: [
.iOS(.v12),
.macOS(.v10_15),
Expand Down Expand Up @@ -75,6 +77,9 @@ let package = Package(
"buf.work.yaml",
"proto",
"README.md",
],
swiftSettings: [
.unsafeFlags(["-package-name", packageName])
]
),
.testTarget(
Expand Down Expand Up @@ -104,6 +109,9 @@ let package = Package(
path: "Libraries/ConnectMocks",
exclude: [
"README.md",
],
swiftSettings: [
.unsafeFlags(["-package-name", packageName])
]
),
.executableTarget(
Expand All @@ -129,6 +137,9 @@ let package = Package(
path: "Libraries/ConnectNIO",
exclude: [
"README.md",
],
swiftSettings: [
.unsafeFlags(["-package-name", packageName])
]
),
.target(
Expand Down
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import Connect
@testable import Connect
import Foundation
import SwiftProtobuf
import XCTest
Expand Down
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import Connect
@testable import Connect
import ConnectMocks
import SwiftProtobuf
import XCTest
Expand Down
2 changes: 1 addition & 1 deletion Tests/ConnectLibraryTests/ConnectTests/EnvelopeTests.swift
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import Connect
@testable import Connect
import XCTest

final class EnvelopeTests: XCTestCase {
Expand Down

0 comments on commit c3a8716

Please sign in to comment.