diff --git a/src/erc7730/model/paths/__init__.py b/src/erc7730/model/paths/__init__.py index 48c0214..175c98f 100644 --- a/src/erc7730/model/paths/__init__.py +++ b/src/erc7730/model/paths/__init__.py @@ -67,24 +67,30 @@ class ArraySlice(Model): description="The path component type identifier (discriminator for path components discriminated union).", ) - start: ArrayIndex = PydanticField( + start: ArrayIndex | None = PydanticField( + default=None, title="Slice Start Index", - description="The start index of the slice. Must be lower than the end index.", + description="The start index of the slice (inclusive). Must be lower than the end index. If unset, slice " + "starts from the beginning of the array.", ) - end: ArrayIndex = PydanticField( + end: ArrayIndex | None = PydanticField( + default=None, title="Slice End Index", - description="The end index of the slice. Must be greater than the start index.", + description="The end index of the slice (exclusive). Must be greater than the start index. If unset, slice " + "ends at the end of the array.", ) @model_validator(mode="after") def _validate(self) -> Self: - if self.start > self.end: - raise ValueError("Array slice start index must be lower than end index.") + if (start := self.start) is None or (end := self.end) is None: + return self + if 0 <= end <= start or end <= start < 0: + raise ValueError("Array slice start index must be strictly lower than end index.") return self def __str__(self) -> str: - return f"[{self.start}:{self.end}]" + return f"[{'' if self.start is None else self.start}:{'' if self.end is None else self.end}]" class Array(Model): diff --git a/src/erc7730/model/paths/path_parser.py b/src/erc7730/model/paths/path_parser.py index 4b44fd9..b9f1363 100644 --- a/src/erc7730/model/paths/path_parser.py +++ b/src/erc7730/model/paths/path_parser.py @@ -36,7 +36,8 @@ array: "[]" array_index: /-?[0-9]+/ array_element: "[" array_index "]" - array_slice: "[" array_index ":" array_index "]" + slice_array_index: array_index? + array_slice: "[" slice_array_index ":" slice_array_index "]" """, start="path", ) @@ -60,6 +61,12 @@ def array_element(self, ast: Any) -> ArrayElement: (value,) = ast return ArrayElement(index=value) + def slice_array_index(self, ast: Any) -> ArrayIndex | None: + if len(ast) == 1: + (value,) = ast + return value + return None + def array_slice(self, ast: Any) -> ArraySlice: (start, end) = ast return ArraySlice(start=start, end=end) diff --git a/tests/model/paths/test_path_parser.py b/tests/model/paths/test_path_parser.py index 6b92fc1..7c133c1 100644 --- a/tests/model/paths/test_path_parser.py +++ b/tests/model/paths/test_path_parser.py @@ -42,7 +42,7 @@ def test_valid_input_container_path() -> None: def test_valid_input_data_path_absolute() -> None: _test_valid_input_path( - string="#.params.[].[-2].[1:5].amountIn", + string="#.params.[].[-2].[1:5].[:5].[5:].amountIn", obj=DataPath( absolute=True, elements=[ @@ -50,6 +50,8 @@ def test_valid_input_data_path_absolute() -> None: Array(), ArrayElement(index=-2), ArraySlice(start=1, end=5), + ArraySlice(start=None, end=5), + ArraySlice(start=5, end=None), Field(identifier="amountIn"), ], ), @@ -62,6 +64,8 @@ def test_valid_input_data_path_absolute() -> None: { "type": "array" }, { "type": "array_element", "index": -2 }, { "type": "array_slice", "start": 1, "end": 5 }, + { "type": "array_slice", "end": 5 }, + { "type": "array_slice", "start": 5 }, { "type": "field", "identifier": "amountIn" } ] } @@ -211,7 +215,7 @@ def test_invalid_array_slice_inverted() -> None: message = str(e.value) assert "Invalid path" in message assert "#.[1:0]" in message - assert "Array slice start index must be lower than end index" in message + assert "Array slice start index must be strictly lower than end index" in message def test_invalid_array_slice_used_in_descriptor_path() -> None: