diff --git a/README.md b/README.md index 8462d211..88d3b4d4 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ validate := validator.New(validator.WithRequiredStructEnabled()) | origin | Web origin (URL with HTTP(S) scheme and host, but no path/query/fragment) | | url_encoded | URL Encoded | | urn_rfc2141 | Urn RFC 2141 String | +| urn_rfc8141 | Urn RFC 8141 String | ### Strings: diff --git a/baked_in.go b/baked_in.go index 679729a5..86a35a21 100644 --- a/baked_in.go +++ b/baked_in.go @@ -143,6 +143,7 @@ var ( "https_url": isHttpsURL, "uri": isURI, "origin": isOrigin, + "urn_rfc8141": isUrnRFC8141, // RFC 8141 "urn_rfc2141": isUrnRFC2141, // RFC 2141 "file": isFile, "filepath": isFilePath, @@ -1695,6 +1696,26 @@ func isHttpsURL(fl FieldLevel) bool { panic(fmt.Sprintf("Bad field type %s", field.Type())) } +// isUrnRFC8141 is the validation function for validating if the current field's value is a valid URN as per RFC 8141. +func isUrnRFC8141(fl FieldLevel) bool { + field := fl.Field() + + switch field.Kind() { + case reflect.String: + + str := field.String() + if str == "" { + return false + } + + _, match := urn.Parse([]byte(str), urn.WithParsingMode(urn.RFC8141Only)) + + return match + } + + panic(fmt.Sprintf("Bad field type %T", field.Interface())) +} + // isUrnRFC2141 is the validation function for validating if the current field's value is a valid URN as per RFC 2141. func isUrnRFC2141(fl FieldLevel) bool { field := fl.Field() diff --git a/doc.go b/doc.go index 7b1fd078..a8c5c40c 100644 --- a/doc.go +++ b/doc.go @@ -1026,6 +1026,13 @@ This will accept any uri the golang request uri accepts Usage: uri +# Urn RFC 8141 String + +This validataes that a string value contains a valid URN +according to the RFC 8141 spec. + + Usage: urn_rfc8141 + # Urn RFC 2141 String This validates that a string value contains a valid URN diff --git a/validator_test.go b/validator_test.go index 67d3f83a..ac0cf313 100644 --- a/validator_test.go +++ b/validator_test.go @@ -8692,6 +8692,83 @@ func TestUrnRFC2141(t *testing.T) { PanicMatches(t, func() { _ = validate.Var(i, tag) }, "Bad field type int") } +func TestUrnRFC8141(t *testing.T) { + tests := []struct { + param string + expected bool + }{ + {"urn:lex:it:ministero.giustizia:decreto:1992-07-24;358~art5", true}, + {"urn:nid:nss/", true}, + {"urn:nid:nss&", true}, + {"urn:example:1/406/47452/2", true}, + {"urn:example:foo-bar-baz-qux?+CCResolve:cc=uk", true}, + {"urn:example:foo-bar-baz-qux?+&", true}, + {"urn:example:foo-bar-baz-qux?+%16CCResolve:cc=uk", true}, + {"urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z", true}, + {"urn:example:CamelCase1/406/47452/2?=lat=41.22255&long=16.06596#frag?some/slash/~%D0", true}, + {"urn:example:CamelCase1/406/47452/2?=lat=41.22255&long=16.06596#frag", true}, + {"URN:signs:()+,-.:=@;$_!*alnum123456789", true}, + {"URN:abcd-abcd:x", true}, + {"urn:urnx:urn", true}, + {"urn:ciao:a:b:c", true}, + {"urn:aaa:x:y:", true}, + {"urn:ciao:-", true}, + {"urn:colon:::::nss", true}, + {"urn:ciao:@!=%2C(xyz)+a,b.*@g=$_'", true}, + {"URN:hexes:%25", true}, + {"URN:xyz:abc%1Dz%2F%3az", true}, + {"URN:foo:a123,456", true}, + {"urn:foo:a123,456", true}, + {"urn:FOO:a123,456", true}, + {"urn:foo:A123,456", true}, + {"urn:foo:a123%2C456", true}, + {"URN:FOO:a123%2c456", true}, + {"URN:FOO:ABC%FFabc123%2c456", true}, + {"URN:FOO:ABC%FFabc123%2C456%9A", true}, + {"urn:ietf:params:scim:schemas:core:2.0:User", true}, + {"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:meta.lastModified", true}, + {"urn:urn-7:informal", true}, + {"", false}, + {"urn:aa:", false}, + {"URN:x:abc%1Dz%2F%3az", false}, + {"urn:123456789-1234567890-abcdefghilmn:o", false}, + {"urn:ex:ex?+a?+", false}, + {"urn:xn--:nss", false}, + {"urn:a:nss", false}, + {"URN:trailing-:w", false}, + {"URN:-leading:w", false}, + {"urn:urn-s:nss", false}, + {"urn:urn-0:nss", false}, + {"urn:", false}, + } + + tag := "urn_rfc8141" + + validate := New() + + for i, test := range tests { + errs := validate.Var(test.param, tag) + + if test.expected { + if !IsEqual(errs, nil) { + t.Fatalf("Index: %d URN failed Error: %s", i, errs) + } + } else { + if IsEqual(errs, nil) { + t.Fatalf("Index: %d URN failed Error: %s", i, errs) + } else { + val := getError(errs, "", "") + if val.Tag() != tag { + t.Fatalf("Index: %d URN failed Error: %s", i, errs) + } + } + } + } + + i := 1 + PanicMatches(t, func() { _ = validate.Var(i, tag) }, "Bad field type int") +} + func TestUrl(t *testing.T) { tests := []struct { param string