Skip to content

Commit cd9b999

Browse files
committed
Implement SignalFuture and TemporalFutureValue for workflow signals
- Added SignalFutureNative class to handle signal awaiting using Temporal's CompletablePromise. - Introduced TemporalFutureValue class to bridge Temporal's CompletablePromise with Ballerina's wait mechanism. - Enhanced WorkflowRuntime to support sending events with optional signal names. - Updated WorkflowNative to allow sending events with inferred signal names from event data structure. - Created EventExtractor utility methods to infer signal names and get events record type from process functions. - Added EventFutureCreator to create TemporalFutureValue instances for workflow events. - Modified WorkflowWorkerNative to inject events record into workflow function calls.
1 parent 3555153 commit cd9b999

29 files changed

Lines changed: 2456 additions & 25 deletions

File tree

ballerina/functions.bal

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ public isolated function startProcess(function processFunction, map<anydata> inp
3939
#
4040
# + processFunction - The process function that identifies the workflow type
4141
# + eventData - The signal data (must contain "id" field for workflow correlation)
42+
# + signalName - Optional name of the signal. This should match a field name in the
43+
# workflow's events record parameter. If not provided, defaults to process name.
4244
# + return - `true` if the event was sent successfully, or an error if sending fails
43-
public isolated function sendEvent(function processFunction, map<anydata> eventData) returns boolean|error = @java:Method {
45+
public isolated function sendEvent(function processFunction, map<anydata> eventData, string? signalName = ()) returns boolean|error = @java:Method {
4446
'class: "io.ballerina.stdlib.workflow.runtime.nativeimpl.WorkflowNative"
4547
} external;
4648

ballerina/tests/Config.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
# Auto-generated test configuration
2-
# This file is generated by Gradle before running tests
3-
# DO NOT EDIT - changes will be overwritten
42

53
[ballerina.workflow.workflowConfig]
64
provider = "TEMPORAL"

compiler-plugin-tests/src/test/java/io/ballerina/stdlib/workflow/compiler/WorkflowCompilerPluginTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,51 @@ private void assertDiagnosticContains(DiagnosticResult diagnosticResult, String
254254
Assert.assertTrue(found, "Expected diagnostic with code " + expectedCode + ". Got: "
255255
+ getDiagnosticMessages(diagnosticResult));
256256
}
257+
258+
// ===== sendEvent validation test cases - Ambiguous signal types =====
259+
260+
@Test(groups = "valid")
261+
public void testValidSendEventWithExplicitSignalName() {
262+
String packagePath = "valid_send_event_with_signal_name";
263+
DiagnosticResult diagnosticResult = getValidationDiagnosticResult(packagePath);
264+
Assert.assertEquals(diagnosticResult.errorCount(), 0,
265+
"Expected no errors when sendEvent provides explicit signalName with ambiguous signals. Errors: "
266+
+ getDiagnosticMessages(diagnosticResult));
267+
}
268+
269+
@Test(groups = "valid")
270+
public void testValidSendEventWithDistinctTypes() {
271+
String packagePath = "valid_send_event_distinct_types";
272+
DiagnosticResult diagnosticResult = getValidationDiagnosticResult(packagePath);
273+
Assert.assertEquals(diagnosticResult.errorCount(), 0,
274+
"Expected no errors when signal types are structurally different. Errors: "
275+
+ getDiagnosticMessages(diagnosticResult));
276+
}
277+
278+
@Test(groups = "valid")
279+
public void testValidSendEventWithSingleSignal() {
280+
String packagePath = "valid_send_event_single_signal";
281+
DiagnosticResult diagnosticResult = getValidationDiagnosticResult(packagePath);
282+
Assert.assertEquals(diagnosticResult.errorCount(), 0,
283+
"Expected no errors when process has only one signal. Errors: "
284+
+ getDiagnosticMessages(diagnosticResult));
285+
}
286+
287+
@Test(groups = "invalid")
288+
public void testInvalidSendEventAmbiguousNoSignalName() {
289+
String packagePath = "invalid_send_event_ambiguous_no_signal_name";
290+
DiagnosticResult diagnosticResult = getValidationDiagnosticResult(packagePath);
291+
Assert.assertTrue(diagnosticResult.errorCount() > 0,
292+
"Expected validation error for sendEvent without signalName when signals are ambiguous");
293+
assertDiagnosticContains(diagnosticResult, WorkflowConstants.WORKFLOW_112);
294+
}
295+
296+
@Test(groups = "invalid")
297+
public void testInvalidSendEventAmbiguousThreeSignals() {
298+
String packagePath = "invalid_send_event_ambiguous_three_signals";
299+
DiagnosticResult diagnosticResult = getValidationDiagnosticResult(packagePath);
300+
Assert.assertTrue(diagnosticResult.errorCount() > 0,
301+
"Expected validation error for sendEvent without signalName when three signals are ambiguous");
302+
assertDiagnosticContains(diagnosticResult, WorkflowConstants.WORKFLOW_112);
303+
}
257304
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
org = "test"
3+
name = "invalid_send_event_ambiguous_no_signal_name"
4+
version = "0.1.0"
5+
distribution = "2201.13.0"
6+
7+
[[dependency]]
8+
org = "ballerina"
9+
name = "workflow"
10+
version = "0.1.0"
11+
repository = "local"
12+
13+
[build-options]
14+
observabilityIncluded = false
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved.
2+
//
3+
// WSO2 LLC. licenses this file to you under the Apache License,
4+
// Version 2.0 (the "License"); you may not use this file except
5+
// in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing,
11+
// software distributed under the License is distributed on an
12+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13+
// KIND, either express or implied. See the License for the
14+
// specific language governing permissions and limitations
15+
// under the License.
16+
17+
import ballerina/workflow;
18+
19+
// Types with SAME structure - this creates ambiguity
20+
type SignalType1 record {|
21+
string id;
22+
string value;
23+
|};
24+
25+
type SignalType2 record {|
26+
string id;
27+
string value;
28+
|};
29+
30+
type TestInput record {|
31+
string id;
32+
string name;
33+
|};
34+
35+
type TestResult record {|
36+
string status;
37+
|};
38+
39+
// Process with ambiguous signal types (same structure)
40+
@workflow:Process
41+
function ambiguousSignalProcess(
42+
workflow:Context ctx,
43+
TestInput input,
44+
record {|
45+
future<SignalType1> signal1;
46+
future<SignalType2> signal2;
47+
|} signals
48+
) returns TestResult|error {
49+
SignalType1 s1 = check wait signals.signal1;
50+
return {status: "OK"};
51+
}
52+
53+
// INVALID: sendEvent without signalName when signals are ambiguous
54+
// Should trigger WORKFLOW_112 error
55+
function invalidSendWithoutSignalName() returns error? {
56+
SignalType1 data = {id: "test-1", value: "test"};
57+
// Missing signalName parameter - ambiguous, cannot infer which signal to send
58+
_ = check workflow:sendEvent(ambiguousSignalProcess, data);
59+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
org = "test"
3+
name = "invalid_send_event_ambiguous_three_signals"
4+
version = "0.1.0"
5+
distribution = "2201.13.0"
6+
7+
[[dependency]]
8+
org = "ballerina"
9+
name = "workflow"
10+
version = "0.1.0"
11+
repository = "local"
12+
13+
[build-options]
14+
observabilityIncluded = false
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved.
2+
//
3+
// WSO2 LLC. licenses this file to you under the Apache License,
4+
// Version 2.0 (the "License"); you may not use this file except
5+
// in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing,
11+
// software distributed under the License is distributed on an
12+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13+
// KIND, either express or implied. See the License for the
14+
// specific language governing permissions and limitations
15+
// under the License.
16+
17+
import ballerina/workflow;
18+
19+
// Three signals with same structure - tests detection of first two ambiguous signals
20+
type StringSignal record {|
21+
string id;
22+
string data;
23+
|};
24+
25+
type TestInput record {|
26+
string id;
27+
string name;
28+
|};
29+
30+
type TestResult record {|
31+
string status;
32+
|};
33+
34+
// Process with three signals of same type - ambiguous
35+
@workflow:Process
36+
function threeAmbiguousSignalProcess(
37+
workflow:Context ctx,
38+
TestInput input,
39+
record {|
40+
future<StringSignal> signalA;
41+
future<StringSignal> signalB;
42+
future<StringSignal> signalC;
43+
|} signals
44+
) returns TestResult|error {
45+
StringSignal s = check wait signals.signalA;
46+
return {status: "OK"};
47+
}
48+
49+
// INVALID: sendEvent without signalName when three signals are ambiguous
50+
// Should trigger WORKFLOW_112 error
51+
function invalidSendToThreeAmbiguousSignals() returns error? {
52+
StringSignal data = {id: "test-1", data: "test"};
53+
_ = check workflow:sendEvent(threeAmbiguousSignalProcess, data);
54+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
org = "test"
3+
name = "valid_send_event_distinct_types"
4+
version = "0.1.0"
5+
distribution = "2201.13.0"
6+
7+
[[dependency]]
8+
org = "ballerina"
9+
name = "workflow"
10+
version = "0.1.0"
11+
repository = "local"
12+
13+
[build-options]
14+
observabilityIncluded = false
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com) All Rights Reserved.
2+
//
3+
// WSO2 LLC. licenses this file to you under the Apache License,
4+
// Version 2.0 (the "License"); you may not use this file except
5+
// in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing,
11+
// software distributed under the License is distributed on an
12+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
13+
// KIND, either express or implied. See the License for the
14+
// specific language governing permissions and limitations
15+
// under the License.
16+
17+
import ballerina/workflow;
18+
19+
// Types with DIFFERENT structure (not ambiguous)
20+
type ApprovalSignal record {|
21+
string id;
22+
boolean approved;
23+
string approver;
24+
|};
25+
26+
type PaymentSignal record {|
27+
string id;
28+
string txnId;
29+
decimal amount;
30+
|};
31+
32+
type TestInput record {|
33+
string id;
34+
string name;
35+
|};
36+
37+
type TestResult record {|
38+
string status;
39+
|};
40+
41+
// Valid: Process with distinct signal types - no ambiguity
42+
@workflow:Process
43+
function distinctSignalProcess(
44+
workflow:Context ctx,
45+
TestInput input,
46+
record {|
47+
future<ApprovalSignal> approval;
48+
future<PaymentSignal> payment;
49+
|} signals
50+
) returns TestResult|error {
51+
ApprovalSignal a = check wait signals.approval;
52+
return {status: "OK"};
53+
}
54+
55+
// This is VALID - distinct types allow signal name inference without explicit signalName
56+
function validSendWithoutSignalName() returns error? {
57+
ApprovalSignal data = {id: "test-1", approved: true, approver: "admin"};
58+
_ = check workflow:sendEvent(distinctSignalProcess, data);
59+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
org = "test"
3+
name = "valid_send_event_single_signal"
4+
version = "0.1.0"
5+
distribution = "2201.13.0"
6+
7+
[[dependency]]
8+
org = "ballerina"
9+
name = "workflow"
10+
version = "0.1.0"
11+
repository = "local"
12+
13+
[build-options]
14+
observabilityIncluded = false

0 commit comments

Comments
 (0)