Skip to content

Commit 02bc9ff

Browse files
committed
Merge master into request_header_code_gen
2 parents f4f0aac + 18615a7 commit 02bc9ff

55 files changed

Lines changed: 4426 additions & 172 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cargo/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ integ-test = [
1212
"test-utilities",
1313
"--features",
1414
"ephemeral-server",
15+
"--features",
16+
"temporalio-sdk-core/otel",
1517
"--package",
1618
"temporalio-sdk-core",
1719
"--test",

.github/workflows/heavy.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,10 @@ jobs:
1919
with:
2020
submodules: recursive
2121
- uses: dtolnay/rust-toolchain@stable
22-
with:
23-
toolchain: 1.90.0
2422
- name: Install protoc
2523
uses: arduino/setup-protoc@v3
2624
with:
2725
# TODO: Upgrade proto once https://github.com/arduino/setup-protoc/issues/99 is fixed
28-
version: '23.x'
26+
version: "23.x"
2927
repo-token: ${{ secrets.GITHUB_TOKEN }}
3028
- run: cargo integ-test -c "--release" -t heavy_tests -- --test-threads 1

.github/workflows/per-pr.yml

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ jobs:
2424
with:
2525
submodules: recursive
2626
- uses: dtolnay/rust-toolchain@stable
27-
with:
28-
toolchain: 1.92.0
2927
- name: Install protoc
3028
uses: arduino/setup-protoc@v3
3129
with:
@@ -66,8 +64,6 @@ jobs:
6664
steps:
6765
- uses: actions/checkout@v4
6866
- uses: dtolnay/rust-toolchain@stable
69-
with:
70-
toolchain: 1.92.0
7167
- name: Install protoc
7268
uses: arduino/setup-protoc@v3
7369
with:
@@ -108,6 +104,30 @@ jobs:
108104
name: state-machine-coverage
109105
path: machine_coverage/
110106

107+
msrv:
108+
name: "Verify MSRV"
109+
timeout-minutes: ${{ github.ref == 'refs/heads/master' && 15 || 10 }}
110+
runs-on: ubuntu-latest
111+
steps:
112+
- uses: actions/checkout@v4
113+
with:
114+
submodules: recursive
115+
- uses: dtolnay/rust-toolchain@stable
116+
- name: Install protoc
117+
uses: arduino/setup-protoc@v3
118+
with:
119+
# TODO: Upgrade proto once https://github.com/arduino/setup-protoc/issues/99 is fixed
120+
version: "23.x"
121+
repo-token: ${{ secrets.GITHUB_TOKEN }}
122+
- run: rustup component add rustfmt clippy
123+
- uses: Swatinem/rust-cache@v2
124+
with:
125+
save-if: ${{ github.ref == 'refs/heads/master' }}
126+
key: msrv
127+
- run: cargo install cargo-msrv
128+
- run: cargo msrv verify
129+
working-directory: ./crates/sdk-core
130+
111131
cgroup-tests:
112132
name: cgroup tests
113133
needs: test
@@ -147,8 +167,6 @@ jobs:
147167
steps:
148168
- uses: actions/checkout@v4
149169
- uses: dtolnay/rust-toolchain@stable
150-
with:
151-
toolchain: 1.92.0
152170
- name: Install protoc
153171
uses: arduino/setup-protoc@v3
154172
with:
@@ -182,8 +200,6 @@ jobs:
182200
steps:
183201
- uses: actions/checkout@v4
184202
- uses: dtolnay/rust-toolchain@stable
185-
with:
186-
toolchain: 1.92.0
187203
- name: Install protoc
188204
uses: arduino/setup-protoc@v3
189205
with:
@@ -209,8 +225,6 @@ jobs:
209225
steps:
210226
- uses: actions/checkout@v4
211227
- uses: dtolnay/rust-toolchain@stable
212-
with:
213-
toolchain: 1.92.0
214228
- name: Install protoc
215229
uses: arduino/setup-protoc@v3
216230
with:
@@ -232,8 +246,6 @@ jobs:
232246
steps:
233247
- uses: actions/checkout@v4
234248
- uses: dtolnay/rust-toolchain@stable
235-
with:
236-
toolchain: 1.92.0
237249
- name: Install protoc
238250
uses: arduino/setup-protoc@v3
239251
with:

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ document as your quick reference when submitting pull requests.
2222
integration tests with `timeout 180`, as it is possible to introduce test hangs.
2323
- DO NOT put use statements in function scope. Always put them at the top of the file, unless doing
2424
so helps prevent ambiguous method resolution because of traits. Putting them at the top of a tests module is also acceptable.
25+
- If you want to format, don't bother checking first. Just run formatting, and run it by using
26+
`cargo +nightly fmt`, because some settings require the nightly formatter.
2527

2628

2729
## Repo Specific Utilities

REQUEST_HEADER_CODE_GEN_PLAN.md

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
# Rust Implementation Plan: Request Header Code Generation
2+
3+
## Overview
4+
5+
This plan outlines the implementation of a Rust equivalent to the Go code generator added in [temporalio/api-go#236](https://github.com/temporalio/api-go/pull/236) that automatically extracts field values from request messages and propagates them as HTTP headers based on protobuf annotations defined in [temporalio/api#728](https://github.com/temporalio/api/pull/728).
6+
7+
## Background
8+
9+
The Go implementation adds:
10+
1. Proto annotations (`temporal.api.protometa.v1.request_header`) that specify which fields should be extracted from request messages and set as headers
11+
2. A code generator that parses these annotations and generates header extraction logic
12+
3. Runtime functions to extract headers from request messages and add them to outgoing gRPC metadata
13+
14+
The annotations support template interpolation (e.g., `"workflow:{workflow_id}"`) where field paths in braces are replaced with actual field values.
15+
16+
## Current State Analysis
17+
18+
### Existing Rust Infrastructure
19+
- **Protobuf Generation**: Uses `tonic_prost_build` in `crates/common/build.rs` to generate Rust types from proto files
20+
- **Descriptor Access**: Already generates and saves `descriptors.bin` for use by existing code generators
21+
- **Code Generation Pattern**: Existing `PayloadVisitorGenerator` shows the pattern for parsing descriptors and generating Rust code
22+
- **gRPC Client**: `crates/client/src/grpc.rs` handles gRPC service calls with existing header support
23+
- **Request Extensions**: `crates/client/src/request_extensions.rs` provides mechanisms for request modification
24+
25+
### Proto Annotations Available
26+
The `temporal.api.protometa.v1.annotations.proto` file defines:
27+
- `RequestHeaderAnnotation` message with `header` and `value` fields
28+
- `request_header` extension for `google.protobuf.MethodOptions`
29+
- Support for template interpolation with field paths in braces
30+
31+
## Implementation Plan
32+
33+
### Phase 1: Core Generator Infrastructure
34+
35+
#### 1.1 Request Header Generator Module
36+
**Location**: `crates/common/build.rs` (extend existing build script)
37+
38+
**Components**:
39+
- `RequestHeaderGenerator` struct to parse proto descriptors and extract annotation information
40+
- Logic to identify methods with `request_header` annotations
41+
- Template parsing to extract field paths from value templates (e.g., `"{workflow_execution.workflow_id}"`)
42+
- Field accessor generation for nested proto field navigation
43+
44+
**Key Functions**:
45+
```rust
46+
struct RequestHeaderGenerator {
47+
// Maps method full names to their header extraction info
48+
method_headers: HashMap<String, Vec<MethodHeaderInfo>>,
49+
}
50+
51+
struct MethodHeaderInfo {
52+
service_name: String,
53+
method_name: String,
54+
request_type: String,
55+
headers: Vec<HeaderInfo>,
56+
}
57+
58+
struct HeaderInfo {
59+
header_name: String,
60+
value_template: String,
61+
field_paths: Vec<String>, // Extracted from template
62+
}
63+
```
64+
65+
#### 1.2 Code Generation Templates
66+
Generate Rust code similar to Go's approach but idiomatic to Rust:
67+
68+
```rust
69+
// Generated function signature
70+
pub fn extract_temporal_request_headers<T>(
71+
request: &T,
72+
existing_metadata: Option<&tonic::metadata::MetadataMap>,
73+
) -> Vec<(String, String)>
74+
where
75+
T: std::any::Any,
76+
{
77+
// Type-based dispatch and header extraction
78+
}
79+
```
80+
81+
#### 1.3 Integration with Build Process
82+
Extend `crates/common/build.rs` to:
83+
- Call the header generator after proto compilation
84+
- Generate `request_header_impl.rs` alongside existing generated files
85+
- Include the generated file in the build output
86+
87+
### Phase 2: Runtime Header Extraction
88+
89+
#### 2.1 Header Extraction API
90+
**Location**: `crates/client/src/request_headers.rs` (new module)
91+
92+
**Core API**:
93+
```rust
94+
pub struct HeaderExtractionOptions<'a> {
95+
pub existing_metadata: Option<&'a tonic::metadata::MetadataMap>,
96+
pub include_namespace: bool, // Whether to add temporal-namespace header
97+
}
98+
99+
pub fn extract_request_headers<T>(
100+
request: &T,
101+
opts: HeaderExtractionOptions<'_>,
102+
) -> Vec<(String, String)>
103+
where
104+
T: std::any::Any + Send + Sync,
105+
```
106+
107+
#### 2.2 Field Accessor Utilities
108+
Generate helper functions for navigating proto field paths:
109+
```rust
110+
// Example generated accessor for "{workflow_execution.workflow_id}"
111+
fn get_workflow_execution_workflow_id(request: &StartWorkflowExecutionRequest) -> Option<&str> {
112+
request.workflow_execution.as_ref()?.workflow_id.as_deref()
113+
}
114+
```
115+
116+
#### 2.3 Template Interpolation
117+
Handle template strings with field path substitution:
118+
```rust
119+
fn interpolate_template(template: &str, field_values: &[(&str, &str)]) -> String {
120+
// Replace "{field_path}" with actual values
121+
}
122+
```
123+
124+
### Phase 3: gRPC Client Integration
125+
126+
#### 3.1 Request Interceptor
127+
**Location**: `crates/client/src/grpc.rs` (extend existing)
128+
129+
Add header extraction to the gRPC call pipeline:
130+
```rust
131+
impl<T> RawGrpcCaller for HeaderExtractingClient<T>
132+
where
133+
T: RawGrpcCaller,
134+
{
135+
async fn call<F, Req, Resp>(
136+
&mut self,
137+
call_name: &'static str,
138+
mut callfn: F,
139+
mut req: Request<Req>,
140+
) -> Result<Response<Resp>, Status>
141+
where
142+
// ... trait bounds
143+
{
144+
// Extract headers from request body
145+
let headers = extract_request_headers(
146+
req.get_ref(),
147+
HeaderExtractionOptions {
148+
existing_metadata: Some(req.metadata()),
149+
include_namespace: true,
150+
},
151+
);
152+
153+
// Add headers to request metadata
154+
for (key, value) in headers {
155+
if !req.metadata().contains_key(&key) {
156+
req.metadata_mut().insert(
157+
key.try_into()?,
158+
value.try_into()?,
159+
);
160+
}
161+
}
162+
163+
self.inner.call(call_name, callfn, req).await
164+
}
165+
}
166+
```
167+
168+
### Phase 4: Error Handling & Edge Cases
169+
170+
#### 4.1 Error Handling Strategy
171+
- **Build-time errors**: Invalid annotations, missing fields, type mismatches
172+
- **Runtime errors**: Invalid header values, metadata insertion failures
173+
- **Graceful degradation**: Continue operation if header extraction fails
174+
175+
#### 4.2 Edge Cases
176+
- **Empty/missing fields**: Skip header if field value is empty
177+
- **Nested message navigation**: Handle Option<> types in field chains
178+
- **Duplicate headers**: Respect existing metadata, don't override
179+
- **Invalid template syntax**: Compile-time validation of template strings
180+
181+
### Phase 5: Testing & Validation
182+
183+
#### 5.1 Unit Tests
184+
- Template parsing and field path extraction
185+
- Header generation for various request types
186+
- Edge cases and error conditions
187+
188+
#### 5.2 Integration Tests
189+
- End-to-end header propagation in gRPC calls
190+
- Compatibility with existing client functionality
191+
- Performance impact measurement
192+
193+
#### 5.3 Compatibility Testing
194+
- Verify generated headers match Go implementation
195+
- Test with actual Temporal server routing
196+
197+
## Implementation Questions & Considerations
198+
199+
### 1. Code Generation Approach
200+
**Question**: Should we generate a single function that handles all request types, or individual functions per request type?
201+
202+
**Recommendation**: Single function with type-based dispatch (using `std::any::Any`) for better maintainability and smaller generated code size.
203+
204+
### 2. Performance Considerations
205+
**Question**: What's the performance impact of reflection-based type dispatch?
206+
207+
**Considerations**:
208+
- Use compile-time type information where possible
209+
- Consider caching field accessors
210+
- Benchmark against Go implementation
211+
212+
### 3. Integration Point
213+
**Question**: Where should header extraction be integrated in the client pipeline?
214+
215+
**Recommendation**: In `RawGrpcCaller::call` to ensure all requests go through header extraction automatically.
216+
217+
### 4. Namespace Header Handling
218+
**Question**: Should we always include the namespace header like the Go implementation?
219+
220+
**Recommendation**: Yes, maintain compatibility by always checking for namespace field and adding the header if present.
221+
222+
### 5. Template Syntax Validation
223+
**Question**: Should we validate template syntax at compile time or runtime?
224+
225+
**Recommendation**: Compile-time validation during code generation to catch errors early.
226+
227+
## Dependencies
228+
229+
- No new external dependencies required
230+
- Uses existing `prost-types`, `tonic`, and `std::any` functionality
231+
- Builds on existing code generation patterns in the codebase
232+
233+
## Future Considerations
234+
235+
1. **Performance optimization**: Consider compile-time code generation for better performance
236+
2. **Additional header types**: Support for other header patterns beyond resource-id
237+
3. **Dynamic header configuration**: Runtime configuration of header extraction rules
238+
4. **Metrics**: Add telemetry for header extraction success/failure rates
239+
240+
## Conclusion
241+
242+
This implementation will provide Rust SDK users with automatic request header propagation equivalent to the Go SDK, enabling proper request routing in multi-cluster Temporal deployments while maintaining the existing client API and performance characteristics.

0 commit comments

Comments
 (0)