From 21135240c30fb0930ff518d98b2f8c8470900b40 Mon Sep 17 00:00:00 2001 From: Yasir Ali Date: Mon, 3 Aug 2020 11:16:39 +0500 Subject: [PATCH 1/3] sem-ver initial commit. --- pkg/decision/evaluator/condition.go | 19 +++++++ pkg/decision/evaluator/matchers/ge.go | 53 ++++++++++++++++++++ pkg/decision/evaluator/matchers/le.go | 53 ++++++++++++++++++++ pkg/decision/evaluator/matchers/semver_eq.go | 47 +++++++++++++++++ pkg/decision/evaluator/matchers/semver_ge.go | 47 +++++++++++++++++ pkg/decision/evaluator/matchers/semver_gt.go | 47 +++++++++++++++++ pkg/decision/evaluator/matchers/semver_le.go | 47 +++++++++++++++++ pkg/decision/evaluator/matchers/semver_lt.go | 47 +++++++++++++++++ pkg/entities/semantic_version.go | 46 +++++++++++++++++ 9 files changed, 406 insertions(+) create mode 100644 pkg/decision/evaluator/matchers/ge.go create mode 100644 pkg/decision/evaluator/matchers/le.go create mode 100644 pkg/decision/evaluator/matchers/semver_eq.go create mode 100644 pkg/decision/evaluator/matchers/semver_ge.go create mode 100644 pkg/decision/evaluator/matchers/semver_gt.go create mode 100644 pkg/decision/evaluator/matchers/semver_le.go create mode 100644 pkg/decision/evaluator/matchers/semver_lt.go create mode 100644 pkg/entities/semantic_version.go diff --git a/pkg/decision/evaluator/condition.go b/pkg/decision/evaluator/condition.go index ef8da5d09..8a13bbe89 100644 --- a/pkg/decision/evaluator/condition.go +++ b/pkg/decision/evaluator/condition.go @@ -28,8 +28,15 @@ const ( exactMatchType = "exact" existsMatchType = "exists" ltMatchType = "lt" + leMatchType = "le" gtMatchType = "gt" + geMatchType = "ge" substringMatchType = "substring" + semverEqMatchType = "semver_eq" + semverLtMatchType = "semver_lt" + semverLeMatchType = "semver_le" + semverGtMatchType = "semver_gt" + semverGeMatchType = "semver_ge" ) // ItemEvaluator evaluates a condition against the given user's attributes @@ -66,14 +73,26 @@ func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition matcher = matchers.LtMatcher{ Condition: condition, } + case leMatchType: + matcher = matchers.LEMatcher{ + Condition: condition, + } case gtMatchType: matcher = matchers.GtMatcher{ Condition: condition, } + case geMatchType: + matcher = matchers.GEMatcher{ + Condition: condition, + } case substringMatchType: matcher = matchers.SubstringMatcher{ Condition: condition, } + case semverLtMatchType: + matcher = matchers.SemVerLtMatcher{ + Condition: condition, + } default: return false, fmt.Errorf(`invalid Condition matcher "%s"`, condition.Match) } diff --git a/pkg/decision/evaluator/matchers/ge.go b/pkg/decision/evaluator/matchers/ge.go new file mode 100644 index 000000000..264687d50 --- /dev/null +++ b/pkg/decision/evaluator/matchers/ge.go @@ -0,0 +1,53 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +// Package matchers // +package matchers + +import ( + "github.com/optimizely/go-sdk/pkg/decision/evaluator/matchers" + "github.com/optimizely/go-sdk/pkg/entities" +) + +// GEMatcher matches against the "ge" match type +type GEMatcher struct { + Condition entities.Condition +} + +// Match returns true if the user's attribute is greater than or equal to the condition's string value +func (m GEMatcher) Match(user entities.UserContext) (bool, error) { + + var result bool + var err error + + gtMatcher := matchers.GtMatcher{ + Condition: m.Condition, + } + + if result, err = gtMatcher.Match(user); err == nil && result { + return true, nil + } + + exactMatcher := matchers.ExactMatcher{ + Condition: m.Condition, + } + + if result, err = exactMatcher.Match(user); err == nil && result { + return true, nil + } + + return false, err +} diff --git a/pkg/decision/evaluator/matchers/le.go b/pkg/decision/evaluator/matchers/le.go new file mode 100644 index 000000000..3bf10b666 --- /dev/null +++ b/pkg/decision/evaluator/matchers/le.go @@ -0,0 +1,53 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +// Package matchers // +package matchers + +import ( + "github.com/optimizely/go-sdk/pkg/decision/evaluator/matchers" + "github.com/optimizely/go-sdk/pkg/entities" +) + +// LEMatcher matches against the "le" match type +type LEMatcher struct { + Condition entities.Condition +} + +// Match returns true if the user's attribute is less than or equal to the condition's string value +func (m LEMatcher) Match(user entities.UserContext) (bool, error) { + + var result bool + var err error + + ltMatcher := matchers.LtMatcher{ + Condition: m.Condition, + } + + if result, err = ltMatcher.Match(user); err == nil && result { + return true, nil + } + + exactMatcher := matchers.ExactMatcher{ + Condition: m.Condition, + } + + if result, err = exactMatcher.Match(user); err == nil && result { + return true, nil + } + + return false, err +} diff --git a/pkg/decision/evaluator/matchers/semver_eq.go b/pkg/decision/evaluator/matchers/semver_eq.go new file mode 100644 index 000000000..a792f47fb --- /dev/null +++ b/pkg/decision/evaluator/matchers/semver_eq.go @@ -0,0 +1,47 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +// Package matchers // +package matchers + +import ( + "fmt" + + "github.com/optimizely/go-sdk/pkg/entities" +) + +// SemVerEqMatcher matches against the "semver_eq" match type +type SemVerEqMatcher struct { + Condition entities.Condition +} + +// Match returns true if condition value for semantic versions is equal to the target semantic version +func (m SemVerEqMatcher) Match(user entities.UserContext) (bool, error) { + + if stringValue, ok := m.Condition.Value.(string); ok { + attributeValue, err := user.GetStringAttribute(m.Condition.Name) + if err != nil { + return false, err + } + result, err := entities.SemanticVersion(stringValue).CompareVersion(entities.SemanticVersion(attributeValue)) + if err == nil { + return result == 0, nil + } + return false, err + } + + return false, fmt.Errorf("audience condition %s evaluated to NULL because the condition value type is not supported", m.Condition.Name) +} diff --git a/pkg/decision/evaluator/matchers/semver_ge.go b/pkg/decision/evaluator/matchers/semver_ge.go new file mode 100644 index 000000000..7b8f86be9 --- /dev/null +++ b/pkg/decision/evaluator/matchers/semver_ge.go @@ -0,0 +1,47 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +// Package matchers // +package matchers + +import ( + "fmt" + + "github.com/optimizely/go-sdk/pkg/entities" +) + +// SemVerGeMatcher matches against the "semver_gt" match type +type SemVerGeMatcher struct { + Condition entities.Condition +} + +// Match returns true if condition value for semantic versions is greater than or equal to target semantic version +func (m SemVerGeMatcher) Match(user entities.UserContext) (bool, error) { + + if stringValue, ok := m.Condition.Value.(string); ok { + attributeValue, err := user.GetStringAttribute(m.Condition.Name) + if err != nil { + return false, err + } + result, err := entities.SemanticVersion(stringValue).CompareVersion(entities.SemanticVersion(attributeValue)) + if err == nil { + return result >= 0, nil + } + return false, err + } + + return false, fmt.Errorf("audience condition %s evaluated to NULL because the condition value type is not supported", m.Condition.Name) +} diff --git a/pkg/decision/evaluator/matchers/semver_gt.go b/pkg/decision/evaluator/matchers/semver_gt.go new file mode 100644 index 000000000..aa11a0019 --- /dev/null +++ b/pkg/decision/evaluator/matchers/semver_gt.go @@ -0,0 +1,47 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +// Package matchers // +package matchers + +import ( + "fmt" + + "github.com/optimizely/go-sdk/pkg/entities" +) + +// SemVerGtMatcher matches against the "semver_gt" match type +type SemVerGtMatcher struct { + Condition entities.Condition +} + +// Match returns true if condition value for semantic versions is greater than target semantic version +func (m SemVerGtMatcher) Match(user entities.UserContext) (bool, error) { + + if stringValue, ok := m.Condition.Value.(string); ok { + attributeValue, err := user.GetStringAttribute(m.Condition.Name) + if err != nil { + return false, err + } + result, err := entities.SemanticVersion(stringValue).CompareVersion(entities.SemanticVersion(attributeValue)) + if err == nil { + return result > 0, nil + } + return false, err + } + + return false, fmt.Errorf("audience condition %s evaluated to NULL because the condition value type is not supported", m.Condition.Name) +} diff --git a/pkg/decision/evaluator/matchers/semver_le.go b/pkg/decision/evaluator/matchers/semver_le.go new file mode 100644 index 000000000..40302f054 --- /dev/null +++ b/pkg/decision/evaluator/matchers/semver_le.go @@ -0,0 +1,47 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +// Package matchers // +package matchers + +import ( + "fmt" + + "github.com/optimizely/go-sdk/pkg/entities" +) + +// SemVerLeMatcher matches against the "semver_lt" match type +type SemVerLeMatcher struct { + Condition entities.Condition +} + +// Match returns true if condition value for semantic versions is less than or equal to target semantic version +func (m SemVerLeMatcher) Match(user entities.UserContext) (bool, error) { + + if stringValue, ok := m.Condition.Value.(string); ok { + attributeValue, err := user.GetStringAttribute(m.Condition.Name) + if err != nil { + return false, err + } + result, err := entities.SemanticVersion(stringValue).CompareVersion(entities.SemanticVersion(attributeValue)) + if err == nil { + return result <= 0, nil + } + return false, err + } + + return false, fmt.Errorf("audience condition %s evaluated to NULL because the condition value type is not supported", m.Condition.Name) +} diff --git a/pkg/decision/evaluator/matchers/semver_lt.go b/pkg/decision/evaluator/matchers/semver_lt.go new file mode 100644 index 000000000..d09b8e86c --- /dev/null +++ b/pkg/decision/evaluator/matchers/semver_lt.go @@ -0,0 +1,47 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +// Package matchers // +package matchers + +import ( + "fmt" + + "github.com/optimizely/go-sdk/pkg/entities" +) + +// SemVerLtMatcher matches against the "semver_lt" match type +type SemVerLtMatcher struct { + Condition entities.Condition +} + +// Match returns true if condition value for semantic versions is less than target semantic version +func (m SemVerLtMatcher) Match(user entities.UserContext) (bool, error) { + + if stringValue, ok := m.Condition.Value.(string); ok { + attributeValue, err := user.GetStringAttribute(m.Condition.Name) + if err != nil { + return false, err + } + result, err := entities.SemanticVersion(stringValue).CompareVersion(entities.SemanticVersion(attributeValue)) + if err == nil { + return result < 0, nil + } + return false, err + } + + return false, fmt.Errorf("audience condition %s evaluated to NULL because the condition value type is not supported", m.Condition.Name) +} diff --git a/pkg/entities/semantic_version.go b/pkg/entities/semantic_version.go new file mode 100644 index 000000000..a161f5fb1 --- /dev/null +++ b/pkg/entities/semantic_version.go @@ -0,0 +1,46 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +// Package entities // +package entities + +import ( + "errors" + + "golang.org/x/mod/semver" +) + +// SemanticVersion represents semantic version +type SemanticVersion string + +// CompareVersion compares two semantic versions +// result will be 0 if v == w, -1 if v < w, or +1 if v > w. +func (s SemanticVersion) CompareVersion(targetedVersion SemanticVersion) (int, error) { + + sVersion := string(s) + sTargetedVersion := string(targetedVersion) + + if sTargetedVersion == "" { + // Any version. + return 0, nil + } + + // Up to the precision of targetedVersion, expect version to match exactly. + if semver.IsValid(sVersion) && semver.IsValid(sTargetedVersion) { + return semver.Compare(sVersion, sTargetedVersion), nil + } + return 0, errors.New("provided versions are in an invalid format") +} From 76defb9cff33e9a25937b2c0268f91314262ba18 Mon Sep 17 00:00:00 2001 From: Yasir Ali Date: Tue, 11 Aug 2020 13:33:01 +0500 Subject: [PATCH 2/3] semantic version with tests implemented. --- pkg/decision/evaluator/condition.go | 22 +- pkg/decision/evaluator/condition_test.go | 186 +++++++++++++---- pkg/decision/evaluator/matchers/ge.go | 13 +- pkg/decision/evaluator/matchers/ge_test.go | 141 +++++++++++++ pkg/decision/evaluator/matchers/le.go | 13 +- pkg/decision/evaluator/matchers/le_test.go | 142 +++++++++++++ pkg/decision/evaluator/matchers/semver_eq.go | 2 +- .../evaluator/matchers/semver_eq_test.go | 101 +++++++++ pkg/decision/evaluator/matchers/semver_ge.go | 2 +- .../evaluator/matchers/semver_ge_test.go | 74 +++++++ pkg/decision/evaluator/matchers/semver_gt.go | 2 +- .../evaluator/matchers/semver_gt_test.go | 74 +++++++ pkg/decision/evaluator/matchers/semver_le.go | 2 +- .../evaluator/matchers/semver_le_test.go | 74 +++++++ pkg/decision/evaluator/matchers/semver_lt.go | 2 +- .../evaluator/matchers/semver_lt_test.go | 74 +++++++ pkg/entities/semantic_version.go | 132 +++++++++++- pkg/entities/semantic_version_test.go | 197 ++++++++++++++++++ 18 files changed, 1183 insertions(+), 70 deletions(-) create mode 100644 pkg/decision/evaluator/matchers/ge_test.go create mode 100644 pkg/decision/evaluator/matchers/le_test.go create mode 100644 pkg/decision/evaluator/matchers/semver_eq_test.go create mode 100644 pkg/decision/evaluator/matchers/semver_ge_test.go create mode 100644 pkg/decision/evaluator/matchers/semver_gt_test.go create mode 100644 pkg/decision/evaluator/matchers/semver_le_test.go create mode 100644 pkg/decision/evaluator/matchers/semver_lt_test.go create mode 100644 pkg/entities/semantic_version_test.go diff --git a/pkg/decision/evaluator/condition.go b/pkg/decision/evaluator/condition.go index 8a13bbe89..bad01b928 100644 --- a/pkg/decision/evaluator/condition.go +++ b/pkg/decision/evaluator/condition.go @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2019, Optimizely, Inc. and contributors * + * Copyright 2019-2020, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -74,7 +74,7 @@ func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition Condition: condition, } case leMatchType: - matcher = matchers.LEMatcher{ + matcher = matchers.LeMatcher{ Condition: condition, } case gtMatchType: @@ -82,17 +82,33 @@ func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition Condition: condition, } case geMatchType: - matcher = matchers.GEMatcher{ + matcher = matchers.GeMatcher{ Condition: condition, } case substringMatchType: matcher = matchers.SubstringMatcher{ Condition: condition, } + case semverEqMatchType: + matcher = matchers.SemVerEqMatcher{ + Condition: condition, + } case semverLtMatchType: matcher = matchers.SemVerLtMatcher{ Condition: condition, } + case semverLeMatchType: + matcher = matchers.SemVerLeMatcher{ + Condition: condition, + } + case semverGtMatchType: + matcher = matchers.SemVerGtMatcher{ + Condition: condition, + } + case semverGeMatchType: + matcher = matchers.SemVerGeMatcher{ + Condition: condition, + } default: return false, fmt.Errorf(`invalid Condition matcher "%s"`, condition.Match) } diff --git a/pkg/decision/evaluator/condition_test.go b/pkg/decision/evaluator/condition_test.go index 5db738a7e..55763d7d0 100644 --- a/pkg/decision/evaluator/condition_test.go +++ b/pkg/decision/evaluator/condition_test.go @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright 2019, Optimizely, Inc. and contributors * + * Copyright 2019-2020, Optimizely, Inc. and contributors * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * @@ -17,14 +17,24 @@ package evaluator import ( - "testing" - "github.com/optimizely/go-sdk/pkg/entities" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) -func TestCustomAttributeConditionEvaluator(t *testing.T) { - conditionEvaluator := CustomAttributeConditionEvaluator{} +type ConditionTestSuite struct { + suite.Suite + user entities.UserContext + conditionEvaluator CustomAttributeConditionEvaluator +} + +func (s *ConditionTestSuite) SetupTest() { + s.conditionEvaluator = CustomAttributeConditionEvaluator{} + s.user = entities.UserContext{ + Attributes: map[string]interface{}{}, + } +} + +func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluator() { condition := entities.Condition{ Match: "exact", Value: "foo", @@ -33,28 +43,19 @@ func TestCustomAttributeConditionEvaluator(t *testing.T) { } // Test condition passes - user := entities.UserContext{ - Attributes: map[string]interface{}{ - "string_foo": "foo", - }, - } + s.user.Attributes["string_foo"] = "foo" - condTreeParams := entities.NewTreeParameters(&user, map[string]entities.Audience{}) - result, _ := conditionEvaluator.Evaluate(condition, condTreeParams) - assert.Equal(t, result, true) + condTreeParams := entities.NewTreeParameters(&s.user, map[string]entities.Audience{}) + result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams) + s.Equal(result, true) // Test condition fails - user = entities.UserContext{ - Attributes: map[string]interface{}{ - "string_foo": "not_foo", - }, - } - result, _ = conditionEvaluator.Evaluate(condition, condTreeParams) - assert.Equal(t, result, false) + s.user.Attributes["string_foo"] = "not_foo" + result, _ = s.conditionEvaluator.Evaluate(condition, condTreeParams) + s.Equal(result, false) } -func TestCustomAttributeConditionEvaluatorWithoutMatchType(t *testing.T) { - conditionEvaluator := CustomAttributeConditionEvaluator{} +func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorWithoutMatchType() { condition := entities.Condition{ Value: "foo", Name: "string_foo", @@ -62,22 +63,133 @@ func TestCustomAttributeConditionEvaluatorWithoutMatchType(t *testing.T) { } // Test condition passes - user := entities.UserContext{ - Attributes: map[string]interface{}{ - "string_foo": "foo", - }, - } + s.user.Attributes["string_foo"] = "foo" - condTreeParams := entities.NewTreeParameters(&user, map[string]entities.Audience{}) - result, _ := conditionEvaluator.Evaluate(condition, condTreeParams) - assert.Equal(t, result, true) + condTreeParams := entities.NewTreeParameters(&s.user, map[string]entities.Audience{}) + result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams) + s.Equal(result, true) // Test condition fails - user = entities.UserContext{ - Attributes: map[string]interface{}{ - "string_foo": "not_foo", - }, + s.user.Attributes["string_foo"] = "not_foo" + result, _ = s.conditionEvaluator.Evaluate(condition, condTreeParams) + s.Equal(result, false) +} + +func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorSemanticSame() { + // Test if same when all target is only major.minor + condition := entities.Condition{ + Value: "2.0.0", + Match: "semver_eq", + Name: "version", + Type: "custom_attribute", + } + + // Test condition passes + s.user.Attributes["version"] = "2.0" + + condTreeParams := entities.NewTreeParameters(&s.user, map[string]entities.Audience{}) + result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams) + s.Equal(result, true) +} + +func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorSemanticSameFull() { + // Test when target is full semantic version major.minor.patch + condition := entities.Condition{ + Value: "3.0.0", + Match: "semver_eq", + Name: "version", + Type: "custom_attribute", + } + + // Test condition passes + s.user.Attributes["version"] = "3.0.0" + + condTreeParams := entities.NewTreeParameters(&s.user, map[string]entities.Audience{}) + result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams) + s.Equal(result, true) +} + +func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorSemanticLess() { + // Test compare less when target is only major.minor + condition := entities.Condition{ + Value: "2.1.6", + Match: "semver_lt", + Name: "version", + Type: "custom_attribute", + } + + // Test condition passes + s.user.Attributes["version"] = "2.2" + + condTreeParams := entities.NewTreeParameters(&s.user, map[string]entities.Audience{}) + result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams) + s.Equal(result, true) +} + +func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorSemanticFullLess() { + // Test compare less when target is full major.minor.patch + condition := entities.Condition{ + Value: "2.1.6", + Match: "semver_lt", + Name: "version", + Type: "custom_attribute", + } + + // Test condition passes + s.user.Attributes["version"] = "2.1.9" + + condTreeParams := entities.NewTreeParameters(&s.user, map[string]entities.Audience{}) + result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams) + s.Equal(result, true) +} + +func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorSemanticMore() { + // Test compare greater when target is only major.minor + condition := entities.Condition{ + Value: "2.3.6", + Match: "semver_gt", + Name: "version", + Type: "custom_attribute", + } + + // Test condition passes + s.user.Attributes["version"] = "2.2" + + condTreeParams := entities.NewTreeParameters(&s.user, map[string]entities.Audience{}) + result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams) + s.Equal(result, true) +} + +func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorSemanticFullMore() { + // Test compare greater when target is major.minor.patch + condition := entities.Condition{ + Value: "2.1.9", + Match: "semver_gt", + Name: "version", + Type: "custom_attribute", + } + + // Test condition passes + s.user.Attributes["version"] = "2.1.6" + + condTreeParams := entities.NewTreeParameters(&s.user, map[string]entities.Audience{}) + result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams) + s.Equal(result, true) +} + +func (s *ConditionTestSuite) TestCustomAttributeConditionEvaluatorSemanticFullEqual() { + // Test compare equal when target is major.minor.patch-beta + condition := entities.Condition{ + Value: "2.1.9-beta", + Match: "semver_eq", + Name: "version", + Type: "custom_attribute", } - result, _ = conditionEvaluator.Evaluate(condition, condTreeParams) - assert.Equal(t, result, false) + + // Test condition passes + s.user.Attributes["version"] = "2.1.9-beta" + + condTreeParams := entities.NewTreeParameters(&s.user, map[string]entities.Audience{}) + result, _ := s.conditionEvaluator.Evaluate(condition, condTreeParams) + s.Equal(result, true) } diff --git a/pkg/decision/evaluator/matchers/ge.go b/pkg/decision/evaluator/matchers/ge.go index 264687d50..4405ce403 100644 --- a/pkg/decision/evaluator/matchers/ge.go +++ b/pkg/decision/evaluator/matchers/ge.go @@ -18,30 +18,29 @@ package matchers import ( - "github.com/optimizely/go-sdk/pkg/decision/evaluator/matchers" "github.com/optimizely/go-sdk/pkg/entities" ) -// GEMatcher matches against the "ge" match type -type GEMatcher struct { +// GeMatcher matches against the "ge" match type +type GeMatcher struct { Condition entities.Condition } // Match returns true if the user's attribute is greater than or equal to the condition's string value -func (m GEMatcher) Match(user entities.UserContext) (bool, error) { +func (m GeMatcher) Match(user entities.UserContext) (bool, error) { var result bool var err error - gtMatcher := matchers.GtMatcher{ + ltMatcher := LtMatcher{ Condition: m.Condition, } - if result, err = gtMatcher.Match(user); err == nil && result { + if result, err = ltMatcher.Match(user); err == nil && result { return true, nil } - exactMatcher := matchers.ExactMatcher{ + exactMatcher := ExactMatcher{ Condition: m.Condition, } diff --git a/pkg/decision/evaluator/matchers/ge_test.go b/pkg/decision/evaluator/matchers/ge_test.go new file mode 100644 index 000000000..9c6dcdda0 --- /dev/null +++ b/pkg/decision/evaluator/matchers/ge_test.go @@ -0,0 +1,141 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +package matchers + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/optimizely/go-sdk/pkg/entities" +) + +func TestGeMatcherInt(t *testing.T) { + matcher := GeMatcher{ + Condition: entities.Condition{ + Match: "ge", + Value: 42, + Name: "int_42", + }, + } + + user := entities.UserContext{ + Attributes: map[string]interface{}{ + "int_42": 41, + }, + } + result, err := matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "int_42": 42.9999, + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.False(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "int_42": 42.00000, + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "int_42": 42, + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "int_43": 42, + }, + } + + _, err = matcher.Match(user) + assert.Error(t, err) +} + +func TestGeMatcherFloat(t *testing.T) { + matcher := GeMatcher{ + Condition: entities.Condition{ + Match: "ge", + Value: 4.2, + Name: "float_4_2", + }, + } + + user := entities.UserContext{ + Attributes: map[string]interface{}{ + "float_4_2": 5, + }, + } + result, err := matcher.Match(user) + assert.NoError(t, err) + assert.False(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "float_4_2": 4.29999, + }, + } + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.False(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "float_4_2": 4.2, + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "float_4_2": 4.1, + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + // Test attribute not found + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "float_4_3": 4.2, + }, + } + + _, err = matcher.Match(user) + assert.Error(t, err) +} diff --git a/pkg/decision/evaluator/matchers/le.go b/pkg/decision/evaluator/matchers/le.go index 3bf10b666..b3c02b8ec 100644 --- a/pkg/decision/evaluator/matchers/le.go +++ b/pkg/decision/evaluator/matchers/le.go @@ -18,30 +18,29 @@ package matchers import ( - "github.com/optimizely/go-sdk/pkg/decision/evaluator/matchers" "github.com/optimizely/go-sdk/pkg/entities" ) -// LEMatcher matches against the "le" match type -type LEMatcher struct { +// LeMatcher matches against the "le" match type +type LeMatcher struct { Condition entities.Condition } // Match returns true if the user's attribute is less than or equal to the condition's string value -func (m LEMatcher) Match(user entities.UserContext) (bool, error) { +func (m LeMatcher) Match(user entities.UserContext) (bool, error) { var result bool var err error - ltMatcher := matchers.LtMatcher{ + gtMatcher := GtMatcher{ Condition: m.Condition, } - if result, err = ltMatcher.Match(user); err == nil && result { + if result, err = gtMatcher.Match(user); err == nil && result { return true, nil } - exactMatcher := matchers.ExactMatcher{ + exactMatcher := ExactMatcher{ Condition: m.Condition, } diff --git a/pkg/decision/evaluator/matchers/le_test.go b/pkg/decision/evaluator/matchers/le_test.go new file mode 100644 index 000000000..baec4ff82 --- /dev/null +++ b/pkg/decision/evaluator/matchers/le_test.go @@ -0,0 +1,142 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +package matchers + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/optimizely/go-sdk/pkg/entities" +) + +func TestLeMatcherInt(t *testing.T) { + matcher := LeMatcher{ + Condition: entities.Condition{ + Match: "le", + Value: 42, + Name: "int_42", + }, + } + + user := entities.UserContext{ + Attributes: map[string]interface{}{ + "int_42": 41, + }, + } + result, err := matcher.Match(user) + assert.NoError(t, err) + assert.False(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "int_42": 42.9999, + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "int_42": 42.00000, + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "int_42": 42, + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + // Test attribute not found + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "int_43": 42, + }, + } + + _, err = matcher.Match(user) + assert.Error(t, err) +} + +func TestLeMatcherFloat(t *testing.T) { + matcher := LeMatcher{ + Condition: entities.Condition{ + Match: "le", + Value: 4.2, + Name: "float_4_2", + }, + } + + user := entities.UserContext{ + Attributes: map[string]interface{}{ + "float_4_2": 5, + }, + } + result, err := matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "float_4_2": 4.29999, + }, + } + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "float_4_2": 4.2, + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "float_4_2": 4.1, + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.False(t, result) + + // Test attribute not found + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "float_4_3": 4.2, + }, + } + + _, err = matcher.Match(user) + assert.Error(t, err) +} diff --git a/pkg/decision/evaluator/matchers/semver_eq.go b/pkg/decision/evaluator/matchers/semver_eq.go index a792f47fb..af369f0e6 100644 --- a/pkg/decision/evaluator/matchers/semver_eq.go +++ b/pkg/decision/evaluator/matchers/semver_eq.go @@ -36,7 +36,7 @@ func (m SemVerEqMatcher) Match(user entities.UserContext) (bool, error) { if err != nil { return false, err } - result, err := entities.SemanticVersion(stringValue).CompareVersion(entities.SemanticVersion(attributeValue)) + result, err := entities.SemanticVersion(attributeValue).CompareVersion(entities.SemanticVersion(stringValue)) if err == nil { return result == 0, nil } diff --git a/pkg/decision/evaluator/matchers/semver_eq_test.go b/pkg/decision/evaluator/matchers/semver_eq_test.go new file mode 100644 index 000000000..81deefe4d --- /dev/null +++ b/pkg/decision/evaluator/matchers/semver_eq_test.go @@ -0,0 +1,101 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +package matchers + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/optimizely/go-sdk/pkg/entities" +) + +func TestSemverEqMatcher(t *testing.T) { + matcher := SemVerEqMatcher{ + Condition: entities.Condition{ + Match: "semver_eq", + Value: "2.0", + Name: "version", + }, + } + + user := entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "2.0.0", + }, + } + result, err := matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "2.9", + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.False(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "1.9", + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.False(t, result) + + // Test attribute not found + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version1": "2.0", + }, + } + + _, err = matcher.Match(user) + assert.Error(t, err) +} + +func TestSemverEqMatcherInvalidType(t *testing.T) { + matcher := SemVerEqMatcher{ + Condition: entities.Condition{ + Match: "semver_eq", + Value: "2.0", + Name: "version", + }, + } + + user := entities.UserContext{ + Attributes: map[string]interface{}{ + "version": true, + }, + } + _, err := matcher.Match(user) + assert.Error(t, err) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version": 37, + }, + } + + _, err = matcher.Match(user) + assert.Error(t, err) +} diff --git a/pkg/decision/evaluator/matchers/semver_ge.go b/pkg/decision/evaluator/matchers/semver_ge.go index 7b8f86be9..0625ebe6b 100644 --- a/pkg/decision/evaluator/matchers/semver_ge.go +++ b/pkg/decision/evaluator/matchers/semver_ge.go @@ -36,7 +36,7 @@ func (m SemVerGeMatcher) Match(user entities.UserContext) (bool, error) { if err != nil { return false, err } - result, err := entities.SemanticVersion(stringValue).CompareVersion(entities.SemanticVersion(attributeValue)) + result, err := entities.SemanticVersion(attributeValue).CompareVersion(entities.SemanticVersion(stringValue)) if err == nil { return result >= 0, nil } diff --git a/pkg/decision/evaluator/matchers/semver_ge_test.go b/pkg/decision/evaluator/matchers/semver_ge_test.go new file mode 100644 index 000000000..f4b87c5c5 --- /dev/null +++ b/pkg/decision/evaluator/matchers/semver_ge_test.go @@ -0,0 +1,74 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +package matchers + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/optimizely/go-sdk/pkg/entities" +) + +func TestSemverGeMatcher(t *testing.T) { + matcher := SemVerGeMatcher{ + Condition: entities.Condition{ + Match: "semver_ge", + Value: "2.0", + Name: "version", + }, + } + + user := entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "2.0.0", + }, + } + result, err := matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "2.9", + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "1.9", + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.False(t, result) + + // Test attribute not found + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version1": "2.0", + }, + } + + _, err = matcher.Match(user) + assert.Error(t, err) +} diff --git a/pkg/decision/evaluator/matchers/semver_gt.go b/pkg/decision/evaluator/matchers/semver_gt.go index aa11a0019..2225c545d 100644 --- a/pkg/decision/evaluator/matchers/semver_gt.go +++ b/pkg/decision/evaluator/matchers/semver_gt.go @@ -36,7 +36,7 @@ func (m SemVerGtMatcher) Match(user entities.UserContext) (bool, error) { if err != nil { return false, err } - result, err := entities.SemanticVersion(stringValue).CompareVersion(entities.SemanticVersion(attributeValue)) + result, err := entities.SemanticVersion(attributeValue).CompareVersion(entities.SemanticVersion(stringValue)) if err == nil { return result > 0, nil } diff --git a/pkg/decision/evaluator/matchers/semver_gt_test.go b/pkg/decision/evaluator/matchers/semver_gt_test.go new file mode 100644 index 000000000..8b457ad21 --- /dev/null +++ b/pkg/decision/evaluator/matchers/semver_gt_test.go @@ -0,0 +1,74 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +package matchers + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/optimizely/go-sdk/pkg/entities" +) + +func TestSemverGtMatcher(t *testing.T) { + matcher := SemVerGtMatcher{ + Condition: entities.Condition{ + Match: "semver_gt", + Value: "2.0", + Name: "version", + }, + } + + user := entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "2.0.0", + }, + } + result, err := matcher.Match(user) + assert.NoError(t, err) + assert.False(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "2.9", + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "1.9", + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.False(t, result) + + // Test attribute not found + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version1": "2.0", + }, + } + + _, err = matcher.Match(user) + assert.Error(t, err) +} diff --git a/pkg/decision/evaluator/matchers/semver_le.go b/pkg/decision/evaluator/matchers/semver_le.go index 40302f054..225bba728 100644 --- a/pkg/decision/evaluator/matchers/semver_le.go +++ b/pkg/decision/evaluator/matchers/semver_le.go @@ -36,7 +36,7 @@ func (m SemVerLeMatcher) Match(user entities.UserContext) (bool, error) { if err != nil { return false, err } - result, err := entities.SemanticVersion(stringValue).CompareVersion(entities.SemanticVersion(attributeValue)) + result, err := entities.SemanticVersion(attributeValue).CompareVersion(entities.SemanticVersion(stringValue)) if err == nil { return result <= 0, nil } diff --git a/pkg/decision/evaluator/matchers/semver_le_test.go b/pkg/decision/evaluator/matchers/semver_le_test.go new file mode 100644 index 000000000..2676ffbab --- /dev/null +++ b/pkg/decision/evaluator/matchers/semver_le_test.go @@ -0,0 +1,74 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +package matchers + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/optimizely/go-sdk/pkg/entities" +) + +func TestSemverLeMatcher(t *testing.T) { + matcher := SemVerLeMatcher{ + Condition: entities.Condition{ + Match: "semver_le", + Value: "2.0", + Name: "version", + }, + } + + user := entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "2.0.0", + }, + } + result, err := matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "2.5.1", + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.False(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "1.9", + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + // Test attribute not found + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version1": "2.0", + }, + } + + _, err = matcher.Match(user) + assert.Error(t, err) +} diff --git a/pkg/decision/evaluator/matchers/semver_lt.go b/pkg/decision/evaluator/matchers/semver_lt.go index d09b8e86c..79d41e86c 100644 --- a/pkg/decision/evaluator/matchers/semver_lt.go +++ b/pkg/decision/evaluator/matchers/semver_lt.go @@ -36,7 +36,7 @@ func (m SemVerLtMatcher) Match(user entities.UserContext) (bool, error) { if err != nil { return false, err } - result, err := entities.SemanticVersion(stringValue).CompareVersion(entities.SemanticVersion(attributeValue)) + result, err := entities.SemanticVersion(attributeValue).CompareVersion(entities.SemanticVersion(stringValue)) if err == nil { return result < 0, nil } diff --git a/pkg/decision/evaluator/matchers/semver_lt_test.go b/pkg/decision/evaluator/matchers/semver_lt_test.go new file mode 100644 index 000000000..daf14d432 --- /dev/null +++ b/pkg/decision/evaluator/matchers/semver_lt_test.go @@ -0,0 +1,74 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +package matchers + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/optimizely/go-sdk/pkg/entities" +) + +func TestSemverLtMatcher(t *testing.T) { + matcher := SemVerLtMatcher{ + Condition: entities.Condition{ + Match: "semver_lt", + Value: "2.0", + Name: "version", + }, + } + + user := entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "2.0.0", + }, + } + result, err := matcher.Match(user) + assert.NoError(t, err) + assert.False(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "2.5.1", + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.False(t, result) + + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version": "1.9", + }, + } + + result, err = matcher.Match(user) + assert.NoError(t, err) + assert.True(t, result) + + // Test attribute not found + user = entities.UserContext{ + Attributes: map[string]interface{}{ + "version1": "2.0", + }, + } + + _, err = matcher.Match(user) + assert.Error(t, err) +} diff --git a/pkg/entities/semantic_version.go b/pkg/entities/semantic_version.go index a161f5fb1..c6dc73bd2 100644 --- a/pkg/entities/semantic_version.go +++ b/pkg/entities/semantic_version.go @@ -19,28 +19,138 @@ package entities import ( "errors" - - "golang.org/x/mod/semver" + "strconv" + "strings" ) // SemanticVersion represents semantic version type SemanticVersion string // CompareVersion compares two semantic versions -// result will be 0 if v == w, -1 if v < w, or +1 if v > w. -func (s SemanticVersion) CompareVersion(targetedVersion SemanticVersion) (int, error) { - - sVersion := string(s) - sTargetedVersion := string(targetedVersion) +func (s SemanticVersion) CompareVersion(targetedVersion SemanticVersion) (val int, err error) { - if sTargetedVersion == "" { + if string(targetedVersion) == "" { // Any version. return 0, nil } + targetedVersionParts, err := targetedVersion.splitSemanticVersion() + if err != nil { + return val, err + } + + versionParts, err := s.splitSemanticVersion() + if err != nil { + return val, err + } + // Up to the precision of targetedVersion, expect version to match exactly. - if semver.IsValid(sVersion) && semver.IsValid(sTargetedVersion) { - return semver.Compare(sVersion, sTargetedVersion), nil + for idx := range targetedVersionParts { + if len(versionParts) <= idx { + // even if they are equal at this point. if the target is a prerelease then it must be greater than the pre release. + if targetedVersion.isPreRelease() { + return 1, nil + } + return -1, nil + } + + if !SemanticVersion(versionParts[idx]).isNumber() { + //Compare strings + if versionParts[idx] < targetedVersionParts[idx] { + return -1, nil + } else if versionParts[idx] > targetedVersionParts[idx] { + return 1, nil + } + } else if part, err := strconv.Atoi(versionParts[idx]); err == nil { + if target, err := strconv.Atoi(targetedVersionParts[idx]); err == nil { + if part < target { + return -1, nil + } else if part > target { + return 1, nil + } + } + } else { + return -1, nil + } + } + if s.isPreRelease() && !targetedVersion.isPreRelease() { + return -1, nil + } + return 0, nil +} + +func (s SemanticVersion) splitSemanticVersion() (targetedVersionParts []string, err error) { + + var targetParts []string + var targetPrefix = string(s) + var targetSuffix []string + invalidAttributesError := errors.New("Provided attributes are in an invalid format") + + if s.hasWhiteSpace() { + return targetedVersionParts, invalidAttributesError + } + + if s.isPreRelease() || s.isBuild() { + if s.isPreRelease() { + targetParts = strings.Split(targetPrefix, s.preReleaseSeperator()) + } else { + targetParts = strings.Split(targetPrefix, s.buildSeperator()) + } + targetParts = s.deleteEmpty(targetParts) + if len(targetParts) <= 1 { + return targetedVersionParts, invalidAttributesError + } + targetPrefix = targetParts[0] + targetSuffix = targetParts[1:] + } + + // Expect a version string of the form x.y.z + dotCount := strings.Count(targetPrefix, ".") + if dotCount > 2 { + return targetedVersionParts, invalidAttributesError + } + targetedVersionParts = strings.Split(targetPrefix, ".") + targetedVersionParts = s.deleteEmpty(targetedVersionParts) + + if len(targetedVersionParts) != dotCount+1 { + return []string{}, invalidAttributesError + } + targetedVersionParts = append(targetedVersionParts, targetSuffix...) + + return targetedVersionParts, nil +} + +func (s SemanticVersion) hasWhiteSpace() bool { + return strings.Contains(string(s), " ") +} + +func (s SemanticVersion) isNumber() bool { + _, err := strconv.Atoi(string(s)) + return err == nil +} + +func (s SemanticVersion) isPreRelease() bool { + return strings.Contains(string(s), s.preReleaseSeperator()) +} + +func (s SemanticVersion) isBuild() bool { + return strings.Contains(string(s), s.buildSeperator()) +} + +func (s SemanticVersion) buildSeperator() string { + return "+" +} + +func (s SemanticVersion) preReleaseSeperator() string { + return "-" +} + +func (s SemanticVersion) deleteEmpty(arr []string) []string { + var finalArray []string + for _, str := range arr { + if str != "" { + finalArray = append(finalArray, str) + } } - return 0, errors.New("provided versions are in an invalid format") + return finalArray } diff --git a/pkg/entities/semantic_version_test.go b/pkg/entities/semantic_version_test.go new file mode 100644 index 000000000..37eea5f59 --- /dev/null +++ b/pkg/entities/semantic_version_test.go @@ -0,0 +1,197 @@ +/**************************************************************************** + * Copyright 2020, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ + +package entities + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTargetString(t *testing.T) { + target := SemanticVersion("2.0") + version := SemanticVersion("2.0.1") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, 0, result) +} + +func TestTargetFullStringTargetLess(t *testing.T) { + target := SemanticVersion("2.0.0") + version := SemanticVersion("2.0.1") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, 1, result) +} + +func TestTargetFullStringTargetMore(t *testing.T) { + target := SemanticVersion("2.0.1") + version := SemanticVersion("2.0.0") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, -1, result) +} + +func TestTargetFullStringTargetEq(t *testing.T) { + target := SemanticVersion("2.0.0") + version := SemanticVersion("2.0.0") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, 0, result) +} + +func TestTargetMajorPartGreater(t *testing.T) { + target := SemanticVersion("3.0") + version := SemanticVersion("2.0.1") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, -1, result) +} + +func TestTargetMajorPartLess(t *testing.T) { + target := SemanticVersion("2.0") + version := SemanticVersion("3.0.1") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, 1, result) +} + +func TestTargetMinorPartGreater(t *testing.T) { + target := SemanticVersion("2.3") + version := SemanticVersion("2.0.1") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, -1, result) +} + +func TestTargetMinorPartLess(t *testing.T) { + target := SemanticVersion("2.0") + version := SemanticVersion("2.9.1") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, 1, result) +} + +func TestTargetMinorPartEqual(t *testing.T) { + target := SemanticVersion("2.9") + version := SemanticVersion("2.9.1") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, 0, result) +} + +func TestTargetPatchGreater(t *testing.T) { + target := SemanticVersion("2.3.5") + version := SemanticVersion("2.3.1") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, -1, result) +} + +func TestTargetPatchLess(t *testing.T) { + target := SemanticVersion("2.9.0") + version := SemanticVersion("2.9.1") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, 1, result) +} + +func TestTargetPatchEqual(t *testing.T) { + target := SemanticVersion("2.9.9") + version := SemanticVersion("2.9.9") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, 0, result) +} + +func TestTargetPatchWithBetaTagEqual(t *testing.T) { + target := SemanticVersion("2.9.9-beta") + version := SemanticVersion("2.9.9-beta") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, 0, result) +} + +func TestPartialVersionEqual(t *testing.T) { + target := SemanticVersion("2.9.8") + version := SemanticVersion("2.9") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, -1, result) +} + +func TestBetaTagGreater(t *testing.T) { + target := SemanticVersion("2.1.2") + version := SemanticVersion("2.1.3-beta") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, 1, result) +} + +func TestBetaToRelease(t *testing.T) { + target := SemanticVersion("2.1.2-release") + version := SemanticVersion("2.1.2-beta") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, -1, result) +} + +func TestReleaseToBeta(t *testing.T) { + target := SemanticVersion("2.1.2-beta") + version := SemanticVersion("2.1.2-release") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, 1, result) +} + +func TestTargetWithVersionBetaLess(t *testing.T) { + target := SemanticVersion("2.1.3") + version := SemanticVersion("2.1.3-beta") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, -1, result) +} + +func TestTargetBetaLess(t *testing.T) { + target := SemanticVersion("2.1.3-beta") + version := SemanticVersion("2.1.3") + result, err := version.CompareVersion(target) + assert.Nil(t, err) + assert.Equal(t, 1, result) +} + +func TestOtherTests(t *testing.T) { + + targets := []string{"2.1", "2.1", "2", "2"} + versions := []string{"2.1.0", "2.1.215", "2.12", "2.785.13"} + + for idx, target := range targets { + result, err := SemanticVersion(versions[idx]).CompareVersion(SemanticVersion(target)) + assert.Nil(t, err) + assert.Equal(t, 0, result) + } +} + +func TestInvalidAttributes(t *testing.T) { + + target := "2.1.0" + versions := []string{"-", ".", "..", "+", "+test", " ", "2 .3. 0", "2.", ".2.2", "3.7.2.2"} + for _, version := range versions { + _, err := SemanticVersion(version).CompareVersion(SemanticVersion(target)) + assert.Error(t, err) + } +} From 7f13723781644f028b6ba5a421b2fe91f5d371c2 Mon Sep 17 00:00:00 2001 From: Yasir Ali Date: Tue, 11 Aug 2020 16:38:24 +0500 Subject: [PATCH 3/3] linter fixes. --- pkg/decision/evaluator/condition.go | 15 +++----- pkg/decision/evaluator/matchers/ge.go | 9 ++--- pkg/decision/evaluator/matchers/le.go | 8 ++--- pkg/decision/evaluator/matchers/semver_eq.go | 36 +++++++++++++++++--- pkg/decision/evaluator/matchers/semver_ge.go | 19 ++--------- pkg/decision/evaluator/matchers/semver_gt.go | 17 +-------- pkg/decision/evaluator/matchers/semver_le.go | 19 ++--------- pkg/decision/evaluator/matchers/semver_lt.go | 17 +-------- pkg/entities/semantic_version.go | 4 +-- 9 files changed, 48 insertions(+), 96 deletions(-) diff --git a/pkg/decision/evaluator/condition.go b/pkg/decision/evaluator/condition.go index bad01b928..3cc443679 100644 --- a/pkg/decision/evaluator/condition.go +++ b/pkg/decision/evaluator/condition.go @@ -32,11 +32,6 @@ const ( gtMatchType = "gt" geMatchType = "ge" substringMatchType = "substring" - semverEqMatchType = "semver_eq" - semverLtMatchType = "semver_lt" - semverLeMatchType = "semver_le" - semverGtMatchType = "semver_gt" - semverGeMatchType = "semver_ge" ) // ItemEvaluator evaluates a condition against the given user's attributes @@ -89,23 +84,23 @@ func (c CustomAttributeConditionEvaluator) Evaluate(condition entities.Condition matcher = matchers.SubstringMatcher{ Condition: condition, } - case semverEqMatchType: + case matchers.SemverEqMatchType: matcher = matchers.SemVerEqMatcher{ Condition: condition, } - case semverLtMatchType: + case matchers.SemverLtMatchType: matcher = matchers.SemVerLtMatcher{ Condition: condition, } - case semverLeMatchType: + case matchers.SemverLeMatchType: matcher = matchers.SemVerLeMatcher{ Condition: condition, } - case semverGtMatchType: + case matchers.SemverGtMatchType: matcher = matchers.SemVerGtMatcher{ Condition: condition, } - case semverGeMatchType: + case matchers.SemverGeMatchType: matcher = matchers.SemVerGeMatcher{ Condition: condition, } diff --git a/pkg/decision/evaluator/matchers/ge.go b/pkg/decision/evaluator/matchers/ge.go index 4405ce403..ab0a09da6 100644 --- a/pkg/decision/evaluator/matchers/ge.go +++ b/pkg/decision/evaluator/matchers/ge.go @@ -32,17 +32,12 @@ func (m GeMatcher) Match(user entities.UserContext) (bool, error) { var result bool var err error - ltMatcher := LtMatcher{ - Condition: m.Condition, - } - + ltMatcher := LtMatcher(m) if result, err = ltMatcher.Match(user); err == nil && result { return true, nil } - exactMatcher := ExactMatcher{ - Condition: m.Condition, - } + exactMatcher := ExactMatcher(m) if result, err = exactMatcher.Match(user); err == nil && result { return true, nil diff --git a/pkg/decision/evaluator/matchers/le.go b/pkg/decision/evaluator/matchers/le.go index b3c02b8ec..1427ba01f 100644 --- a/pkg/decision/evaluator/matchers/le.go +++ b/pkg/decision/evaluator/matchers/le.go @@ -32,17 +32,13 @@ func (m LeMatcher) Match(user entities.UserContext) (bool, error) { var result bool var err error - gtMatcher := GtMatcher{ - Condition: m.Condition, - } + gtMatcher := GtMatcher(m) if result, err = gtMatcher.Match(user); err == nil && result { return true, nil } - exactMatcher := ExactMatcher{ - Condition: m.Condition, - } + exactMatcher := ExactMatcher(m) if result, err = exactMatcher.Match(user); err == nil && result { return true, nil diff --git a/pkg/decision/evaluator/matchers/semver_eq.go b/pkg/decision/evaluator/matchers/semver_eq.go index af369f0e6..d87ab05f2 100644 --- a/pkg/decision/evaluator/matchers/semver_eq.go +++ b/pkg/decision/evaluator/matchers/semver_eq.go @@ -23,6 +23,19 @@ import ( "github.com/optimizely/go-sdk/pkg/entities" ) +const ( + // SemverEqMatchType represents match type for semantic version equality comparator + SemverEqMatchType = "semver_eq" + // SemverLtMatchType represents match type for semantic version less than comparator + SemverLtMatchType = "semver_lt" + // SemverLeMatchType represents match type for semantic version less than or equal to comparator + SemverLeMatchType = "semver_le" + // SemverGtMatchType represents match type for semantic version greater than comparator + SemverGtMatchType = "semver_gt" + // SemverGeMatchType represents match type for semantic version greater than or equal to comparator + SemverGeMatchType = "semver_ge" +) + // SemVerEqMatcher matches against the "semver_eq" match type type SemVerEqMatcher struct { Condition entities.Condition @@ -30,18 +43,31 @@ type SemVerEqMatcher struct { // Match returns true if condition value for semantic versions is equal to the target semantic version func (m SemVerEqMatcher) Match(user entities.UserContext) (bool, error) { + return match(m.Condition, user) +} - if stringValue, ok := m.Condition.Value.(string); ok { - attributeValue, err := user.GetStringAttribute(m.Condition.Name) +func match(condition entities.Condition, user entities.UserContext) (bool, error) { + if stringValue, ok := condition.Value.(string); ok { + attributeValue, err := user.GetStringAttribute(condition.Name) if err != nil { return false, err } result, err := entities.SemanticVersion(attributeValue).CompareVersion(entities.SemanticVersion(stringValue)) if err == nil { - return result == 0, nil + switch condition.Match { + case SemverEqMatchType: + return result == 0, nil + case SemverLtMatchType: + return result < 0, nil + case SemverLeMatchType: + return result <= 0, nil + case SemverGtMatchType: + return result > 0, nil + case SemverGeMatchType: + return result >= 0, nil + } } return false, err } - - return false, fmt.Errorf("audience condition %s evaluated to NULL because the condition value type is not supported", m.Condition.Name) + return false, fmt.Errorf("audience condition %s evaluated to NULL because the condition value type is not supported", condition.Name) } diff --git a/pkg/decision/evaluator/matchers/semver_ge.go b/pkg/decision/evaluator/matchers/semver_ge.go index 0625ebe6b..aab88f6eb 100644 --- a/pkg/decision/evaluator/matchers/semver_ge.go +++ b/pkg/decision/evaluator/matchers/semver_ge.go @@ -18,30 +18,15 @@ package matchers import ( - "fmt" - "github.com/optimizely/go-sdk/pkg/entities" ) -// SemVerGeMatcher matches against the "semver_gt" match type +// SemVerGeMatcher matches against the "semver_ge" match type type SemVerGeMatcher struct { Condition entities.Condition } // Match returns true if condition value for semantic versions is greater than or equal to target semantic version func (m SemVerGeMatcher) Match(user entities.UserContext) (bool, error) { - - if stringValue, ok := m.Condition.Value.(string); ok { - attributeValue, err := user.GetStringAttribute(m.Condition.Name) - if err != nil { - return false, err - } - result, err := entities.SemanticVersion(attributeValue).CompareVersion(entities.SemanticVersion(stringValue)) - if err == nil { - return result >= 0, nil - } - return false, err - } - - return false, fmt.Errorf("audience condition %s evaluated to NULL because the condition value type is not supported", m.Condition.Name) + return match(m.Condition, user) } diff --git a/pkg/decision/evaluator/matchers/semver_gt.go b/pkg/decision/evaluator/matchers/semver_gt.go index 2225c545d..db6c4a825 100644 --- a/pkg/decision/evaluator/matchers/semver_gt.go +++ b/pkg/decision/evaluator/matchers/semver_gt.go @@ -18,8 +18,6 @@ package matchers import ( - "fmt" - "github.com/optimizely/go-sdk/pkg/entities" ) @@ -30,18 +28,5 @@ type SemVerGtMatcher struct { // Match returns true if condition value for semantic versions is greater than target semantic version func (m SemVerGtMatcher) Match(user entities.UserContext) (bool, error) { - - if stringValue, ok := m.Condition.Value.(string); ok { - attributeValue, err := user.GetStringAttribute(m.Condition.Name) - if err != nil { - return false, err - } - result, err := entities.SemanticVersion(attributeValue).CompareVersion(entities.SemanticVersion(stringValue)) - if err == nil { - return result > 0, nil - } - return false, err - } - - return false, fmt.Errorf("audience condition %s evaluated to NULL because the condition value type is not supported", m.Condition.Name) + return match(m.Condition, user) } diff --git a/pkg/decision/evaluator/matchers/semver_le.go b/pkg/decision/evaluator/matchers/semver_le.go index 225bba728..6ef6d1ab4 100644 --- a/pkg/decision/evaluator/matchers/semver_le.go +++ b/pkg/decision/evaluator/matchers/semver_le.go @@ -18,30 +18,15 @@ package matchers import ( - "fmt" - "github.com/optimizely/go-sdk/pkg/entities" ) -// SemVerLeMatcher matches against the "semver_lt" match type +// SemVerLeMatcher matches against the "semver_le" match type type SemVerLeMatcher struct { Condition entities.Condition } // Match returns true if condition value for semantic versions is less than or equal to target semantic version func (m SemVerLeMatcher) Match(user entities.UserContext) (bool, error) { - - if stringValue, ok := m.Condition.Value.(string); ok { - attributeValue, err := user.GetStringAttribute(m.Condition.Name) - if err != nil { - return false, err - } - result, err := entities.SemanticVersion(attributeValue).CompareVersion(entities.SemanticVersion(stringValue)) - if err == nil { - return result <= 0, nil - } - return false, err - } - - return false, fmt.Errorf("audience condition %s evaluated to NULL because the condition value type is not supported", m.Condition.Name) + return match(m.Condition, user) } diff --git a/pkg/decision/evaluator/matchers/semver_lt.go b/pkg/decision/evaluator/matchers/semver_lt.go index 79d41e86c..6fa5da4d9 100644 --- a/pkg/decision/evaluator/matchers/semver_lt.go +++ b/pkg/decision/evaluator/matchers/semver_lt.go @@ -18,8 +18,6 @@ package matchers import ( - "fmt" - "github.com/optimizely/go-sdk/pkg/entities" ) @@ -30,18 +28,5 @@ type SemVerLtMatcher struct { // Match returns true if condition value for semantic versions is less than target semantic version func (m SemVerLtMatcher) Match(user entities.UserContext) (bool, error) { - - if stringValue, ok := m.Condition.Value.(string); ok { - attributeValue, err := user.GetStringAttribute(m.Condition.Name) - if err != nil { - return false, err - } - result, err := entities.SemanticVersion(attributeValue).CompareVersion(entities.SemanticVersion(stringValue)) - if err == nil { - return result < 0, nil - } - return false, err - } - - return false, fmt.Errorf("audience condition %s evaluated to NULL because the condition value type is not supported", m.Condition.Name) + return match(m.Condition, user) } diff --git a/pkg/entities/semantic_version.go b/pkg/entities/semantic_version.go index c6dc73bd2..bb6a09a70 100644 --- a/pkg/entities/semantic_version.go +++ b/pkg/entities/semantic_version.go @@ -55,7 +55,7 @@ func (s SemanticVersion) CompareVersion(targetedVersion SemanticVersion) (val in } if !SemanticVersion(versionParts[idx]).isNumber() { - //Compare strings + // Compare strings if versionParts[idx] < targetedVersionParts[idx] { return -1, nil } else if versionParts[idx] > targetedVersionParts[idx] { @@ -84,7 +84,7 @@ func (s SemanticVersion) splitSemanticVersion() (targetedVersionParts []string, var targetParts []string var targetPrefix = string(s) var targetSuffix []string - invalidAttributesError := errors.New("Provided attributes are in an invalid format") + invalidAttributesError := errors.New("provided attributes are in an invalid format") if s.hasWhiteSpace() { return targetedVersionParts, invalidAttributesError