Skip to content
Merged
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
17 changes: 17 additions & 0 deletions src/integration-tests/tests/serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@
#[cfg(test)]
mod serialization {
use anyhow::Result;
use serde_json::json;

// The generator introduces synthetic fields for some OpenAPI-based
// services. Those are skipped during serialization, we need a test to
// verify this is the case.
#[test]
fn skip_synthetic_fields() -> Result<()> {
use smo::model::{CreateSecretRequest, Secret};
let input = CreateSecretRequest::new()
.set_project("a-synthetic-field")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am late to the party and confused. I read:

// The OpenAPI specifications have incomplete `*Request` messages. We inject
// some helper fields. These need to be marked so they can be excluded
// from serialized messages and in other places.
Synthetic bool

So these synthetic fields are part of the path we would use to make the RPC, not part the request? That makes sense. But don't things work the same way with gRPC transcoding?

Hmm, I thought fields in the path need not be repeated in the body (even when the body is *). But I don't see that stated in https://google.aip.dev/127.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, 2.4.2 in go/actools-regapic-grpc-transcoding says fields from the path should not be included in the body, but this is optional. So what we are doing is fine.

Still the way this is unified feels weird to me. Seems like we could serialize the fields, even if they are synthetic. But then in the transport.rs layer, we could std::mem::take the fields out of the request for use in the path.

Thoughts?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts?

OpenAPI needs a lot of work in this area.

Seems like we could serialize the fields, even if they are synthetic.

I do not think we can, because (sometimes) they do not match any existing field in the original request.

Consider this:

pub struct CreateSecretByProjectAndLocationRequest {
/// The request body.
#[serde(skip_serializing_if = "std::option::Option::is_none")]
pub request_body: std::option::Option<crate::model::Secret>,
/// The `{project}` component of the target path.
///
/// The full target path will be in the form `/v1/projects/{project}/locations/{location}/secrets`.
#[serde(skip)]
pub project: std::string::String,
/// The `{location}` component of the target path.
///
/// The full target path will be in the form `/v1/projects/{project}/locations/{location}/secrets`.
#[serde(skip)]
pub location: std::string::String,
/// Required. This must be unique within the project.
///
/// A secret ID is a string with a maximum length of 255 characters and can
/// contain uppercase and lowercase letters, numerals, and the hyphen (`-`) and
/// underscore (`_`) characters.
#[serde(skip)]
pub secret_id: std::string::String,
#[serde(flatten, skip_serializing_if = "serde_json::Map::is_empty")]
_unknown_fields: serde_json::Map<std::string::String, serde_json::Value>,
}

The synthetic fields are project, location and secret_id. That is all the info we have in the source specification:

"parameters": [
{
"name": "project",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "location",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "secretId",
"description": "Required. This must be unique within the project.\n\nA secret ID is a string with a maximum length of 255 characters and can\ncontain uppercase and lowercase letters, numerals, and the hyphen (`-`) and\nunderscore (`_`) characters.",
"in": "query",
"required": true,
"schema": {
"type": "string"
}
}
],

If we wanted to include them in the body we should be sending secret_id and parent:

https://github.com/googleapis/googleapis/blob/1804a603281707a1f0e6fff27851cae115ac3c8b/google/cloud/secretmanager/v1/service.proto#L65-L68

https://github.com/googleapis/googleapis/blob/1804a603281707a1f0e6fff27851cae115ac3c8b/google/cloud/secretmanager/v1/service.proto#L322-L343

But we do not even know the original name of the (decomposed) project and location fields.

.set_secret_id("another-synthetic-field")
.set_request_body(Secret::new().set_labels([("test-name", "test-value")]));
let got = serde_json::to_value(input)?;
let want = json!({"requestBody": {"labels": {"test-name": "test-value"}}});
assert_eq!(got, want);
Ok(())
}

#[test]
fn multiple_serde_attributes() -> Result<()> {
Expand Down
Loading