From 0e3b1149bfab0caf16b1aa8122463588ac13c21f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Thu, 10 Jul 2025 18:13:55 +0200 Subject: [PATCH] feat: ensure PatchOp.path is not none for remove operations --- scim2_models/rfc7644/patch_op.py | 14 ++++++++++++++ tests/test_patch_op.py | 26 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/scim2_models/rfc7644/patch_op.py b/scim2_models/rfc7644/patch_op.py index 80768ed..39fdb25 100644 --- a/scim2_models/rfc7644/patch_op.py +++ b/scim2_models/rfc7644/patch_op.py @@ -5,6 +5,8 @@ from pydantic import Field from pydantic import field_validator +from pydantic import model_validator +from typing_extensions import Self from ..base import ComplexAttribute from ..base import Required @@ -32,6 +34,18 @@ class Op(str, Enum): """The "path" attribute value is a String containing an attribute path describing the target of the operation.""" + @model_validator(mode="after") + def validate_path(self) -> Self: + # The "path" attribute value is a String containing an attribute path + # describing the target of the operation. The "path" attribute is + # OPTIONAL for "add" and "replace" and is REQUIRED for "remove" + # operations. See relevant operation sections below for details. + + if self.path is None and self.op == PatchOperation.Op.remove: + raise ValueError("Op.path is required for remove operations") + + return self + value: Optional[Any] = None @field_validator("op", mode="before") diff --git a/tests/test_patch_op.py b/tests/test_patch_op.py index e3fdf8f..6481777 100644 --- a/tests/test_patch_op.py +++ b/tests/test_patch_op.py @@ -35,3 +35,29 @@ def test_validate_patchop_case_insensitivith(): "operations": [{"op": 42, "path": "userName", "value": "Rivard"}], }, ) + + +def test_path_required_for_remove_operations(): + PatchOp.model_validate( + { + "operations": [ + {"op": "replace", "value": "foobar"}, + ], + } + ) + PatchOp.model_validate( + { + "operations": [ + {"op": "add", "value": "foobar"}, + ], + } + ) + + with pytest.raises(ValidationError): + PatchOp.model_validate( + { + "operations": [ + {"op": "remove", "value": "foobar"}, + ], + } + )