Skip to content

Commit

Permalink
add base implementation for encode-decode intrinsic functions (#7709)
Browse files Browse the repository at this point in the history
  • Loading branch information
MEPalma committed Mar 1, 2023
1 parent 0212b37 commit 8da70d7
Show file tree
Hide file tree
Showing 12 changed files with 497 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class ArrayLength(StatesFunction):
#
# Returns
# 9

def __init__(self, arg_list: FunctionArgumentList):
super().__init__(
states_name=StatesFunctionName(function_type=StatesFunctionNameType.ArrayLength),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import base64
from typing import Final

from localstack.services.stepfunctions.asl.component.intrinsic.argument.function_argument_list import (
FunctionArgumentList,
)
from localstack.services.stepfunctions.asl.component.intrinsic.function.statesfunction.states_function import (
StatesFunction,
)
from localstack.services.stepfunctions.asl.component.intrinsic.functionname.state_fuinction_name_types import (
StatesFunctionNameType,
)
from localstack.services.stepfunctions.asl.component.intrinsic.functionname.states_function_name import (
StatesFunctionName,
)
from localstack.services.stepfunctions.asl.eval.environment import Environment


class Base64Decode(StatesFunction):
# Encodes data based on MIME Base64 encoding scheme.
#
# For example:
# With input
# {
# "input": "Data to encode"
# }
#
# The call
# "base64.$": "States.Base64Encode($.input)"
#
# Returns
# {"base64": "RGF0YSB0byBlbmNvZGU="}

MAX_INPUT_CHAR_LEN: Final[int] = 10_000

def __init__(self, arg_list: FunctionArgumentList):
super().__init__(
states_name=StatesFunctionName(function_type=StatesFunctionNameType.Base64Decode),
arg_list=arg_list,
)
if arg_list.size != 1:
raise ValueError(
f"Expected 1 argument for function type '{type(self)}', but got: '{arg_list}'."
)

def _eval_body(self, env: Environment) -> None:
self.arg_list.eval(env=env)

base64_string: str = env.stack.pop()
if len(base64_string) > self.MAX_INPUT_CHAR_LEN:
raise ValueError(
f"Maximum input string for function type '{type(self)}' "
f"is '{self.MAX_INPUT_CHAR_LEN}', but got '{len(base64_string)}'."
)

base64_string_bytes = base64_string.encode("ascii")
string_bytes = base64.b64decode(base64_string_bytes)
string = string_bytes.decode("ascii")
env.stack.append(string)
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import base64
from typing import Final

from localstack.services.stepfunctions.asl.component.intrinsic.argument.function_argument_list import (
FunctionArgumentList,
)
from localstack.services.stepfunctions.asl.component.intrinsic.function.statesfunction.states_function import (
StatesFunction,
)
from localstack.services.stepfunctions.asl.component.intrinsic.functionname.state_fuinction_name_types import (
StatesFunctionNameType,
)
from localstack.services.stepfunctions.asl.component.intrinsic.functionname.states_function_name import (
StatesFunctionName,
)
from localstack.services.stepfunctions.asl.eval.environment import Environment


class Base64Encode(StatesFunction):
# Decodes data based on MIME Base64 encoding scheme.
#
# For example:
# With input
# {
# "base64": "RGF0YSB0byBlbmNvZGU="
# }
#
# The call
# "data.$": "States.Base64Decode($.base64)"
#
# Returns
# {"data": "Decoded data"}

MAX_INPUT_CHAR_LEN: Final[int] = 10_000

def __init__(self, arg_list: FunctionArgumentList):
super().__init__(
states_name=StatesFunctionName(function_type=StatesFunctionNameType.Base64Encode),
arg_list=arg_list,
)
if arg_list.size != 1:
raise ValueError(
f"Expected 1 argument for function type '{type(self)}', but got: '{arg_list}'."
)

def _eval_body(self, env: Environment) -> None:
self.arg_list.eval(env=env)

string: str = env.stack.pop()
if len(string) > self.MAX_INPUT_CHAR_LEN:
raise ValueError(
f"Maximum input string for function type '{type(self)}' "
f"is '{self.MAX_INPUT_CHAR_LEN}', but got '{len(string)}'."
)

string_bytes = string.encode("ascii")
string_base64_bytes = base64.b64encode(string_bytes)
base64_string = string_base64_bytes.decode("ascii")
env.stack.append(base64_string)
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
array_range,
array_unique,
)
from localstack.services.stepfunctions.asl.component.intrinsic.function.statesfunction.encoding_decoding import (
base_64_decode,
base_64_encode,
)
from localstack.services.stepfunctions.asl.component.intrinsic.function.statesfunction.generic import (
string_format,
)
Expand Down Expand Up @@ -83,6 +87,12 @@ def from_name(func_name: StatesFunctionName, arg_list: FunctionArgumentList) ->
case StatesFunctionNameType.Hash:
return hash_func.HashFunc(arg_list=arg_list)

# Encoding and Decoding.
case StatesFunctionNameType.Base64Encode:
return base_64_encode.Base64Encode(arg_list=arg_list)
case StatesFunctionNameType.Base64Decode:
return base_64_decode.Base64Decode(arg_list=arg_list)

# Math Operations.
case StatesFunctionNameType.MathRandom:
return math_random.MathRandom(arg_list=arg_list)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ def __init__(self, arg_list: FunctionArgumentList):
f"Expected at least 1 argument for function type '{type(self)}', but got: '{arg_list}'."
)
if not isinstance(arg_list.arg_list[0], FunctionArgumentString):
raise TypeError(
f"Expected the first argument for function type '{type(self)}' "
f"to be a string, but got: '{arg_list.arg_list[0]}'."
raise ValueError(
f"Expected the first argument for function type '{type(self)}' to be a string, but got: '{arg_list.arg_list[0]}'."
)

def _eval_body(self, env: Environment) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ class StringSplit(StatesFunction):
# "test",
# "string"
# ]}

def __init__(self, arg_list: FunctionArgumentList):
super().__init__(
states_name=StatesFunctionName(function_type=StatesFunctionNameType.StringSplit),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ class IntrinsicFunctionTemplate(TemplateLoader):
_THIS_FOLDER, "statemachines/string_operations/string_split.json5"
)

# Encode and Decode.
BASE_64_ENCODE: Final[str] = os.path.join(
_THIS_FOLDER, "statemachines/encode_decode/base64encode.json5"
)
BASE_64_DECODE: Final[str] = os.path.join(
_THIS_FOLDER, "statemachines/encode_decode/base64decode.json5"
)

# Hash Calculations.
HASH: Final[str] = os.path.join(_THIS_FOLDER, "statemachines/hash_calculations/hash.json5")

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"Comment": "BASE_64_DECODE",
"StartAt": "State_0",
"States": {
"State_0": {
"Type": "Pass",
"Parameters": {
"FunctionResult.$": "States.Base64Decode($.FunctionInput)",
},
"End": true,
},
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"Comment": "BASE_64_ENCODE",
"StartAt": "State_0",
"States": {
"State_0": {
"Type": "Pass",
"Parameters": {
"FunctionResult.$": "States.Base64Encode($.FunctionInput)",
},
"End": true,
},
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import pytest

from tests.integration.stepfunctions.templates.intrinsicfunctions.intrinsic_functions_templates import (
IntrinsicFunctionTemplate as IFT,
)
from tests.integration.stepfunctions.utils import is_old_provider
from tests.integration.stepfunctions.v2.intrinsic_functions.utils import create_and_test_on_inputs

pytestmark = pytest.mark.skipif(
condition=is_old_provider(), reason="Test suite for v2 provider only."
)


# TODO: test for validation errors, and boundary testing.


@pytest.mark.skip_snapshot_verify(
paths=["$..loggingConfiguration", "$..tracingConfiguration", "$..previousEventId"]
)
class TestEncodeDecode:
def test_base_64_encode(
self,
stepfunctions_client,
create_iam_role_for_sfn,
create_state_machine,
snapshot,
):
input_values = ["", "Data to encode"]
create_and_test_on_inputs(
stepfunctions_client,
create_iam_role_for_sfn,
create_state_machine,
snapshot,
IFT.BASE_64_ENCODE,
input_values,
)

def test_base_64_decode(
self,
stepfunctions_client,
create_iam_role_for_sfn,
create_state_machine,
snapshot,
):
input_values = ["", "RGF0YSB0byBlbmNvZGU="]
create_and_test_on_inputs(
stepfunctions_client,
create_iam_role_for_sfn,
create_state_machine,
snapshot,
IFT.BASE_64_DECODE,
input_values,
)

0 comments on commit 8da70d7

Please sign in to comment.