Skip to content

Commit

Permalink
Add deprecation warnings for package-internal APIs (#315)
Browse files Browse the repository at this point in the history
This PR adds deprecation warnings to APIs that are considered
package-internal but cannot yet be marked as `package` instead of
`public` until we adopt Swift 6 (see
#310). We can't make
these APIs _currently_ deprecated since that will introduce deprecation
warnings where they're used within the library, but we can at least add
_future deprecation_ warnings which will show up in the IDE as shown
below:

<img width="684" alt="Screenshot 2024-10-16 at 11 27 45 AM"
src="https://github.com/user-attachments/assets/be489f7f-bf32-466d-8aa3-1fc999bba0c6">

The goal of doing this is to help enable us to tag a 1.0 before adopting
Swift 6 so we can mark those APIs as `package` afterwards and reasonably
consider it a non-breaking change.

---------

Signed-off-by: Michael Rebello <[email protected]>
  • Loading branch information
rebello95 authored Oct 17, 2024
1 parent 69661a2 commit 9bed141
Show file tree
Hide file tree
Showing 9 changed files with 106 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ extension ConnectInterceptor: StreamInterceptor {

@Sendable
func handleStreamRawInput(_ input: Data, proceed: @escaping (Data) -> Void) {
proceed(Envelope.packMessage(input, using: self.config.requestCompression))
proceed(Envelope._packMessage(input, using: self.config.requestCompression))
}

@Sendable
Expand Down Expand Up @@ -190,7 +190,7 @@ extension ConnectInterceptor: StreamInterceptor {
let responseCompressionPool = self.streamResponseHeaders.value?[
HeaderConstants.connectStreamingContentEncoding
]?.first.flatMap { self.config.responseCompressionPool(forName: $0) }
if responseCompressionPool == nil && Envelope.isCompressed(data) {
if responseCompressionPool == nil && Envelope._isCompressed(data) {
proceed(.complete(
code: .internalError, error: ConnectError(
code: .internalError, message: "received unexpected compressed message"
Expand All @@ -199,7 +199,7 @@ extension ConnectInterceptor: StreamInterceptor {
return
}

let (headerByte, message) = try Envelope.unpackMessage(
let (headerByte, message) = try Envelope._unpackMessage(
data, compressionPool: responseCompressionPool
)
let isEndStream = 0b00000010 & headerByte != 0
Expand Down
30 changes: 15 additions & 15 deletions Libraries/Connect/Internal/Interceptors/GRPCWebInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ extension GRPCWebInterceptor: UnaryInterceptor {
proceed: @escaping (Result<HTTPRequest<Data?>, ConnectError>) -> Void
) {
// gRPC-Web unary payloads are enveloped.
let envelopedRequestBody = Envelope.packMessage(
let envelopedRequestBody = Envelope._packMessage(
request.message ?? Data(), using: self.config.requestCompression
)
proceed(.success(HTTPRequest(
url: request.url,
headers: request.headers.addingGRPCHeaders(using: self.config, grpcWeb: true),
headers: request.headers._addingGRPCHeaders(using: self.config, grpcWeb: true),
message: envelopedRequestBody,
method: request.method,
trailers: nil,
Expand All @@ -57,7 +57,7 @@ extension GRPCWebInterceptor: UnaryInterceptor {
}

guard let responseData = response.message, !responseData.isEmpty else {
let (grpcCode, connectError) = ConnectError.parseGRPCHeaders(
let (grpcCode, connectError) = ConnectError._parseGRPCHeaders(
response.headers,
trailers: response.trailers
)
Expand Down Expand Up @@ -102,7 +102,7 @@ extension GRPCWebInterceptor: UnaryInterceptor {
let compressionPool = response.headers[HeaderConstants.grpcContentEncoding]?
.first
.flatMap { self.config.responseCompressionPool(forName: $0) }
if compressionPool == nil && Envelope.isCompressed(responseData) {
if compressionPool == nil && Envelope._isCompressed(responseData) {
proceed(HTTPResponse(
code: .internalError, headers: response.headers, message: nil,
trailers: response.trailers,
Expand All @@ -120,9 +120,9 @@ extension GRPCWebInterceptor: UnaryInterceptor {
// message data.
// 2. The (headers and length prefixed) trailers data.
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md
let firstChunkLength = Envelope.messageLength(forPackedData: responseData)
let prefixedFirstChunkLength = Envelope.prefixLength + firstChunkLength
let firstChunk = try Envelope.unpackMessage(
let firstChunkLength = Envelope._messageLength(forPackedData: responseData)
let prefixedFirstChunkLength = Envelope._prefixLength + firstChunkLength
let firstChunk = try Envelope._unpackMessage(
Data(responseData.prefix(upTo: prefixedFirstChunkLength)),
compressionPool: compressionPool
)
Expand All @@ -135,7 +135,7 @@ extension GRPCWebInterceptor: UnaryInterceptor {
} else {
let trailersData = Data(responseData.suffix(from: prefixedFirstChunkLength))
let unpackedTrailers = try Trailers.fromGRPCHeadersBlock(
try Envelope.unpackMessage(
try Envelope._unpackMessage(
trailersData, compressionPool: compressionPool
).unpacked
)
Expand Down Expand Up @@ -165,7 +165,7 @@ extension GRPCWebInterceptor: StreamInterceptor {
) {
proceed(.success(HTTPRequest(
url: request.url,
headers: request.headers.addingGRPCHeaders(using: self.config, grpcWeb: true),
headers: request.headers._addingGRPCHeaders(using: self.config, grpcWeb: true),
message: request.message,
method: request.method,
trailers: nil,
Expand All @@ -175,7 +175,7 @@ extension GRPCWebInterceptor: StreamInterceptor {

@Sendable
func handleStreamRawInput(_ input: Data, proceed: @escaping (Data) -> Void) {
proceed(Envelope.packMessage(input, using: self.config.requestCompression))
proceed(Envelope._packMessage(input, using: self.config.requestCompression))
}

@Sendable
Expand All @@ -198,11 +198,11 @@ extension GRPCWebInterceptor: StreamInterceptor {
return
}

if let grpcCode = headers.grpcStatus() {
if let grpcCode = headers._grpcStatus() {
// Headers-only response.
proceed(.complete(
code: grpcCode,
error: ConnectError.parseGRPCHeaders(nil, trailers: headers).error,
error: ConnectError._parseGRPCHeaders(nil, trailers: headers).error,
trailers: headers
))
} else {
Expand All @@ -215,13 +215,13 @@ extension GRPCWebInterceptor: StreamInterceptor {
let responseCompressionPool = self.streamResponseHeaders.value?[
HeaderConstants.grpcContentEncoding
]?.first.flatMap { self.config.responseCompressionPool(forName: $0) }
let (headerByte, unpackedData) = try Envelope.unpackMessage(
let (headerByte, unpackedData) = try Envelope._unpackMessage(
data, compressionPool: responseCompressionPool
)
let isTrailers = 0b10000000 & headerByte != 0
if isTrailers {
let trailers = try Trailers.fromGRPCHeadersBlock(unpackedData)
let (grpcCode, error) = ConnectError.parseGRPCHeaders(
let (grpcCode, error) = ConnectError._parseGRPCHeaders(
self.streamResponseHeaders.value, trailers: trailers
)
if grpcCode == .ok {
Expand Down Expand Up @@ -290,7 +290,7 @@ private extension Trailers {

private extension HTTPResponse {
func withHandledGRPCWebTrailers(_ trailers: Trailers, message: Data?) -> Self {
let (grpcCode, error) = ConnectError.parseGRPCHeaders(self.headers, trailers: trailers)
let (grpcCode, error) = ConnectError._parseGRPCHeaders(self.headers, trailers: trailers)
if grpcCode != .ok || error != nil {
return HTTPResponse(
// Rewrite the gRPC code if it is "ok" but `connectError` is non-nil.
Expand Down
9 changes: 7 additions & 2 deletions Libraries/Connect/PackageInternal/ConnectError+GRPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@ extension ConnectError {
/// passed in the headers block for gRPC-Web.
///
/// - returns: A tuple containing the gRPC status code and an optional error.
public static func parseGRPCHeaders(
@available(
swift,
deprecated: 100.0,
message: "This is an internal-only API which will be made package-private in Swift 6."
)
public static func _parseGRPCHeaders(
_ headers: Headers?, trailers: Trailers?
) -> (grpcCode: Code, error: ConnectError?) {
// "Trailers-only" responses can be sent in the headers or trailers block.
guard let grpcCode = trailers?.grpcStatus() ?? headers?.grpcStatus() else {
guard let grpcCode = trailers?._grpcStatus() ?? headers?._grpcStatus() else {
return (.unknown, ConnectError(code: .unknown, message: "RPC response missing status"))
}

Expand Down
44 changes: 37 additions & 7 deletions Libraries/Connect/PackageInternal/Envelope.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,19 @@ import SwiftProtobuf
/// to change. When the compiler supports it, this should be package-internal.**
///
/// Provides functionality for packing and unpacking (headers and length prefixed) messages.
@available(
swift,
deprecated: 100.0,
message: "This is an internal-only API which will be made package-private in Swift 6."
)
public enum Envelope {
/// The total number of bytes that will prefix a message.
public static var prefixLength: Int {
@available(
swift,
deprecated: 100.0,
message: "This is an internal-only API which will be made package-private in Swift 6."
)
public static var _prefixLength: Int {
return 5 // Header flags (1 byte) + message length (4 bytes)
}

Expand All @@ -35,7 +45,12 @@ public enum Envelope {
/// - parameter compression: Configuration to use for compressing the message.
///
/// - returns: Serialized/enveloped data for transmission.
public static func packMessage(
@available(
swift,
deprecated: 100.0,
message: "This is an internal-only API which will be made package-private in Swift 6."
)
public static func _packMessage(
_ source: Data, using compression: ProtocolClientConfig.RequestCompression?
) -> Data {
var buffer = Data()
Expand Down Expand Up @@ -70,7 +85,12 @@ public enum Envelope {
///
/// - returns: A tuple that includes the header byte and the un-prefixed and decompressed
/// message.
public static func unpackMessage(
@available(
swift,
deprecated: 100.0,
message: "This is an internal-only API which will be made package-private in Swift 6."
)
public static func _unpackMessage(
_ source: Data, compressionPool: CompressionPool?
) throws -> (headerByte: UInt8, unpacked: Data) {
if source.isEmpty {
Expand All @@ -79,7 +99,7 @@ public enum Envelope {

let headerByte = source[0]
let isCompressed = 0b00000001 & headerByte != 0
let messageData = Data(source.dropFirst(self.prefixLength))
let messageData = Data(source.dropFirst(self._prefixLength))
if isCompressed {
guard let compressionPool = compressionPool else {
throw Error.missingExpectedCompressionPool
Expand All @@ -96,7 +116,12 @@ public enum Envelope {
/// - parameter packedData: The packed data to analyze.
///
/// - returns: True if the data is compressed.
public static func isCompressed(_ packedData: Data) -> Bool {
@available(
swift,
deprecated: 100.0,
message: "This is an internal-only API which will be made package-private in Swift 6."
)
public static func _isCompressed(_ packedData: Data) -> Bool {
return !packedData.isEmpty && (0b00000001 & packedData[0] != 0)
}

Expand All @@ -111,8 +136,13 @@ public enum Envelope {
/// - 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 {
@available(
swift,
deprecated: 100.0,
message: "This is an internal-only API which will be made package-private in Swift 6."
)
public static func _messageLength(forPackedData data: Data) -> Int {
guard data.count >= self._prefixLength else {
return -1
}

Expand Down
7 changes: 6 additions & 1 deletion Libraries/Connect/PackageInternal/Headers+GRPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ 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 {
@available(
swift,
deprecated: 100.0,
message: "This is an internal-only API which will be made package-private in Swift 6."
)
public func _addingGRPCHeaders(using config: ProtocolClientConfig, grpcWeb: Bool) -> Self {
var headers = self
headers[HeaderConstants.grpcAcceptEncoding] = config
.acceptCompressionPoolNames()
Expand Down
7 changes: 6 additions & 1 deletion Libraries/Connect/PackageInternal/Trailers+gRPC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ extension Trailers {
/// Identifies the status code from gRPC and gRPC-Web trailers.
///
/// - returns: The gRPC status code, if specified.
public func grpcStatus() -> Code? {
@available(
swift,
deprecated: 100.0,
message: "This is an internal-only API which will be made package-private in Swift 6."
)
public func _grpcStatus() -> Code? {
return self[HeaderConstants.grpcStatus]?
.first
.flatMap(Int.init)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,12 +299,12 @@ extension ProtocolClient: ProtocolClientInterface {
// Handle cases where multiple messages are received in a single chunk.
responseBuffer += data
while true {
let messageLength = Envelope.messageLength(forPackedData: responseBuffer)
let messageLength = Envelope._messageLength(forPackedData: responseBuffer)
if messageLength < 0 {
return
}

let prefixedMessageLength = Envelope.prefixLength + messageLength
let prefixedMessageLength = Envelope._prefixLength + messageLength
guard responseBuffer.count >= prefixedMessageLength else {
return
}
Expand Down
26 changes: 13 additions & 13 deletions Libraries/ConnectNIO/Internal/GRPCInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ extension GRPCInterceptor: UnaryInterceptor {
proceed: @escaping (Result<HTTPRequest<Data?>, ConnectError>) -> Void
) {
// gRPC unary payloads are enveloped.
let envelopedRequestBody = Envelope.packMessage(
let envelopedRequestBody = Envelope._packMessage(
request.message ?? Data(), using: self.config.requestCompression
)

proceed(.success(HTTPRequest(
url: request.url,
headers: request.headers.addingGRPCHeaders(using: self.config, grpcWeb: false),
headers: request.headers._addingGRPCHeaders(using: self.config, grpcWeb: false),
message: envelopedRequestBody,
method: request.method,
trailers: nil,
Expand Down Expand Up @@ -72,12 +72,12 @@ extension GRPCInterceptor: UnaryInterceptor {
return
}

let (grpcCode, connectError) = ConnectError.parseGRPCHeaders(
let (grpcCode, connectError) = ConnectError._parseGRPCHeaders(
response.headers,
trailers: response.trailers
)
guard grpcCode == .ok, let rawData = response.message, !rawData.isEmpty else {
if response.trailers.grpcStatus() == nil && response.message?.isEmpty == false {
if response.trailers._grpcStatus() == nil && response.message?.isEmpty == false {
proceed(HTTPResponse(
code: .internalError,
headers: response.headers,
Expand Down Expand Up @@ -117,7 +117,7 @@ extension GRPCInterceptor: UnaryInterceptor {
.headers[HeaderConstants.grpcContentEncoding]?
.first
.flatMap { self.config.responseCompressionPool(forName: $0) }
if compressionPool == nil && Envelope.isCompressed(rawData) {
if compressionPool == nil && Envelope._isCompressed(rawData) {
proceed(HTTPResponse(
code: .internalError, headers: response.headers, message: nil,
trailers: response.trailers, error: ConnectError(
Expand All @@ -140,7 +140,7 @@ extension GRPCInterceptor: UnaryInterceptor {
}

do {
let messageData = try Envelope.unpackMessage(
let messageData = try Envelope._unpackMessage(
rawData, compressionPool: compressionPool
).unpacked
proceed(HTTPResponse(
Expand Down Expand Up @@ -172,7 +172,7 @@ extension GRPCInterceptor: StreamInterceptor {
) {
proceed(.success(HTTPRequest(
url: request.url,
headers: request.headers.addingGRPCHeaders(using: self.config, grpcWeb: false),
headers: request.headers._addingGRPCHeaders(using: self.config, grpcWeb: false),
message: request.message,
method: request.method,
trailers: nil,
Expand All @@ -182,7 +182,7 @@ extension GRPCInterceptor: StreamInterceptor {

@Sendable
func handleStreamRawInput(_ input: Data, proceed: @escaping (Data) -> Void) {
proceed(Envelope.packMessage(input, using: self.config.requestCompression))
proceed(Envelope._packMessage(input, using: self.config.requestCompression))
}

@Sendable
Expand Down Expand Up @@ -214,7 +214,7 @@ extension GRPCInterceptor: StreamInterceptor {
let responseCompressionPool = self.streamResponseHeaders.value?[
HeaderConstants.grpcContentEncoding
]?.first.flatMap { self.config.responseCompressionPool(forName: $0) }
if responseCompressionPool == nil && Envelope.isCompressed(rawData) {
if responseCompressionPool == nil && Envelope._isCompressed(rawData) {
proceed(.complete(
code: .internalError, error: ConnectError(
code: .internalError, message: "received unexpected compressed message"
Expand All @@ -223,7 +223,7 @@ extension GRPCInterceptor: StreamInterceptor {
return
}

let unpackedMessage = try Envelope.unpackMessage(
let unpackedMessage = try Envelope._unpackMessage(
rawData, compressionPool: responseCompressionPool
).unpacked
proceed(.message(unpackedMessage))
Expand All @@ -239,7 +239,7 @@ extension GRPCInterceptor: StreamInterceptor {
return
}

let (grpcCode, connectError) = ConnectError.parseGRPCHeaders(
let (grpcCode, connectError) = ConnectError._parseGRPCHeaders(
self.streamResponseHeaders.value,
trailers: trailers
)
Expand Down Expand Up @@ -275,8 +275,8 @@ extension GRPCInterceptor: StreamInterceptor {

private extension Envelope {
static func containsMultipleGRPCMessages(_ packedData: Data) -> Bool {
let messageLength = self.messageLength(forPackedData: packedData)
return packedData.count > messageLength + self.prefixLength
let messageLength = self._messageLength(forPackedData: packedData)
return packedData.count > messageLength + self._prefixLength
}
}

Expand Down
Loading

0 comments on commit 9bed141

Please sign in to comment.