Skip to content

Conversation

@jazev-stripe
Copy link
Contributor

@jazev-stripe jazev-stripe commented Aug 30, 2025

What was changed

This PR allows configuring gRPC binary metadata key/values (where the key ends in -bin) on the Temporal client struct. It's already possible for Rust users of the Temporal client struct to set per-request gRPC binary metadata key/values; this allows setting client-wide headers.

The main API changes are:

  • Added: ClientOptions.binary_headers: Option<HashMap<String, Vec<u8>>>
  • Added: ConfiguredClient::set_binary_headers(&self, binary_headers: HashMap<String, Vec<u8>>) -> Result<(), InvalidHeaderError>
  • Added: InvalidHeaderError
  • Modified: ConfiguredClient::set_headers (for setting ASCII headers) now returns Err(InvalidHeaderError) if the keys or values are invalid, and no headers are modified. Before, it would just silently discard/skip invalid keys or values.
  • Modified: ClientOptions::connect (and the related methods) now returns an Err(ClientInitError::InvalidHeaders(InvalidHeaderError)) if any of the keys or values passed in ClientOptions are invalid

The approach taken is to parse the metadata keys/values early, and store an already-parsed representation (that can be infallibly applied to the outbound metadata, in ClientHeaders ::apply_to_metadata).

This PR is depended on by temporalio/sdk-python#1070

Why?

#991

(for the specific parts in Core SDK that are needed for the Python SDK; this doesn't cover all Core-SDK changes that are needed (for e.g. the Rust SDK or the C bridge)).

Checklist

  1. Closes

  2. How was this tested:

    • See added/modified unit tests in client/src/lib.rs
    • Existing tests pass (so the behavior for ASCII headers shouldn't have regressed)
    • In Add support for gRPC binary metadata values sdk-python#1070, I modified the client tests to cover both per-request gRPC binary metadata and client-wide gRPC binary metadata
  3. Any docs updates needed?

    • I don't think so? Afaik there are no docs on the Core SDK from a "Rust integrators" perspective. The updates to docs.rs should be sufficient IMO.

@CLAassistant
Copy link

CLAassistant commented Aug 30, 2025

CLA assistant check
All committers have signed the CLA.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@Sushisource
Copy link
Member

@jazev-stripe I haven't reviewed this since it's still DRAFT. Thanks for contributing though! Anything you need to un-draft? Happy to review it once it is.

@jazev-stripe
Copy link
Contributor Author

jazev-stripe commented Sep 4, 2025

@Sushisource sorry about the lack of details; I've been meaning to finish this PR (and the corresponding Python one).

I have a thread in the Stripe support channel in the Temporal Slack about this with @cretz where we've discussed the shape of the change, and I think we're mostly aligned [0]. I'm aiming to get this PR in a more reviewable state by the end of the week (once I get a spare couple hours at work🤞).

[0] One change I think I'll make based on our discussion is to make ClientHeaders.user_binary_headers and ClientHeaders.user_headers store pre-validated tonic metadata keys/values, so that applying them to the metadata is infallible (and instead return errors earlier, when the client is constructed or when set_headers/set_binary_headers is called).

@jazev-stripe jazev-stripe force-pushed the jazev-stripe/binary-grpc-headers branch from 741c6ba to deeb57a Compare September 5, 2025 17:26
@jazev-stripe jazev-stripe changed the title [WIP] Add support for gRPC binary metadata values Add support for configuring client-wide gRPC binary metadata values, validate metadata earlier Sep 5, 2025
@jazev-stripe jazev-stripe marked this pull request as ready for review September 5, 2025 17:27
@jazev-stripe jazev-stripe requested a review from a team as a code owner September 5, 2025 17:27
Copy link
Member

@cretz cretz left a comment

Choose a reason for hiding this comment

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

This LGTM, would like @Sushisource to take a peek before merging


/// Errors thrown when a gRPC metadata header is invalid.
#[derive(thiserror::Error, Debug)]
pub enum InvalidHeaderError {
Copy link
Member

Choose a reason for hiding this comment

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

Would have also been ok w/ just anyhow::Error (if it works here) to keep it simple since this is rare, but this more verbose one is probably fine too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Happy to switch over to an anyhow::Error if you feel strongly; otherwise I'll leave it as-is

Part of why I biased towards wrapper errors is that the tonic errors don't include the key/value that was invalid (though we could always add one/both to the anyhow error context if we wanted)

let key = match AsciiMetadataKey::from_str(&k) {
Ok(key) => key,
Err(err) => {
return Err(InvalidHeaderError::InvalidAsciiHeaderKey {
Copy link
Contributor Author

@jazev-stripe jazev-stripe Sep 5, 2025

Choose a reason for hiding this comment

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

The match statement here is somewhat verbose, but I used it instead of .map_err to avoid cloning the key, in the case that we need to return an error

}
};
let value = match MetadataValue::from_str(&v) {
Ok(value) => value,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same here (for the value)

source: tonic::metadata::errors::InvalidMetadataKey,
},
/// An ASCII header value was invalid
#[error("Invalid ASCII header value for key '{key}': {source}")]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't include the invalid value here because I was a bit worried that including it in error messages may lead to a credentials logging risk for users (if headers are used for security values, for example).

Let me know if that makes sense, or if I should make any changes

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense to me

Copy link
Member

@Sushisource Sushisource left a comment

Choose a reason for hiding this comment

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

Looking good to me. Thanks for the contribution! Just one potential docstring update.

Comment on lines 159 to 160
/// HTTP headers to include on every RPC call as binary gRPC metadata (typically encoded as
/// base64).
Copy link
Member

Choose a reason for hiding this comment

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

Not sure what "typically" means here. Presumably the encoding will be happening when the client transmits them, so could we make a more definitive statement? Or is it dependent on some negotiation with the server?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I was doing research, I saw https://github.com/grpc/proposal/blob/HEAD/G1-true-binary-metadata.md, which involves sometimes not using base64 for binary headers, if negotiated. However, it's unclear to me if that proposal is actually used in practice (or what the state of it is). It doesn't seem accepted based on the header of the doc, but I also see references to that symbol in the gRPC C++ codebase (possibly in a partially-implemented state).

Either way, I think it's almost always encoded as base64, and whatever the encoding is, it's internal to the gRPC transport mechanism. I'll just remove "typically"; this distinction probably doesn't matter much for consumers. The doc-comments on tonic seem to reflect that it only uses base64 too: https://docs.rs/tonic/latest/tonic/metadata/struct.MetadataValue.html

@cretz cretz merged commit 042372d into temporalio:master Sep 5, 2025
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants