From e1e8d7ca2aec0ff5a10aeb2623e826f70b1b4cdc Mon Sep 17 00:00:00 2001 From: Jordan Tseng Date: Wed, 8 Sep 2021 14:43:47 -0700 Subject: [PATCH] Including regex version validation --- README.md | 1 + baked_in.go | 9 ++++++ doc.go | 6 ++++ regexes.go | 2 ++ validator_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 96 insertions(+) diff --git a/README.md b/README.md index f56cff15..46d19890 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,7 @@ Baked-in Validations | uuid5 | Universally Unique Identifier UUID v5 | | uuid5_rfc4122 | Universally Unique Identifier UUID v5 RFC4122 | | uuid_rfc4122 | Universally Unique Identifier UUID RFC4122 | +| semver | Semantic Versioning 2.0.0 | ### Comparisons: | Tag | Description | diff --git a/baked_in.go b/baked_in.go index f5fd2391..5626fe02 100644 --- a/baked_in.go +++ b/baked_in.go @@ -198,6 +198,7 @@ var ( "postcode_iso3166_alpha2": isPostcodeByIso3166Alpha2, "postcode_iso3166_alpha2_field": isPostcodeByIso3166Alpha2Field, "bic": isIsoBicFormat, + "semver": isSemverFormat, } ) @@ -2407,3 +2408,11 @@ func isIsoBicFormat(fl FieldLevel) bool { return bicRegex.MatchString(bicString) } + +// isSemverFormat is the validation function for validating if the current field's value is a valid semver version, defined in Semantic Versioning 2.0.0 +func isSemverFormat(fl FieldLevel) bool { + semverString := fl.Field().String() + + return semverRegex.MatchString(semverString) +} + diff --git a/doc.go b/doc.go index 8c258479..ce48a8da 100644 --- a/doc.go +++ b/doc.go @@ -1263,6 +1263,12 @@ More information on https://golang.org/pkg/time/#LoadLocation Usage: timezone +Semantic Version + +This validates that a string value is a valid semver version, defined in Semantic Versioning 2.0.0. +More information on https://semver.org/ + + Usage: semver Alias Validators and Tags diff --git a/regexes.go b/regexes.go index df00c4eb..49d75459 100644 --- a/regexes.go +++ b/regexes.go @@ -51,6 +51,7 @@ const ( jWTRegexString = "^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]*$" splitParamsRegexString = `'[^']*'|\S+` bicRegexString = `^[A-Za-z]{6}[A-Za-z0-9]{2}([A-Za-z0-9]{3})?$` + semverRegexString = `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` // numbered capture groups https://semver.org/ ) var ( @@ -102,4 +103,5 @@ var ( jWTRegex = regexp.MustCompile(jWTRegexString) splitParamsRegex = regexp.MustCompile(splitParamsRegexString) bicRegex = regexp.MustCompile(bicRegexString) + semverRegex = regexp.MustCompile(semverRegexString) ) diff --git a/validator_test.go b/validator_test.go index f6942037..85bca239 100644 --- a/validator_test.go +++ b/validator_test.go @@ -11307,6 +11307,84 @@ func TestBicIsoFormatValidation(t *testing.T) { } } +func TestSemverFormatValidation(t *testing.T) { + tests := []struct { + value string `validate:"semver"` + tag string + expected bool + }{ + {"1.2.3", "semver", true}, + {"10.20.30", "semver", true}, + {"1.1.2-prerelease+meta", "semver", true}, + {"1.1.2+meta", "semver", true}, + {"1.1.2+meta-valid", "semver", true}, + {"1.0.0-alpha", "semver", true}, + {"1.0.0-alpha.1", "semver", true}, + {"1.0.0-alpha.beta", "semver", true}, + {"1.0.0-alpha.beta.1", "semver", true}, + {"1.0.0-alpha0.valid", "semver", true}, + {"1.0.0-alpha.0valid", "semver", true}, + {"1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay", "semver", true}, + {"1.0.0-rc.1+build.1", "semver", true}, + {"1.0.0-rc.1+build.123", "semver", true}, + {"1.2.3-beta", "semver", true}, + {"1.2.3-DEV-SNAPSHOT", "semver", true}, + {"1.2.3-SNAPSHOT-123", "semver", true}, + {"2.0.0+build.1848", "semver", true}, + {"2.0.1-alpha.1227", "semver", true}, + {"1.0.0-alpha+beta", "semver", true}, + {"1.2.3----RC-SNAPSHOT.12.9.1--.12+788", "semver", true}, + {"1.2.3----R-S.12.9.1--.12+meta", "semver", true}, + {"1.2.3----RC-SNAPSHOT.12.9.1--.12", "semver", true}, + {"1.0.0+0.build.1-rc.10000aaa-kk-0.1", "semver", true}, + {"99999999999999999999999.999999999999999999.99999999999999999", "semver", true}, + {"1.0.0-0A.is.legal", "semver", true}, + {"1", "semver", false}, + {"1.2", "semver", false}, + {"1.2.3-0123", "semver", false}, + {"1.2.3-0123.0123", "semver", false}, + {"1.1.2+.123", "semver", false}, + {"+invalid", "semver", false}, + {"-invalid", "semver", false}, + {"-invalid+invalid", "semver", false}, + {"alpha", "semver", false}, + {"alpha.beta.1", "semver", false}, + {"alpha.1", "semver", false}, + {"1.0.0-alpha_beta", "semver", false}, + {"1.0.0-alpha_beta", "semver", false}, + {"1.0.0-alpha...1", "semver", false}, + {"01.1.1", "semver", false}, + {"1.01.1", "semver", false}, + {"1.1.01", "semver", false}, + {"1.2", "semver", false}, + {"1.2.Dev", "semver", false}, + {"1.2.3.Dev", "semver", false}, + {"1.2-SNAPSHOT", "semver", false}, + } + + validate := New() + + for i, test := range tests { + + errs := validate.Var(test.value, test.tag) + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d semver failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d semver failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != "semver" { + t.Fatalf("Index: %d semver failed Error: %s", i, errs) + } + } + } + } +} + func TestPostCodeByIso3166Alpha2(t *testing.T) { tests := map[string][]struct { value string