Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Libraries/Connect/Interfaces/ConnectError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,27 @@ public struct ConnectError: Swift.Error, Sendable {
case payload = "value"
}

public init(from decoder: Swift.Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

// Read the base64-encoded payload and then pad it if needed:
// Base64-encoded strings should be a length that is a multiple of four. If the
// original string is not, it should be padded with "=" to guard against a
// corrupted string.
let encodedPayload = try container.decodeIfPresent(String.self, forKey: .payload) ?? ""
let paddedPayload = encodedPayload.padding(
// Calculate the nearest multiple of 4 that is >= the length of encodedPayload,
// then pad the string to that length.
toLength: ((encodedPayload.count + 3) / 4) * 4,
withPad: "=",
startingAt: 0
)
self.init(
type: try container.decodeIfPresent(String.self, forKey: .type) ?? "",
payload: Data(base64Encoded: paddedPayload)
)
}

public init(type: String, payload: Data?) {
self.type = type
self.payload = payload
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ MAKEFLAGS += --no-builtin-rules
MAKEFLAGS += --no-print-directory
BIN := .tmp/bin
LICENSE_HEADER_YEAR_RANGE := 2022-2023
CONFORMANCE_VERSION := 36ce5b0ce50bc8cb3314eee08a72eeda4c96721e
CONFORMANCE_VERSION := 8e6893a4b801c282eb6cf9ad03d38b993b66ca66
LICENSE_HEADER_VERSION := v1.12.0
LICENSE_IGNORE := -e Package.swift \
-e $(BIN)\/ \
Expand Down Expand Up @@ -47,10 +47,10 @@ conformanceserverstop: ## Stop the conformance server
.PHONY: conformanceserverrun
conformanceserverrun: conformanceserverstop ## Start the conformance server
docker run --rm --name serverconnect -p 8080:8080 -p 8081:8081 -d \
bufbuild/connect-crosstest:$(CONFORMANCE_VERSION) \
connectrpc/conformance:$(CONFORMANCE_VERSION) \
/usr/local/bin/serverconnect --h1port "8080" --h2port "8081" --cert "cert/localhost.crt" --key "cert/localhost.key"
docker run --rm --name servergrpc -p 8083:8083 -d \
bufbuild/connect-crosstest:$(CONFORMANCE_VERSION) \
connectrpc/conformance:$(CONFORMANCE_VERSION) \
/usr/local/bin/servergrpc --port "8083" --cert "cert/localhost.crt" --key "cert/localhost.key"

.PHONY: generate
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ for details.
- [connect-go][connect-go]: Go service stubs for servers
- [connect-es][connect-es]: Type-safe APIs with Protobuf and TypeScript
- [Buf Studio][buf-studio]: Web UI for ad-hoc RPCs
- [connect-conformance][connect-conformance]: Connect, gRPC, and gRPC-Web
- [conformance][connect-conformance]: Connect, gRPC, and gRPC-Web
interoperability tests

## Status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import XCTest

private let kTimeout = TimeInterval(10.0)

private typealias TestServiceClient = Grpc_Testing_TestServiceClient
private typealias UnimplementedServiceClient = Grpc_Testing_UnimplementedServiceClient
private typealias TestServiceClient = Connectrpc_Conformance_V1_TestServiceClient
private typealias UnimplementedServiceClient = Connectrpc_Conformance_V1_UnimplementedServiceClient

/// This test suite runs against multiple protocols and serialization formats.
/// Tests are based on https://github.com/connectrpc/conformance
Expand Down Expand Up @@ -67,7 +67,7 @@ final class AsyncAwaitConformance: XCTestCase {
func testLargeUnary() async {
await self.executeTestWithClients { client in
let size = 314_159
let message = Grpc_Testing_SimpleRequest.with { proto in
let message = Connectrpc_Conformance_V1_SimpleRequest.with { proto in
proto.responseSize = Int32(size)
proto.payload = .with { $0.body = Data(repeating: 0, count: size) }
}
Expand All @@ -81,7 +81,7 @@ final class AsyncAwaitConformance: XCTestCase {
try await self.executeTestWithClients { client in
let sizes = [31_415, 9, 2_653, 58_979]
let stream = client.streamingOutputCall()
try stream.send(Grpc_Testing_StreamingOutputCallRequest.with { proto in
try stream.send(Connectrpc_Conformance_V1_StreamingOutputCallRequest.with { proto in
proto.responseParameters = sizes.enumerated().map { index, size in
return .with { parameters in
parameters.size = Int32(size)
Expand Down Expand Up @@ -117,7 +117,7 @@ final class AsyncAwaitConformance: XCTestCase {
try await self.executeTestWithClients { client in
let closeExpectation = self.expectation(description: "Stream completes")
let stream = client.streamingOutputCall()
try stream.send(Grpc_Testing_StreamingOutputCallRequest.with { proto in
try stream.send(Connectrpc_Conformance_V1_StreamingOutputCallRequest.with { proto in
proto.responseParameters = []
})
for await result in stream.results() {
Expand Down Expand Up @@ -150,7 +150,7 @@ final class AsyncAwaitConformance: XCTestCase {
leadingKey: [leadingValue],
trailingKey: [trailingValue.base64EncodedString()],
]
let message = Grpc_Testing_SimpleRequest.with { proto in
let message = Connectrpc_Conformance_V1_SimpleRequest.with { proto in
proto.responseSize = Int32(size)
proto.payload = .with { $0.body = Data(repeating: 0, count: size) }
}
Expand Down Expand Up @@ -182,7 +182,7 @@ final class AsyncAwaitConformance: XCTestCase {
let messageExpectation = self.expectation(description: "Receives message")
let trailersExpectation = self.expectation(description: "Receives trailers")
let stream = client.streamingOutputCall(headers: headers)
try stream.send(Grpc_Testing_StreamingOutputCallRequest.with { proto in
try stream.send(Connectrpc_Conformance_V1_StreamingOutputCallRequest.with { proto in
proto.responseParameters = [.with { $0.size = Int32(size) }]
})
for await result in stream.results() {
Expand Down Expand Up @@ -210,7 +210,7 @@ final class AsyncAwaitConformance: XCTestCase {
}

func testStatusCodeAndMessage() async {
let message = Grpc_Testing_SimpleRequest.with { proto in
let message = Connectrpc_Conformance_V1_SimpleRequest.with { proto in
proto.responseStatus = .with { status in
status.code = Int32(Code.unknown.rawValue)
status.message = "test status message"
Expand All @@ -227,7 +227,7 @@ final class AsyncAwaitConformance: XCTestCase {
func testSpecialStatus() async {
let statusMessage =
"\\t\\ntest with whitespace\\r\\nand Unicode BMP ☺ and non-BMP \\uD83D\\uDE08\\t\\n"
let message = Grpc_Testing_SimpleRequest.with { proto in
let message = Connectrpc_Conformance_V1_SimpleRequest.with { proto in
proto.responseStatus = .with { status in
status.code = 2
status.message = statusMessage
Expand All @@ -244,7 +244,7 @@ final class AsyncAwaitConformance: XCTestCase {
func testTimeoutOnSleepingServer() async throws {
try await self.executeTestWithClients(timeout: 0.01) { client in
let expectation = self.expectation(description: "Stream times out")
let message = Grpc_Testing_StreamingOutputCallRequest.with { proto in
let message = Connectrpc_Conformance_V1_StreamingOutputCallRequest.with { proto in
proto.payload = .with { $0.body = Data(count: 271_828) }
proto.responseParameters = [
.with { parameters in
Expand Down Expand Up @@ -282,7 +282,7 @@ final class AsyncAwaitConformance: XCTestCase {
XCTAssertEqual(response.code, .unimplemented)
XCTAssertEqual(
response.error?.message,
"grpc.testing.TestService.UnimplementedCall is not implemented"
"connectrpc.conformance.v1.TestService.UnimplementedCall is not implemented"
)
}
}
Expand All @@ -302,7 +302,8 @@ final class AsyncAwaitConformance: XCTestCase {
XCTAssertEqual(
(error as? ConnectError)?.message,
"""
grpc.testing.TestService.UnimplementedStreamingOutputCall is not implemented
connectrpc.conformance.v1.TestService.UnimplementedStreamingOutputCall is \
not implemented
"""
)
expectation.fulfill()
Expand Down Expand Up @@ -348,11 +349,13 @@ final class AsyncAwaitConformance: XCTestCase {

func testFailUnary() async {
await self.executeTestWithClients { client in
let expectedErrorDetail = Grpc_Testing_ErrorDetail.with { proto in
let expectedErrorDetail = Connectrpc_Conformance_V1_ErrorDetail.with { proto in
proto.reason = "soirée 🎉"
proto.domain = "connect-crosstest"
proto.domain = "connect-conformance"
}
let response = await client.failUnaryCall(request: Grpc_Testing_SimpleRequest())
let response = await client.failUnaryCall(
request: Connectrpc_Conformance_V1_SimpleRequest()
)
XCTAssertEqual(response.error?.code, .resourceExhausted)
XCTAssertEqual(response.error?.message, "soirée 🎉")
XCTAssertEqual(response.error?.unpackedDetails(), [expectedErrorDetail])
Expand All @@ -361,13 +364,14 @@ final class AsyncAwaitConformance: XCTestCase {

func testFailServerStreaming() async throws {
try await self.executeTestWithClients { client in
let expectedErrorDetail = Grpc_Testing_ErrorDetail.with { proto in
let expectedErrorDetail = Connectrpc_Conformance_V1_ErrorDetail.with { proto in
proto.reason = "soirée 🎉"
proto.domain = "connect-crosstest"
proto.domain = "connect-conformance"
}
let expectation = self.expectation(description: "Stream completes")
let stream = client.failStreamingOutputCall()
try stream.send(Grpc_Testing_StreamingOutputCallRequest())

try stream.send(Connectrpc_Conformance_V1_StreamingOutputCallRequest())
for await result in stream.results() {
switch result {
case .headers:
Expand All @@ -394,13 +398,13 @@ final class AsyncAwaitConformance: XCTestCase {

func testFailServerStreamingAfterResponse() async throws {
try await self.executeTestWithClients { client in
let expectedErrorDetail = Grpc_Testing_ErrorDetail.with { proto in
let expectedErrorDetail = Connectrpc_Conformance_V1_ErrorDetail.with { proto in
proto.reason = "soirée 🎉"
proto.domain = "connect-crosstest"
proto.domain = "connect-conformance"
}
let sizes = [31_415, 9, 2_653, 58_979]
let stream = client.failStreamingOutputCall()
try stream.send(Grpc_Testing_StreamingOutputCallRequest.with { proto in
try stream.send(Connectrpc_Conformance_V1_StreamingOutputCallRequest.with { proto in
proto.responseParameters = sizes.enumerated().map { index, size in
return .with { parameters in
parameters.size = Int32(size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import XCTest

private let kTimeout = TimeInterval(10.0)

private typealias TestServiceClient = Grpc_Testing_TestServiceClient
private typealias UnimplementedServiceClient = Grpc_Testing_UnimplementedServiceClient
private typealias TestServiceClient = Connectrpc_Conformance_V1_TestServiceClient
private typealias UnimplementedServiceClient = Connectrpc_Conformance_V1_UnimplementedServiceClient

/// This test suite runs against multiple protocols and serialization formats.
/// Tests are based on https://github.com/connectrpc/conformance
Expand Down Expand Up @@ -69,7 +69,7 @@ final class CallbackConformance: XCTestCase {
func testLargeUnary() {
self.executeTestWithClients { client in
let size = 314_159
let message = Grpc_Testing_SimpleRequest.with { proto in
let message = Connectrpc_Conformance_V1_SimpleRequest.with { proto in
proto.responseSize = Int32(size)
proto.payload = .with { $0.body = Data(repeating: 0, count: size) }
}
Expand Down Expand Up @@ -103,7 +103,7 @@ final class CallbackConformance: XCTestCase {
expectation.fulfill()
}
}
try stream.send(Grpc_Testing_StreamingOutputCallRequest.with { proto in
try stream.send(Connectrpc_Conformance_V1_StreamingOutputCallRequest.with { proto in
proto.responseParameters = sizes.enumerated().map { index, size in
return .with { parameters in
parameters.size = Int32(size)
Expand Down Expand Up @@ -134,7 +134,7 @@ final class CallbackConformance: XCTestCase {
closeExpectation.fulfill()
}
}
try stream.send(Grpc_Testing_StreamingOutputCallRequest.with { proto in
try stream.send(Connectrpc_Conformance_V1_StreamingOutputCallRequest.with { proto in
proto.responseParameters = []
})

Expand All @@ -153,7 +153,7 @@ final class CallbackConformance: XCTestCase {
leadingKey: [leadingValue],
trailingKey: [trailingValue.base64EncodedString()],
]
let message = Grpc_Testing_SimpleRequest.with { proto in
let message = Connectrpc_Conformance_V1_SimpleRequest.with { proto in
proto.responseSize = Int32(size)
proto.payload = .with { $0.body = Data(repeating: 0, count: size) }
}
Expand Down Expand Up @@ -206,7 +206,7 @@ final class CallbackConformance: XCTestCase {
trailersExpectation.fulfill()
}
}
try stream.send(Grpc_Testing_StreamingOutputCallRequest.with { proto in
try stream.send(Connectrpc_Conformance_V1_StreamingOutputCallRequest.with { proto in
proto.responseParameters = [.with { $0.size = Int32(size) }]
})

Expand All @@ -217,7 +217,7 @@ final class CallbackConformance: XCTestCase {
}

func testStatusCodeAndMessage() {
let message = Grpc_Testing_SimpleRequest.with { proto in
let message = Connectrpc_Conformance_V1_SimpleRequest.with { proto in
proto.responseStatus = .with { status in
status.code = Int32(Code.unknown.rawValue)
status.message = "test status message"
Expand All @@ -243,7 +243,7 @@ final class CallbackConformance: XCTestCase {
func testSpecialStatus() {
let statusMessage =
"\\t\\ntest with whitespace\\r\\nand Unicode BMP ☺ and non-BMP \\uD83D\\uDE08\\t\\n"
let message = Grpc_Testing_SimpleRequest.with { proto in
let message = Connectrpc_Conformance_V1_SimpleRequest.with { proto in
proto.responseStatus = .with { status in
status.code = 2
status.message = statusMessage
Expand All @@ -269,7 +269,7 @@ final class CallbackConformance: XCTestCase {
func testTimeoutOnSleepingServer() throws {
try self.executeTestWithClients(timeout: 0.01) { client in
let expectation = self.expectation(description: "Stream times out")
let message = Grpc_Testing_StreamingOutputCallRequest.with { proto in
let message = Connectrpc_Conformance_V1_StreamingOutputCallRequest.with { proto in
proto.payload = .with { $0.body = Data(count: 271_828) }
proto.responseParameters = [
.with { parameters in
Expand Down Expand Up @@ -305,7 +305,7 @@ final class CallbackConformance: XCTestCase {
XCTAssertEqual(response.code, .unimplemented)
XCTAssertEqual(
response.error?.message,
"grpc.testing.TestService.UnimplementedCall is not implemented"
"connectrpc.conformance.v1.TestService.UnimplementedCall is not implemented"
)
expectation.fulfill()
}
Expand All @@ -327,7 +327,7 @@ final class CallbackConformance: XCTestCase {
XCTAssertEqual(
(error as? ConnectError)?.message,
"""
grpc.testing.TestService.UnimplementedStreamingOutputCall is \
connectrpc.conformance.v1.TestService.UnimplementedStreamingOutputCall is \
not implemented
"""
)
Expand Down Expand Up @@ -377,12 +377,12 @@ final class CallbackConformance: XCTestCase {

func testFailUnary() {
self.executeTestWithClients { client in
let expectedErrorDetail = Grpc_Testing_ErrorDetail.with { proto in
let expectedErrorDetail = Connectrpc_Conformance_V1_ErrorDetail.with { proto in
proto.reason = "soirée 🎉"
proto.domain = "connect-crosstest"
proto.domain = "connect-conformance"
}
let expectation = self.expectation(description: "Request completes")
client.failUnaryCall(request: Grpc_Testing_SimpleRequest()) { response in
client.failUnaryCall(request: Connectrpc_Conformance_V1_SimpleRequest()) { response in
XCTAssertEqual(response.error?.code, .resourceExhausted)
XCTAssertEqual(response.error?.message, "soirée 🎉")
XCTAssertEqual(response.error?.unpackedDetails(), [expectedErrorDetail])
Expand All @@ -395,9 +395,9 @@ final class CallbackConformance: XCTestCase {

func testFailServerStreaming() throws {
try self.executeTestWithClients { client in
let expectedErrorDetail = Grpc_Testing_ErrorDetail.with { proto in
let expectedErrorDetail = Connectrpc_Conformance_V1_ErrorDetail.with { proto in
proto.reason = "soirée 🎉"
proto.domain = "connect-crosstest"
proto.domain = "connect-conformance"
}
let expectation = self.expectation(description: "Stream completes")
let stream = client.failStreamingOutputCall { result in
Expand All @@ -420,17 +420,17 @@ final class CallbackConformance: XCTestCase {
expectation.fulfill()
}
}
try stream.send(Grpc_Testing_StreamingOutputCallRequest())
try stream.send(Connectrpc_Conformance_V1_StreamingOutputCallRequest())

XCTAssertEqual(XCTWaiter().wait(for: [expectation], timeout: kTimeout), .completed)
}
}

func testFailServerStreamingAfterResponse() throws {
try self.executeTestWithClients { client in
let expectedErrorDetail = Grpc_Testing_ErrorDetail.with { proto in
let expectedErrorDetail = Connectrpc_Conformance_V1_ErrorDetail.with { proto in
proto.reason = "soirée 🎉"
proto.domain = "connect-crosstest"
proto.domain = "connect-conformance"
}
let expectation = self.expectation(description: "Stream completes")
var responseCount = 0
Expand All @@ -456,7 +456,7 @@ final class CallbackConformance: XCTestCase {
expectation.fulfill()
}
}
try stream.send(Grpc_Testing_StreamingOutputCallRequest.with { proto in
try stream.send(Connectrpc_Conformance_V1_StreamingOutputCallRequest.with { proto in
proto.responseParameters = sizes.enumerated().map { index, size in
return .with { parameters in
parameters.size = Int32(size)
Expand Down
Loading