From bff3d6560198400030100a974528604b8f4da2e2 Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Wed, 7 Oct 2026 23:48:03 +0000 Subject: [PATCH] Fix Rust handler argument declarations for command admission --- CHANGELOG.md | 8 +++++ Cargo.toml | 4 +-- src/lib.rs | 86 +++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 92 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01a8679..773e516 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 3.2.2 + +- Register complete query and update argument contracts for JSON and lossless + Avro handlers. Server can admit commands for new Rust workflow runs using + the handlers' positional argument vectors. +- Keep unsupported update validators refused and preserve explicitly supplied + low-level registration metadata. Existing runs retain their original declarations. + ## 3.2.1 - Add `Worker::recover_transient_outages(true)` for long-running service workers. diff --git a/Cargo.toml b/Cargo.toml index d2be90a..9c2bd3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "durable-workflow" -version = "3.2.1" +version = "3.2.2" edition = "2021" description = "Rust client and worker SDK for Durable Workflow Cloud and self-hosted Server" license = "MIT" @@ -14,7 +14,7 @@ categories = ["api-bindings", "asynchronous"] include = ["/src/**", "/schema/**", "/examples/**", "/Cargo.toml", "/README.md", "/CHANGELOG.md", "/LICENSE"] [package.metadata.durable-workflow] -product-train = "3.2.1" +product-train = "3.2.2" compatibility-authority = "protocol-manifests" supported-server-versions = "2.0.0" qualified-server-version = "2.5.1" diff --git a/src/lib.rs b/src/lib.rs index 87e36e4..dda2685 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6283,6 +6283,27 @@ struct RegisteredWorkflow { definition_fingerprint: Option, } +fn argument_vector_contracts(names: &[String]) -> Vec { + names + .iter() + .map(|name| { + json!({ + "name": name, + "parameters": [{ + "name": "args", + "position": 0, + "required": false, + "variadic": true, + "type": null, + "allows_null": true, + "default_available": false, + "default": null, + }], + }) + }) + .collect() +} + #[derive(Debug)] struct WorkflowTaskDecision { commands: Vec, @@ -7106,11 +7127,11 @@ impl Worker { workflow_type.clone(), json!({ "queries": queries, - "query_contracts": [], + "query_contracts": argument_vector_contracts(&queries), "signals": [], "signal_contracts": [], "updates": updates, - "update_contracts": [], + "update_contracts": argument_vector_contracts(&updates), "update_validators": [], }), ); @@ -25706,6 +25727,49 @@ mod tests { assert_eq!(server.request_count("/api/worker/register"), 0); } + #[tokio::test] + async fn worker_registers_complete_json_and_lossless_handler_argument_contracts() { + let server = MockWorkerServer::start(); + let client = Client::builder(server.base_url()) + .timeout(Duration::from_secs(2)) + .build() + .expect("client"); + let mut worker = Worker::new(client, "rust-workers"); + worker.register_workflow("orders", |_ctx, input| async move { Ok(input) }); + worker.register_query("orders", "z-json", |_ctx, args| async move { Ok(args) }); + worker.register_query_avro_value("orders", "a-avro", |_ctx, args| async move { Ok(args) }); + worker.register_update("orders", "z-json", |_ctx, args| async move { Ok(args) }); + worker.register_update_avro_value("orders", "a-avro", |_ctx, args| async move { Ok(args) }); + + worker.register().await.expect("register handler contracts"); + + let declaration = + &server.request_body("/api/worker/register")["workflow_command_contracts"]["orders"]; + let expected_contracts = json!([ + { + "name": "a-avro", + "parameters": [{ + "name": "args", "position": 0, "required": false, + "variadic": true, "type": null, "allows_null": true, + "default_available": false, "default": null, + }], + }, + { + "name": "z-json", + "parameters": [{ + "name": "args", "position": 0, "required": false, + "variadic": true, "type": null, "allows_null": true, + "default_available": false, "default": null, + }], + }, + ]); + assert_eq!(declaration["queries"], json!(["a-avro", "z-json"])); + assert_eq!(declaration["query_contracts"], expected_contracts); + assert_eq!(declaration["updates"], json!(["a-avro", "z-json"])); + assert_eq!(declaration["update_contracts"], expected_contracts); + assert_eq!(declaration["update_validators"], json!([])); + } + #[tokio::test] async fn low_level_registration_preserves_query_and_update_contracts() { let server = MockWorkerServer::start(); @@ -27290,11 +27354,25 @@ mod tests { server.request_body("/api/worker/register")["workflow_command_contracts"]["snapshot"], json!({ "queries": ["current"], - "query_contracts": [], + "query_contracts": [{ + "name": "current", + "parameters": [{ + "name": "args", "position": 0, "required": false, + "variadic": true, "type": null, "allows_null": true, + "default_available": false, "default": null, + }], + }], "signals": [], "signal_contracts": [], "updates": ["replace"], - "update_contracts": [], + "update_contracts": [{ + "name": "replace", + "parameters": [{ + "name": "args", "position": 0, "required": false, + "variadic": true, "type": null, "allows_null": true, + "default_available": false, "default": null, + }], + }], "update_validators": [], }) );