Skip to content

Automatic request header code generation for resource routing#1179

Open
tconley1428 wants to merge 17 commits intomasterfrom
request_header_code_gen
Open

Automatic request header code generation for resource routing#1179
tconley1428 wants to merge 17 commits intomasterfrom
request_header_code_gen

Conversation

@tconley1428
Copy link
Copy Markdown
Contributor

Summary

  • Add build-time code generator that reads temporal.api.protometa.v1.request_header proto annotations and generates Rust header extraction logic
  • Replace namespaced_request! macro with request_with_headers! that extracts both namespace and annotation-based headers (e.g., temporal-resource-id)
  • Populate resource_id fields on all relevant request messages for multi-cell routing:
    • Workflow task completed/failed: workflow:{workflow_id}
    • Activity task completed/failed/canceled (and ById variants): workflow:{workflow_id} or activity:{activity_id} for standalone
    • Activity heartbeat (and ById): same logic
    • Worker heartbeat: worker:{worker_grouping_key}

Rust equivalent of temporalio/sdk-go#2226.

🤖 Generated with Claude Code

tconley1428 and others added 11 commits March 17, 2026 10:12
- Fix generate_resource_id to return empty string when both IDs are empty,
  matching Go's getActivityResourceId behavior
- Add missing resource_id to RespondActivityTaskCanceledByIdRequest
- Reformat resource_id() in activities.rs for readability and consistency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract to_snake_case, to_pascal_case, proto_to_rust_path as shared
  free functions used by both PayloadVisitorGenerator and
  RequestHeaderGenerator
- Simplify to_map_entry_name to delegate to to_pascal_case
- Remove HeaderExtractionOptions wrapper; call
  extract_temporal_request_headers directly with Option<&MetadataMap>

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The descriptor file includes full proto metadata (including extension
annotations) regardless of server codegen. build_server(true) only
generates unused server stubs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace regex-based template parsing with a simple char iterator.
The only usage was extracting {field_path} placeholders from templates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update all .returning() and .withf() closures in tests to account for
the extra resource_id parameter on activity methods and workflow_id
parameter on fail_workflow_task.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…types

18 tests in request_headers.rs verifying the code-generated
extract_temporal_request_headers produces correct temporal-resource-id
headers for:
- Workflow requests (start, signal, task completed/failed)
- Activity requests (completed, failed, canceled + ById variants)
- Heartbeat requests (by token and by ID)
- Worker heartbeat
- Batch operations (ExecuteMultiOperation)
- Duplicate header suppression and empty field handling

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Five tests exercising the full worker pipeline (poll → complete/fail/cancel/heartbeat)
verifying the correct resource_id reaches the client:
- Workflow activity completion: "workflow:{workflow_id}"
- Standalone activity completion: "activity:{activity_id}"
- Activity failure: "workflow:{workflow_id}"
- Activity cancellation: "workflow:{workflow_id}"
- Activity heartbeat: "workflow:{workflow_id}"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tconley1428 tconley1428 force-pushed the request_header_code_gen branch from 02bc9ff to a5bf2c4 Compare March 26, 2026 16:19
tconley1428 and others added 3 commits March 26, 2026 09:23
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace map_or(true, ...) with is_none_or(...) in generated code
- Use val.to_string() instead of format!("{}", val) for passthrough templates
- Collapse nested if/if-let into single if-let chains
- Remove unused request_type field from MethodHeaderInfo
- Remove unused generate_field_accessor (logic moved inline)
- Fix missed test mock closure in worker_tests.rs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revert cosmetic enum reordering that was unrelated to this PR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
let descriptor_file = out.join("descriptors.bin");
let mut builder = tonic_prost_build::configure()
// We don't actually want to build the grpc definitions - we don't need them (for now).
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Put comment back

}
output.push_str(&format!(
r#" if let Some(req) = request.downcast_ref::<{}>()
&& let Some(val) = (|| -> Option<&str> {{ Some({}) }})()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

What is the point of this lambda?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is exceptionally weird. I think it did this for "performance" reasons so that it wouldn't construct an option if the if check short-circuits on the first thing... but super unnecessary

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tconley1428 tconley1428 marked this pull request as ready for review March 26, 2026 19:08
@tconley1428 tconley1428 requested a review from a team as a code owner March 26, 2026 19:09
tconley1428 and others added 2 commits March 27, 2026 09:08
The generated code for nested field access used immediately-invoked
closures as a workaround to use the ? operator. Now uses chained
let bindings in if-let expressions instead, which is clearer and
idiomatic.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Unify simple and nested field paths into one code generation path
- Extract parse_annotation and parse_field_paths as free functions
- Flatten process_descriptors_reflect by inlining process_method_reflect
- Remove intermediate val binding in generated code for simple fields
- Remove unused generate_field_accessor and generate_extraction_function

Generated output is semantically identical (verified by diff and tests).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

2 participants