diff --git a/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableActivityContext.cs b/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableActivityContext.cs index 560fedf48..e0b5b9900 100644 --- a/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableActivityContext.cs +++ b/src/WebJobs.Extensions.DurableTask/ContextImplementations/DurableActivityContext.cs @@ -127,15 +127,18 @@ internal object GetInput(Type destinationType) string serializedValue = jToken.ToString(Formatting.None); - // Object inputs for out-of-proc activities are passed in their JSON-stringified form with a destination - // type of System.String. Unfortunately, deserializing a JSON string to a string causes - // MessagePayloadDataConverter to throw an exception. This is a workaround for that case. All other - // inputs with destination System.String (in-proc: JSON and not JSON; out-of-proc: not-JSON) inputs with - // destination System.String should cast to JValues and be handled above.) - if (this.rawInput) + if (this.rawInput) // the "modern" OOProc protocol case { return serializedValue; } + else if (destinationType.Equals(typeof(string))) // legacy OOProc protocol case (JS, Python, PowerShell) + { + // Object/complex inputs in "legacy" out-of-proc activities are passed with a destination + // type of System.String (to be precise, if inspected with a debugger, you'll see a stringified JSON). + // Unfortunately, deserializing a JSON string to a string causes MessagePayloadDataConverter to throw an exception, so the + // return statement prevents that. + return serializedValue; + } return this.messageDataConverter.Deserialize(serializedValue, destinationType); } diff --git a/test/SmokeTests/OOProcSmokeTests/durableJS/DurableFunctionsOrchestratorJS/index.js b/test/SmokeTests/OOProcSmokeTests/durableJS/DurableFunctionsOrchestratorJS/index.js index b76067416..088dad5b7 100644 --- a/test/SmokeTests/OOProcSmokeTests/durableJS/DurableFunctionsOrchestratorJS/index.js +++ b/test/SmokeTests/OOProcSmokeTests/durableJS/DurableFunctionsOrchestratorJS/index.js @@ -13,12 +13,15 @@ const df = require("durable-functions"); module.exports = df.orchestrator(function* (context) { const outputs = []; + const city = {city:"Paris", country:"France"}; // Replace "Hello" with the name of your Durable Activity Function. outputs.push(yield context.df.callActivity("Hello", "Tokyo")); outputs.push(yield context.df.callActivity("Hello", "Seattle")); outputs.push(yield context.df.callActivity("Hello", "London")); + outputs.push(yield context.df.callActivity("Hello", 123)); + outputs.push(yield context.df.callActivity("PrintArray", ["Dubai", "New York", "Vancouver"])); + outputs.push(yield context.df.callActivity("PrintObject", city)); - // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"] return outputs; -}); \ No newline at end of file +}); diff --git a/test/SmokeTests/OOProcSmokeTests/durableJS/PrintArray/function.json b/test/SmokeTests/OOProcSmokeTests/durableJS/PrintArray/function.json new file mode 100644 index 000000000..e04ea285f --- /dev/null +++ b/test/SmokeTests/OOProcSmokeTests/durableJS/PrintArray/function.json @@ -0,0 +1,9 @@ +{ + "bindings": [ + { + "name": "array", + "type": "activityTrigger", + "direction": "in" + } + ] +} diff --git a/test/SmokeTests/OOProcSmokeTests/durableJS/PrintArray/index.js b/test/SmokeTests/OOProcSmokeTests/durableJS/PrintArray/index.js new file mode 100644 index 000000000..0b35afabe --- /dev/null +++ b/test/SmokeTests/OOProcSmokeTests/durableJS/PrintArray/index.js @@ -0,0 +1,14 @@ +/* + * This function is not intended to be invoked directly. Instead it will be + * triggered by an orchestrator function. + * + * Before running this sample, please: + * - create a Durable orchestration function + * - create a Durable HTTP starter function + * - run 'npm install durable-functions' from the wwwroot folder of your + * function app in Kudu + */ + +module.exports = async function (context) { + return context.bindings.array.toString(); +}; diff --git a/test/SmokeTests/OOProcSmokeTests/durableJS/PrintObject/function.json b/test/SmokeTests/OOProcSmokeTests/durableJS/PrintObject/function.json new file mode 100644 index 000000000..51d541c46 --- /dev/null +++ b/test/SmokeTests/OOProcSmokeTests/durableJS/PrintObject/function.json @@ -0,0 +1,9 @@ +{ + "bindings": [ + { + "name": "cities", + "type": "activityTrigger", + "direction": "in" + } + ] +} diff --git a/test/SmokeTests/OOProcSmokeTests/durableJS/PrintObject/index.js b/test/SmokeTests/OOProcSmokeTests/durableJS/PrintObject/index.js new file mode 100644 index 000000000..f280765cd --- /dev/null +++ b/test/SmokeTests/OOProcSmokeTests/durableJS/PrintObject/index.js @@ -0,0 +1,14 @@ +/* + * This function is not intended to be invoked directly. Instead it will be + * triggered by an orchestrator function. + * + * Before running this sample, please: + * - create a Durable orchestration function + * - create a Durable HTTP starter function + * - run 'npm install durable-functions' from the wwwroot folder of your + * function app in Kudu + */ + +module.exports = async function (context) { + return JSON.stringify(context.bindings.obj); +}; diff --git a/test/SmokeTests/OOProcSmokeTests/durableJava/src/main/java/com/functions/AzureFunctions.java b/test/SmokeTests/OOProcSmokeTests/durableJava/src/main/java/com/functions/AzureFunctions.java index aa8a7078d..e9c92d761 100644 --- a/test/SmokeTests/OOProcSmokeTests/durableJava/src/main/java/com/functions/AzureFunctions.java +++ b/test/SmokeTests/OOProcSmokeTests/durableJava/src/main/java/com/functions/AzureFunctions.java @@ -40,10 +40,18 @@ public String citiesOrchestrator( @DurableOrchestrationTrigger(name = "orchestratorRequestProtoBytes") String orchestratorRequestProtoBytes) { return OrchestrationRunner.loadAndRun(orchestratorRequestProtoBytes, ctx -> { String result = ""; + String[] cities = {"Dubai", "New York", "Vancouver"}; + City paris = new City("France", "Paris"); + result += ctx.callActivity("Capitalize", "Tokyo", String.class).await() + ", "; result += ctx.callActivity("Capitalize", "London", String.class).await() + ", "; result += ctx.callActivity("Capitalize", "Seattle", String.class).await() + ", "; - result += ctx.callActivity("Capitalize", "Austin", String.class).await(); + result += ctx.callActivity("Capitalize", "Austin", String.class).await()+ ", "; + + result += ctx.callActivity("Print", 123, String.class).await()+ ", "; + result += ctx.callActivity("PrintArray", cities, String.class).await()+ ", "; + result += ctx.callActivity("PrintObject", paris, String.class).await()+ ", "; + return result; }); } @@ -58,4 +66,59 @@ public String capitalize( context.getLogger().info("Capitalizing: " + name); return name.toUpperCase(); } + + @FunctionName("Print") + public String print( + @DurableActivityTrigger(name = "input") String input, + final ExecutionContext context) { + context.getLogger().info("Printing input: " + input); + return input.toString(); + } + + @FunctionName("PrintArray") + public String printArray( + @DurableActivityTrigger(name = "array") String[] array, + final ExecutionContext context) { + context.getLogger().info(Arrays.toString(array)); + return Arrays.toString(array); + } + + @FunctionName("PrintObject") + public String printObject( + @DurableActivityTrigger(name = "city") City city, + final ExecutionContext context) { + context.getLogger().info("Printing object" + city.toString()); + return city.toString(); + } + + public class City { + private String country; + private String name; + + public City(String country, String name){ + this.country = country; + this.name = name; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "City [Country=" + country + ", name=" + name + "]"; + } + } } diff --git a/test/SmokeTests/OOProcSmokeTests/durablePy/DurableFunctionsOrchestrator/__init__.py b/test/SmokeTests/OOProcSmokeTests/durablePy/DurableFunctionsOrchestrator/__init__.py index 3cc2e3e03..313e30766 100644 --- a/test/SmokeTests/OOProcSmokeTests/durablePy/DurableFunctionsOrchestrator/__init__.py +++ b/test/SmokeTests/OOProcSmokeTests/durablePy/DurableFunctionsOrchestrator/__init__.py @@ -12,11 +12,34 @@ import azure.functions as func import azure.durable_functions as df +class City: + def __init__(self, country, name): + self.country = country + self.name = name + + def to_json(self): + return json.dumps({"name": self.name, "country": self.country}) + + @classmethod + def from_json(cls, json_str): + data = json.loads(json_str) + return cls(name=data['name'], country=data['country']) + + def __str__(self): + return f"City(name= {self.name}, country= {self.country})" def orchestrator_function(context: df.DurableOrchestrationContext): result1 = yield context.call_activity('Hello', "Tokyo") result2 = yield context.call_activity('Hello', "Seattle") result3 = yield context.call_activity('Hello', "London") - return [result1, result2, result3] + result4 = yield context.call_activity('Print', 123) + + cities = ["Tokyo", "Seattle", "Cairo"] + result5 = yield context.call_activity("PrintArray", cities) + + city = City("France", "Paris") + result6 = yield context.call_activity("PrintObject", city) + + return [result1, result2, result3, result4, result5, result6] main = df.Orchestrator.create(orchestrator_function) \ No newline at end of file diff --git a/test/SmokeTests/OOProcSmokeTests/durablePy/Print/__init__.py b/test/SmokeTests/OOProcSmokeTests/durablePy/Print/__init__.py new file mode 100644 index 000000000..71c39e6a9 --- /dev/null +++ b/test/SmokeTests/OOProcSmokeTests/durablePy/Print/__init__.py @@ -0,0 +1,13 @@ +# This function is not intended to be invoked directly. Instead it will be +# triggered by an orchestrator function. +# Before running this sample, please: +# - create a Durable orchestration function +# - create a Durable HTTP starter function +# - add azure-functions-durable to requirements.txt +# - run pip install -r requirements.txt + +import logging + + +def main(input: int) -> str: + return f"{input}" diff --git a/test/SmokeTests/OOProcSmokeTests/durablePy/Print/function.json b/test/SmokeTests/OOProcSmokeTests/durablePy/Print/function.json new file mode 100644 index 000000000..fdc7a42b0 --- /dev/null +++ b/test/SmokeTests/OOProcSmokeTests/durablePy/Print/function.json @@ -0,0 +1,10 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "name": "input", + "type": "activityTrigger", + "direction": "in" + } + ] +} \ No newline at end of file diff --git a/test/SmokeTests/OOProcSmokeTests/durablePy/PrintArray/__init__.py b/test/SmokeTests/OOProcSmokeTests/durablePy/PrintArray/__init__.py new file mode 100644 index 000000000..2f3252058 --- /dev/null +++ b/test/SmokeTests/OOProcSmokeTests/durablePy/PrintArray/__init__.py @@ -0,0 +1,16 @@ +# This function is not intended to be invoked directly. Instead it will be +# triggered by an orchestrator function. +# Before running this sample, please: +# - create a Durable orchestration function +# - create a Durable HTTP starter function +# - add azure-functions-durable to requirements.txt +# - run pip install -r requirements.txt + +import logging + +def main(cities: list) -> str: + results = ""; + for city in cities: + result = f"{city} " + results += result + return results diff --git a/test/SmokeTests/OOProcSmokeTests/durablePy/PrintArray/function.json b/test/SmokeTests/OOProcSmokeTests/durablePy/PrintArray/function.json new file mode 100644 index 000000000..f1af9c9bf --- /dev/null +++ b/test/SmokeTests/OOProcSmokeTests/durablePy/PrintArray/function.json @@ -0,0 +1,10 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "name": "cities", + "type": "activityTrigger", + "direction": "in" + } + ] +} \ No newline at end of file diff --git a/test/SmokeTests/OOProcSmokeTests/durablePy/PrintObject/__init__.py b/test/SmokeTests/OOProcSmokeTests/durablePy/PrintObject/__init__.py new file mode 100644 index 000000000..3974e957d --- /dev/null +++ b/test/SmokeTests/OOProcSmokeTests/durablePy/PrintObject/__init__.py @@ -0,0 +1,13 @@ +# This function is not intended to be invoked directly. Instead it will be +# triggered by an orchestrator function. +# Before running this sample, please: +# - create a Durable orchestration function +# - create a Durable HTTP starter function +# - add azure-functions-durable to requirements.txt +# - run pip install -r requirements.txt + +import logging + + +def main(obj: object) -> str: + return f"Printing object: {obj}" diff --git a/test/SmokeTests/OOProcSmokeTests/durablePy/PrintObject/function.json b/test/SmokeTests/OOProcSmokeTests/durablePy/PrintObject/function.json new file mode 100644 index 000000000..962f775c5 --- /dev/null +++ b/test/SmokeTests/OOProcSmokeTests/durablePy/PrintObject/function.json @@ -0,0 +1,10 @@ +{ + "scriptFile": "__init__.py", + "bindings": [ + { + "name": "obj", + "type": "activityTrigger", + "direction": "in" + } + ] +} \ No newline at end of file