Skip to content

Commit 93fee85

Browse files
aepfligruebel
andauthored
fix(flagd): fix semver version parsing to allow "v" prefix(#106) (#107)
* fix(flagd): fix semver version parsing to allow "v" prefix(#106) Signed-off-by: Simon Schrottner <[email protected]> * Update providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/custom_ops.py Co-authored-by: Anton Grübel <[email protected]> Signed-off-by: Simon Schrottner <[email protected]> --------- Signed-off-by: Simon Schrottner <[email protected]> Co-authored-by: Anton Grübel <[email protected]>
1 parent 038a343 commit 93fee85

File tree

2 files changed

+74
-54
lines changed

2 files changed

+74
-54
lines changed

providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/resolvers/process/custom_ops.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ def sem_ver(data: dict, *args: JsonLogicArg) -> typing.Optional[bool]: # noqa:
130130
arg1, op, arg2 = args
131131

132132
try:
133-
v1 = semver.Version.parse(str(arg1))
134-
v2 = semver.Version.parse(str(arg2))
133+
v1 = parse_version(arg1)
134+
v2 = parse_version(arg2)
135135
except ValueError as e:
136136
logger.exception(e)
137137
return None
@@ -155,3 +155,11 @@ def sem_ver(data: dict, *args: JsonLogicArg) -> typing.Optional[bool]: # noqa:
155155
else:
156156
logger.error(f"Op not supported by sem_ver: {op}")
157157
return None
158+
159+
160+
def parse_version(arg: typing.Any) -> semver.Version:
161+
version = str(arg)
162+
if version.startswith(("v", "V")):
163+
version = version[1:]
164+
165+
return semver.Version.parse(version)

providers/openfeature-provider-flagd/tests/test_targeting.py

Lines changed: 64 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import itertools
12
import time
3+
import typing
24
import unittest
5+
from dataclasses import dataclass
6+
from enum import Enum
37
from math import floor
48

59
import pytest
@@ -84,64 +88,72 @@ def test_non_string_comparator(self):
8488
assert not targeting(flag_key, rule, EvaluationContext(attributes=context))
8589

8690

87-
@pytest.mark.skip(
88-
"semvers are not working as expected, 'v' prefix is not valid within current implementation"
91+
class VersionPrefixed(Enum):
92+
NONE = "None"
93+
FIRST = "First"
94+
SECOND = "Second"
95+
BOTH = "Both"
96+
97+
98+
@dataclass
99+
class SemVerTest:
100+
title: str
101+
rule: typing.List[str]
102+
result: typing.Optional[bool]
103+
104+
105+
semver_operations: typing.List[SemVerTest] = [
106+
# Successful and working rules
107+
SemVerTest("equals", ["1.2.3", "=", "1.2.3"], True),
108+
SemVerTest("not equals", ["1.2.3", "!=", "1.2.4"], True),
109+
SemVerTest("lesser", ["1.2.3", "<", "1.2.4"], True),
110+
SemVerTest("lesser equals", ["1.2.3", "<=", "1.2.3"], True),
111+
SemVerTest("greater", ["1.2.4", ">", "1.2.3"], True),
112+
SemVerTest("greater equals", ["1.2.3", ">=", "1.2.3"], True),
113+
SemVerTest("match major", ["1.2.3", "^", "1.0.0"], True),
114+
SemVerTest("match minor", ["5.0.3", "~", "5.0.8"], True),
115+
# Wrong rules
116+
SemVerTest("wrong operator", ["1.0.0", "-", "1.0.0"], None),
117+
SemVerTest("wrong versions", ["myVersion_1", "=", "myVersion_1"], None),
118+
SemVerTest(
119+
"too many arguments", ["myVersion_2", "+", "myVersion_1", "myVersion_1"], None
120+
),
121+
SemVerTest("too many arguments", ["1.2.3", "=", "1.2.3", "myVersion_1"], None),
122+
]
123+
124+
125+
def semver_test_naming(vals):
126+
if isinstance(vals, SemVerTest):
127+
return vals.title
128+
elif isinstance(vals, VersionPrefixed):
129+
return f"prefixing '{vals.value}'"
130+
elif isinstance(vals, str):
131+
return f"with '{vals}'"
132+
133+
134+
@pytest.mark.parametrize(
135+
("semver_test", "prefix_state", "prefix"),
136+
itertools.product(semver_operations, VersionPrefixed, ["V", "v"]),
137+
ids=semver_test_naming,
89138
)
90-
class SemVerOperator(unittest.TestCase):
91-
def test_should_support_equal_operator(self):
92-
rule = {"sem_ver": ["v1.2.3", "=", "1.2.3"]}
139+
def test_sem_ver_operator(semver_test: SemVerTest, prefix_state, prefix):
140+
"""Testing SemVer operator `semver_test.title` for `semver_test.rule` prefixing `prefix_state.value` version(s) with `prefix`"""
141+
version1 = semver_test.rule[0]
142+
operator = semver_test.rule[1]
143+
version2 = semver_test.rule[2]
93144

94-
assert targeting(flag_key, rule)
95-
96-
def test_should_support_neq_operator(self):
97-
rule = {"sem_ver": ["v1.2.3", "!=", "1.2.4"]}
98-
99-
assert targeting(flag_key, rule)
100-
101-
def test_should_support_lt_operator(self):
102-
rule = {"sem_ver": ["v1.2.3", "<", "1.2.4"]}
103-
104-
assert targeting(flag_key, rule)
105-
106-
def test_should_support_lte_operator(self):
107-
rule = {"sem_ver": ["v1.2.3", "<=", "1.2.3"]}
108-
109-
assert targeting(flag_key, rule)
110-
111-
def test_should_support_gte_operator(self):
112-
rule = {"sem_ver": ["v1.2.3", ">=", "1.2.3"]}
113-
114-
assert targeting(flag_key, rule)
145+
if prefix_state is VersionPrefixed.FIRST or prefix_state is VersionPrefixed.BOTH:
146+
version1 = prefix + version1
115147

116-
def test_should_support_gt_operator(self):
117-
rule = {"sem_ver": ["v1.2.4", ">", "1.2.3"]}
148+
if prefix_state is VersionPrefixed.SECOND or prefix_state is VersionPrefixed.BOTH:
149+
version2 = prefix + version2
118150

119-
assert targeting(flag_key, rule)
120-
121-
def test_should_support_major_comparison_operator(self):
122-
rule = {"sem_ver": ["v1.2.3", "^", "v1.0.0"]}
123-
124-
assert targeting(flag_key, rule)
125-
126-
def test_should_support_minor_comparison_operator(self):
127-
rule = {"sem_ver": ["v5.0.3", "~", "v5.0.8"]}
128-
129-
assert targeting(flag_key, rule)
151+
semver_rule = [version1, operator, version2]
152+
semver_rule.extend(semver_test.rule[3:])
130153

131-
def test_should_handle_unknown_operator(self):
132-
rule = {"sem_ver": ["v1.0.0", "-", "v1.0.0"]}
154+
gen_rule = {"sem_ver": semver_rule}
133155

134-
assert targeting(flag_key, rule)
135-
136-
def test_should_handle_invalid_targetings(self):
137-
rule = {"sem_ver": ["myVersion_1", "=", "myVersion_1"]}
138-
139-
assert not targeting(flag_key, rule)
140-
141-
def test_should_validate_targetings(self):
142-
rule = {"sem_ver": ["myVersion_2", "+", "myVersion_1", "myVersion_1"]}
143-
144-
assert targeting(flag_key, rule)
156+
assert targeting(flag_key, gen_rule) is semver_test.result
145157

146158

147159
class FractionalOperator(unittest.TestCase):

0 commit comments

Comments
 (0)