From 907f356fec3da4cc0f0203b9de3c65db8a05c006 Mon Sep 17 00:00:00 2001 From: Arjen Kroezen Date: Fri, 13 Dec 2024 14:18:34 +0100 Subject: [PATCH] feat: support null-conditional operator (#147) --- .../expression_transformer.py | 8 +-- ..._testing_framework_expression_evaluator.py | 59 +++++++++++++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/data_factory_testing_framework/_expression_runtime/data_factory_expression/expression_transformer.py b/src/data_factory_testing_framework/_expression_runtime/data_factory_expression/expression_transformer.py index 981ea783..ce26ad8e 100644 --- a/src/data_factory_testing_framework/_expression_runtime/data_factory_expression/expression_transformer.py +++ b/src/data_factory_testing_framework/_expression_runtime/data_factory_expression/expression_transformer.py @@ -40,7 +40,7 @@ def __init__(self) -> None: expression_grammar = f""" expression_start: "@" expression_evaluation - expression_evaluation: (expression_logical_bool | expression_branch | expression_call) ((("." EXPRESSION_PARAMETER_NAME) | EXPRESSION_ARRAY_INDEX)+)? EXPRESSION_WS* + expression_evaluation: (expression_logical_bool | expression_branch | expression_call) ((EXPRESSION_NULL_CONDITIONAL_OPERATOR? (expression_field_reference | EXPRESSION_ARRAY_INDEX))+)? EXPRESSION_WS* ?expression_call: expression_function_call // used to translate to expression_pipeline_reference | expression_datafactory_parameters_reference @@ -55,9 +55,8 @@ def __init__(self) -> None: expression_datafactory_parameters_reference: EXPRESSION_DATAFACTORY_REFERENCE "()" expression_datafactory_activity_reference: "activity" "(" EXPRESSION_ACTIVITY_NAME ")" expression_item_reference: "item" "()" - expression_pipeline_reference: "pipeline" "()" "." EXPRESSION_PIPELINE_PROPERTY - - + expression_pipeline_reference: "pipeline" "()" EXPRESSION_NULL_CONDITIONAL_OPERATOR? "." EXPRESSION_PIPELINE_PROPERTY + expression_field_reference: "." EXPRESSION_PARAMETER_NAME // branch rules expression_logical_bool: EXPRESSION_LOGICAL_BOOL "(" expression_parameter "," expression_parameter ")" @@ -83,6 +82,7 @@ def __init__(self) -> None: EXPRESSION_STRING: SINGLE_QUOTED_STRING EXPRESSION_SYSTEM_VARIABLE_NAME: /[a-zA-Z0-9_]+/ EXPRESSION_VARIABLE_NAME: "'" /[^']*/ "'" + EXPRESSION_NULL_CONDITIONAL_OPERATOR: "?" EXPRESSION_WS: WS """ # noqa: E501 diff --git a/tests/unit/functions/test_data_factory_testing_framework_expression_evaluator.py b/tests/unit/functions/test_data_factory_testing_framework_expression_evaluator.py index 03335080..54efe646 100644 --- a/tests/unit/functions/test_data_factory_testing_framework_expression_evaluator.py +++ b/tests/unit/functions/test_data_factory_testing_framework_expression_evaluator.py @@ -368,6 +368,65 @@ def test_evaluate_function_names_are_case_insensitive() -> None: assert evaluated_value == "ab" +def test_evaluate_function_with_null_conditional_operator() -> None: + # Arrange + expression = "@pipeline().parameters.parameter.field1?.field2" + expression_runtime = ExpressionRuntime() + state = PipelineRunState( + parameters=[ + RunParameter( + RunParameterType.Pipeline, + "parameter", + { + "field1": {"field2": "value1"}, + }, + ), + ] + ) + + # Act + evaluated_value = expression_runtime.evaluate(expression, state) + + # Assert + assert evaluated_value == "value1" + + +def test_evaluate_function_with_null_conditional_operator_and_null_value() -> None: + # Arrange + expression = "@pipeline().parameters.parameter?.field1?.field2" + expression_runtime = ExpressionRuntime() + state = PipelineRunState( + parameters=[ + RunParameter( + RunParameterType.Pipeline, + "parameter", + { + "field1": None, + }, + ), + ] + ) + + # Act + evaluated_value = expression_runtime.evaluate(expression, state) + + # Assert + assert evaluated_value is None + + +def test_evaluate_function_with_null_conditional_operator_and_system_variable() -> None: + # Arrange + expression = "@pipeline()?.TriggeredByPipelineRunId" + expression_runtime = ExpressionRuntime() + state = PipelineRunState() + + # Act + evaluated_value = expression_runtime.evaluate(expression, state) + + # Assert + assert evaluated_value is None + + def test_evaluate_parameter_with_complex_object_and_array_index() -> None: # Arrange expression = "@pipeline().parameters.parameter[0].field1.field2"