-
Notifications
You must be signed in to change notification settings - Fork 18
feat: Semantic Versioning implementation #289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 5 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
80d1d25
feat: added semver support to our existing matchers
pawels-optimizely 2981c20
fix linters
pawels-optimizely ad65d4b
fix linters
pawels-optimizely cf66c93
added more changes
pawels-optimizely a044757
added semver for registry pattern
pawels-optimizely 068ac3d
consolidate all the semver code
pawels-optimizely e3d7150
added ge and le evaluators
pawels-optimizely de95b6a
simplify tests
pawels-optimizely e89d07d
Update pkg/decision/evaluator/matchers/semver_test.go
pawels-optimizely 5cafc5a
cleaning up
pawels-optimizely 89b9406
addressing nit comments
pawels-optimizely e272112
addressing PR comments
pawels-optimizely a91371e
increase coverage
pawels-optimizely ff5136d
increase coverage
pawels-optimizely 18361fd
corrected tests
pawels-optimizely ae97a2d
increased coverage
pawels-optimizely 9a9ca1b
increase coverage
pawels-optimizely be1e4a5
small improvement in the coverage
pawels-optimizely 8eaadb8
add a few more tests and fix split when not build or release
thomaszurkan-optimizely 7849cae
Merge branch 'master' into pawel/semver
thomaszurkan-optimizely ef25dcd
fix merge errors
thomaszurkan-optimizely 57bebb1
slight refactor for lint
thomaszurkan-optimizely a1ba38b
fixing after a refactor right in the middle of doing this pr.
thomaszurkan-optimizely f4c2f88
Merge branch 'master' into pawel/semver
thomaszurkan-optimizely 64ae09c
fix lint error
thomaszurkan-optimizely d2f1ba5
Merge branch 'pawel/semver' of https://github.com/optimizely/go-sdk i…
thomaszurkan-optimizely File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,163 @@ | ||
| /**************************************************************************** | ||
| * 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" | ||
| "regexp" | ||
| "strconv" | ||
| "strings" | ||
|
|
||
| "github.com/optimizely/go-sdk/pkg/decision/reasons" | ||
| "github.com/optimizely/go-sdk/pkg/entities" | ||
|
|
||
| "github.com/pkg/errors" | ||
| ) | ||
|
|
||
| // SemanticVersion defines the class | ||
| type SemanticVersion struct { | ||
| Condition string // condition is always a string here | ||
| } | ||
|
|
||
| func (sv SemanticVersion) compareVersion(attribute string) (int, error) { | ||
|
|
||
| targetedVersionParts, err := sv.splitSemanticVersion(sv.Condition) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
| versionParts, e := sv.splitSemanticVersion(attribute) | ||
| if e != nil { | ||
| return 0, e | ||
| } | ||
|
|
||
| // Up to the precision of targetedVersion, expect version to match exactly. | ||
| for idx := range targetedVersionParts { | ||
|
|
||
| switch { | ||
| case len(versionParts) <= idx: | ||
| return -1, nil | ||
pawels-optimizely marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| case !sv.isNumber(versionParts[idx]): | ||
| // Compare strings | ||
| if versionParts[idx] < targetedVersionParts[idx] { | ||
| return -1, nil | ||
| } else if versionParts[idx] > targetedVersionParts[idx] { | ||
| return 1, nil | ||
| } | ||
| case sv.isNumber(targetedVersionParts[idx]): // both targetedVersionParts and versionParts are digits | ||
| if sv.toInt(versionParts[idx]) < sv.toInt(targetedVersionParts[idx]) { | ||
| return -1, nil | ||
| } else if sv.toInt(versionParts[idx]) > sv.toInt(targetedVersionParts[idx]) { | ||
| return 1, nil | ||
| } | ||
| default: | ||
| return -1, nil | ||
| } | ||
| } | ||
|
|
||
| if sv.isPreRelease(attribute) && !sv.isPreRelease(sv.Condition) { | ||
| return -1, nil | ||
| } | ||
|
|
||
| return 0, nil | ||
| } | ||
|
|
||
| func (sv SemanticVersion) splitSemanticVersion(targetedVersion string) ([]string, error) { | ||
|
|
||
| if sv.hasWhiteSpace(targetedVersion) { | ||
| return []string{}, errors.New(string(reasons.AttributeFormatInvalid)) | ||
| } | ||
|
|
||
| splitBy := "" | ||
| if sv.isBuild(targetedVersion) { | ||
| splitBy = sv.buildSeperator() | ||
| } else if sv.isPreRelease(targetedVersion) { | ||
| splitBy = sv.preReleaseSeperator() | ||
| } | ||
| targetParts := strings.Split(targetedVersion, splitBy) | ||
| if len(targetParts) == 0 { | ||
| return []string{}, errors.New(string(reasons.AttributeFormatInvalid)) | ||
| } | ||
|
|
||
| targetPrefix := targetParts[0] | ||
| targetSuffix := targetParts[1:] | ||
|
|
||
| // Expect a version string of the form x.y.z | ||
| targetedVersionParts := strings.Split(targetPrefix, ".") | ||
|
|
||
| if len(targetedVersionParts) > 3 { | ||
| return []string{}, errors.New(string(reasons.AttributeFormatInvalid)) | ||
| } | ||
|
|
||
| if len(targetedVersionParts) == 0 { | ||
| return []string{}, errors.New(string(reasons.AttributeFormatInvalid)) | ||
| } | ||
|
|
||
| targetedVersionParts = append(targetedVersionParts, targetSuffix...) | ||
| return targetedVersionParts, nil | ||
| } | ||
|
|
||
| func (sv SemanticVersion) isNumber(str string) bool { | ||
| var digitCheck = regexp.MustCompile(`^[0-9]+$`) | ||
pawels-optimizely marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return (digitCheck.MatchString(str)) | ||
| } | ||
|
|
||
| func (sv SemanticVersion) toInt(str string) int { | ||
| i, e := strconv.Atoi(str) | ||
| if e != nil { | ||
| return 0 | ||
| } | ||
| return i | ||
| } | ||
|
|
||
| func (sv SemanticVersion) isPreRelease(str string) bool { | ||
| return strings.Contains(str, "-") | ||
pawels-optimizely marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| func (sv SemanticVersion) isBuild(str string) bool { | ||
| return strings.Contains(str, "+") | ||
| } | ||
|
|
||
| func (sv SemanticVersion) hasWhiteSpace(str string) bool { | ||
| return strings.Contains(str, " ") | ||
| } | ||
|
|
||
| func (sv SemanticVersion) buildSeperator() string { | ||
pawels-optimizely marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return "+" | ||
| } | ||
|
|
||
| func (sv SemanticVersion) preReleaseSeperator() string { | ||
pawels-optimizely marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return "-" | ||
| } | ||
|
|
||
| // SemverEvaluator is a help function to wrap a common evaluation code | ||
| func SemverEvaluator(cond entities.Condition, user entities.UserContext) (int, error) { | ||
|
|
||
| if stringValue, ok := cond.Value.(string); ok { | ||
| attributeValue, err := user.GetStringAttribute(cond.Name) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
| semVer := SemanticVersion{stringValue} | ||
| comparison, e := semVer.compareVersion(attributeValue) | ||
| if e != nil { | ||
| return 0, e | ||
| } | ||
| return comparison, nil | ||
| } | ||
| return 0, fmt.Errorf("audience condition %s evaluated to NULL because the condition value type is not supported", cond.Name) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| /**************************************************************************** | ||
| * 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/entities" | ||
| ) | ||
|
|
||
| // SemverEqMatcher returns true if the user's semver attribute is equal to the semver condition value | ||
| func SemverEqMatcher(condition entities.Condition, user entities.UserContext) (bool, error) { | ||
| comparison, err := SemverEvaluator(condition, user) | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
| return comparison == 0, nil | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| /**************************************************************************** | ||
| * 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) { | ||
|
|
||
| condition := entities.Condition{ | ||
| Match: "semver_eq", | ||
| Value: "2.0", | ||
| Name: "version", | ||
| } | ||
|
|
||
| user := entities.UserContext{ | ||
| Attributes: map[string]interface{}{ | ||
| "version": "2.0.0", | ||
| }, | ||
| } | ||
| result, err := SemverEqMatcher(condition, user) | ||
| assert.NoError(t, err) | ||
| assert.True(t, result) | ||
|
|
||
| user = entities.UserContext{ | ||
| Attributes: map[string]interface{}{ | ||
| "version": "2.9", | ||
| }, | ||
| } | ||
|
|
||
| result, err = SemverEqMatcher(condition, user) | ||
| assert.NoError(t, err) | ||
| assert.False(t, result) | ||
|
|
||
| user = entities.UserContext{ | ||
| Attributes: map[string]interface{}{ | ||
| "version": "1.9", | ||
| }, | ||
| } | ||
|
|
||
| result, err = SemverEqMatcher(condition, user) | ||
| assert.NoError(t, err) | ||
| assert.False(t, result) | ||
|
|
||
| // Test attribute not found | ||
| user = entities.UserContext{ | ||
| Attributes: map[string]interface{}{ | ||
| "version1": "2.0", | ||
| }, | ||
| } | ||
|
|
||
| _, err = SemverEqMatcher(condition, user) | ||
| assert.Error(t, err) | ||
| } | ||
|
|
||
| func TestSemverEqMatcherInvalidType(t *testing.T) { | ||
| condition := entities.Condition{ | ||
| Match: "semver_eq", | ||
| Value: "2.0", | ||
| Name: "version", | ||
| } | ||
|
|
||
| user := entities.UserContext{ | ||
| Attributes: map[string]interface{}{ | ||
| "version": true, | ||
| }, | ||
| } | ||
| _, err := SemverEqMatcher(condition, user) | ||
| assert.Error(t, err) | ||
|
|
||
| user = entities.UserContext{ | ||
| Attributes: map[string]interface{}{ | ||
| "version": 37, | ||
| }, | ||
| } | ||
|
|
||
| _, err = SemverEqMatcher(condition, user) | ||
| assert.Error(t, err) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| /**************************************************************************** | ||
| * 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/entities" | ||
| ) | ||
|
|
||
| // SemverGeMatcher returns true if the user's semver attribute is greater or equal to the semver condition value | ||
| func SemverGeMatcher(condition entities.Condition, user entities.UserContext) (bool, error) { | ||
| comparison, err := SemverEvaluator(condition, user) | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
| return comparison >= 0, nil | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.