Skip to content

Commit 8484624

Browse files
authored
impl: a manual test for ProtoJSON conformance (#2339)
Automating this test will take some doing, the driver is in the Protobuf repository and it is implemented in C++.
1 parent 610c856 commit 8484624

26 files changed

+5078
-0
lines changed

.sidekick.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ protobuf-src-extracted-name = 'protobuf-29.3'
2828
protobuf-src-root = 'https://github.com/protocolbuffers/protobuf/releases/download/v29.3/protobuf-29.3.tar.gz'
2929
protobuf-src-sha256 = '008a11cc56f9b96679b4c285fd05f46d317d685be3ab524b2a310be0fbad987e'
3030
protobuf-src-subdir = 'src'
31+
conformance-extracted-name = 'protobuf-29.3'
32+
conformance-root = 'https://github.com/protocolbuffers/protobuf/releases/download/v29.3/protobuf-29.3.tar.gz'
33+
conformance-sha256 = '008a11cc56f9b96679b4c285fd05f46d317d685be3ab524b2a310be0fbad987e'
3134

3235
[codec]
3336
# The default version for all crates. This can be overridden in the crate's

Cargo.lock

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ default-members = [
2929
"src/gax-internal",
3030
"src/integration-tests",
3131
"src/lro",
32+
"src/protojson-conformance",
3233
"src/pubsub",
3334
"src/root",
3435
"src/spanner",
@@ -250,6 +251,7 @@ members = [
250251
"src/generated/type",
251252
"src/integration-tests",
252253
"src/lro",
254+
"src/protojson-conformance",
253255
"src/pubsub",
254256
"src/root",
255257
"src/spanner",

src/protojson-conformance/Cargo.toml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright 2025 Google LLC
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+
# https://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+
[package]
16+
description = "Google Cloud client libraries for Rust: ProtoJSON conformance"
17+
edition.workspace = true
18+
name = "protojson-conformance"
19+
publish = false
20+
version = "0.0.0"
21+
22+
[dependencies]
23+
anyhow.workspace = true
24+
bytes.workspace = true
25+
crc32c.workspace = true
26+
futures.workspace = true
27+
prost.workspace = true
28+
prost-types.workspace = true
29+
serde.workspace = true
30+
serde_json.workspace = true
31+
serde_with.workspace = true
32+
tokio.workspace = true
33+
# Local dependencies
34+
wkt.workspace = true
35+
gaxi = { workspace = true, features = ["_internal-grpc-client"] }

src/protojson-conformance/README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Google Cloud client libraries for Rust: ProtoJSON conformance
2+
3+
This directory contains a test to verify the client libraries follow the
4+
[ProtoJSON] specification.
5+
6+
The conformance test is a Rust binary, it accepts serialized messages from its
7+
stdin, and returns serialized messages in stdout. A driver in the
8+
[Protocol Buffers repository] exercises the conformance test by providing
9+
specific inputs and validating the output.
10+
11+
## Compiling the conformance test
12+
13+
In this guide we will assume you used `$HOME/rust-conformance` to build the
14+
conformance test. Change the directories below if needed.
15+
16+
```shell
17+
cd $HOME
18+
git clone https://github.com/googleapis/google-cloud-rust rust-conformance
19+
cd rust-conformance
20+
cargo build -p protojson-conformance
21+
```
22+
23+
## Compiling the test runner
24+
25+
We will also need to checkout the Protobuf code:
26+
27+
```shell
28+
cd $HOME
29+
git clone -b 31.x https://github.com/protocolbuffers/protobuf.git
30+
cd protobuf
31+
```
32+
33+
Install bazelisk:
34+
35+
```shell
36+
curl -fsSL https://github.com/bazelbuild/bazelisk/releases/download/v1.26.0/bazelisk-darwin-arm64 -o bazelisk
37+
chmod 755 bazelisk
38+
./bazelisk version
39+
```
40+
41+
```shell
42+
USE_BAZEL_VERSION=8.2.1 ./bazelisk build --repo_env=BAZEL_NO_APPLE_CPP_TOOLCHAIN=1 --enable_bzlmod //conformance:conformance_test_runner
43+
```
44+
45+
## Running
46+
47+
Use bazelisk to compile and run the test program:
48+
49+
```shell
50+
USE_BAZEL_VERSION=8.2.1 ./bazelisk run --repo_env=BAZEL_NO_APPLE_CPP_TOOLCHAIN=1 --enable_bzlmod //conformance:conformance_test_runner $HOME/rust-conformance/target/debug/protojson-conformance
51+
```
52+
53+
[protocol buffers repository]: https://github.com/protocolbuffers/protobuf/blob/main/conformance/README.md
54+
[protojson]: https://protobuf.dev/programming-guides/json/
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2025 Google LLC
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+
// https://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+
pub mod gapic;
16+
#[allow(non_snake_case)]
17+
pub mod test_protos;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright 2025 Google LLC
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+
# https://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+
[general]
16+
language = 'rust'
17+
specification-source = 'conformance'
18+
19+
[source]
20+
roots = 'conformance'
21+
include-list = 'conformance.proto'
22+
23+
[codec]
24+
copyright-year = '2025'
25+
template-override = 'templates/convert-prost'
26+
module-path = 'crate::generated::gapic::model'
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// Copyright 2025 Google LLC
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+
// https://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+
// Code generated by sidekick. DO NOT EDIT.
16+
17+
impl gaxi::prost::ToProto<WireFormat> for crate::generated::gapic::model::WireFormat {
18+
type Output = i32;
19+
fn to_proto(self) -> std::result::Result<Self::Output, gaxi::prost::ConvertError> {
20+
self.value().ok_or(gaxi::prost::ConvertError::EnumNoIntegerValue("crate::generated::gapic::model::WireFormat"))
21+
}
22+
}
23+
24+
impl gaxi::prost::ToProto<TestCategory> for crate::generated::gapic::model::TestCategory {
25+
type Output = i32;
26+
fn to_proto(self) -> std::result::Result<Self::Output, gaxi::prost::ConvertError> {
27+
self.value().ok_or(gaxi::prost::ConvertError::EnumNoIntegerValue("crate::generated::gapic::model::TestCategory"))
28+
}
29+
}
30+
31+
impl gaxi::prost::ToProto<TestStatus> for crate::generated::gapic::model::TestStatus {
32+
type Output = TestStatus;
33+
fn to_proto(self) -> std::result::Result<TestStatus, gaxi::prost::ConvertError> {
34+
Ok(Self::Output {
35+
name: self.name.to_proto()?,
36+
failure_message: self.failure_message.to_proto()?,
37+
matched_name: self.matched_name.to_proto()?,
38+
})
39+
}
40+
}
41+
42+
impl gaxi::prost::FromProto<crate::generated::gapic::model::TestStatus> for TestStatus {
43+
fn cnv(self) -> std::result::Result<crate::generated::gapic::model::TestStatus, gaxi::prost::ConvertError> {
44+
Ok(
45+
crate::generated::gapic::model::TestStatus::new()
46+
.set_name(self.name)
47+
.set_failure_message(self.failure_message)
48+
.set_matched_name(self.matched_name)
49+
)
50+
}
51+
}
52+
53+
impl gaxi::prost::ToProto<FailureSet> for crate::generated::gapic::model::FailureSet {
54+
type Output = FailureSet;
55+
fn to_proto(self) -> std::result::Result<FailureSet, gaxi::prost::ConvertError> {
56+
Ok(Self::Output {
57+
test: self.test
58+
.into_iter()
59+
.map(|v| v.to_proto())
60+
.collect::<std::result::Result<std::vec::Vec<_>, _>>()?,
61+
})
62+
}
63+
}
64+
65+
impl gaxi::prost::FromProto<crate::generated::gapic::model::FailureSet> for FailureSet {
66+
fn cnv(self) -> std::result::Result<crate::generated::gapic::model::FailureSet, gaxi::prost::ConvertError> {
67+
Ok(
68+
crate::generated::gapic::model::FailureSet::new()
69+
.set_test(self.test.into_iter().map(|v| v.cnv())
70+
.collect::<std::result::Result<std::vec::Vec<_>, _>>()?)
71+
)
72+
}
73+
}
74+
75+
impl gaxi::prost::ToProto<conformance_request::Payload> for crate::generated::gapic::model::conformance_request::Payload {
76+
type Output = conformance_request::Payload;
77+
fn to_proto(self) -> std::result::Result<Self::Output, gaxi::prost::ConvertError> {
78+
match self {
79+
Self::ProtobufPayload(v) => Ok(Self::Output::ProtobufPayload(v.to_proto()?)),
80+
Self::JsonPayload(v) => Ok(Self::Output::JsonPayload(v.to_proto()?)),
81+
Self::JspbPayload(v) => Ok(Self::Output::JspbPayload(v.to_proto()?)),
82+
Self::TextPayload(v) => Ok(Self::Output::TextPayload(v.to_proto()?)),
83+
}
84+
}
85+
}
86+
87+
impl gaxi::prost::FromProto<crate::generated::gapic::model::conformance_request::Payload> for conformance_request::Payload {
88+
fn cnv(self) -> std::result::Result<crate::generated::gapic::model::conformance_request::Payload, gaxi::prost::ConvertError> {
89+
use crate::generated::gapic::model::conformance_request::Payload as T;
90+
match self {
91+
Self::ProtobufPayload(v) => Ok(T::from_protobuf_payload(v.cnv()?)),
92+
Self::JsonPayload(v) => Ok(T::from_json_payload(v.cnv()?)),
93+
Self::JspbPayload(v) => Ok(T::from_jspb_payload(v.cnv()?)),
94+
Self::TextPayload(v) => Ok(T::from_text_payload(v.cnv()?)),
95+
}
96+
}
97+
}
98+
99+
impl gaxi::prost::ToProto<ConformanceRequest> for crate::generated::gapic::model::ConformanceRequest {
100+
type Output = ConformanceRequest;
101+
fn to_proto(self) -> std::result::Result<ConformanceRequest, gaxi::prost::ConvertError> {
102+
Ok(Self::Output {
103+
requested_output_format: self.requested_output_format.to_proto()?,
104+
message_type: self.message_type.to_proto()?,
105+
test_category: self.test_category.to_proto()?,
106+
jspb_encoding_options: self.jspb_encoding_options.map(|v| v.to_proto()).transpose()?,
107+
print_unknown_fields: self.print_unknown_fields.to_proto()?,
108+
payload: self.payload.map(|v| v.to_proto()).transpose()?,
109+
})
110+
}
111+
}
112+
113+
impl gaxi::prost::FromProto<crate::generated::gapic::model::ConformanceRequest> for ConformanceRequest {
114+
fn cnv(self) -> std::result::Result<crate::generated::gapic::model::ConformanceRequest, gaxi::prost::ConvertError> {
115+
Ok(
116+
crate::generated::gapic::model::ConformanceRequest::new()
117+
.set_requested_output_format(self.requested_output_format)
118+
.set_message_type(self.message_type)
119+
.set_test_category(self.test_category)
120+
.set_or_clear_jspb_encoding_options(self.jspb_encoding_options.map(|v| v.cnv()).transpose()?)
121+
.set_print_unknown_fields(self.print_unknown_fields)
122+
.set_payload(self.payload.map(|v| v.cnv()).transpose()?)
123+
)
124+
}
125+
}
126+
127+
impl gaxi::prost::ToProto<conformance_response::Result> for crate::generated::gapic::model::conformance_response::Result {
128+
type Output = conformance_response::Result;
129+
fn to_proto(self) -> std::result::Result<Self::Output, gaxi::prost::ConvertError> {
130+
match self {
131+
Self::ParseError(v) => Ok(Self::Output::ParseError(v.to_proto()?)),
132+
Self::SerializeError(v) => Ok(Self::Output::SerializeError(v.to_proto()?)),
133+
Self::TimeoutError(v) => Ok(Self::Output::TimeoutError(v.to_proto()?)),
134+
Self::RuntimeError(v) => Ok(Self::Output::RuntimeError(v.to_proto()?)),
135+
Self::ProtobufPayload(v) => Ok(Self::Output::ProtobufPayload(v.to_proto()?)),
136+
Self::JsonPayload(v) => Ok(Self::Output::JsonPayload(v.to_proto()?)),
137+
Self::Skipped(v) => Ok(Self::Output::Skipped(v.to_proto()?)),
138+
Self::JspbPayload(v) => Ok(Self::Output::JspbPayload(v.to_proto()?)),
139+
Self::TextPayload(v) => Ok(Self::Output::TextPayload(v.to_proto()?)),
140+
}
141+
}
142+
}
143+
144+
impl gaxi::prost::FromProto<crate::generated::gapic::model::conformance_response::Result> for conformance_response::Result {
145+
fn cnv(self) -> std::result::Result<crate::generated::gapic::model::conformance_response::Result, gaxi::prost::ConvertError> {
146+
use crate::generated::gapic::model::conformance_response::Result as T;
147+
match self {
148+
Self::ParseError(v) => Ok(T::from_parse_error(v.cnv()?)),
149+
Self::SerializeError(v) => Ok(T::from_serialize_error(v.cnv()?)),
150+
Self::TimeoutError(v) => Ok(T::from_timeout_error(v.cnv()?)),
151+
Self::RuntimeError(v) => Ok(T::from_runtime_error(v.cnv()?)),
152+
Self::ProtobufPayload(v) => Ok(T::from_protobuf_payload(v.cnv()?)),
153+
Self::JsonPayload(v) => Ok(T::from_json_payload(v.cnv()?)),
154+
Self::Skipped(v) => Ok(T::from_skipped(v.cnv()?)),
155+
Self::JspbPayload(v) => Ok(T::from_jspb_payload(v.cnv()?)),
156+
Self::TextPayload(v) => Ok(T::from_text_payload(v.cnv()?)),
157+
}
158+
}
159+
}
160+
161+
impl gaxi::prost::ToProto<ConformanceResponse> for crate::generated::gapic::model::ConformanceResponse {
162+
type Output = ConformanceResponse;
163+
fn to_proto(self) -> std::result::Result<ConformanceResponse, gaxi::prost::ConvertError> {
164+
Ok(Self::Output {
165+
result: self.result.map(|v| v.to_proto()).transpose()?,
166+
})
167+
}
168+
}
169+
170+
impl gaxi::prost::FromProto<crate::generated::gapic::model::ConformanceResponse> for ConformanceResponse {
171+
fn cnv(self) -> std::result::Result<crate::generated::gapic::model::ConformanceResponse, gaxi::prost::ConvertError> {
172+
Ok(
173+
crate::generated::gapic::model::ConformanceResponse::new()
174+
.set_result(self.result.map(|v| v.cnv()).transpose()?)
175+
)
176+
}
177+
}
178+
179+
impl gaxi::prost::ToProto<JspbEncodingConfig> for crate::generated::gapic::model::JspbEncodingConfig {
180+
type Output = JspbEncodingConfig;
181+
fn to_proto(self) -> std::result::Result<JspbEncodingConfig, gaxi::prost::ConvertError> {
182+
Ok(Self::Output {
183+
use_jspb_array_any_format: self.use_jspb_array_any_format.to_proto()?,
184+
})
185+
}
186+
}
187+
188+
impl gaxi::prost::FromProto<crate::generated::gapic::model::JspbEncodingConfig> for JspbEncodingConfig {
189+
fn cnv(self) -> std::result::Result<crate::generated::gapic::model::JspbEncodingConfig, gaxi::prost::ConvertError> {
190+
Ok(
191+
crate::generated::gapic::model::JspbEncodingConfig::new()
192+
.set_use_jspb_array_any_format(self.use_jspb_array_any_format)
193+
)
194+
}
195+
}

0 commit comments

Comments
 (0)