From b51847a91e80984a4ffa2ad23650afb8bad5e225 Mon Sep 17 00:00:00 2001 From: lea konvalinka Date: Fri, 24 Apr 2026 10:09:10 +0200 Subject: [PATCH 1/3] fix: allow partial versions in semver Signed-off-by: Lea Konvalinka --- .../openfeature-provider-flagd/openfeature/test-harness | 2 +- .../contrib/tools/flagd/core/targeting/custom_ops.py | 6 ++++++ tools/openfeature-flagd-core/tests/test_targeting.py | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/providers/openfeature-provider-flagd/openfeature/test-harness b/providers/openfeature-provider-flagd/openfeature/test-harness index ff2fbe6c..190307b3 160000 --- a/providers/openfeature-provider-flagd/openfeature/test-harness +++ b/providers/openfeature-provider-flagd/openfeature/test-harness @@ -1 +1 @@ -Subproject commit ff2fbe6c6584953cb2753ae9188d1cee14f7f57f +Subproject commit 190307b3b1982773976f05464942f69bb23528a4 diff --git a/tools/openfeature-flagd-core/src/openfeature/contrib/tools/flagd/core/targeting/custom_ops.py b/tools/openfeature-flagd-core/src/openfeature/contrib/tools/flagd/core/targeting/custom_ops.py index 50d82994..751323b8 100644 --- a/tools/openfeature-flagd-core/src/openfeature/contrib/tools/flagd/core/targeting/custom_ops.py +++ b/tools/openfeature-flagd-core/src/openfeature/contrib/tools/flagd/core/targeting/custom_ops.py @@ -176,4 +176,10 @@ def parse_version(arg: typing.Any) -> semver.Version: if version.startswith(("v", "V")): version = version[1:] + # Pad partial versions (e.g. "1" → "1.0.0", "1.2" → "1.2.0") + numeric_part = version.split("-")[0].split("+")[0] + dot_count = numeric_part.count(".") + if dot_count < 2: + version = version + ".0" * (2 - dot_count) + return semver.Version.parse(version) diff --git a/tools/openfeature-flagd-core/tests/test_targeting.py b/tools/openfeature-flagd-core/tests/test_targeting.py index 8d12ad14..1482e849 100644 --- a/tools/openfeature-flagd-core/tests/test_targeting.py +++ b/tools/openfeature-flagd-core/tests/test_targeting.py @@ -113,6 +113,9 @@ def test_minor_no_match(self) -> None: def test_v_prefix(self) -> None: assert sem_ver({}, "v2.0.0", "=", "2.0.0") is True + def test_partial_version(self) -> None: + assert sem_ver({}, "2", "=", "2.0.0") is True + def test_invalid_version(self) -> None: result = sem_ver({}, "not-a-version", "=", "1.0.0") assert result is None From 74d0e4370c41b4c8c6e920f3ab3a35b41934ebec Mon Sep 17 00:00:00 2001 From: lea konvalinka Date: Fri, 24 Apr 2026 11:12:46 +0200 Subject: [PATCH 2/3] fix: exclude tags, adjust starts and ends with Signed-off-by: Lea Konvalinka --- .../provider/flagd/resolvers/process/custom_ops.py | 2 +- .../tests/e2e/inprocess/conftest.py | 1 - .../tests/e2e/rpc/conftest.py | 2 ++ .../contrib/tools/flagd/core/targeting/custom_ops.py | 10 +++++----- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/custom_ops.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/custom_ops.py index 22c4ae69..9e834cfb 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/custom_ops.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/custom_ops.py @@ -6,7 +6,7 @@ JsonPrimitive, ends_with, fractional, - parse_version, + normalize_version, sem_ver, starts_with, string_comp, diff --git a/providers/openfeature-provider-flagd/tests/e2e/inprocess/conftest.py b/providers/openfeature-provider-flagd/tests/e2e/inprocess/conftest.py index 12f7520e..804bea12 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/inprocess/conftest.py +++ b/providers/openfeature-provider-flagd/tests/e2e/inprocess/conftest.py @@ -9,7 +9,6 @@ "~unixsocket", "~deprecated", "~fractional-v1", - "~operator-errors", ] diff --git a/providers/openfeature-provider-flagd/tests/e2e/rpc/conftest.py b/providers/openfeature-provider-flagd/tests/e2e/rpc/conftest.py index e00d257e..91be9148 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/rpc/conftest.py +++ b/providers/openfeature-provider-flagd/tests/e2e/rpc/conftest.py @@ -12,6 +12,8 @@ "~deprecated", "~fractional-v1", "~operator-errors", + "~semver-v-prefix", + "~fractional-single-entry", ] diff --git a/tools/openfeature-flagd-core/src/openfeature/contrib/tools/flagd/core/targeting/custom_ops.py b/tools/openfeature-flagd-core/src/openfeature/contrib/tools/flagd/core/targeting/custom_ops.py index 751323b8..fe65a5df 100644 --- a/tools/openfeature-flagd-core/src/openfeature/contrib/tools/flagd/core/targeting/custom_ops.py +++ b/tools/openfeature-flagd-core/src/openfeature/contrib/tools/flagd/core/targeting/custom_ops.py @@ -125,10 +125,10 @@ def string_comp( arg1, arg2 = args if not isinstance(arg1, str): logger.debug(f"incorrect argument for first argument, expected string: {arg1}") - return False + return None if not isinstance(arg2, str): logger.debug(f"incorrect argument for second argument, expected string: {arg2}") - return False + return None return comparator(arg1, arg2) @@ -144,8 +144,8 @@ def sem_ver(data: dict, *args: JsonLogicArg) -> bool | None: # noqa: C901 arg1, op, arg2 = args try: - v1 = parse_version(arg1) - v2 = parse_version(arg2) + v1 = normalize_version(arg1) + v2 = normalize_version(arg2) except ValueError as e: logger.exception(e) return None @@ -171,7 +171,7 @@ def sem_ver(data: dict, *args: JsonLogicArg) -> bool | None: # noqa: C901 return None -def parse_version(arg: typing.Any) -> semver.Version: +def normalize_version(arg: typing.Any) -> semver.Version: version = str(arg) if version.startswith(("v", "V")): version = version[1:] From ee5e7b6219595c427bd4dbaf1f403fc74a560f74 Mon Sep 17 00:00:00 2001 From: Lea Konvalinka Date: Fri, 24 Apr 2026 11:26:39 +0200 Subject: [PATCH 3/3] fix: copilot review Signed-off-by: Lea Konvalinka --- .../openfeature-provider-flagd/tests/e2e/rpc/conftest.py | 2 ++ .../contrib/tools/flagd/core/targeting/custom_ops.py | 2 +- tools/openfeature-flagd-core/tests/test_targeting.py | 6 +++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/providers/openfeature-provider-flagd/tests/e2e/rpc/conftest.py b/providers/openfeature-provider-flagd/tests/e2e/rpc/conftest.py index 91be9148..7922da21 100644 --- a/providers/openfeature-provider-flagd/tests/e2e/rpc/conftest.py +++ b/providers/openfeature-provider-flagd/tests/e2e/rpc/conftest.py @@ -14,7 +14,9 @@ "~operator-errors", "~semver-v-prefix", "~fractional-single-entry", + "~semver-numeric-context", ] +# TODO remove last 4 tags when adjusted flagd is released def pytest_collection_modifyitems(config, items): diff --git a/tools/openfeature-flagd-core/src/openfeature/contrib/tools/flagd/core/targeting/custom_ops.py b/tools/openfeature-flagd-core/src/openfeature/contrib/tools/flagd/core/targeting/custom_ops.py index fe65a5df..c47eec2e 100644 --- a/tools/openfeature-flagd-core/src/openfeature/contrib/tools/flagd/core/targeting/custom_ops.py +++ b/tools/openfeature-flagd-core/src/openfeature/contrib/tools/flagd/core/targeting/custom_ops.py @@ -180,6 +180,6 @@ def normalize_version(arg: typing.Any) -> semver.Version: numeric_part = version.split("-")[0].split("+")[0] dot_count = numeric_part.count(".") if dot_count < 2: - version = version + ".0" * (2 - dot_count) + version = numeric_part + ".0" * (2 - dot_count) + version[len(numeric_part) :] return semver.Version.parse(version) diff --git a/tools/openfeature-flagd-core/tests/test_targeting.py b/tools/openfeature-flagd-core/tests/test_targeting.py index 1482e849..9554a903 100644 --- a/tools/openfeature-flagd-core/tests/test_targeting.py +++ b/tools/openfeature-flagd-core/tests/test_targeting.py @@ -66,7 +66,7 @@ def test_starts_with_no_args(self) -> None: def test_starts_with_non_string(self) -> None: result = starts_with({}, 123, "abc") - assert result is False + assert result is None class TestEndsWith: @@ -78,6 +78,10 @@ def test_ends_with_false(self) -> None: result = ends_with({}, "hello world", "hello") assert result is False + def test_ends_with_non_string(self) -> None: + result = ends_with({}, 123, "abc") + assert result is None + class TestSemVer: def test_equal(self) -> None: