├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── contains.go ├── contains_test.go ├── cspell.json ├── equals.go ├── equals_test.go ├── go.mod ├── go.sum ├── isAlpham.go ├── isabarouting.go ├── isabarouting_test.go ├── isafter.go ├── isafter_test.go ├── isalpham_test.go ├── isalphanum.go ├── isalphanum_test.go ├── isarray.go ├── isarray_test.go ├── isascii.go ├── isascii_test.go ├── isbase32.go ├── isbase32_test.go ├── isbase58.go ├── isbase58_test.go ├── isbase64.go ├── isbase64_test.go ├── isbefore.go ├── isbefore_test.go ├── isbic.go ├── isbic_test.go ├── isboolean.go ├── isboolean_test.go ├── isbtcaddress.go ├── isbtcaddress_test.go ├── isbytelength.go ├── isbytelength_test.go ├── iscountrycode.go ├── iscountrycode_test.go ├── iscreditcard.go ├── iscreditcard_test.go ├── iscurrency.go ├── iscurrency_test.go ├── isdatauri.go ├── isdatauri_test.go ├── isdate.go ├── isdate_test.go ├── isdecimal.go ├── isdecimal_test.go ├── isdivisibleby.go ├── isdivisibleby_test.go ├── isean.go ├── isean_test.go ├── isemail.go ├── isemail_test.go ├── isempty.go ├── isempty_test.go ├── isetheruemaddress.go ├── isetheruemaddress_test.go ├── isfloat.go ├── isfloat_test.go ├── isfqdn.go ├── isfqdn_test.go ├── isfreightcontainerid.go ├── isfreightcontainerid_test.go ├── isfullwidth.go ├── isfullwidth_test.go ├── ishalfwidth.go ├── ishalfwidth_test.go ├── ishash.go ├── ishash_test.go ├── ishexadecimal.go ├── ishexadecimal_test.go ├── ishexcolor.go ├── ishexcolor_test.go ├── ishsl.go ├── ishsl_test.go ├── isiban.go ├── isiban_test.go ├── isidentitycard.go ├── isidentitycard_test.go ├── isimei.go ├── isimei_test.go ├── isin.go ├── isin_test.go ├── isint.go ├── isint_test.go ├── isip.go ├── isip_test.go ├── isiprange.go ├── isiprange_test.go ├── isisbn.go ├── isisbn_test.go ├── isisin.go ├── isisin_test.go ├── isiso31661alpha2.go ├── isiso31661alpha2_test.go ├── isiso31661alpha3.go ├── isiso31661alpha3_test.go ├── isiso31661numeric.go ├── isiso31661numeric_test.go ├── isiso4217.go ├── isiso4217_test.go ├── isiso6346.go ├── isiso6346_test.go ├── isiso6391.go ├── isiso6391_test.go ├── isiso8601.go ├── isiso8601_test.go ├── isisrc.go ├── isisrc_test.go ├── isissn.go ├── isissn_test.go ├── isjson.go ├── isjson_test.go ├── isjwt.go ├── isjwt_test.go ├── islatlong.go ├── islatlong_test.go ├── islength.go ├── islength_test.go ├── islicenseplate.go ├── islicenseplate_test.go ├── islocale.go ├── islocale_test.go ├── islowercase.go ├── islowercase_test.go ├── isluhnnumber.go ├── isluhnnumber_test.go ├── ismacaddress.go ├── ismacaddress_test.go ├── ismagneturi.go ├── ismagneturi_test.go ├── ismailtouri.go ├── ismailtouri_test.go ├── ismd5str.go ├── ismd5str_test.go ├── ismimetype.go ├── ismimetype_test.go ├── ismobilephone.go ├── ismobilephone_test.go ├── ismongoid.go ├── ismongoid_test.go ├── ismultibyte.go ├── ismultibyte_test.go ├── isnumeric.go ├── isnumeric_test.go ├── isobject.go ├── isobject_test.go ├── isoctal.go ├── isoctal_test.go ├── ispassport.go ├── ispassport_test.go ├── isport.go ├── isport_test.go ├── ispostalcode.go ├── ispostalcode_test.go ├── isrfc3339.go ├── isrfc3339_test.go ├── isrgbcolor.go ├── isrgbcolor_test.go ├── issemver.go ├── issemver_test.go ├── isslug.go ├── isslug_test.go ├── isstrongpassword.go ├── isstrongpassword_test.go ├── issurrogate_test.go ├── issurrogatepair.go ├── istaxid.go ├── istaxid_test.go ├── istime.go ├── istime_test.go ├── isulid.go ├── isulid_test.go ├── isuppercase.go ├── isuppercase_test.go ├── isurl.go ├── isurl_test.go ├── isuuid.go ├── isuuid_test.go ├── isvariablewidth.go ├── isvariablewidth_test.go ├── isvat.go ├── isvat_test.go ├── iswhilelisted.go ├── iswhilelisted_test.go ├── matches.go ├── matches_test.go ├── sanitizer ├── blacklist.go ├── blacklist_test.go ├── escape.go ├── escape_test.go ├── normalize_email.go ├── normalize_email_test.go ├── stripLow_test.go ├── striplow.go ├── toboolean.go ├── toboolean_test.go ├── todate.go ├── todate_test.go ├── tofloat.go ├── tofloat_test.go ├── toint.go ├── toint_test.go ├── trim.go ├── trim_test.go ├── unescape.go ├── unescape_test.go ├── whitelist.go └── whitelist_test.go ├── utils.go └── utils_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | cover.html 17 | 18 | # Dependency directories (remove the comment below to include it) 19 | # vendor/ 20 | 21 | # Go workspace file 22 | go.work 23 | go.work.sum 24 | 25 | # env file 26 | .env 27 | 28 | .vscode/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to validatorgo 2 | 3 | Welcome to validatorgo repository!! We appreciate your interest in contributing to this open source library and for helping our community grow. 4 | 5 | ## How to Contribute 6 | 7 | ### Code Contribution 8 | 9 | In general, we follow the "fork-and-pull" Git workflow. 10 | 11 | 1. [Fork](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project) the repository on GitHub 12 | 2. Clone the forked project to your local machine 13 | 14 | ```bash 15 | git clone https://github.com//.git 16 | cd 17 | ``` 18 | 19 | 3. Work on your fork 20 | 21 | - Make your changes and additions 22 | - Most of your changes should be focused on root directory, the sanitizer package and the [README.md](https://github.com/bube054/validatorgo/blob/main/README.md). 23 | - Change or add tests if needed 24 | - Add or update tests in the relevant `*_test.go` files. 25 | - Run tests and make sure they pass 26 | 27 | ```bash 28 | go test ./... 29 | ``` 30 | 31 | * Update documentation if necessary 32 | - Update [README.md](https://github.com/bube054/validatorgo/blob/main/README.md) and other go doc comments to reflect your changes. 33 | 34 | 4. Commit changes to your own branch 35 | 36 | - Create a new branch for your work: 37 | ```bash 38 | git checkout -b 39 | ``` 40 | - Stage and commit your changes 41 | ```bash 42 | git add . 43 | git commit -m "Describe your changes" 44 | ``` 45 | 46 | 5. Merge the latest from "upstream" and resolve conflicts if any 47 | 48 | - Add the upstream repository 49 | ```bash 50 | git remote add upstream https://github.com//.git 51 | ``` 52 | - Fetch and merge the latest changes 53 | ```bash 54 | git fetch upstream 55 | git merge upstream/main 56 | ``` 57 | 58 | 6. Repeat step 3 59 | 60 | - Re-test your changes after merging upstream updates to ensure everything still works as intended. 61 | 62 | 7. Push your work back up to your fork 63 | 64 | ```bash 65 | git push origin 66 | ``` 67 | 68 | 8. Submit a Pull Request (PR) 69 | 70 | - Go to the original repository on GitHub and navigate to the Pull Requests tab. 71 | - Click on "New Pull Request" and select the `dev` branch. 72 | - Add a meaningful title and description for your changes. 73 | - Submit the Pull Request for review. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Attah Gbubemi David 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /contains.go: -------------------------------------------------------------------------------- 1 | // A package of string validators. 2 | package validatorgo 3 | 4 | import "strings" 5 | 6 | var ( 7 | containsOptsDefaultIgnoreCase bool = false 8 | containsOptsDefaultMinOccurrences int = 1 9 | ) 10 | 11 | // ContainsOpt is used to configure Contains 12 | type ContainsOpt struct { 13 | IgnoreCase bool // ignore case when doing comparison, default false. 14 | MinOccurrences int // minimum number of occurrences for the seed in the string. Defaults to 1. 15 | } 16 | 17 | // A validator that checks if the string contains the seed. 18 | // 19 | // ContainsOpt is a struct that defaults to { IgnoreCase: false, MinOccurrences: 1 }. 20 | // 21 | // ContainsOpt: 22 | // 23 | // IgnoreCase: Ignore case when doing comparison, default false. 24 | // 25 | // MinOccurrences: Minimum number of occurrences for the seed in the string. Defaults to 1. 26 | // 27 | // ok := validatorgo.Contains("hello world", "world", &ContainsOpt{}) 28 | // fmt.Println(ok) // true 29 | // ok := validatorgo.Contains("hello world", "earth", &ContainsOpt{}) 30 | // fmt.Println(ok) // false 31 | func Contains(str, seed string, opts *ContainsOpt) bool { 32 | if opts == nil { 33 | opts = setContainOptsToDefault() 34 | } 35 | 36 | if opts.IgnoreCase { 37 | strLowerCase, seedLowerCase := strings.ToLower(str), strings.ToLower(seed) 38 | return strings.Contains(strLowerCase, seedLowerCase) && strings.Count(strLowerCase, seedLowerCase) >= opts.MinOccurrences 39 | } else { 40 | return strings.Contains(str, seed) && strings.Count(str, seed) >= opts.MinOccurrences 41 | } 42 | } 43 | 44 | func setContainOptsToDefault() *ContainsOpt { 45 | return &ContainsOpt{ 46 | IgnoreCase: containsOptsDefaultIgnoreCase, 47 | MinOccurrences: containsOptsDefaultMinOccurrences, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /contains_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestContains(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 string 10 | param3 *ContainsOpt 11 | want bool 12 | }{ 13 | // Valid default config 14 | {name: "Basic match", param1: "hello world", param2: "world", param3: &ContainsOpt{}, want: true}, 15 | {name: "Basic match digits", param1: "abc123", param2: "123", param3: &ContainsOpt{}, want: true}, 16 | 17 | // Valid with ignoreCase true 18 | {name: "Case-insensitive match", param1: "Hello World", param2: "hello", param3: &ContainsOpt{IgnoreCase: true}, want: true}, 19 | {name: "Case-insensitive match mixed", param1: "FOOBAR", param2: "bar", param3: &ContainsOpt{IgnoreCase: true}, want: true}, 20 | 21 | // Valid with minimum occurrences 22 | {name: "Minimum occurrences met", param1: "hello hello world", param2: "hello", param3: &ContainsOpt{MinOccurrences: 2}, want: true}, 23 | {name: "Minimum occurrences default", param1: "abc123", param2: "123", param3: &ContainsOpt{MinOccurrences: 1}, want: true}, 24 | 25 | // Invalid default config 26 | {name: "No match", param1: "hello world", param2: "earth", param3: &ContainsOpt{}, want: false}, 27 | {name: "No match digits", param1: "abc123", param2: "xyz", param3: &ContainsOpt{}, want: false}, 28 | 29 | // Invalid with ignoreCase false 30 | {name: "Case-sensitive no match", param1: "Hello World", param2: "WORLD", param3: &ContainsOpt{IgnoreCase: false}, want: false}, 31 | {name: "Case-insensitive fail", param1: "FOOBAR", param2: "baz", param3: &ContainsOpt{IgnoreCase: true}, want: false}, 32 | 33 | // Invalid with minimum occurrences 34 | {name: "Minimum occurrences not met", param1: "hello world", param2: "hello", param3: &ContainsOpt{MinOccurrences: 2}, want: false}, 35 | {name: "Zero occurrences required", param1: "abc123", param2: "123", param3: &ContainsOpt{MinOccurrences: 0}, want: true}, 36 | 37 | // Test for nil param3 38 | {name: "Nil default, basic match", param1: "hello world", param2: "world", param3: nil, want: true}, 39 | {name: "Nil default, no match", param1: "hello world", param2: "earth", param3: nil, want: false}, 40 | } 41 | 42 | for _, test := range tests { 43 | t.Run(test.name, func(t *testing.T) { 44 | result := Contains(test.param1, test.param2, test.param3) 45 | 46 | if result != test.want { 47 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 48 | } 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | {"ignoreWords": ["govalidator", "validatorgo", "Ctry"]} -------------------------------------------------------------------------------- /equals.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | // A validator that checks if the string matches the comparison. 4 | // 5 | // ok := govalidator.Equals("Hello", "Hello") 6 | // fmt.Println(ok) // true 7 | // ok := govalidator.Equals("Hello", "World") 8 | // fmt.Println(ok) // false 9 | func Equals(str, comparison string) bool { 10 | return str == comparison 11 | } 12 | -------------------------------------------------------------------------------- /equals_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestEquals(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 string 10 | want bool 11 | }{ 12 | {name: "Valid equals", param1: "Hello", param2: "Hello", want: true}, 13 | {name: "Valid not equals", param1: "Hello", param2: "World", want: false}, 14 | } 15 | 16 | for _, test := range tests { 17 | t.Run(test.name, func(t *testing.T) { 18 | result := Equals(test.param1, test.param2) 19 | 20 | if result != test.want { 21 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 22 | } 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bube054/validatorgo 2 | 3 | go 1.13.0 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bube054/validatorgo/5b9f51f1e75c756f8fc0b32ca208312ca9b625e8/go.sum -------------------------------------------------------------------------------- /isabarouting.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "strconv" 5 | "unicode/utf8" 6 | ) 7 | 8 | // A validator that checks if the string is an ABA routing number for US bank account / cheque. 9 | // 10 | // ok := govalidator.IsAbaRouting("123456789") 11 | // fmt.Println(ok) // false 12 | // ok := govalidator.IsAbaRouting("021000021") 13 | // fmt.Println(ok) // true 14 | func IsAbaRouting(str string) bool { 15 | strWithoutDashes := stripDashesAndSpaces(str) 16 | 17 | if utf8.RuneCountInString(strWithoutDashes) != 9 || !IsNumeric(strWithoutDashes, &IsNumericOpts{NoSymbols: true}) { 18 | return false 19 | } 20 | 21 | digits := make([]int, 9) 22 | 23 | for i, char := range strWithoutDashes { 24 | digit, _ := strconv.Atoi(string(char)) 25 | // err is ignore because we are assured IsNumericAbove to block all non numbers 26 | // if err != nil { 27 | // return false 28 | // } 29 | digits[i] = digit 30 | } 31 | 32 | checksum := 3*(digits[0]+digits[3]+digits[6]) + 7*(digits[1]+digits[4]+digits[7]) + 1*(digits[2]+digits[5]+digits[8]) 33 | 34 | return checksum%10 == 0 35 | } 36 | -------------------------------------------------------------------------------- /isabarouting_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsAbaRouting(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | want bool 12 | }{ 13 | // Valid ABA routing number 14 | {name: "Valid ABA routing", param1: "021000021", want: true}, 15 | {name: "Valid ABA routing, with dashes", param1: "0260-0959-3", want: true}, 16 | // Invalid ABA routing number 17 | {name: "Invalid ABA routing (too short)", param1: "12345678", want: false}, 18 | {name: "Invalid ABA routing (non-digit characters)", param1: "12A456789", want: false}, 19 | {name: "Invalid ABA routing (checksum failure)", param1: "987654321", want: false}, 20 | } 21 | 22 | for _, test := range tests { 23 | t.Run(test.name, func(t *testing.T) { 24 | result := IsAbaRouting(test.param1) 25 | 26 | if result != test.want { 27 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 28 | } 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /isafter.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/bube054/validatorgo/sanitizer" 7 | ) 8 | 9 | var ( 10 | isAfterOptsDefaultComparisonDate string = "" 11 | ) 12 | 13 | // IsAfterOpts is used to configure IsAfter 14 | type IsAfterOpts struct { 15 | ComparisonDate string // date to be compared to. Valid layouts are from the time package e.g Layout, ANSIC, UnixDate, RubyDate, RFC822, RFC822Z, RFC850, RFC1123, RFC1123Z, Kitchen, Stamp, StampMilli, StampMicro, StampNano, DateTime, DateOnly, TimeOnly, StandardDateLayout, SlashDateLayout, DateTimeLayout, ISO8601Layout, ISO8601ZuluLayout, ISO8601WithMillisecondsLayout. 16 | } 17 | 18 | // A validator that checks if the string is a date that is after the specified date. 19 | // 20 | // IsAfterOpts is a struct that defaults to { ComparisonDate: "" }. 21 | // 22 | // IsAfterOpts: 23 | // 24 | // ComparisonDate: defaults to the current time. 25 | // 26 | // string layouts for str and ComparisonDate can be different layout. 27 | // 28 | // these are the only valid layouts from the time package 29 | // e.g Layout, ANSIC, UnixDate, RubyDate, RFC822, RFC822Z, RFC850, RFC1123, RFC1123Z, Kitchen, Stamp, StampMilli, StampMicro, StampNano, DateTime, DateOnly, TimeOnly, StandardDateLayout, SlashDateLayout, DateTimeLayout, ISO8601Layout, ISO8601ZuluLayout, ISO8601WithMillisecondsLayout. 30 | // 31 | // ok := validatorgo.IsAfter("2023-09-15", &validatorgo.IsAfterOpts{ComparisonDate: "2023-01-01"}) 32 | // fmt.Println(ok) // true 33 | // ok = validatorgo.IsAfter("2023-01-01", &validatorgo.IsAfterOpts{ComparisonDate: "2023-09-15"}) 34 | // fmt.Println(ok) // false 35 | func IsAfter(str string, opts *IsAfterOpts) bool { 36 | if opts == nil { 37 | opts = setIsAfterOptsToDefault() 38 | } 39 | 40 | date1 := sanitizer.ToDate(str) 41 | 42 | var date2 *time.Time 43 | if opts.ComparisonDate == "" { 44 | now := time.Now() 45 | date2 = &now 46 | } else { 47 | date2 = sanitizer.ToDate(opts.ComparisonDate) 48 | } 49 | 50 | if date1 == nil || date2 == nil { 51 | return false 52 | } 53 | return date1.After(*date2) 54 | } 55 | 56 | func setIsAfterOptsToDefault() *IsAfterOpts { 57 | 58 | return &IsAfterOpts{ 59 | ComparisonDate: isAfterOptsDefaultComparisonDate, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /isafter_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsAfter(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | param2 *IsAfterOpts 12 | want bool 13 | }{ 14 | // Valid cases with StandardDateLayout 15 | {name: "StandardDateLayout - param1 after", param1: "2023-09-15", param2: &IsAfterOpts{ComparisonDate: "2023-01-01"}, want: true}, 16 | {name: "StandardDateLayout - param1 not after", param1: "2023-01-01", param2: &IsAfterOpts{ComparisonDate: "2023-09-15"}, want: false}, 17 | 18 | // Valid cases with SlashDateLayout 19 | {name: "SlashDateLayout - param1 after", param1: "2023/09/15", param2: &IsAfterOpts{ComparisonDate: "2023/01/01"}, want: true}, 20 | {name: "SlashDateLayout - param1 not after", param1: "2023/01/01", param2: &IsAfterOpts{ComparisonDate: "2023/09/15"}, want: false}, 21 | 22 | // ISO8601 Layouts 23 | {name: "ISO8601Layout - param1 after", param1: "2023-09-15T12:00:00", param2: &IsAfterOpts{ComparisonDate: "2023-09-15T11:59:59"}, want: true}, 24 | {name: "ISO8601ZuluLayout - param1 not after", param1: "2023-09-15T12:00:00Z", param2: &IsAfterOpts{ComparisonDate: "2023-09-15T12:00:01Z"}, want: false}, 25 | {name: "ISO8601WithMillisecondsLayout - param1 after", param1: "2023-09-15T12:00:00.001Z", param2: &IsAfterOpts{ComparisonDate: "2023-09-15T12:00:00.000Z"}, want: true}, 26 | 27 | // Invalid formats 28 | {name: "Invalid param1 format", param1: "15-09-2023", param2: &IsAfterOpts{ComparisonDate: "2023-01-01"}, want: false}, 29 | {name: "Invalid ComparisonDate format", param1: "2023-09-15", param2: &IsAfterOpts{ComparisonDate: "01/01/2023"}, want: false}, 30 | 31 | // Default to current time 32 | {name: "Empty ComparisonDate - param1 after current time", param1: "3000-01-01", param2: &IsAfterOpts{ComparisonDate: ""}, want: true}, 33 | {name: "Empty ComparisonDate - param1 before current time", param1: "2000-01-01", param2: &IsAfterOpts{ComparisonDate: ""}, want: false}, 34 | 35 | // Edge cases 36 | {name: "Leap year comparison", param1: "2020-02-29", param2: &IsAfterOpts{ComparisonDate: "2019-12-31"}, want: true}, 37 | {name: "Non-leap year comparison", param1: "2021-02-28", param2: &IsAfterOpts{ComparisonDate: "2021-03-01"}, want: false}, 38 | } 39 | 40 | for _, test := range tests { 41 | t.Run(test.name, func(t *testing.T) { 42 | result := IsAfter(test.param1, test.param2) 43 | 44 | if result != test.want { 45 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /isarray.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "encoding/json" 4 | 5 | var ( 6 | isArrayOptsDefaultMin *uint = nil 7 | isArrayOptsDefaultMax *uint = nil 8 | ) 9 | 10 | // IsArrayOpts is used to configure IsArray 11 | type IsArrayOpts struct { 12 | Min *uint // minimum array length 13 | Max *uint // maximum array length 14 | } 15 | 16 | // A validator to check that a value is an array. 17 | // 18 | // IsArrayOpts is a struct which defaults to { Min: nil, Max: nil }. 19 | // 20 | // You can also check that the array's length is greater than or equal to IsArrayOpts.Min and/or that it's less than or equal to IsArrayOpts.Max. 21 | // 22 | // ok := validatorgo.IsArray(`["item1", "item2"]`, nil) 23 | // fmt.Println(ok) // true 24 | // ok := validatorgo.IsArray(`{"name": "John", "age": 30}`, nil) 25 | // fmt.Println(ok) // false 26 | func IsArray(str string, opts *IsArrayOpts) bool { 27 | if opts == nil { 28 | opts = setIsArrayOptsToDefault() 29 | } 30 | 31 | var arr []interface{} 32 | 33 | err := json.Unmarshal([]byte(str), &arr) 34 | if err != nil { 35 | return false 36 | } 37 | 38 | arrLength := len(arr) 39 | withinLimits := true 40 | 41 | if opts.Min != nil { 42 | isMin := *(opts.Min) <= uint(arrLength) 43 | withinLimits = withinLimits && isMin 44 | } 45 | 46 | if opts.Max != nil { 47 | isMax := *(opts.Max) >= uint(arrLength) 48 | withinLimits = withinLimits && isMax 49 | } 50 | 51 | return withinLimits 52 | 53 | } 54 | 55 | func setIsArrayOptsToDefault() *IsArrayOpts { 56 | return &IsArrayOpts{ 57 | Min: isArrayOptsDefaultMin, 58 | Max: isArrayOptsDefaultMax, 59 | } 60 | } -------------------------------------------------------------------------------- /isarray_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsArray(t *testing.T) { 8 | var one uint = 1 9 | var two uint = 2 10 | var three uint = 3 11 | 12 | tests := []struct { 13 | name string 14 | param1 string 15 | param2 *IsArrayOpts 16 | want bool 17 | }{ 18 | // Valid json array 19 | {name: "Valid array", param1: `["item1", "item2"]`, param2: nil, want: true}, 20 | {name: "Valid empty array", param1: `[]`, param2: nil, want: true}, 21 | {name: "Valid array with Min", param1: `["item1", "item2"]`, param2: &IsArrayOpts{Min: &two}, want: true}, 22 | {name: "Valid array with Max", param1: `["item1", "item2"]`, param2: &IsArrayOpts{Max: &three}, want: true}, 23 | {name: "Valid array with MIn & Max", param1: `["item1", "item2"]`, param2: &IsArrayOpts{Min: &two, Max: &three}, want: true}, 24 | // Invalid json array 25 | {name: "Invalid array is object", param1: `{"name": "John", "age": 30}`, param2: nil, want: false}, 26 | {name: "Invalid array bad format", param1: `[`, param2: nil, want: false}, 27 | {name: "Invalid array precedes Min", param1: `["item1", "item2"]`, param2: &IsArrayOpts{Min: &three}, want: false}, 28 | {name: "Invalid array exceeds Max", param1: `["item1", "item2"]`, param2: &IsArrayOpts{Max: &one}, want: false}, 29 | {name: "Invalid array precedes Min and exceeds Max", param1: `["item1", "item2"]`, param2: &IsArrayOpts{Min: &three, Max: &one}, want: false}, 30 | } 31 | 32 | for _, test := range tests { 33 | t.Run(test.name, func(t *testing.T) { 34 | result := IsArray(test.param1, test.param2) 35 | 36 | if result != test.want { 37 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /isascii.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | // A validator that checks if the string contains ASCII chars only. 4 | // 5 | // ok := validatorgo.IsAscii("Hello") 6 | // fmt.Println(ok) // true 7 | // ok := validatorgo.IsAscii("こんにちは") 8 | // fmt.Println(ok) // false 9 | func IsAscii(str string) bool { 10 | for _, char := range str { 11 | if !(char >= 0 && char <= 127) { 12 | return false 13 | } 14 | } 15 | 16 | return true 17 | } 18 | -------------------------------------------------------------------------------- /isascii_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsAscii(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | {name: "Basic ASCII check", param1: "hello", want: true}, 12 | {name: "Uppercase ASCII check", param1: "HELLO", want: true}, 13 | {name: "Alphanumeric ASCII", param1: "Hello123", want: true}, 14 | {name: "ASCII special characters", param1: "!@#$%^&*()", want: true}, 15 | {name: "ASCII space", param1: "Hello World", want: true}, 16 | {name: "Numeric ASCII", param1: "1234567890", want: true}, 17 | {name: "Empty string", param1: "", want: true}, 18 | {name: "Newline character", param1: "Hello\nWorld", want: true}, 19 | {name: "Tab character", param1: "Hello\tWorld", want: true}, 20 | {name: "Extended ASCII (non-ASCII)", param1: "Olá", want: false}, 21 | {name: "Non-ASCII Unicode character", param1: "こんにちは", want: false}, 22 | {name: "Emoji character", param1: "Hello🙂", want: false}, 23 | {name: "Non-ASCII accented character", param1: "Café", want: false}, 24 | {name: "Non-ASCII control character", param1: "Hello\u200BWorld", want: false}, 25 | } 26 | 27 | for _, test := range tests { 28 | t.Run(test.name, func(t *testing.T) { 29 | result := IsAscii(test.param1) 30 | 31 | if result != test.want { 32 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 33 | } 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /isbase32.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | isBase32OptsDefaultCrockford bool = false 10 | ) 11 | 12 | // IsBase32Opts is used to configure IsBase32 13 | type IsBase32Opts struct { 14 | Crockford bool // whether to use crockfords base32 alternative encoding scheme 15 | 16 | // ZBase bool // whether to use crockfords base32 alternative encoding scheme 17 | } 18 | 19 | // A validator that checks if the string is base32 encoded. 20 | // 21 | // IsBase32Opts defaults to { Crockford: false }. 22 | // When Crockford is true it tests the given base32 encoded string using [crockford's] base32 alternative. 23 | // 24 | // isAlpha := validatorgo.IsBase32("JBSWY3DPEBLW64TMMQ", &validatorgo.IsBase32Opts{}) 25 | // fmt.Println(isAlpha) // true 26 | // isAlpha := validatorgo.IsBase32("jbswy3dpeblw64tmmq======", &validatorgo.IsBase32Opts{}) 27 | // fmt.Println(isAlpha) // false 28 | // 29 | // [crockford's]: http://www.crockford.com/base32.html 30 | func IsBase32(str string, opts *IsBase32Opts) bool { 31 | if opts == nil { 32 | opts = setIsBase32OptsToDefault() 33 | } 34 | 35 | strWithoutEq := strings.TrimRight(str, "=") 36 | strWithoutHyp := stripHyphens(strWithoutEq) 37 | 38 | if len(strWithoutHyp) < 2 { 39 | return false 40 | } 41 | 42 | if opts.Crockford { 43 | return regexp.MustCompile(`^[A-HJ-KM-NP-TV-Z0-9]+$`).MatchString(strings.ToUpper(strWithoutHyp)) 44 | } else { 45 | return regexp.MustCompile(`^[A-Z2-7]+$`).MatchString(strWithoutHyp) 46 | } 47 | } 48 | 49 | func setIsBase32OptsToDefault() (opts *IsBase32Opts) { 50 | return &IsBase32Opts{ 51 | Crockford: isBase32OptsDefaultCrockford, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /isbase32_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsBase32(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | param2 *IsBase32Opts 12 | want bool 13 | }{ 14 | // Standard Base32 (RFC 4648) tests 15 | {name: "Basic Base32 valid", param1: "JBSWY3DPEBLW64TMMQ======", param2: &IsBase32Opts{}, want: true}, 16 | {name: "Valid Base32 no padding", param1: "JBSWY3DPEBLW64TMMQ", param2: &IsBase32Opts{}, want: true}, 17 | {name: "Valid Base32 but short", param1: "JBSWY3DP", param2: &IsBase32Opts{}, want: true}, 18 | {name: "Valid Base32", param1: "JBSWY3DPEBLW64TMMQ=====", param2: &IsBase32Opts{}, want: true}, 19 | {name: "Valid Base32 with $ characters", param1: "JBSWY3DP$BLW64TMMQ======", param2: &IsBase32Opts{}, want: false}, 20 | {name: "Invalid Base32 lowercase", param1: "jbswy3dpeblw64tmmq======", param2: &IsBase32Opts{}, want: false}, 21 | {name: "Nil config basic valid Base32", param1: "JBSWY3DPEBLW64TMMQ======", param2: nil, want: true}, 22 | 23 | // Crockford Base32 tests 24 | {name: "Crockford Base32 valid", param1: "91JPRV3F41VPYWKCCG", param2: &IsBase32Opts{Crockford: true}, want: true}, 25 | {name: "Crockford Base32 valid with hyphens", param1: "91-JP-RV-3F-41-VP-YW-KC-CG", param2: &IsBase32Opts{Crockford: true}, want: true}, 26 | {name: "Crockford Base32 lowercase", param1: "91jprv3f41vpywkccg", param2: &IsBase32Opts{Crockford: true}, want: true}, 27 | {name: "Crockford Base32 with special characters", param1: "91JPRV3F!41VPYWKCCG", param2: &IsBase32Opts{Crockford: true}, want: false}, 28 | {name: "Crockford Base32 invalid due to incorrect padding", param1: "A1B2C3D4E5F6G7H8I9J", param2: &IsBase32Opts{Crockford: true}, want: false}, 29 | {name: "Nil config invalid Base32 with special character", param1: "JBSWY3DP$BLW64TMMQ======", param2: nil, want: false}, 30 | {name: "Nil config empty string", param1: "", param2: nil, want: false}, 31 | 32 | // Edge cases 33 | {name: "Empty string", param1: "", param2: &IsBase32Opts{}, want: false}, 34 | {name: "Single Base32 character", param1: "J", param2: &IsBase32Opts{}, want: false}, 35 | {name: "Valid Base32 with spaces", param1: "JBSWY 3DPE BLW64 TMMQ======", param2: &IsBase32Opts{}, want: false}, 36 | } 37 | 38 | for _, test := range tests { 39 | t.Run(test.name, func(t *testing.T) { 40 | result := IsBase32(test.param1, test.param2) 41 | 42 | if result != test.want { 43 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 44 | } 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /isbase58.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | // A validator that checks if the string is base58 encoded. 8 | func IsBase58(str string) bool { 9 | return regexp.MustCompile("^[A-HJ-NP-Za-km-z1-9]+$").MatchString(str) 10 | } 11 | -------------------------------------------------------------------------------- /isbase58_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsBase58(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | want bool 12 | }{ 13 | // Valid Base58 test cases 14 | {name: "Basic Base58 valid", param1: "JDEe4eBcQWTe1GV34By3dz7", want: true}, 15 | {name: "Base58 valid with mixed case", param1: "JDEE4EBCQWTE1GV34BY3DZ7", want: true}, 16 | {name: "Base58 valid with varying length", param1: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz", want: true}, 17 | {name: "Base58 valid with short length", param1: "1A3", want: true}, 18 | 19 | // Invalid Base58 test cases 20 | {name: "Invalid Base58 with non-base58 characters", param1: "JDEe4eBcQWTe1GV34By3dz7@", want: false}, 21 | {name: "Invalid Base58 with spaces", param1: "JDEe 4eBcQWTe1GV34By3dz7", want: false}, 22 | {name: "Invalid Base58 with special characters", param1: "JDEe4eBcQWTe1GV34By3dz7!", want: false}, 23 | {name: "Invalid Base58 incorrect characters", param1: "JDEe4eBcQWTe1GV34BY3DZ#", want: false}, 24 | {name: "Empty string", param1: "", want: false}, 25 | } 26 | 27 | for _, test := range tests { 28 | t.Run(test.name, func(t *testing.T) { 29 | result := IsBase58(test.param1) 30 | 31 | if result != test.want { 32 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 33 | } 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /isbase64.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "regexp" 5 | "unicode/utf8" 6 | ) 7 | 8 | var ( 9 | isBase64OptsDefaultUrlSafe bool = false 10 | ) 11 | 12 | // IsBase64Opts is used to configure IsBase64 13 | type IsBase64Opts struct { 14 | UrlSafe bool // checks whether string is url safe. 15 | } 16 | 17 | // A validator that checks if the string is base64 encoded. 18 | // 19 | // IsBase64Opts is an optional struct which defaults to { UrlSafe: false }. 20 | // When UrlSafe is true it tests the given base64 encoded string is [url safe]. 21 | // 22 | // ok := validatorgo.IsBase64("SGVsbG8gd29ybGQ", &validatorgo.IsBase64Opts{}) 23 | // fmt.Println(ok) // true 24 | // ok := validatorgo.IsBase64("SGVsbG8g@d29ybGQ=", &validatorgo.IsBase64Opts{}) 25 | // fmt.Println(ok) // false 26 | // 27 | // [url safe]: https://base64.guru/standards/base64url 28 | func IsBase64(str string, opts *IsBase64Opts) bool { 29 | if opts == nil { 30 | opts = setIsBase64OptsToDefault() 31 | } 32 | 33 | if utf8.RuneCountInString(str) < 2 { 34 | return false 35 | } 36 | 37 | if opts.UrlSafe { 38 | return regexp.MustCompile(`^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2}(?:==)?|[A-Za-z0-9_-]{3}=?)?$`).MatchString(str) 39 | } else { 40 | return regexp.MustCompile(`^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$`).MatchString(str) 41 | } 42 | } 43 | 44 | func setIsBase64OptsToDefault() (opts *IsBase64Opts) { 45 | opts = &IsBase64Opts{} 46 | opts.UrlSafe = isBase64OptsDefaultUrlSafe 47 | 48 | return 49 | } 50 | -------------------------------------------------------------------------------- /isbase64_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsBase64(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | param2 *IsBase64Opts 12 | want bool 13 | }{ 14 | // Valid Base64 strings 15 | {name: "Valid Base64 standard", param1: "SGVsbG8gd29ybGQ=", param2: &IsBase64Opts{UrlSafe: false}, want: true}, 16 | {name: "Valid Base64 URL-safe", param1: "SGVsbG8gd29ybGQ", param2: &IsBase64Opts{UrlSafe: true}, want: true}, 17 | {name: "Valid Base64 URL-safe with padding", param1: "SGVsbG8gd29ybGQ=", param2: &IsBase64Opts{UrlSafe: true}, want: true}, 18 | {name: "Valid Base64 standard with padding", param1: "U29tZSB0ZXN0IHN0cmluZw==", param2: &IsBase64Opts{UrlSafe: false}, want: true}, 19 | {name: "Valid Base64 standard with nil config", param1: "SGVsbG8gd29ybGQ=", param2: nil, want: true}, 20 | 21 | // Invalid Base64 strings 22 | {name: "Invalid Base64 string with special characters", param1: "SGVsbG8g@d29ybGQ=", param2: &IsBase64Opts{UrlSafe: false}, want: false}, 23 | {name: "Invalid Base64 string with space", param1: "SGVsbG8gd29y bGQ=", param2: &IsBase64Opts{UrlSafe: false}, want: false}, 24 | {name: "Invalid Base64 URL-safe with unsafe characters", param1: "SGVsbG8gd29ybGQ$", param2: &IsBase64Opts{UrlSafe: true}, want: false}, 25 | {name: "Empty string", param1: "", param2: &IsBase64Opts{UrlSafe: false}, want: false}, 26 | {name: "Invalid Base64 with special characters and nil config", param1: "SGVsbG8g@d29ybGQ=", param2: nil, want: false}, 27 | {name: "Empty string with nil config", param1: "", param2: nil, want: false}, 28 | 29 | // Edge cases 30 | {name: "Valid URL-safe Base64 without padding", param1: "U29tZVRlc3RTdHJpbmc", param2: &IsBase64Opts{UrlSafe: true}, want: true}, 31 | {name: "Valid Base64 standard with numbers", param1: "MTIzNDU2Nzg5MA==", param2: &IsBase64Opts{UrlSafe: false}, want: true}, 32 | } 33 | 34 | for _, test := range tests { 35 | t.Run(test.name, func(t *testing.T) { 36 | result := IsBase64(test.param1, test.param2) 37 | 38 | if result != test.want { 39 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /isbefore.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/bube054/validatorgo/sanitizer" 7 | ) 8 | 9 | var ( 10 | isBeforeOptsDefaultComparisonDate string = "" 11 | ) 12 | 13 | // IsBeforeOpts is used to configure IsBefore 14 | type IsBeforeOpts struct { 15 | ComparisonDate string // date to be compared to. Valid layouts are from the time package e.g LLayout, ANSIC, UnixDate, RubyDate, RFC822, RFC822Z, RFC850, RFC1123, RFC1123Z, Kitchen, Stamp, StampMilli, StampMicro, StampNano, DateTime, DateOnly, TimeOnly, StandardDateLayout, SlashDateLayout, DateTimeLayout, ISO8601Layout, ISO8601ZuluLayout, ISO8601WithMillisecondsLayout. 16 | } 17 | 18 | // A validator that checks if the string is a date that is before the specified date. 19 | // 20 | // IsBeforeOpts is a struct that defaults to { ComparisonDate: "" }. 21 | // 22 | // IsBeforeOpts: 23 | // 24 | // ComparisonDate: defaults to the current time. 25 | // string layouts for str and ComparisonDate can be different layout. 26 | // these are the only valid layouts from the time package e.g Layout, ANSIC, UnixDate, RubyDate, RFC822, RFC822Z, RFC850, RFC1123, RFC1123Z, Kitchen, Stamp, StampMilli, StampMicro, StampNano, DateTime, DateOnly, TimeOnly, StandardDateLayout, SlashDateLayout, DateTimeLayout, ISO8601Layout, ISO8601ZuluLayout, ISO8601WithMillisecondsLayout. 27 | // 28 | // ok := validatorgo.IsBefore("2023-01-01", &IsBeforeOpts{ComparisonDate: "2023-09-15"}) 29 | // fmt.Println(ok) // true 30 | // ok = validatorgo.IsBefore("2024-01-01", &IsBeforeOpts{ComparisonDate: "2023-01-01"}) 31 | // fmt.Println(ok) // false 32 | func IsBefore(str string, opts *IsBeforeOpts) bool { 33 | if opts == nil { 34 | opts = setIsBeforeOptsToDefault() 35 | } 36 | 37 | date1 := sanitizer.ToDate(str) 38 | 39 | var date2 *time.Time 40 | if opts.ComparisonDate == "" { 41 | now := time.Now() 42 | date2 = &now 43 | } else { 44 | date2 = sanitizer.ToDate(opts.ComparisonDate) 45 | } 46 | 47 | if date1 == nil || date2 == nil { 48 | return false 49 | } 50 | 51 | return date1.Before(*date2) 52 | } 53 | 54 | func setIsBeforeOptsToDefault() (opts *IsBeforeOpts) { 55 | return &IsBeforeOpts{ 56 | ComparisonDate: isBeforeOptsDefaultComparisonDate, 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /isbefore_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsBefore(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | param2 *IsBeforeOpts 12 | want bool 13 | }{ 14 | // Valid cases with StandardDateLayout 15 | {name: "StandardDateLayout - param1 before", param1: "2023-01-01", param2: &IsBeforeOpts{ComparisonDate: "2023-09-15"}, want: true}, 16 | {name: "StandardDateLayout - param1 not before", param1: "2023-09-15", param2: &IsBeforeOpts{ComparisonDate: "2023-01-01"}, want: false}, 17 | 18 | // Valid cases with SlashDateLayout 19 | {name: "SlashDateLayout - param1 before", param1: "2023/01/01", param2: &IsBeforeOpts{ComparisonDate: "2023/09/15"}, want: true}, 20 | {name: "SlashDateLayout - param1 not before", param1: "2023/09/15", param2: &IsBeforeOpts{ComparisonDate: "2023/01/01"}, want: false}, 21 | 22 | // ISO8601 Layouts 23 | {name: "ISO8601Layout - param1 before", param1: "2023-09-15T11:59:59", param2: &IsBeforeOpts{ComparisonDate: "2023-09-15T12:00:00"}, want: true}, 24 | {name: "ISO8601ZuluLayout - param1 not before", param1: "2023-09-15T12:00:01Z", param2: &IsBeforeOpts{ComparisonDate: "2023-09-15T12:00:00Z"}, want: false}, 25 | {name: "ISO8601WithMillisecondsLayout - param1 before", param1: "2023-09-15T12:00:00.000Z", param2: &IsBeforeOpts{ComparisonDate: "2023-09-15T12:00:00.001Z"}, want: true}, 26 | 27 | // Invalid formats 28 | {name: "Invalid param1 format", param1: "15-09-2023", param2: &IsBeforeOpts{ComparisonDate: "2023-01-01"}, want: false}, 29 | {name: "Invalid ComparisonDate format", param1: "2023-09-15", param2: &IsBeforeOpts{ComparisonDate: "01/01/2023"}, want: false}, 30 | 31 | // Default to current time 32 | {name: "Empty ComparisonDate - param1 before current time", param1: "2000-01-01", param2: &IsBeforeOpts{ComparisonDate: ""}, want: true}, 33 | {name: "Empty ComparisonDate - param1 after current time", param1: "3000-01-01", param2: &IsBeforeOpts{ComparisonDate: ""}, want: false}, 34 | 35 | // Edge cases 36 | {name: "Leap year comparison", param1: "2019-12-31", param2: &IsBeforeOpts{ComparisonDate: "2020-02-29"}, want: true}, 37 | {name: "Non-leap year comparison", param1: "2021-03-01", param2: &IsBeforeOpts{ComparisonDate: "2021-02-28"}, want: false}, 38 | } 39 | 40 | for _, test := range tests { 41 | t.Run(test.name, func(t *testing.T) { 42 | result := IsBefore(test.param1, test.param2) 43 | 44 | if result != test.want { 45 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /isbic.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator that checks if the string is a BIC (Bank Identification Code) or SWIFT code. 6 | // 7 | // ok := validatorgo.IsBic("DEUTDEFF") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsBic("ABCDUS12XXXX") 10 | // fmt.Println(ok) // false 11 | func IsBic(str string) bool { 12 | return regexp.MustCompile(`^[A-Za-z]{4}(AF|af|AX|ax|AL|al|DZ|dz|AS|as|AD|ad|AO|ao|AI|ai|AQ|aq|AG|ag|AR|ar|AM|am|AW|aw|AU|au|AT|at|AZ|az|BS|bs|BH|bh|BD|bd|BB|bb|BY|by|BE|be|BZ|bz|BJ|bj|BM|bm|BT|bt|BO|bo|BQ|bq|BA|ba|BW|bw|BV|bv|BR|br|IO|io|BN|bn|BG|bg|BF|bf|BI|bi|CV|cv|KH|kh|CM|cm|CA|ca|KY|ky|CF|cf|TD|td|CL|cl|CN|cn|CX|cx|CC|cc|CO|co|KM|km|CG|cg|CD|cd|CK|ck|CR|cr|CI|ci|HR|hr|CU|cu|CW|cw|CY|cy|CZ|cz|DK|dk|DJ|dj|DM|dm|DO|do|EC|ec|EG|eg|SV|sv|GQ|gq|ER|er|EE|ee|SZ|sz|ET|et|FK|fk|FO|fo|FJ|fj|FI|fi|FR|fr|GF|gf|PF|pf|TF|tf|GA|ga|GM|gm|GE|ge|DE|de|GH|gh|GI|gi|GR|gr|GL|gl|GD|gd|GP|gp|GU|gu|GT|gt|GG|gg|GN|gn|GW|gw|GY|gy|HT|ht|HM|hm|VA|va|HN|hn|HK|hk|HU|hu|IS|is|IN|in|ID|id|IR|ir|IQ|iq|IE|ie|IM|im|IL|il|IT|it|JM|jm|JP|jp|JE|je|JO|jo|KZ|kz|KE|ke|KI|ki|KP|kp|KR|kr|KW|kw|KG|kg|LA|la|LV|lv|LB|lb|LS|ls|LR|lr|LY|ly|LI|li|LT|lt|LU|lu|MO|mo|MG|mg|MW|mw|MY|my|MV|mv|ML|ml|MT|mt|MH|mh|MQ|mq|MR|mr|MU|mu|YT|yt|MX|mx|FM|fm|MD|md|MC|mc|MN|mn|ME|me|MS|ms|MA|ma|MZ|mz|MM|mm|NA|na|NR|nr|NP|np|NL|nl|NC|nc|NZ|nz|NI|ni|NE|ne|NG|ng|NU|nu|NF|nf|MK|mk|MP|mp|NO|no|OM|om|PK|pk|PW|pw|PS|ps|PA|pa|PG|pg|PY|py|PE|pe|PH|ph|PN|pn|PL|pl|PT|pt|PR|pr|QA|qa|RE|re|RO|ro|RU|ru|RW|rw|BL|bl|SH|sh|KN|kn|LC|lc|MF|mf|PM|pm|VC|vc|WS|ws|SM|sm|ST|st|SA|sa|SN|sn|RS|rs|SC|sc|SL|sl|SG|sg|SX|sx|SK|sk|SI|si|SB|sb|SO|so|ZA|za|GS|gs|SS|ss|ES|es|LK|lk|SD|sd|SR|sr|SJ|sj|SE|se|CH|ch|SY|sy|TW|tw|TJ|tj|TZ|tz|TH|th|TL|tl|TG|tg|TK|tk|TO|to|TT|tt|TN|tn|TR|tr|TM|tm|TC|tc|TV|tv|UG|ug|UA|ua|AE|ae|GB|gb|US|us|UM|um|UY|uy|UZ|uz|VU|vu|VE|ve|VN|vn|VG|vg|VI|vi|WF|wf|EH|eh|YE|ye|ZM|zm|ZW|zw)[0-9A-Za-z]{2}([A-Za-z0-9]{3})?$`).MatchString(str) 13 | } 14 | -------------------------------------------------------------------------------- /isbic_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsBic(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | want bool 12 | }{ 13 | // Valid BIC codes 14 | {name: "Valid BIC 8 characters", param1: "DEUTDEFF", want: true}, 15 | {name: "Valid BIC 11 characters", param1: "DEUTDEFF500", want: true}, 16 | {name: "Valid BIC with digits", param1: "NEDSZAJJXXX", want: true}, 17 | {name: "Valid barclays swift or bic code", param1: "BARCGB22XXX", want: true}, 18 | {name: "Valid HSBC swift or bic code", param1: "HBUKGB4106W", want: true}, 19 | {name: "Valid Goldman Sachs swift or bic code", param1: "GOSGGB21", want: true}, 20 | {name: "Valid Union Bank swift or bic code", param1: "UBNINGLA", want: true}, 21 | 22 | // Invalid BIC codes 23 | {name: "Invalid BIC with too few characters", param1: "DEUTDE", want: false}, 24 | {name: "Invalid BIC with too many characters", param1: "DEUTDEFF500XYZ", want: false}, 25 | {name: "Invalid BIC with special characters", param1: "DEUT!EFF500", want: false}, 26 | {name: "Invalid BIC with spaces", param1: "DEUT DEFF 500", want: false}, 27 | {name: "Invalid swift or bic code length", param1: "ABCDUS12XXXX", want: false}, 28 | {name: "Invalid swift or bic code bank code", param1: "ABCDEFGH", want: false}, 29 | 30 | // Edge cases 31 | {name: "Empty string", param1: "", want: false}, 32 | {name: "Valid BIC with lowercase letters", param1: "deutdeff500", want: true}, 33 | } 34 | 35 | for _, test := range tests { 36 | t.Run(test.name, func(t *testing.T) { 37 | result := IsBic(test.param1) 38 | 39 | if result != test.want { 40 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 41 | } 42 | }) 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /isboolean.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | var ( 6 | isBooleanOptsDefaultLoose bool = false 7 | ) 8 | 9 | // IsBooleanOpts is used to configure IsBoolean 10 | type IsBooleanOpts struct { 11 | Loose bool // strictness of the equality 12 | } 13 | 14 | // A validator that check if the string is a boolean. 15 | // 16 | // IsBooleanOpts is a struct which defaults to { Loose: false } and that can be supplied with the following key(s): 17 | // 18 | // Loose: If Loose is set to false, the validator will strictly match ['true', 'false', '0', '1']. 19 | // If Loose is set to true, the validator will also match 'yes', 'no', and will match a valid boolean string of any case. (e.g.: ['true', 'True', 'TRUE', "false", "False", "FALSE"]). 20 | // 21 | // ok := validatorgo.IsBoolean("true", &validatorgo.IsBooleanOpts{Loose: false}) 22 | // fmt.Println(ok) // true 23 | // ok := validatorgo.IsBoolean("bool", &validatorgo.IsBooleanOpts{Loose: false}) 24 | // fmt.Println(ok) // false 25 | func IsBoolean(str string, opts *IsBooleanOpts) bool { 26 | if opts == nil { 27 | opts = setIsBooleanOptsToDefault() 28 | } 29 | 30 | if opts.Loose { 31 | return regexp.MustCompile("^(true|True|TRUE|false|False|FALSE|yes|no|0|1)$").MatchString(str) 32 | } else { 33 | return regexp.MustCompile("^(true|false|0|1)$").MatchString(str) 34 | } 35 | } 36 | 37 | func setIsBooleanOptsToDefault() (opts *IsBooleanOpts) { 38 | return &IsBooleanOpts{ 39 | Loose: isBooleanOptsDefaultLoose, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /isbtcaddress.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator that checks if the string is a valid BTC address. 6 | // 7 | // ok := validatorgo.IsBTCAddress("1RAHUEYstWetqabcFn5Au4m4GFg7xJaNVN2") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsBTCAddress("0J98t1RHT73CNmQwertyyWrnqRhWNLy") 10 | // fmt.Println(ok) // false 11 | func IsBTCAddress(str string) bool { 12 | return regexp.MustCompile("^(bc1|[13])[a-km-zA-HJ-NP-Z1-9]{25,34}$").MatchString(str) 13 | } 14 | -------------------------------------------------------------------------------- /isbtcaddress_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsBTCAddress(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | want bool 12 | }{ 13 | {name: "Valid BTC", param1: "1RAHUEYstWetqabcFn5Au4m4GFg7xJaNVN2", want: true}, 14 | {name: "Valid BTC", param1: "3J98t1RHT73CNmQwertyyWrnqRhWNLy", want: true}, 15 | {name: "Valid BTC", param1: "bc1qarsrrr7ASHy5643ydab9re59gtzzwfrah", want: true}, 16 | {name: `Invalid BTC address as it starts with "b"`, param1: "b1qarsrrr7ASHy5643ydab9re59gtzzwfrah", want: false}, 17 | {name: `Invalid BTC address as it starts with 0.`, param1: "0J98t1RHT73CNmQwertyyWrnqRhWNLy", want: false}, 18 | {name: "Invalid BTC address with special characters", param1: "1RAHU@EYstWetqabcFn5Au4m4GFg7xJaNVN2", want: false}, 19 | } 20 | 21 | for _, test := range tests { 22 | t.Run(test.name, func(t *testing.T) { 23 | result := IsBTCAddress(test.param1) 24 | 25 | if result != test.want { 26 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 27 | } 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /isbytelength.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | var ( 4 | isIsByteLengthOptsDefaultMin uint = 0 5 | isIsByteLengthOptsDefaultMax *uint = nil 6 | ) 7 | 8 | // IsByteLengthOpts is used to configure IsByteLength 9 | type IsByteLengthOpts struct { 10 | Min uint // minimum byte length 11 | Max *uint // maximum byte length 12 | } 13 | 14 | // A validator that checks if the string's length (in UTF-8 bytes) falls in a range. 15 | // 16 | // IsByteLengthOpts is a struct which defaults to { Min: 0, Max: nil }. 17 | // 18 | // ok := validatorgo.IsByteLength("We♥Go", &IsByteLengthOpts{Min: 5}) 19 | // fmt.Println(ok) // true 20 | // ok := validatorgo.IsByteLength("We♥Go", &IsByteLengthOpts{Min: 8}) 21 | // fmt.Println(ok) // false 22 | func IsByteLength(str string, opts *IsByteLengthOpts) bool { 23 | if opts == nil { 24 | opts = setIsByteLengthOptsToDefault() 25 | } 26 | 27 | lenInBytes := len(str) 28 | if opts.Max == nil { 29 | return lenInBytes >= int(opts.Min) 30 | } else { 31 | return lenInBytes >= int(opts.Min) && lenInBytes <= int(*opts.Max) 32 | } 33 | } 34 | 35 | func setIsByteLengthOptsToDefault() (opts *IsByteLengthOpts) { 36 | opts = &IsByteLengthOpts{} 37 | opts.Min = isIsByteLengthOptsDefaultMin 38 | opts.Max = isIsByteLengthOptsDefaultMax 39 | 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /isbytelength_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsByteLength(t *testing.T) { 8 | var ( 9 | max1 uint = 8 10 | max2 uint = 12 11 | ) 12 | 13 | tests := []struct { 14 | name string 15 | param1 string 16 | param2 *IsByteLengthOpts 17 | want bool 18 | }{ 19 | // Valid vyte length 20 | {name: "Is greater or equals to min", param1: "We♥Go", param2: &IsByteLengthOpts{Min: 5}, want: true}, 21 | {name: "Is greater or equals to min and less than or equals max", param1: "We♥Go", param2: &IsByteLengthOpts{Min: 5, Max: &max1}, want: true}, 22 | {name: "Valid string with exact min length", param1: "Hello", param2: &IsByteLengthOpts{Min: 5}, want: true}, 23 | {name: "Nil config, valid string comparison", param1: "We♥Go", param2: nil, want: true}, 24 | 25 | // Invalid vyte length 26 | {name: "Is not greater or equals to min", param1: "We♥Go", param2: &IsByteLengthOpts{Min: 8}, want: false}, 27 | {name: "Is not greater or equals to min and less than or equals max", param1: "We♥Go", param2: &IsByteLengthOpts{Min: 8, Max: &max2}, want: false}, 28 | {name: "Invalid string, exceeds max length", param1: "We♥Golang", param2: &IsByteLengthOpts{Min: 1, Max: &max1}, want: false}, 29 | } 30 | 31 | for _, test := range tests { 32 | t.Run(test.name, func(t *testing.T) { 33 | result := IsByteLength(test.param1, test.param2) 34 | 35 | if result != test.want { 36 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 37 | } 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /iscountrycode.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | var AllCountryCodes = [...]string{"AF", "AL", "DZ", "AS", "AD", "AO", "AI", "AQ", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BS", "BH", "BD", "BB", "BY", "BE", "BZ", "BJ", "BM", "BT", "BO", "BA", "BW", "BV", "BR", "IO", "BN", "BG", "BF", "BI", "KH", "CM", "CA", "CV", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CD", "CK", "CR", "CI", "HR", "CU", "CY", "CZ", "DK", "DJ", "DM", "DO", "EC", "EG", "SV", "GQ", "ER", "EE", "ET", "FK", "FO", "FJ", "FI", "FR", "GF", "PF", "TF", "GA", "GM", "GE", "DE", "GH", "GI", "GR", "GL", "GD", "GP", "GU", "GT", "GN", "GW", "GY", "HT", "HM", "HN", "HK", "HU", "IS", "IN", "ID", "IR", "IQ", "IE", "IL", "IT", "JM", "JP", "JO", "KZ", "KE", "KI", "KP", "KR", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "LY", "LI", "LT", "LU", "MO", "MG", "MW", "MY", "MV", "ML", "MT", "MH", "MQ", "MR", "MU", "YT", "MX", "FM", "MD", "MC", "MN", "MS", "MA", "MZ", "MM", "NA", "NR", "NP", "NL", "AN", "NC", "NZ", "NI", "NE", "NG", "NU", "NF", "MP", "NO", "OM", "PK", "PW", "PA", "PG", "PY", "PE", "PH", "PN", "PL", "PT", "PR", "QA", "RE", "RO", "RU", "RW", "SH", "KN", "LC", "PM", "VC", "WS", "SM", "ST", "SA", "SN", "CS", "SC", "SL", "SG", "SK", "SI", "SB", "SO", "ZA", "GS", "ES", "LK", "SD", "SR", "SJ", "SZ", "SE", "CH", "SY", "TW", "TJ", "TZ", "TH", "TL", "TG", "TK", "TO", "TT", "TN", "TR", "TM", "TC", "TV", "UG", "UA", "AE", "GB", "US", "UM", "UY", "UZ", "VU", "VA", "VE", "VN", "VG", "VI", "WF", "EH", "YE", "ZM", "ZW"} 4 | 5 | // A validator that checks if the string is a country code. 6 | func IsCountryCode(str string) bool { 7 | return IsIn(str, AllCountryCodes[:]) 8 | 9 | } 10 | -------------------------------------------------------------------------------- /iscountrycode_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsCountryCode(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid County-Codes 12 | {name: "Is valid county code", param1: "AL", want: true}, 13 | {name: "Is valid county code", param1: "BF", want: true}, 14 | {name: "Is valid county code", param1: "PM", want: true}, 15 | {name: "Is valid county code", param1: "TR", want: true}, 16 | // Invalid County-Codes 17 | {name: "Is invalid county code", param1: "XX", want: false}, 18 | {name: "Is invalid county code", param1: "YY", want: false}, 19 | {name: "Is invalid county code", param1: "YY", want: false}, 20 | } 21 | 22 | for _, test := range tests { 23 | t.Run(test.name, func(t *testing.T) { 24 | result := IsCountryCode(test.param1) 25 | 26 | if result != test.want { 27 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 28 | } 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /isdatauri.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | // A validator that checks if the string is a data uri format. 8 | // 9 | // ok := validatorgo.IsDataURI("data:,Hello%2C%20World%21") 10 | // fmt.Println(ok) // true 11 | // ok := validatorgo.IsDataURI("text/plain;base64,SGVsbG8sIFdvcmxkIQ==") 12 | // fmt.Println(ok) // false 13 | func IsDataURI(str string) bool { 14 | re := regexp.MustCompile(`^data:([-\w]+\/[-+\w.]+)?((?:;?[\w]+=[-\w]+)*)(;base64)?,(.*)$`) 15 | 16 | capGrp := re.FindStringSubmatch(str) 17 | 18 | if len(capGrp) == 0 { 19 | return false 20 | } 21 | 22 | mimeTyp := capGrp[1] 23 | basePrt := capGrp[4] 24 | 25 | return IsBase64(basePrt, &IsBase64Opts{UrlSafe: true}) && IsMimeType(mimeTyp) 26 | } 27 | -------------------------------------------------------------------------------- /isdatauri_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsDataURI(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid data uri's 12 | // {name: "The text/plain data Hello, World!", param1: "data:,Hello%2C%20World%21", want: true}, 13 | // {name: "Base64 encoded Hello, World!", param1: "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==", want: true}, 14 | // {name: "Base64 encoded PNG image", param1: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA", want: true}, 15 | // {name: "HTML data with URL encoding", param1: "data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E", want: true}, 16 | // Invalid data uri's 17 | {name: "Missing data prefix", param1: "text/plain;base64,SGVsbG8sIFdvcmxkIQ==", want: false}, 18 | {name: "Invalid base64 encoding", param1: "data:text/plain;base64,InvalidBase64", want: false}, 19 | {name: "Invalid MIME type", param1: "data:invalid/type;base64,SGVsbG8sIFdvcmxkIQ==", want: false}, 20 | {name: "Missing base64 or data encoding", param1: "data:image/png,", want: false}, 21 | } 22 | 23 | for _, test := range tests { 24 | t.Run(test.name, func(t *testing.T) { 25 | result := IsDataURI(test.param1) 26 | 27 | if result != test.want { 28 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /isdate.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // IsDate formats 8 | const ( 9 | StandardDateLayout = "2006-01-02" 10 | SlashDateLayout = "2006/01/02" 11 | DateTimeLayout = "2006-01-02 15:04:05" 12 | ISO8601Layout = "2006-01-02T15:04:05" 13 | ISO8601ZuluLayout = "2006-01-02T15:04:05Z" 14 | ISO8601WithMillisecondsLayout = "2006-01-02T15:04:05.000Z" 15 | ) 16 | 17 | var dateLayouts = [6]string{StandardDateLayout, SlashDateLayout, DateTimeLayout, ISO8601Layout, ISO8601ZuluLayout, ISO8601WithMillisecondsLayout} 18 | 19 | var ( 20 | isDateOptsDefaultFormat string = StandardDateLayout 21 | isDateOptsDefaultStrictMode bool = false 22 | ) 23 | 24 | // IsDateOpts is used to configure IsDate 25 | type IsDateOpts struct { 26 | Format string 27 | StrictMode bool 28 | } 29 | 30 | func dateMatchesAnyFormat(str string) bool { 31 | for _, format := range dateLayouts { 32 | _, err := time.Parse(format, str) 33 | if err == nil { 34 | return true 35 | } 36 | } 37 | return false 38 | } 39 | 40 | // A Validator that checks if the string is a valid date. e.g. 2002-07-15. 41 | // 42 | // IsDateOpts is a struct which can contain the keys Format, StrictMode. 43 | // 44 | // Format: is a string and defaults to validatorgo.StandardDateLayout if "any" or no value is provided. 45 | // 46 | // StrictMode: is a boolean and defaults to false. If StrictMode is set to true, the validator will reject strings different from Format. 47 | // 48 | // ok := validatorgo.IsDate("2006-01-02", &validatorgo.IsDateOpts{}) 49 | // fmt.Println(ok) // true 50 | // ok := validatorgo.IsDate("01/023/2006", &validatorgo.IsDateOpts{}) 51 | // fmt.Println(ok) // false 52 | func IsDate(str string, opts *IsDateOpts) bool { 53 | if opts == nil { 54 | opts = setIsDateOptsToDefault() 55 | } 56 | 57 | switch opts.Format { 58 | case StandardDateLayout, SlashDateLayout, DateTimeLayout, ISO8601Layout, ISO8601ZuluLayout, ISO8601WithMillisecondsLayout: 59 | case "", "any": 60 | opts.Format = isDateOptsDefaultFormat 61 | default: 62 | return false 63 | } 64 | 65 | if opts.StrictMode { 66 | _, err := time.Parse(opts.Format, str) 67 | return err == nil 68 | } else { 69 | return dateMatchesAnyFormat(str) 70 | } 71 | } 72 | 73 | func setIsDateOptsToDefault() *IsDateOpts { 74 | return &IsDateOpts{ 75 | Format: isDateOptsDefaultFormat, 76 | StrictMode: isDateOptsDefaultStrictMode, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /isdate_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsDate(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 *IsDateOpts 10 | want bool 11 | }{ 12 | // Valid cases 13 | {name: "Valid Standard Date", param1: "2007-01-02", param2: nil, want: true}, 14 | {name: "Valid Slash Date", param1: "2007/01/02", param2: &IsDateOpts{Format: SlashDateLayout}, want: true}, 15 | {name: "Valid DateTime", param1: "2007-01-02 15:04:05", param2: &IsDateOpts{Format: DateTimeLayout}, want: true}, 16 | {name: "Valid ISO8601", param1: "2007-01-02T15:04:05", param2: &IsDateOpts{Format: ISO8601Layout}, want: true}, 17 | {name: "Valid ISO8601 Zulu", param1: "2007-01-02T15:04:05Z", param2: &IsDateOpts{Format: ISO8601ZuluLayout}, want: true}, 18 | {name: "Valid ISO8601 with Milliseconds", param1: "2007-01-02T15:04:05.000Z", param2: &IsDateOpts{Format: ISO8601WithMillisecondsLayout}, want: true}, 19 | {name: "Valid Any Format", param1: "2007-01-02", param2: &IsDateOpts{Format: "any"}, want: true}, 20 | 21 | // Invalid cases 22 | {name: "Invalid Date Format", param1: "01-02-2007", param2: nil, want: false}, 23 | {name: "Invalid Slash Date", param1: "2007/01-02", param2: &IsDateOpts{Format: SlashDateLayout}, want: false}, 24 | {name: "Invalid ISO8601", param1: "2007-01-02T15:04", param2: &IsDateOpts{Format: ISO8601Layout}, want: false}, 25 | {name: "Invalid Strict Mode", param1: "2007/01/02", param2: &IsDateOpts{Format: StandardDateLayout, StrictMode: true}, want: false}, 26 | {name: "Invalid Format Option", param1: "2007-01-02", param2: &IsDateOpts{Format: "invalid"}, want: false}, 27 | {name: "Empty String", param1: "", param2: nil, want: false}, 28 | {name: "Whitespace String", param1: " ", param2: nil, want: false}, 29 | } 30 | 31 | for _, test := range tests { 32 | t.Run(test.name, func(t *testing.T) { 33 | result := IsDate(test.param1, test.param2) 34 | if result != test.want { 35 | t.Errorf("Test %s failed: got `%t`, wanted `%t`", test.name, result, test.want) 36 | } 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /isdivisibleby.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "math" 5 | "strconv" 6 | ) 7 | 8 | // A validator thats checks if the string is a number(integer not a floating point) that is divisible by another(integer not a floating point). 9 | // 10 | // ok := validatorgo.IsDivisibleBy("10", 2) 11 | // fmt.Println(ok) // true 12 | // ok := validatorgo.IsDivisibleBy("10", 3) 13 | // fmt.Println(ok) // false 14 | func IsDivisibleBy(str string, num int) bool { 15 | if num == 0 { 16 | return false 17 | } 18 | 19 | strInt, err := strconv.Atoi(str) 20 | 21 | if err != nil { 22 | return false 23 | } 24 | 25 | return math.Abs(float64(strInt%num)) == float64(0) 26 | } 27 | -------------------------------------------------------------------------------- /isdivisibleby_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsDivisibleBy(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 int 10 | want bool 11 | }{ 12 | { 13 | name: "Valid 10/2", 14 | param1: "10", 15 | param2: 2, 16 | want: true, 17 | }, 18 | { 19 | name: "Invalid 10/3", 20 | param1: "10", 21 | param2: 3, 22 | want: false, 23 | }, 24 | { 25 | name: "Invalid abc/3", 26 | param1: "abc", 27 | param2: 3, 28 | want: false, 29 | }, 30 | { 31 | name: "Invalid divisible by 0", 32 | param1: "7", 33 | param2: 0, 34 | want: false, 35 | }, 36 | } 37 | 38 | for _, test := range tests { 39 | t.Run(test.name, func(t *testing.T) { 40 | result := IsDivisibleBy(test.param1, test.param2) 41 | 42 | if result != test.want { 43 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 44 | } 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /isean.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | // IsEAN checks if the string is a valid [EAN] (European Article Number). 8 | // 9 | // ok := validatorgo.IsDecimal("4006381333931") 10 | // fmt.Println(ok) // true 11 | // ok := validatorgo.IsDecimal("123456789012") 12 | // fmt.Println(ok) // false 13 | // 14 | // [EAN]: https://en.wikipedia.org/wiki/International_Article_Number 15 | func IsEAN(str string) bool { 16 | length := len(str) 17 | if length != 8 && length != 13 { 18 | return false 19 | } 20 | 21 | // Check if all characters are digits 22 | for _, r := range str { 23 | if r < '0' || r > '9' { 24 | return false 25 | } 26 | } 27 | 28 | // Calculate checksum 29 | var sum int 30 | for i, r := range str[:length-1] { 31 | digit, _ := strconv.Atoi(string(r)) 32 | if (length == 8 && i%2 == 0) || (length == 13 && i%2 != 0) { 33 | sum += digit * 3 34 | } else { 35 | sum += digit 36 | } 37 | } 38 | 39 | // The checksum digit is the last digit of the EAN 40 | checksum := (10 - (sum % 10)) % 10 41 | lastDigit, _ := strconv.Atoi(string(str[length-1])) 42 | 43 | return checksum == lastDigit 44 | } 45 | -------------------------------------------------------------------------------- /isean_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsEAN(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | { 12 | name: "valid EAN-13", 13 | param1: "4006381333931", 14 | want: true, 15 | }, 16 | { 17 | name: "valid EAN-8", 18 | param1: "73513537", 19 | want: true, 20 | }, 21 | { 22 | name: "invalid EAN-13, checksum fails", 23 | param1: "4006381333932", 24 | want: false, 25 | }, 26 | { 27 | name: "invalid EAN-8", 28 | param1: "12345678", 29 | want: false, 30 | }, 31 | { 32 | name: "12 digits, not a valid EAN", 33 | param1: "123456789012", 34 | want: false, 35 | }, 36 | { 37 | name: "contains a letter, invalid", 38 | param1: "40063A1333931", 39 | want: false, 40 | }, 41 | } 42 | 43 | for _, test := range tests { 44 | t.Run(test.name, func(t *testing.T) { 45 | result := IsEAN(test.param1) 46 | 47 | if result != test.want { 48 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 49 | } 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /isempty.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | var ( 6 | isEmptyOptsDefaultIgnoreWhitespace bool = false 7 | ) 8 | 9 | // IsEmptyOpts is used to configure IsEmpty 10 | type IsEmptyOpts struct { 11 | IgnoreWhitespace bool 12 | } 13 | 14 | // A validator check if the string has a length of zero. 15 | // 16 | // IsEmptyOpts is a struct which defaults to { IgnoreWhitespace: false }. 17 | // 18 | // ok := validatorgo.IsEmpty("", &validatorgo.IsEmpty{}) 19 | // fmt.Println(ok) // true 20 | // ok := validatorgo.IsEmpty("abc", &validatorgo.IsEmpty{}) 21 | // fmt.Println(ok) // false 22 | func IsEmpty(str string, opts *IsEmptyOpts) bool { 23 | if opts == nil { 24 | opts = setIsEmptyOptsToDefault() 25 | } 26 | 27 | if opts.IgnoreWhitespace { 28 | return regexp.MustCompile(`^(\s+)?$`).MatchString(str) 29 | } else { 30 | return regexp.MustCompile(`^$`).MatchString(str) 31 | } 32 | } 33 | 34 | func setIsEmptyOptsToDefault() *IsEmptyOpts { 35 | return &IsEmptyOpts{ 36 | IgnoreWhitespace: isEmptyOptsDefaultIgnoreWhitespace, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /isempty_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsEmpty(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 *IsEmptyOpts 10 | want bool 11 | }{ 12 | {name: "Is empty", param1: "", param2: &IsEmptyOpts{IgnoreWhitespace: false}, want: true}, 13 | {name: "Is not empty", param1: "viyyv", param2: &IsEmptyOpts{IgnoreWhitespace: false}, want: false}, 14 | {name: "Is empty and contains tabs and spaces with ignore whitespace is false", param1: " ", param2: &IsEmptyOpts{IgnoreWhitespace: false}, want: false}, 15 | {name: "Is empty and contains tabs and spaces with ignore whitespace is true", param1: " ", param2: &IsEmptyOpts{IgnoreWhitespace: true}, want: true}, 16 | {name: "Is empty with nil config", param1: "", param2: nil, want: true}, 17 | {name: "Is not empty with nil config", param1: "text", param2: nil, want: false}, 18 | {name: "Is empty with tabs and spaces and nil config", param1: " ", param2: nil, want: false}, 19 | } 20 | 21 | for _, test := range tests { 22 | t.Run(test.name, func(t *testing.T) { 23 | result := IsEmpty(test.param1, test.param2) 24 | 25 | if result != test.want { 26 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 27 | } 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /isetheruemaddress.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator checks if the string is an [Ethereum address]. Does not validate address checksums. 6 | // 7 | // ok := validatorgo.IsDivisibleBy("0xeA0B9657892321121287128712BC78A89F989AAA") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsDivisibleBy("0xiuahbsndakjsd") 10 | // fmt.Println(ok) // false 11 | // 12 | // [Ethereum address]: https://ethereum.org/ 13 | func IsEthereumAddress(str string) bool { 14 | return regexp.MustCompile(`^0x[a-fA-F0-9]{40}$`).MatchString(str) 15 | } 16 | -------------------------------------------------------------------------------- /isetheruemaddress_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsEthereumAddress(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | { 12 | name: "Valid address with mixed case letters and numbers", 13 | param1: "0xeA0B9657892321121287128712BC78A89F989AAA", 14 | want: true, 15 | }, 16 | { 17 | name: "Valid address with all uppercase letters and numbers", 18 | param1: "0xBBAC6AABCFEBACEBCEABCEACB76767867676ACCC", 19 | want: true, 20 | }, 21 | { 22 | name: "Invalid address with non-hex characters", 23 | param1: "0xiuahbsndakjsd", 24 | want: false, 25 | }, 26 | { 27 | name: "Invalid address with incorrect length and special characters", 28 | param1: "0zzFGXD2E$", 29 | want: false, 30 | }, 31 | } 32 | 33 | for _, test := range tests { 34 | t.Run(test.name, func(t *testing.T) { 35 | result := IsEthereumAddress(test.param1) 36 | 37 | if result != test.want { 38 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 39 | } 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /isfqdn.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "unicode/utf8" 7 | ) 8 | 9 | var ( 10 | isFQDNOptsDefaultRequireTld bool = true 11 | isFQDNOptsDefaultAllowUnderscores bool = false 12 | isFQDNOptsDefaultAllowTrailingDot bool = false 13 | isFQDNOptsDefaultAllowNumericTld bool = false 14 | isFQDNOptsDefaultIgnoreMaxLength bool = false 15 | 16 | isFQDNMaxLength int = 253 17 | ) 18 | 19 | // IsFQDNOptsOpts is used to configure IsFQDNOpts 20 | type IsFQDNOpts struct { 21 | RequireTld bool 22 | AllowUnderscores bool 23 | AllowTrailingDot bool 24 | AllowNumericTld bool 25 | IgnoreMaxLength bool 26 | } 27 | 28 | // A validator that checks if the string is a fully qualified domain name (e.g. domain.com). 29 | // 30 | // IsFQDNOpts is a struct which defaults to { RequireTld: true, AllowUnderscores: false, AllowTrailingDot: false, AllowNumericTld: false, allow_wildcard: false, IgnoreMaxLength: false }. 31 | // 32 | // ok := validatorgo.IsFQDN("localhost", &validatorgo.IsFQDNOpts{}) 33 | // fmt.Println(ok) // true 34 | // ok := validatorgo.IsFQDN("example..com", &validatorgo.IsFQDNOpts{}) 35 | // fmt.Println(ok) // false 36 | func IsFQDN(str string, opts *IsFQDNOpts) bool { 37 | if opts == nil { 38 | opts = setIsFQDNOptsToDefault() 39 | } 40 | 41 | ignMaxLength := true 42 | ln := utf8.RuneCountInString(str) 43 | 44 | if !opts.IgnoreMaxLength && ln > isFQDNMaxLength { 45 | ignMaxLength = false 46 | } 47 | 48 | allowUnderScoreRe := `a-zA-Z0-9` 49 | if opts.AllowUnderscores { 50 | allowUnderScoreRe = `\w` 51 | } 52 | requireTldRe := "?" 53 | if opts.RequireTld { 54 | requireTldRe = "" 55 | } 56 | allowTrailingDotRe := `` 57 | if opts.AllowTrailingDot { 58 | allowTrailingDotRe = `\.?` 59 | } 60 | allowNumTldRe := `[a-zA-Z_]` 61 | if opts.AllowNumericTld { 62 | allowNumTldRe = `\w` 63 | } 64 | 65 | reStr := fmt.Sprintf(`^([%s])+(\.[%s]+)?\.%s%s+%s$`, allowUnderScoreRe, allowUnderScoreRe, requireTldRe, allowNumTldRe, allowTrailingDotRe) 66 | re := regexp.MustCompile(reStr) 67 | isValid := re.MatchString(str) 68 | return isValid && ignMaxLength 69 | } 70 | 71 | func setIsFQDNOptsToDefault() *IsFQDNOpts { 72 | return &IsFQDNOpts{ 73 | RequireTld: isFQDNOptsDefaultRequireTld, 74 | AllowUnderscores: isFQDNOptsDefaultAllowUnderscores, 75 | AllowTrailingDot: isFQDNOptsDefaultAllowTrailingDot, 76 | AllowNumericTld: isFQDNOptsDefaultAllowNumericTld, 77 | IgnoreMaxLength: isFQDNOptsDefaultIgnoreMaxLength, 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /isfreightcontainerid.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "math" 5 | "regexp" 6 | "strconv" 7 | "unicode/utf8" 8 | ) 9 | 10 | var alphaFreightNumVal = map[string]int{ 11 | "A": 10, 12 | "B": 12, 13 | "C": 13, 14 | "D": 14, 15 | "E": 15, 16 | "F": 16, 17 | "G": 17, 18 | "H": 18, 19 | "I": 19, 20 | "J": 20, 21 | "K": 21, 22 | "L": 23, 23 | "M": 24, 24 | "N": 25, 25 | "O": 26, 26 | "P": 27, 27 | "Q": 28, 28 | "R": 29, 29 | "S": 30, 30 | "T": 31, 31 | "U": 32, 32 | "V": 34, 33 | "W": 35, 34 | "X": 36, 35 | "Y": 37, 36 | "Z": 38, 37 | "0": 0, 38 | "1": 1, 39 | "2": 2, 40 | "3": 3, 41 | "4": 4, 42 | "5": 5, 43 | "6": 6, 44 | "7": 7, 45 | "8": 8, 46 | "9": 9, 47 | } 48 | 49 | // A validator that checks alias for IsISO6346, check if the string is a valid [ISO 6346] shipping container identification. 50 | // 51 | // ok := validatorgo.IsFreightContainerID("ABCU1234567") 52 | // fmt.Println(ok) // true 53 | // ok := validatorgo.IsFreightContainerID("AB123456789") 54 | // fmt.Println(ok) // false 55 | // 56 | // [ISO 6346]: https://en.wikipedia.org/wiki/ISO_6346 57 | func IsFreightContainerID(str string) bool { 58 | // Check if length is 11 59 | if utf8.RuneCountInString(str) != 11 { 60 | return false 61 | } 62 | 63 | re := regexp.MustCompile(`^([A-Z]{3})([UJZ])([0-9]{6})([0-9])$`) 64 | match := re.MatchString(str) 65 | if !match { 66 | return false 67 | } 68 | 69 | sum := 0 70 | for i := 0; i < 10; i++ { 71 | char := string(str[i]) 72 | mag, ok := alphaFreightNumVal[char] 73 | 74 | if !ok { 75 | return false 76 | } 77 | 78 | mul := math.Pow(2.00, float64(i)) 79 | 80 | sum += int(mul) * mag 81 | } 82 | 83 | actCheck := sum % 11 84 | givCheck, err := strconv.Atoi(string(str[10])) 85 | 86 | if err != nil { 87 | return false 88 | } 89 | 90 | return actCheck == givCheck 91 | } 92 | -------------------------------------------------------------------------------- /isfreightcontainerid_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsFreightContainerID(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid container ID (assuming correct check digit calculation) 12 | {name: "Valid container ID", param1: "CSQU3054383", want: true}, 13 | {name: "Valid container ID", param1: "ABCU1234560", want: true}, 14 | {name: "Valid container ID", param1: "MSKU6011672", want: true}, 15 | 16 | // Invalid container ID (wrong format - less than 11 characters) 17 | {name: "Too short", param1: "ABC12345", want: false}, 18 | // Invalid container ID (wrong format - more than 11 characters) 19 | {name: "Too long", param1: "ABCU123456789", want: false}, 20 | // Invalid container ID (wrong format - invalid characters in prefix) 21 | {name: "Invalid characters in prefix", param1: "AB11234567", want: false}, 22 | // Invalid container ID (invalid UJZ code) 23 | {name: "Invalid UJZ code", param1: "ABCD1234567", want: false}, // 'D' is not a valid UJZ code 24 | // Invalid container ID (non-numeric check digit) 25 | {name: "Non-numeric check digit", param1: "ABCU123456X", want: false}, 26 | // Invalid container ID (invalid check digit, different valid UJZ code) 27 | {name: "Invalid check digit with U", param1: "ABCU1234561", want: false}, 28 | // Edge case: all zeros in the numeric part 29 | {name: "All zeros in the numeric part", param1: "ABCU0000000", want: false}, // Check digit is likely incorrect 30 | } 31 | 32 | for _, test := range tests { 33 | t.Run(test.name, func(t *testing.T) { 34 | result := IsFreightContainerID(test.param1) 35 | 36 | if result != test.want { 37 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /isfullwidth.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | // A validator that checks if the string contains any full-width chars. 4 | // 5 | // ok := validatorgo.IsFullWidth("テスト") 6 | // fmt.Println(ok) // true 7 | // ok := validatorgo.IsFullWidth("abc123") 8 | // fmt.Println(ok) // false 9 | func IsFullWidth(str string) bool { 10 | for _, char := range str { 11 | if (char >= '\uFF00' && char <= '\uFFEF') || 12 | (char >= '\u4E00' && char <= '\u9FFF') || 13 | (char >= '\u3040' && char <= '\u309F') || 14 | (char >= '\u30A0' && char <= '\u30FF') { 15 | return true 16 | } 17 | } 18 | return false 19 | } 20 | -------------------------------------------------------------------------------- /isfullwidth_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsFullWidth(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | {name: "Only full-width Japanese characters", param1: "テスト", want: true}, // All full-width Katakana 12 | {name: "Only full-width CJK characters", param1: "漢字", want: true}, // Full-width Chinese characters 13 | {name: "Full-width and half-width mix", param1: "テストabc", want: true}, // Full-width Katakana with half-width English 14 | {name: "Full-width with numbers", param1: "123", want: true}, // Full-width numbers 15 | {name: "Half-width characters only", param1: "abc123", want: false}, // All half-width 16 | {name: "Mixed full-width and half-width", param1: "ABCdef", want: true}, // Full-width Latin with half-width Latin 17 | {name: "Empty string", param1: "", want: false}, // Empty string 18 | {name: "Only half-width punctuation", param1: ".,!?", want: false}, // All half-width punctuation 19 | {name: "Full-width punctuation", param1: "!#$%", want: true}, // Full-width punctuation 20 | } 21 | 22 | for _, test := range tests { 23 | t.Run(test.name, func(t *testing.T) { 24 | result := IsFullWidth(test.param1) 25 | 26 | if result != test.want { 27 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 28 | } 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ishalfwidth.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | // A validator that checks if the string contains any half-width chars. 4 | // 5 | // ok := validatorgo.IsHalfWidth("abc123") 6 | // fmt.Println(ok) // true 7 | // ok := validatorgo.IsHalfWidth("漢字テスト") 8 | // fmt.Println(ok) // false 9 | func IsHalfWidth(str string) bool { 10 | for _, char := range str { 11 | if char >= '\u0020' && char <= '\u007E' { // ASCII (half-width characters) 12 | return true 13 | } 14 | } 15 | return false 16 | } 17 | -------------------------------------------------------------------------------- /ishalfwidth_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsHalfWidth(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | {name: "Only half-width English characters", param1: "abc123", want: true}, // All half-width 12 | {name: "Half-width punctuation", param1: ".,!?", want: true}, // Half-width punctuation 13 | {name: "Mixed full-width and half-width", param1: "テストabc", want: true}, // Full-width Katakana with half-width English 14 | {name: "Only full-width Japanese characters", param1: "テスト", want: false}, // All full-width 15 | {name: "Only full-width numbers", param1: "123", want: false}, // All full-width numbers 16 | {name: "Empty string", param1: "", want: false}, // Empty string 17 | {name: "Mixed characters with no half-width", param1: "漢字テスト", want: false}, // All full-width CJK characters 18 | {name: "Only half-width special characters", param1: "@#$%^&*", want: true}, // Half-width special characters 19 | {name: "Only full-width punctuation", param1: "!#$%", want: false}, // All full-width punctuation 20 | {name: "Half-width symbols", param1: "©®", want: false}, 21 | } 22 | 23 | for _, test := range tests { 24 | t.Run(test.name, func(t *testing.T) { 25 | result := IsHalfWidth(test.param1) 26 | 27 | if result != test.want { 28 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ishash.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // hashAlgoRegex is the set of regex's for hashing algo 6 | var hashAlgoRegex = map[string]*regexp.Regexp{ 7 | "crc32": regexp.MustCompile(`^[a-fA-F0-9]{8}$`), // 8 hex characters 8 | "crc32b": regexp.MustCompile(`^[a-fA-F0-9]{8}$`), // 8 hex characters 9 | "md4": regexp.MustCompile(`^[a-fA-F0-9]{32}$`), // 32 hex characters 10 | "md5": regexp.MustCompile(`^[a-fA-F0-9]{32}$`), // 32 hex characters 11 | "ripemd128": regexp.MustCompile(`^[a-fA-F0-9]{32}$`), // 32 hex characters 12 | "ripemd160": regexp.MustCompile(`^[a-fA-F0-9]{40}$`), // 40 hex characters 13 | "sha1": regexp.MustCompile(`^[a-fA-F0-9]{40}$`), // 40 hex characters 14 | "sha256": regexp.MustCompile(`^[a-fA-F0-9]{64}$`), // 64 hex characters 15 | "sha384": regexp.MustCompile(`^[a-fA-F0-9]{96}$`), // 96 hex characters 16 | "sha512": regexp.MustCompile(`^[a-fA-F0-9]{128}$`), // 128 hex characters 17 | "tiger128": regexp.MustCompile(`^[a-fA-F0-9]{32}$`), // 32 hex characters 18 | "tiger160": regexp.MustCompile(`^[a-fA-F0-9]{40}$`), // 40 hex characters 19 | "tiger192": regexp.MustCompile(`^[a-fA-F0-9]{48}$`), // 48 hex characters 20 | } 21 | 22 | // A validator that checks if the string is a hash of type algorithm. 23 | // 24 | // Algorithm is one of ("crc32", "crc32b", "md4", "md5", "ripemd128", "ripemd160", "sha1", "sha256", "sha384", "sha512", "tiger128", "tiger160", "tiger192"), No checksum are calculated. 25 | // 26 | // ok := validatorgo.IsHash("d202ef8d", "crc32") 27 | // fmt.Println(ok) // true 28 | // ok := validatorgo.IsHash("d202ef8", "crc32") 29 | // fmt.Println(ok) // false 30 | func IsHash(str, algorithm string) bool { 31 | re, exist := hashAlgoRegex[algorithm] 32 | 33 | if !exist { 34 | return false 35 | } 36 | 37 | return re.MatchString(str) 38 | } 39 | -------------------------------------------------------------------------------- /ishexadecimal.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator that checks if the string is a hexadecimal number. 6 | // 7 | // ok := validatorgo.IsHexadecimal("1234567890abcdef") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsHexadecimal("abcdefg") 10 | // fmt.Println(ok) // false 11 | func IsHexadecimal(str string) bool { 12 | return regexp.MustCompile(`^[a-fA-F0-9]+$`).MatchString(str) 13 | } 14 | -------------------------------------------------------------------------------- /ishexadecimal_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsHexadecimal(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid Hexadecimal 12 | {name: "Lowercase Hexadecimal", param1: "abcdef", want: true}, 13 | {name: "Uppercase Hexadecimal", param1: "ABCDEF", want: true}, 14 | {name: "Mixed Case Hexadecimal", param1: "aBcDeF", want: true}, 15 | {name: "Hexadecimal with Digits", param1: "1234567890abcdef", want: true}, 16 | 17 | // Invalid Hexadecimal 18 | {name: "Non-Hexadecimal Characters", param1: "abcdez", want: false}, 19 | {name: "Empty String", param1: "", want: false}, 20 | {name: "Contains Spaces", param1: "a b c d e f", want: false}, 21 | {name: "Special Characters", param1: "abcd@123", want: false}, 22 | {name: "Alphabetic Characters Beyond 'f'", param1: "abcdefg", want: false}, 23 | } 24 | 25 | for _, test := range tests { 26 | t.Run(test.name, func(t *testing.T) { 27 | result := IsHexadecimal(test.param1) 28 | 29 | if result != test.want { 30 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ishexcolor.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator that checks if the string is a hexadecimal color. 6 | // 7 | // ok := validatorgo.IsHexColor("#abc") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsHexColor("#xyz") 10 | // fmt.Println(ok) // false 11 | func IsHexColor(str string) bool { 12 | return regexp.MustCompile(`^#(?:[0-9a-fA-F]{3}){1,2}$`).MatchString(str) 13 | } 14 | -------------------------------------------------------------------------------- /ishexcolor_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsHexColor(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid Hex Colors 12 | {name: "Valid 3-digit Hex Color", param1: "#abc", want: true}, 13 | {name: "Valid 6-digit Hex Color", param1: "#abcdef", want: true}, 14 | {name: "Valid 6-digit Hex Color Uppercase", param1: "#ABCDEF", want: true}, 15 | {name: "Valid 3-digit Hex Color Mixed Case", param1: "#AbC", want: true}, 16 | 17 | // Invalid Hex Colors 18 | {name: "No Hash Symbol", param1: "abcdef", want: false}, 19 | {name: "Invalid Length", param1: "#abcd", want: false}, 20 | {name: "Invalid Characters", param1: "#xyz", want: false}, 21 | {name: "Empty String", param1: "", want: false}, 22 | {name: "Contains Spaces", param1: "#a b c", want: false}, 23 | } 24 | 25 | for _, test := range tests { 26 | t.Run(test.name, func(t *testing.T) { 27 | result := IsHexColor(test.param1) 28 | 29 | if result != test.want { 30 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ishsl.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "regexp" 5 | "strconv" 6 | ) 7 | 8 | // A validator that checks if the string is an HSL (hue, saturation, lightness, optional alpha) color based on CSS Colors Level 4 specification. 9 | // 10 | // ok := validatorgo.IsHSL("hsl(360, 100%, 50%)") 11 | // fmt.Println(ok) // true 12 | // ok := validatorgo.IsHSL("hsl(360, 100%)") 13 | // fmt.Println(ok) // false 14 | func IsHSL(str string) bool { 15 | re := regexp.MustCompile(`^hsl(a)?\(\s?([^-+].*),\s?([^-+].*)%,\s?([^-+].*)%(,\s?([^-+].*))?\)$`) 16 | capGrp := re.FindStringSubmatch(str) 17 | 18 | if len(capGrp) == 0 { 19 | return false 20 | } 21 | 22 | a := capGrp[1] // "a" 23 | hueVal := capGrp[2] // "360" 24 | satVal := capGrp[3] // "50" 25 | lightVal := capGrp[4] // "50" 26 | alphaVal := capGrp[6] // "0.5" 27 | 28 | if hueFlt, err := strconv.ParseFloat(hueVal, 64); err != nil { 29 | return false 30 | } else { 31 | if hueFlt < 0 || hueFlt > 360 { 32 | return false 33 | } 34 | } 35 | 36 | if satFlt, err := strconv.ParseFloat(satVal, 64); err != nil { 37 | return false 38 | } else { 39 | if satFlt < 0 || satFlt > 100 { 40 | return false 41 | } 42 | } 43 | 44 | if lightFlt, err := strconv.ParseFloat(lightVal, 64); err != nil { 45 | return false 46 | } else { 47 | if lightFlt < 0 || lightFlt > 100 { 48 | return false 49 | } 50 | } 51 | 52 | if alphaVal != "" { 53 | if a == "" { 54 | return false 55 | } 56 | 57 | if alphaVal, err := strconv.ParseFloat(alphaVal, 64); err != nil { 58 | return false 59 | } else { 60 | if alphaVal < 0 || alphaVal > 1 { 61 | return false 62 | } 63 | } 64 | } 65 | 66 | return true 67 | } 68 | -------------------------------------------------------------------------------- /isiban_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsIBAN(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string // IBAN string 9 | param2 string // Country code 10 | want bool // Expected result 11 | }{ 12 | // Valid IBANs 13 | {name: "Valid IBAN for Germany", param1: "DE89370400440532013000", param2: "DE", want: true}, 14 | {name: "Valid IBAN for France", param1: "FR7630006000011234567890189", param2: "FR", want: true}, 15 | {name: "Valid IBAN for Spain", param1: "ES9121000418450200051332", param2: "ES", want: true}, 16 | {name: "Valid IBAN for United Kingdom", param1: "GB33BUKB20201555555555", param2: "GB", want: true}, 17 | {name: "Valid IBAN for Italy", param1: "IT60X0542811101000000123456", param2: "IT", want: true}, 18 | {name: "Valid IBAN for Switzerland", param1: "CH9300762011623852957", param2: "CH", want: true}, 19 | 20 | // Invalid IBANs 21 | {name: "Invalid IBAN for Germany (too short)", param1: "DE8937040044053201300", param2: "DE", want: false}, 22 | {name: "Invalid IBAN for France (wrong format)", param1: "FR7630006000A11234567890189", param2: "FR", want: false}, 23 | {name: "Invalid IBAN for Spain (invalid character)", param1: "ES91210004184502000513A32", param2: "ES", want: false}, 24 | {name: "Invalid IBAN for United Kingdom (wrong country)", param1: "GB33BUKB20201555555555", param2: "DE", want: false}, 25 | {name: "Invalid IBAN for Italy (incorrect length)", param1: "IT60X05428111010000001234", param2: "IT", want: false}, 26 | {name: "Invalid IBAN for unknown country code", param1: "CH9300762011623852957", param2: "XX", want: false}, 27 | } 28 | 29 | for _, test := range tests { 30 | t.Run(test.name, func(t *testing.T) { 31 | result := IsIBAN(test.param1, test.param2) 32 | 33 | if result != test.want { 34 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /isidentitycard.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | var identityCardLocaleRegex = map[string]*regexp.Regexp{ 8 | "LK": regexp.MustCompile(`^\d{9}[VXvx]$`), // Sri Lanka 9 | "PL": regexp.MustCompile(`^[A-Z]{3}\d{6}$`), // Poland 10 | "ES": regexp.MustCompile(`^\d{8}[A-Z]$`), // Spain 11 | "FI": regexp.MustCompile(`^\d{6}[+\-A]\d{3}[A-Z0-9]$`), // Finland 12 | "IN": regexp.MustCompile(`^\d{12}$`), // India 13 | "IT": regexp.MustCompile(`^[A-Z0-9]{9}$`), // Italy 14 | "IR": regexp.MustCompile(`^\d{10}$`), // Iran 15 | "MZ": regexp.MustCompile(`^\d{13}$`), // Mozambique 16 | "NO": regexp.MustCompile(`^\d{11}$`), // Norway 17 | "TH": regexp.MustCompile(`^\d{13}$`), // Thailand 18 | "zh-TW": regexp.MustCompile(`^[A-Z][12]\d{8}$`), // Taiwan 19 | "he-IL": regexp.MustCompile(`^\d{9}$`), // Israel 20 | "ar-LY": regexp.MustCompile(`^\d{12}$`), // Libya 21 | "ar-TN": regexp.MustCompile(`^\d{8}$`), // Tunisia 22 | "zh-CN": regexp.MustCompile(`^\d{17}[\dX]$`), // China 23 | "zh-HK": regexp.MustCompile(`^[A-Z]\d{6}[\dA]$`), // Hong Kong 24 | "PK": regexp.MustCompile(`^\d{13}$`), // Pakistan 25 | } 26 | 27 | // A validator that checks if the string is a valid identity card code. 28 | // 29 | // locale is one of ("LK", "PL", "ES", "FI", "IN", "IT", "IR", "MZ", "NO", "TH", "zh-TW", "he-IL", "ar-LY", "ar-TN", "zh-CN", "zh-HK", "PK") OR "any". If "any" is used, function will check if any of the locales match. Defaults to "any" if locale not present. No checksums calculated. 30 | // 31 | // ok := validatorgo.IsIdentityCard("123456789V", "LK") 32 | // fmt.Println(ok) // true 33 | // ok := validatorgo.IsIdentityCard("12345678X", "LK") 34 | // fmt.Println(ok) // false 35 | func IsIdentityCard(str, locale string) bool { 36 | if locale == "" { 37 | locale = "any" 38 | } 39 | 40 | re, ok := identityCardLocaleRegex[locale] 41 | 42 | if ok { 43 | return re.MatchString(str) 44 | } else { 45 | if locale != "any" { 46 | return false 47 | } 48 | 49 | for _, reg := range identityCardLocaleRegex { 50 | matches := reg.MatchString(str) 51 | if matches { 52 | return true 53 | } 54 | } 55 | return false 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /isimei.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "regexp" 5 | "strconv" 6 | ) 7 | 8 | var ( 9 | isIMEIOptsDefaultAllowHyphens bool = false 10 | ) 11 | 12 | // IsIMEIOpts is used to configure IsIMEI 13 | type IsIMEIOpts struct { 14 | AllowHyphens bool 15 | } 16 | 17 | // A validator that checks if the string is a valid [IMEI number]. IMEI should be of format ############### or ##-######-######-#. 18 | // 19 | // IsIMEIOpts is a struct which can contain the keys AllowHyphens. Defaults to first format. 20 | // 21 | // If AllowHyphens is set to true, the validator will validate the second format. 22 | // 23 | // ok := validatorgo.IsIMEI("490154203237518", &IsIMEIOpts{}) 24 | // fmt.Println(ok) // true 25 | // ok := validatorgo.IsIMEI("359043377500085", &IsIMEIOpts{}) 26 | // fmt.Println(ok) // false 27 | // 28 | // [IMEI number]: https://en.wikipedia.org/wiki/International_Mobile_Equipment_Identity 29 | func IsIMEI(str string, opts *IsIMEIOpts) bool { 30 | if opts == nil { 31 | opts = setIsIMEIOptsToDefault() 32 | } 33 | 34 | var re *regexp.Regexp 35 | 36 | if opts.AllowHyphens { 37 | re = regexp.MustCompile(`^\d{2}-?\d{6}-?\d{6}-?\d$`) 38 | } else { 39 | re = regexp.MustCompile(`^\d{15}$`) 40 | } 41 | 42 | if !re.MatchString(str) { 43 | return false 44 | } 45 | 46 | strWithoutHyphens := stripHyphens(str) 47 | 48 | strLen := len(strWithoutHyphens) 49 | isSecond := false 50 | sum := 0 51 | 52 | for i := strLen - 1; i >= 0; i-- { 53 | dig, _ := strconv.Atoi(string(strWithoutHyphens[i])) 54 | 55 | if isSecond { 56 | dbDig := dig * 2 57 | 58 | if dbDig > 9 { 59 | dbDig = digitSum(dbDig) 60 | } 61 | 62 | sum += dbDig 63 | } else { 64 | sum += dig 65 | } 66 | 67 | isSecond = !isSecond 68 | } 69 | 70 | return sum%10 == 0 71 | } 72 | 73 | func setIsIMEIOptsToDefault() *IsIMEIOpts { 74 | return &IsIMEIOpts{ 75 | AllowHyphens: isIMEIOptsDefaultAllowHyphens, 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /isin.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | // A validator that checks if the string is in a slice of allowed values. 4 | // 5 | // ok := validatorgo.IsIn("apple", []string{"apple", "banana", "grape"}) 6 | // fmt.Println(ok) // true 7 | // ok := validatorgo.IsIn("orange", []string{"apple", "banana", "grape"}) 8 | // fmt.Println(ok) // false 9 | func IsIn(str string, values []string) bool { 10 | for _, val := range values { 11 | if str == val { 12 | return true 13 | } 14 | } 15 | 16 | return false 17 | } 18 | -------------------------------------------------------------------------------- /isin_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsIn(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 []string 10 | want bool 11 | }{ 12 | {name: "Value is present", param1: "apple", param2: []string{"apple", "banana", "grape"}, want: true}, 13 | {name: "Value is not present", param1: "orange", param2: []string{"apple", "banana", "grape"}, want: false}, 14 | } 15 | 16 | for _, test := range tests { 17 | t.Run(test.name, func(t *testing.T) { 18 | result := IsIn(test.param1, test.param2) 19 | 20 | if result != test.want { 21 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 22 | } 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /isip.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | // ipVersionRegex is the set of versions and their validating regex 8 | var ipVersionRegex = map[string]*regexp.Regexp{ 9 | "4": regexp.MustCompile(`^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$`), 10 | "6": regexp.MustCompile(`^([[:xdigit:]]{1,4}(?::[[:xdigit:]]{1,4}){7}|::|:(?::[[:xdigit:]]{1,4}){1,6}|[[:xdigit:]]{1,4}:(?::[[:xdigit:]]{1,4}){1,5}|(?:[[:xdigit:]]{1,4}:){2}(?::[[:xdigit:]]{1,4}){1,4}|(?:[[:xdigit:]]{1,4}:){3}(?::[[:xdigit:]]{1,4}){1,3}|(?:[[:xdigit:]]{1,4}:){4}(?::[[:xdigit:]]{1,4}){1,2}|(?:[[:xdigit:]]{1,4}:){5}:[[:xdigit:]]{1,4}|(?:[[:xdigit:]]{1,4}:){1,6}:)$`), 11 | } 12 | 13 | // A validator that checks if the string is an IP (version 4 or 6). If version is not provide, both versions "4" and "6" will be checked. 14 | // 15 | // ok := validatorgo.IsIP("192.168.0.1", "4") 16 | // fmt.Println(ok) // true 17 | // ok := validatorgo.IsIP("256.256.256.256", "4") 18 | // fmt.Println(ok) // false 19 | func IsIP(str, version string) bool { 20 | if version == "" { 21 | for _, re := range ipVersionRegex { 22 | matches := re.MatchString(str) 23 | if matches { 24 | return true 25 | } 26 | } 27 | return false 28 | } else { 29 | re, ok := ipVersionRegex[version] 30 | 31 | if !ok { 32 | return false 33 | } 34 | 35 | return re.MatchString(str) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /isip_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsIP(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 string 10 | want bool 11 | }{ 12 | {name: "Valid version but valid IPv4", param1: "192.168.0.1", param2: "4", want: true}, 13 | {name: "Valid version but valid IPv6, no", param1: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", param2: "6", want: true}, 14 | {name: "Valid version 4, no version provided", param1: "192.168.0.1", param2: "", want: true}, 15 | {name: "Valid version 6, no version provided", param1: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", param2: "", want: true}, 16 | {name: "Invalid version 4, no version provided", param1: "a2.168.0.1", param2: "", want: false}, 17 | {name: "Invalid version 6, no version provided", param1: "z001:0dbz:85a3:0000:0000:8a2e:0370:7334", param2: "", want: false}, 18 | {name: "valid version 4, incorrect version provided", param1: "a2.168.0.1", param2: "20", want: false}, 19 | {name: "valid version 6, incorrect version provided", param1: "z001:0dbz:85a3:0000:0000:8a2e:0370:7334", param2: "xyz", want: false}, 20 | 21 | {name: "Valid IPv4 Google's public DNS", param1: "8.8.8.8", param2: "4", want: true}, 22 | {name: "Invalid IPv4 Octets must be between 0 and 255", param1: "256.256.256.256", param2: "4", want: false}, 23 | {name: "Valid IPv4 Leading zeros are allowed", param1: "192.168.001.001", param2: "4", want: true}, 24 | {name: "Invalid IPv4 CIDR notation is not a valid pure IP address", param1: "192.168.0.1/24", param2: "4", want: false}, 25 | 26 | {name: "Valid IPv6", param1: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", param2: "6", want: true}, 27 | {name: "Valid IPv6 Shortened form using `::`", param1: "2001:db8::2:1", param2: "6", want: true}, 28 | {name: "Invalid IPv6 should have 8 groups", param1: "2001:0db8:85a3:0000:0000:8a2e:0370:7334:abcd", param2: "6", want: false}, 29 | {name: "Invalid IPv6 hexadecimal character 'g'", param1: "2001:db8:85a3::g", param2: "6", want: false}, 30 | } 31 | 32 | for _, test := range tests { 33 | t.Run(test.name, func(t *testing.T) { 34 | result := IsIP(test.param1, test.param2) 35 | 36 | if result != test.want { 37 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /isisbn.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | // "fmt" 5 | "strconv" 6 | ) 7 | 8 | // A validator that checks if the string is an [ISBN]. 9 | // 10 | // version: ISBN version to compare to. Accepted values are "10" and "13". If none provided, both will be tested. 11 | // 12 | // ok := validatorgo.IsISBN("0-7167-0344-0", "10") 13 | // fmt.Println(ok) // true 14 | // ok := validatorgo.IsISBN("0-7168-0344-0", "10") 15 | // fmt.Println(ok) // false 16 | // 17 | // [ISBN]: https://en.wikipedia.org/wiki/ISBN 18 | func IsISBN(str, version string) bool { 19 | strNum := stripDashesAndSpaces(str) 20 | 21 | if version == "10" { 22 | return valIsISBNv10(strNum) 23 | } else if version == "13" { 24 | return valIsISBNv13(strNum) 25 | } else { 26 | return valIsISBNv10(strNum) || valIsISBNv13(strNum) 27 | } 28 | } 29 | 30 | func valIsISBNv10(str string) bool { 31 | ln := len(str) 32 | sum := 0 33 | 34 | if ln != 10 { 35 | return false 36 | } 37 | 38 | for i, char := range str { 39 | pos := ln - i 40 | num, err := strconv.Atoi(string(char)) 41 | 42 | if err != nil { 43 | return false 44 | } 45 | 46 | sum += pos * num 47 | } 48 | 49 | rem := sum % 11 50 | 51 | return rem == 0 52 | } 53 | 54 | func valIsISBNv13(str string) bool { 55 | ln := len(str) 56 | sum := 0 57 | 58 | if ln != 13 { 59 | return false 60 | } 61 | 62 | for i, char := range str { 63 | pos := ln - i 64 | num, err := strconv.Atoi(string(char)) 65 | 66 | if err != nil { 67 | return false 68 | } 69 | 70 | if pos%2 == 0 { 71 | sum += 3 * num 72 | // fmt.Printf("3 * %d\n", num) 73 | } else { 74 | sum += 1 * num 75 | // fmt.Printf("1 * %d\n", num) 76 | } 77 | } 78 | 79 | // fmt.Println("Sum is", sum) 80 | 81 | rem := sum % 10 82 | 83 | return rem == 0 84 | } 85 | -------------------------------------------------------------------------------- /isisbn_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsValidISBN(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 string 10 | want bool 11 | }{ 12 | // v10 13 | {name: "Is valid ISBN v10", param1: "0-7167-0344-0", param2: "10", want: true}, 14 | {name: "Is invalid ISBN v10", param1: "0-7168-0344-0", param2: "10", want: false}, 15 | {name: "Is invalid ISBN v10, wrong length", param1: "0-7168-0344-09", param2: "10", want: false}, 16 | {name: "Is invalid ISBN v10, alphabets included", param1: "0-7rt8-0344-09", param2: "10", want: false}, 17 | // v13 18 | {name: "Is valid ISBN v13", param1: "978-0-7167-0344-0", param2: "13", want: true}, 19 | {name: "Is invalid ISBN v13", param1: "978-9-7167-0344-0", param2: "13", want: false}, 20 | {name: "Is invalid ISBN v13, wrong length", param1: "978-0-7167-0344-07", param2: "13", want: false}, 21 | {name: "Is invalid ISBN v13, alphabets included", param1: "abc-0-7167-0344-07", param2: "13", want: false}, 22 | // not v10 or v13 23 | {name: "Version is not provided", param1: "978-0-7167-0344-0", param2: "", want: true}, 24 | {name: "Version is also not provided", param1: "0-7167-0344-0", param2: "", want: true}, 25 | } 26 | 27 | for _, test := range tests { 28 | t.Run(test.name, func(t *testing.T) { 29 | result := IsISBN(test.param1, test.param2) 30 | 31 | if result != test.want { 32 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 33 | } 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /isisin.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | var r = regexp.MustCompile(`^[A-Z]{2}[A-Z0-9]{9}\d$`) 6 | 7 | var inc = [2][10]int{ 8 | {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, 9 | {0, 2, 4, 6, 8, 1, 3, 5, 7, 9}, 10 | } 11 | 12 | // A validator that checks if the string is an [ISIN] (stock/security identifier). 13 | // 14 | // ok := validatorgo.IsISIN("US0378331005") 15 | // fmt.Println(ok) // true 16 | // ok := validatorgo.IsISIN("US0373831005") 17 | // fmt.Println(ok) // false 18 | // 19 | // [ISIN]: https://en.wikipedia.org/wiki/International_Securities_Identification_Number 20 | func IsISIN(str string) bool { 21 | if !r.MatchString(str) { 22 | return false 23 | } 24 | var sum, p int 25 | for i := 10; i >= 0; i-- { 26 | p = 1 - p 27 | if d := str[i]; d < 'A' { 28 | sum += inc[p][d-'0'] 29 | } else { 30 | d -= 'A' 31 | sum += inc[p][d%10] 32 | p = 1 - p 33 | sum += inc[p][d/10+1] 34 | } 35 | } 36 | sum += int(str[11] - '0') 37 | return sum%10 == 0 38 | } 39 | -------------------------------------------------------------------------------- /isisin_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsISIN(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid ISIN 12 | {name: "Valid ISIN", param1: "US0378331005", want: true}, 13 | {name: "Valid ISIN", param1: "AU0000XVGZA3", want: true}, 14 | {name: "Valid ISIN", param1: "AU0000VXGZA3", want: true}, 15 | // Invalid ISIN 16 | {name: "The transposition typo is caught by the checksum constraint.", param1: "US0373831005", want: false}, 17 | {name: "The substitution typo is caught by the format constraint.", param1: "U50378331005", want: false}, 18 | {name: "The duplication typo is caught by the format constraint.", param1: "US03378331005", want: false}, 19 | } 20 | 21 | for _, test := range tests { 22 | t.Run(test.name, func(t *testing.T) { 23 | result := IsISIN(test.param1) 24 | 25 | if result != test.want { 26 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 27 | } 28 | }) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /isiso31661alpha2_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsISO31661Alpha2(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid IsISO31661Alpha2 12 | {name: "Valid IsISO31661Alpha2, english", param1: "EN", want: true}, 13 | {name: "Valid IsISO31661Alpha2, french", param1: "FR", want: true}, 14 | {name: "Valid IsISO31661Alpha2, spanish", param1: "ES", want: true}, 15 | {name: "Valid IsISO31661Alpha2, german", param1: "DE", want: true}, 16 | {name: "Valid IsISO31661Alpha2, chinese", param1: "ZH", want: true}, 17 | // Invalid IsISO31661Alpha2 18 | {name: "Not two letters", param1: "eng", want: false}, 19 | {name: "Not a valid code", param1: "frz", want: false}, 20 | {name: "Contains numbers", param1: "123", want: false}, 21 | {name: "Only one letter", param1: "e", want: false}, 22 | {name: "Lowercase, should be uppercase", param1: "fr", want: false}, 23 | } 24 | 25 | for _, test := range tests { 26 | t.Run(test.name, func(t *testing.T) { 27 | result := IsISO31661Alpha2(test.param1) 28 | 29 | if result != test.want { 30 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /isiso31661alpha3.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | var AllISO31661Alpha3 = [...]string{"AFG", "ALB", "DZA", "ASM", "AND", "AGO", "AIA", "ATA", "ATG", "ARG", "ARM", "ABW", "AUS", "AUT", "AZE", 4 | "BHS", "BHR", "BGD", "BRB", "BLR", "BEL", "BLZ", "BEN", "BMU", "BTN", "BOL", "BIH", "BWA", "BVT", "BRA", 5 | "IOT", "BRN", "BGR", "BFA", "BDI", "CPV", "KHM", "CMR", "CAN", "CYM", "CAF", "TCD", "CHL", "CHN", "CXR", 6 | "CCK", "COL", "COM", "COG", "COD", "COK", "CRI", "CIV", "HRV", "CUB", "CUW", "CYP", "CZE", "DNK", "DJI", 7 | "DMA", "DOM", "ECU", "EGY", "SLV", "GNQ", "ERI", "EST", "SWZ", "ETH", "FLK", "FRO", "FJI", "FIN", "FRA", 8 | "GUF", "PYF", "ATF", "GAB", "GMB", "GEO", "DEU", "GHA", "GIB", "GRC", "GRL", "GRD", "GLP", "GUM", "GTM", 9 | "GGY", "GIN", "GNB", "GUY", "HTI", "HMD", "VAT", "HND", "HKG", "HUN", "ISL", "IND", "IDN", "IRN", "IRQ", 10 | "IRL", "IMN", "ISR", "ITA", "JAM", "JPN", "JEY", "JOR", "KAZ", "KEN", "KIR", "PRK", "KOR", "KWT", "KGZ", 11 | "LAO", "LVA", "LBN", "LSO", "LBR", "LBY", "LIE", "LTU", "LUX", "MAC", "MDG", "MWI", "MYS", "MDV", "MLI", 12 | "MLT", "MHL", "MTQ", "MRT", "MUS", "MYT", "MEX", "FSM", "MDA", "MCO", "MNG", "MNE", "MSR", "MAR", "MOZ", 13 | "MMR", "NAM", "NRU", "NPL", "NLD", "NCL", "NZL", "NIC", "NER", "NGA", "NIU", "NFK", "MKD", "MNP", "NOR", 14 | "OMN", "PAK", "PLW", "PAN", "PNG", "PRY", "PER", "PHL", "PCN", "POL", "PRT", "PRI", "QAT", "MKD", "ROU", 15 | "RUS", "RWA", "REU", "BLM", "SHN", "KNA", "LCA", "MAF", "SPM", "VCT", "WSM", "SMR", "STP", "SAU", "SEN", 16 | "SRB", "SYC", "SLE", "SGP", "SXM", "SVK", "SVN", "SLB", "SOM", "ZAF", "SGS", "SSD", "ESP", "LKA", "SDN", 17 | "SUR", "SWE", "CHE", "SYR", "TWN", "TJK", "TZA", "THA", "TLS", "TGO", "TKL", "TON", "TTO", "TUN", "TUR", 18 | "TKM", "TCA", "TUV", "UGA", "UKR", "ARE", "GBR", "USA", "URY", "UZB", "VUT", "VEN", "VNM", "WLF", "ESH", 19 | "YEM", "ZMB", "ZWE", 20 | } 21 | 22 | // A validator that checks if the string is a valid ISO 3166-1 alpha-3 officially assigned country code. 23 | // 24 | // ok := validatorgo.IsISO31661Alpha3("ABW") 25 | // fmt.Println(ok) // true 26 | // ok := validatorgo.IsISO31661Alpha3("ES") 27 | // fmt.Println(ok) // false 28 | // 29 | // [ISO 3166-1 alpha-3]: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-3 30 | func IsISO31661Alpha3(str string) bool { 31 | return IsIn(str, AllISO31661Alpha3[:]) 32 | } 33 | -------------------------------------------------------------------------------- /isiso31661alpha3_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsISO31661Alpha3(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid IsISO31661Alpha3 12 | {name: "Valid IsISO31661Alpha3, Aruba", param1: "ABW", want: true}, 13 | {name: "Valid IsISO31661Alpha3, Benin", param1: "BEN", want: true}, 14 | {name: "Valid IsISO31661Alpha3, Colombia", param1: "COL", want: true}, 15 | {name: "Valid IsISO31661Alpha3, Guam", param1: "GUM", want: true}, 16 | {name: "Valid IsISO31661Alpha3, Zambia", param1: "ZMB", want: true}, 17 | // Invalid IsISO31661Alpha3 18 | {name: "Not three letters", param1: "ES", want: false}, 19 | {name: "Not a valid code", param1: "RGS", want: false}, 20 | {name: "Contains numbers", param1: "123", want: false}, 21 | {name: "Only one letter", param1: "e", want: false}, 22 | {name: "Lowercase, should be uppercase", param1: "sdn", want: false}, 23 | } 24 | 25 | for _, test := range tests { 26 | t.Run(test.name, func(t *testing.T) { 27 | result := IsISO31661Alpha3(test.param1) 28 | 29 | if result != test.want { 30 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /isiso31661numeric.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | var AllISO31661Numeric = [...]string{"004", "008", "010", "012", "016", "020", "024", "028", "031", "032", "036", "040", "044", "048", "050", "051", "052", "056", "060", "064", "068", "070", "072", "074", "076", "084", "086", "090", "092", "096", "100", "104", "108", "112", "116", "120", "124", "132", "136", "140", "144", "148", "150", "154", "156", "158", "162", "166", "170", "174", "175", "178", "180", "184", "188", "192", "196", "200", "204", "208", "212", "214", "218", "222", "226", "231", "232", "233", "234", "236", "240", "244", "248", "250", "254", "258", "260", "262", "266", "268", "270", "275", "276", "288", "292", "296", "300", "304", "308", "312", "316", "320", "324", "328", "332", "334", "336", "340", "344", "348", "352", "356", "360", "364", "368", "372", "376", "380", "384", "388", "392", "398", "400", "404", "408", "410", "414", "417", "418", "422", "426", "428", "430", "434", "438", "440", "442", "446", "450", "454", "458", "462", "466", "470", "474", "478", "480", "484", "492", "496", "498", "499", "500", "504", "508", "512", "516", "520", "524", "528", "531", "533", "534", "536", "540", "548", "552", "556", "562", "566", "570", "574", "578", "580", "581", "583", "584", "585", "586", "591", "598", "600", "604", "608", "612", "616", "620", "624", "626", "630", "634", "638", "642", "643", "646", "652", "654", "659", "660", "662", "663", "666", "670", "674", "678", "682", "686", "688", "690", "694", "702", "703", "704", "705", "706", "710", "716", "724", "728", "729", "732", "740", "744", "748", "750", "754", "758", "760", "762", "764", "768", "772", "776", "780", "784", "788", "792", "795", "796", "798", "800", "804", "808", "812", "816", "818", "826", "831", "832", "833", "834", "840", "850", "854", "858", "860", "862", "876", "882", "887", "894"} 4 | 5 | // A validator that checks check if the string is a valid [ISO 3166-1] numeric officially assigned country code. 6 | // 7 | // ok := validatorgo.IsISO31661Numeric("032") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsISO31661Numeric("56") 10 | // fmt.Println(ok) // false 11 | // 12 | // [ISO 3166-1]: https://en.wikipedia.org/wiki/ISO_3166-1_numeric 13 | func IsISO31661Numeric(str string) bool { 14 | return IsIn(str, AllISO31661Numeric[:]) 15 | } 16 | -------------------------------------------------------------------------------- /isiso31661numeric_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsISO31661Numeric(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid IsISO31661Numeric 12 | {name: "Valid IsISO31661Numeric, Argentina", param1: "032", want: true}, 13 | {name: "Valid IsISO31661Numeric, Belgium", param1: "056", want: true}, 14 | {name: "Valid IsISO31661Numeric, Cuba", param1: "192", want: true}, 15 | {name: "Valid IsISO31661Numeric, India", param1: "356", want: true}, 16 | {name: "Valid IsISO31661Numeric, Oman", param1: "512", want: true}, 17 | // Invalid IsISO31661Numeric 18 | {name: "Not three numbers", param1: "56", want: false}, 19 | {name: "Not a valid code", param1: "525", want: false}, 20 | {name: "Contains letters", param1: "abc", want: false}, 21 | {name: "Only one number", param1: "9", want: false}, 22 | } 23 | 24 | for _, test := range tests { 25 | t.Run(test.name, func(t *testing.T) { 26 | result := IsISO31661Numeric(test.param1) 27 | 28 | if result != test.want { 29 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 30 | } 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /isiso4217.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | var AllISO4217Codes = [...]string{ 4 | "AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", 5 | "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", 6 | "BRL", "BSD", "BTN", "BWP", "BYN", "BZD", "CAD", "CDF", "CHE", "CHF", 7 | "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP", "CVE", 8 | "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", 9 | "FKP", "FOK", "GBP", "GEL", "GGP", "GHS", "GIP", "GMD", "GNF", "GTQ", 10 | "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "IMP", "INR", 11 | "IQD", "IRR", "ISK", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KID", 12 | "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", 13 | "LSL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRU", 14 | "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN", "NAD", "NGN", "NIO", 15 | "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", 16 | "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", 17 | "SEK", "SGD", "SHP", "SLL", "SOS", "SRD", "SSP", "STN", "SVC", "SYP", 18 | "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TVD", "TWD", 19 | "TZS", "UAH", "UGX", "USD", "USN", "UYI", "UYU", "UYW", "UZS", "VED", 20 | "VES", "VND", "VUV", "WST", "XAF", "XAG", "XAU", "XBA", "XBB", "XBC", 21 | "XBD", "XCD", "XDR", "XOF", "XPD", "XPF", "XPT", "XSU", "XTS", "XUA", 22 | "YER", "ZAR", "ZMW", "ZWL", 23 | } 24 | 25 | // A validator that checks if the string is a valid [ISO 4217] officially assigned currency code. 26 | // 27 | // ok := validatorgo.IsIso4217("AED") 28 | // fmt.Println(ok) // true 29 | // ok := validatorgo.IsIso4217("AE") 30 | // fmt.Println(ok) // false 31 | // 32 | // [ISO 4217]: https://en.wikipedia.org/wiki/ISO_4217 33 | func IsIso4217(str string) bool { 34 | return IsIn(str, AllISO4217Codes[:]) 35 | } 36 | -------------------------------------------------------------------------------- /isiso4217_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsIso4217(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid IsIso4217 12 | {name: "Valid IsIso4217, UNITED ARAB EMIRATES (THE)", param1: "AED", want: true}, 13 | {name: "Valid IsIso4217, TURKEY", param1: "TRY", want: true}, 14 | {name: "Valid IsIso4217,YEMEN", param1: "YER", want: true}, 15 | {name: "Valid IsIso4217, TUNISA", param1: "TND", want: true}, 16 | {name: "Valid IsIso4217, SWEDEN", param1: "SEK", want: true}, 17 | // Invalid IsIso4217 18 | {name: "Not three letters", param1: "AE", want: false}, 19 | {name: "Not a valid code", param1: "CHD", want: false}, 20 | {name: "Contains numbers", param1: "123", want: false}, 21 | {name: "Only one letter", param1: "A", want: false}, 22 | } 23 | 24 | for _, test := range tests { 25 | t.Run(test.name, func(t *testing.T) { 26 | result := IsIso4217(test.param1) 27 | 28 | if result != test.want { 29 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 30 | } 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /isiso6346.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "math" 5 | "regexp" 6 | "strconv" 7 | ) 8 | 9 | // iso6346numValues is the set of alphabets and their iso6346 numerical values 10 | var iso6346numValues = map[string]int{ 11 | "A": 10, "B": 12, "C": 13, "D": 14, "E": 15, "F": 16, "G": 17, "H": 18, "I": 19, "J": 20, "K": 21, "L": 23, "M": 24, 12 | "N": 25, "O": 26, "P": 27, "Q": 28, "R": 29, "S": 30, "T": 31, "U": 32, "V": 34, "W": 35, "X": 36, "Y": 37, "Z": 38, 13 | "0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, 14 | } 15 | 16 | // A validator that checks if the string is a valid ISO 6346 shipping container identification. 17 | // 18 | // ok := validatorgo.IsISO6346("CSQU3054383") 19 | // fmt.Println(ok) // true 20 | // ok := validatorgo.IsISO6346("CSQX3054383") 21 | // fmt.Println(ok) // false 22 | // 23 | // [ISO 6346]: https://en.wikipedia.org/wiki/ISO_6346 24 | func IsISO6346(str string) bool { 25 | re := regexp.MustCompile(`^([A-Z]{3})([UJZR])(\d{6})(\d)$`) 26 | capGrps := re.FindStringSubmatch(str) 27 | if capGrps == nil { 28 | return false 29 | } 30 | 31 | checkDig := capGrps[4] 32 | length := len(str) 33 | sum := 0 34 | for ind, char := range str { 35 | if length-1 == ind { 36 | break 37 | } 38 | 39 | numVal, ok := iso6346numValues[string(char)] 40 | 41 | if !ok { 42 | return false 43 | } 44 | 45 | sum += int(float64(numVal) * math.Pow(2.00, float64(ind))) 46 | } 47 | 48 | rem := sum % 11 49 | remStr := strconv.Itoa(rem) 50 | 51 | return remStr == checkDig 52 | } 53 | -------------------------------------------------------------------------------- /isiso6346_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsISO6346(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid ISO6346 12 | {name: "Valid ISO6346", param1: "CSQU3054383", want: true}, 13 | {name: "Valid ISO6346", param1: "HLCU1234568", want: true}, 14 | {name: "Valid ISO6346", param1: "MAEU9876542", want: true}, 15 | {name: "Valid ISO6346", param1: "APLZ3216542", want: true}, 16 | {name: "Valid ISO6346", param1: "CMAU7654327", want: true}, 17 | // Invalid ISO6346 18 | {name: "Invalid owner code", param1: "CSQX3054383", want: false}, 19 | {name: "Contains letters in the serial number", param1: "HLCU12345AB", want: false}, 20 | {name: "Serial number too short", param1: "MAEU98765", want: false}, 21 | {name: "Serial number too long", param1: "APLZ32165488", want: false}, 22 | {name: "Missing one digit in serial number", param1: "CMAU765432", want: false}, 23 | {name: "Wrong check digit", param1: "CMAU7654329", want: false}, 24 | } 25 | 26 | for _, test := range tests { 27 | t.Run(test.name, func(t *testing.T) { 28 | result := IsISO6346(test.param1) 29 | 30 | if result != test.want { 31 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /isiso6391.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | var AllISO6391Codes = [...]string{"aa", "ab", "af", "ak", "am", "an", "ar", "as", "av", "ay", "az", "ba", "be", "bg", "bh", "bi", "bm", "bn", "bo", "br", "bs", "ca", "ce", "ch", "co", "cr", "cs", "cv", "cy", "da", "de", "dv", "dz", "ee", "el", "en", "es", "et", "eu", "fa", "ff", "fi", "fj", "fo", "fr", "fy", "ga", "gd", "gl", "gn", "gu", "gv", "ha", "he", "hi", "ho", "hr", "ht", "hu", "hy", "hz", "id", "ig", "ii", "ik", "io", "is", "it", "iu", "ja", "jv", "ka", "kg", "ki", "kj", "kk", "kl", "km", "kn", "ko", "kr", "ks", "ku", "kv", "kw", "ky", "lb", "lg", "li", "ln", "lo", "lt", "lu", "lv", "mg", "mh", "mi", "mk", "ml", "mn", "mr", "ms", "mt", "my", "na", "nb", "nd", "ne", "ng", "nl", "nn", "no", "nr", "nv", "ny", "oc", "oj", "om", "or", "os", "pa", "pl", "ps", "pt", "qu", "rm", "rn", "ro", "ru", "rw", "sa", "sc", "sd", "se", "sg", "si", "sk", "sl", "sm", "sn", "so", "sq", "sr", "ss", "st", "su", "sv", "sw", "ta", "te", "tg", "th", "ti", "tk", "tl", "tn", "to", "tr", "ts", "tt", "tw", "ty", "ug", "uk", "ur", "uz", "ve", "vi", "wa", "wo", "xh", "yi", "yo", "za", "zh", "zu"} 4 | 5 | // A validator that checks if the string is a valid [ISO 639-1] language code. 6 | // 7 | // ok := validatorgo.IsISO6391("en") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsISO6391("eng") 10 | // fmt.Println(ok) // false 11 | // 12 | // [ISO 639-1]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes 13 | func IsISO6391(str string) bool { 14 | return IsIn(str, AllISO6391Codes[:]) 15 | } 16 | -------------------------------------------------------------------------------- /isiso6391_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsISO6391(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid IsISO6391 12 | {name: "Valid IsISO6391, english", param1: "en", want: true}, 13 | {name: "Valid IsISO6391, french", param1: "fr", want: true}, 14 | {name: "Valid IsISO6391, spanish", param1: "es", want: true}, 15 | {name: "Valid IsISO6391, german", param1: "de", want: true}, 16 | {name: "Valid IsISO6391, chinese", param1: "zh", want: true}, 17 | // Invalid IsISO6391 18 | {name: "Not two letters", param1: "eng", want: false}, 19 | {name: "Not a valid code", param1: "frz", want: false}, 20 | {name: "Contains numbers", param1: "123", want: false}, 21 | {name: "Only one letter", param1: "e", want: false}, 22 | {name: "Uppercase, should be lowercase", param1: "FR", want: false}, 23 | } 24 | 25 | for _, test := range tests { 26 | t.Run(test.name, func(t *testing.T) { 27 | result := IsISO6391(test.param1) 28 | 29 | if result != test.want { 30 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /isiso8601.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | var ( 8 | isISO8601OptsDefaultStrict bool = false 9 | isISO8601OptsDefaultStrictSeparator bool = false 10 | ) 11 | 12 | // IsISO8601Opts is used to configure IsISO8601 13 | type IsISO8601Opts struct { 14 | Strict bool // must be a date that has actually happened, is happening or will happen. 15 | StrictSeparator bool // must be delimited by T 16 | } 17 | 18 | // A validator that checks if the string is a valid [ISO 8601] date. 19 | // 20 | // IsISO8601Opts is a struct which defaults to { Strict: false, StrictSeparator: false }. 21 | // 22 | // If Strict is true, date strings with invalid dates like 2009-02-29 will be invalid. 23 | // 24 | // If StrictSeparator is true, date strings with date and time separated by anything other than a T will be invalid. 25 | // 26 | // ok := validatorgo.IsISO8601("2023-09-05", &validatorgo.IsISO8601Opts{}) 27 | // fmt.Println(ok) // true 28 | // ok := validatorgo.IsISO8601("2023-13-05T14:30:00", &validatorgo.IsISO8601Opts{}) 29 | // fmt.Println(ok) // false 30 | // 31 | // [ISO 8601]: https://en.wikipedia.org/wiki/ISO_8601 32 | func IsISO8601(str string, opts *IsISO8601Opts) bool { 33 | if opts == nil { 34 | opts = setIsISO8601OptsToDefault() 35 | } 36 | 37 | var re *regexp.Regexp 38 | 39 | if opts.StrictSeparator { 40 | re = regexp.MustCompile(`^(\d{4})(-(0[1-9]|1[0-2])(-([12]\d|0[1-9]|3[01]))([T\s]((([01]\d|2[0-3])((:)[0-5]\d))([\:]\d+)?)?(:[0-5]\d([\.]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)$`) 41 | } else { 42 | re = regexp.MustCompile(`^(\d{4})([-\/\. ](0[1-9]|1[0-2])([-\/\. ]([12]\d|0[1-9]|3[01]))([T\s]((([01]\d|2[0-3])([: \.])[0-5]\d)(([: \.])\d+)?([: \.][0-5]\d([\.]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3])[: \.]?([0-5]\d)?)?)?)?)$`) 43 | } 44 | 45 | capGrps := re.FindStringSubmatch(str) 46 | 47 | if capGrps == nil { 48 | return false 49 | } 50 | 51 | if opts.Strict { 52 | year, month, day := capGrps[1], capGrps[3], capGrps[5] 53 | 54 | return validYearMonthDay(year, month, day) 55 | } 56 | 57 | return true 58 | } 59 | 60 | func setIsISO8601OptsToDefault() *IsISO8601Opts { 61 | return &IsISO8601Opts{ 62 | Strict: isISO8601OptsDefaultStrict, 63 | StrictSeparator: isISO8601OptsDefaultStrictSeparator, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /isisrc.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | // A validator that checks if the string is an [ISRC]. 9 | // 10 | // allowHyphens will allow codes with dashes present CC-XXX-YY-NNNNN 11 | // 12 | // ok := validatorgo.IsISRC("AASKG1912345", false) 13 | // fmt.Println(ok) // true 14 | // ok := validatorgo.IsISRC("AA-SKG-19-12345", false) 15 | // fmt.Println(ok) // false 16 | // 17 | // [ISRC]: https://en.wikipedia.org/wiki/International_Standard_Recording_Code 18 | func IsISRC(str string, allowHyphens bool) bool { 19 | var char string 20 | 21 | if allowHyphens { 22 | char = "-?" 23 | } 24 | 25 | re := regexp.MustCompile(fmt.Sprintf(`^([A-Z]{2})%s([A-Z]{3})%s(\d{2})%s(\d{5})$`, char, char, char)) 26 | capGrp := re.FindStringSubmatch(str) 27 | 28 | if capGrp == nil { 29 | return false 30 | } 31 | 32 | cntryCode := capGrp[1] 33 | 34 | return IsISO31661Alpha2(cntryCode) 35 | } 36 | -------------------------------------------------------------------------------- /isisrc_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsISRC(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 bool 10 | want bool 11 | }{ 12 | // Valid ISRC without hyphens 13 | {name: "Valid ISRC without hyphens", param1: "AASKG1912345", param2: false, want: true}, 14 | {name: "Valid ISRC without hyphens", param1: "USABC2021234", param2: false, want: true}, 15 | {name: "Valid ISRC without hyphens", param1: "GBXYZ2112345", param2: false, want: true}, 16 | {name: "Valid ISRC without hyphens", param1: "FRDEF1912345", param2: false, want: true}, 17 | // Invalid ISRC without hyphens 18 | {name: "Valid ISRC but contains hyphens", param1: "AA-SKG-19-12345", param2: false, want: false}, 19 | {name: "Too few digits in the unique identifier section", param1: "USABC201234", param2: false, want: false}, 20 | {name: "Starts with numbers instead of country code", param1: "1234ABC202001234", param2: false, want: false}, 21 | {name: "Too many characters in the unique identifier", param1: "USABC20987654321", param2: false, want: false}, 22 | // Valid ISRC with hyphens 23 | {name: "Valid ISRC with hyphens", param1: "AA-SKG-19-12345", param2: true, want: true}, 24 | {name: "Valid ISRC without hyphens", param1: "USABC2021234", param2: true, want: true}, 25 | {name: "Valid ISRC with hyphens", param1: "GB-XYZ-21-12345", param2: true, want: true}, 26 | {name: "Valid ISRC without hyphens", param1: "FRDEF1912345", param2: true, want: true}, 27 | } 28 | 29 | for _, test := range tests { 30 | t.Run(test.name, func(t *testing.T) { 31 | result := IsISRC(test.param1, test.param2) 32 | 33 | if result != test.want { 34 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /isissn_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsISSN(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 *IsISSNOpts 10 | want bool 11 | }{ 12 | // Valid default options 13 | {name: "valid ISSN with hyphen", param1: "0378-5955", param2: nil, want: true}, 14 | {name: "Valid ISSN with hyphen", param1: "0317-8471", param2: nil, want: true}, 15 | {name: "Valid ISSN without hyphen", param1: "34244530", param2: nil, want: true}, 16 | {name: "Valid ISSN without hyphen", param1: "92657087", param2: nil, want: true}, 17 | // Valid CaseSensitive true 18 | {name: "Valid ISSN with uppercase 'X'", param1: "2434-561X", param2: &IsISSNOpts{CaseSensitive: true}, want: true}, 19 | {name: "Valid ISSN without hyphen", param1: "98765434", param2: &IsISSNOpts{CaseSensitive: true}, want: true}, 20 | // Valid RequireHyphen true 21 | {name: "Valid ISSN without hyphen", param1: "2049-3630", param2: &IsISSNOpts{RequireHyphen: true}, want: true}, 22 | // Valid CaseSensitive and RequireHyphen true 23 | {name: "Valid ISSN without hyphen", param1: "2434-561X", param2: &IsISSNOpts{RequireHyphen: true, CaseSensitive: true}, want: true}, 24 | // Invalid default options 25 | {name: "Too short", param1: "1234567", param2: &IsISSNOpts{}, want: false}, 26 | {name: "Too Long", param1: "1234567", param2: &IsISSNOpts{}, want: false}, 27 | {name: "Invalid character", param1: "0317-847A", param2: &IsISSNOpts{}, want: false}, 28 | // Invalid CaseSensitive true 29 | {name: "Invalid because 'X' is lowercase", param1: "2434-561x", param2: &IsISSNOpts{CaseSensitive: true}, want: false}, 30 | {name: "Invalid begins with non numbers", param1: "2abc-561x", param2: &IsISSNOpts{CaseSensitive: true}, want: false}, 31 | // Invalid RequireHyphen true 32 | {name: "Valid ISSN without hyphen", param1: "20493630", param2: &IsISSNOpts{RequireHyphen: true}, want: false}, 33 | // Valid CaseSensitive and RequireHyphen true 34 | {name: "Valid ISSN without hyphen", param1: "2434561x", param2: &IsISSNOpts{RequireHyphen: true, CaseSensitive: true}, want: false}, 35 | } 36 | 37 | for _, test := range tests { 38 | t.Run(test.name, func(t *testing.T) { 39 | result := IsISSN(test.param1, test.param2) 40 | 41 | if result != test.want { 42 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 43 | } 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /isjson.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "encoding/json" 4 | 5 | // type IsJSONOpts struct { 6 | // AllowPrimitives bool 7 | // } 8 | 9 | // A validator that checks if the string is valid JSON (note: uses json.Valid()). 10 | // 11 | // ok := validatorgo.IsJSON(`{"name": "John", "age": 30, "city": "New York"}`) 12 | // fmt.Println(ok) // true 13 | // ok := validatorgo.IsJSON(`{'name': 'John', 'age': 30}`) 14 | // fmt.Println(ok) // false 15 | func IsJSON(str string) bool { 16 | return json.Valid([]byte(str)) 17 | } 18 | -------------------------------------------------------------------------------- /isjson_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsJSON(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid JSON 12 | {name: "Basic json object", param1: `{"name": "John", "age": 30, "city": "New York"}`, want: true}, 13 | {name: "Nested JSON object", param1: `{"person": {"name": "Alice", "age": 25}, "status": true}`, want: true}, 14 | {name: "JSON array", param1: `[{"id": 1, "value": "A"}, {"id": 2, "value": "B"}]`, want: true}, 15 | {name: "Empty object", param1: `{}`, want: true}, 16 | {name: "Empty array", param1: `[]`, want: true}, 17 | {name: "Only string", param1: `"Hello World"`, want: true}, 18 | {name: "Only number", param1: `30`, want: true}, 19 | {name: "Only boolean True", param1: `true`, want: true}, 20 | {name: "Only boolean False", param1: `false`, want: true}, 21 | {name: "Only null", param1: `null`, want: true}, 22 | // Invalid JSON 23 | {name: "Single quotes around strings", param1: `{'name': 'John', 'age': 30}`, want: false}, 24 | {name: "Trailing comma", param1: `{"name": "John", "age": 30,}`, want: false}, 25 | {name: "Unquoted keys", param1: `{name: "John", age: 30}`, want: false}, 26 | {name: "Incorrect boolean values", param1: `{"status": True}`, want: false}, 27 | {name: "Unterminated JSON object or array", param1: `{"name": "John", "age": 30`, want: false}, 28 | {name: "Mixed data types without proper structure", param1: `{123: "abc", "key": value}`, want: false}, 29 | } 30 | 31 | for _, test := range tests { 32 | t.Run(test.name, func(t *testing.T) { 33 | result := IsJSON(test.param1) 34 | 35 | if result != test.want { 36 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 37 | } 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /isjwt.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "encoding/base64" 5 | "regexp" 6 | ) 7 | 8 | // A validator that checks if the string is valid JWT token. 9 | // 10 | // ok := validatorgo.IsJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c") 11 | // fmt.Println(ok) // true 12 | // ok := validatorgo.IsJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c") 13 | // fmt.Println(ok) // false 14 | func IsJWT(str string) bool { 15 | re := regexp.MustCompile(`^([a-zA-Z0-9_=]+)\.([a-zA-Z0-9_=]+)\.([a-zA-Z0-9_\-\+\/=]*)`) 16 | capGrp := re.FindStringSubmatch(str) 17 | 18 | if capGrp == nil { 19 | return false 20 | } 21 | 22 | payload := capGrp[2] 23 | 24 | rawDecodedTxt, err := base64.RawURLEncoding.DecodeString(payload) 25 | if err != nil { 26 | return false 27 | } 28 | jsonDecodedTxt := string(rawDecodedTxt) 29 | 30 | return IsJSON(jsonDecodedTxt) 31 | } 32 | -------------------------------------------------------------------------------- /isjwt_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsJWT(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid JWT 12 | {name: "Valid JWT with HS256", param1: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", want: true}, 13 | {name: "Valid JWT with RS256", param1: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NSIsImV4cCI6MTYwNjMwOTg5OH0.DQOqtr6lUVxH3nIRcfT7jq0MI5QBSwyFgxj1gPP6U5A", want: true}, 14 | {name: "Valid JWT with custom claims", param1: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImphbmVkbyIsImFkbWluIjp0cnVlfQ.VCkOUqBwdp4hXdZoBh0F4WtYXRVZtFD93PcH6ozZ1Qs", want: true}, 15 | // Invalid JWT 16 | {name: "Missing periods (.)", param1: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", want: false}, 17 | {name: "Improperly Base64-encoded payload", param1: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9%lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", want: false}, 18 | {name: "No signature", param1: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", want: false}, 19 | {name: "Invalid JSON structure in payload", param1: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwLCJuYW1lIjoiSm9obiBEb2UifQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", want: false}, 20 | } 21 | 22 | for _, test := range tests { 23 | t.Run(test.name, func(t *testing.T) { 24 | result := IsJWT(test.param1) 25 | 26 | if result != test.want { 27 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 28 | } 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /islatlong.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | var ( 8 | isLatLongOptsDefaultCheckDMS bool = false 9 | ) 10 | 11 | func setIsLatLongOptsToDefault() *IsLatLongOpts { 12 | return &IsLatLongOpts{ 13 | CheckDMS: isLatLongOptsDefaultCheckDMS, 14 | } 15 | } 16 | 17 | // IsLatLongOpts is used to configure IsLatLong 18 | type IsLatLongOpts struct { 19 | CheckDMS bool // checks DMS(degrees, minutes, and seconds) 20 | } 21 | 22 | // A validator that checks if the string is a valid latitude-longitude coordinate in the format lat,long or lat, long. 23 | // 24 | // IsLatLongOpts is a struct that defaults to { CheckDMS: false }. 25 | // 26 | // Pass CheckDMS as true to validate DMS(degrees, minutes, and seconds) latitude-longitude format. 27 | // 28 | // ok := validatorgo.IsLatLong("40.730610,-73.935242" , &validatorgo.IsLatLongOpts{}) 29 | // fmt.Println(ok) // true 30 | // ok := validatorgo.IsLatLong("91,181" , &validatorgo.IsLatLongOpts{}) 31 | // fmt.Println(ok) // false 32 | func IsLatLong(str string, opts *IsLatLongOpts) bool { 33 | if opts == nil { 34 | opts = setIsLatLongOptsToDefault() 35 | } 36 | 37 | var re *regexp.Regexp 38 | if opts.CheckDMS { 39 | re = regexp.MustCompile(`^([+\-]?[0-8]?\d|90)[°˚º\s-]+([0-5]?\d)['′\s-]+([0-5]?\d(\.\d*)?)["¨˝\s-]*([NnSs])[\s,]+([+\-]?(0?\d?\d|1[0-7]\d|180))[°˚º\s-]+([0-5]?\d)['′\s-]+([0-5]?\d(\.\d*)?)["¨˝\s-]*([EeWw])$`) 40 | return re.MatchString(str) 41 | } else { 42 | re = regexp.MustCompile(`^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$`) 43 | return re.MatchString(str) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /islength.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "unicode/utf8" 5 | ) 6 | 7 | var ( 8 | isLengthOptsDefaultMin uint = 0 9 | isLengthOptsDefaultMax *uint = nil 10 | ) 11 | 12 | // IsLengthOpts is used to configure IsLength 13 | type IsLengthOpts struct { 14 | Min uint // Minimum character length 15 | Max *uint // Maximum character length 16 | } 17 | 18 | // A validator that checks if the string's length falls in a range. 19 | // 20 | // IsLengthOpts is a struct which defaults to { Min: 0, Max: nil }. 21 | // 22 | // Note: this function takes into account surrogate pairs. 23 | // 24 | // ok := validatorgo.IsLength("hello", &validatorgo.IsLengthOpts{Min: 3}) 25 | // fmt.Println(ok) // true 26 | // ok := validatorgo.IsLength("hi", &validatorgo.IsLengthOpts{Min: 3}) 27 | // fmt.Println(ok) // false 28 | func IsLength(str string, opts *IsLengthOpts) bool { 29 | if opts == nil { 30 | opts = setIsLengthOptsToDefault() 31 | } 32 | 33 | length := uint(utf8.RuneCountInString(str)) 34 | 35 | withinLimits := true 36 | 37 | if opts.Max != nil { 38 | isMax := *(opts.Max) >= length 39 | withinLimits = withinLimits && isMax 40 | } 41 | 42 | isMin := opts.Min <= length 43 | withinLimits = withinLimits && isMin 44 | 45 | return withinLimits 46 | } 47 | 48 | func setIsLengthOptsToDefault() *IsLengthOpts { 49 | return &IsLengthOpts{ 50 | Min: isLengthOptsDefaultMin, 51 | Max: isLengthOptsDefaultMax, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /islength_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsLength(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 *IsLengthOpts 10 | want bool 11 | }{ 12 | // Valid examples 13 | {name: "Default config used", param1: "hello", param2: nil, want: true}, 14 | {name: "Only minimum length specified", param1: "hello", param2: &IsLengthOpts{Min: 3}, want: true}, 15 | {name: "Only minimum length specified", param1: "world", param2: &IsLengthOpts{Min: 5}, want: true}, 16 | {name: "Both minimum and maximum length specified", param1: "example", param2: &IsLengthOpts{Min: 2, Max: uintPtr(10)}, want: true}, 17 | {name: "Both minimum and maximum length specified", param1: "longword", param2: &IsLengthOpts{Min: 5, Max: uintPtr(10)}, want: true}, 18 | {name: "No options, default to checking if string exists", param1: "", param2: &IsLengthOpts{Min: 0}, want: true}, 19 | {name: "No options, default to checking if string exists", param1: "a", param2: &IsLengthOpts{Min: 1}, want: true}, 20 | {name: "String with spaces", param1: " spaced ", param2: &IsLengthOpts{Min: 5, Max: uintPtr(15)}, want: true}, 21 | // Invalid examples 22 | {name: "Below the minimum length", param1: "hi", param2: &IsLengthOpts{Min: 3}, want: false}, 23 | {name: "Above the maximum length", param1: "toolongword", param2: &IsLengthOpts{Min: 2, Max: uintPtr(10)}, want: false}, 24 | {name: "Empty string with a positive minimum", param1: "", param2: &IsLengthOpts{Min: 1}, want: false}, 25 | {name: "String containing spaces (spaces count toward length)", param1: " ", param2: &IsLengthOpts{Min: 3, Max: uintPtr(5)}, want: false}, 26 | {name: "Missing max constraint but exceeding the default allowed length", param1: "overlimit", param2: &IsLengthOpts{Min: 1, Max: uintPtr(5)}, want: false}, 27 | } 28 | 29 | for _, test := range tests { 30 | t.Run(test.name, func(t *testing.T) { 31 | result := IsLength(test.param1, test.param2) 32 | 33 | if result != test.want { 34 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /islicenseplate.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | var localeLicensePlateRegex = map[string]*regexp.Regexp{ 6 | "cs-CZ": regexp.MustCompile(`^[A-Z]{1,2}[0-9]{1,5}[A-Z]{0,2}$`), // Czech Republic (e.g., AA12345) 7 | "de-DE": regexp.MustCompile(`^[A-Z]{1,3}-[A-Z]{1,2}-[0-9]{1,4}$`), // Germany (e.g., B-MA-1234) 8 | "de-LI": regexp.MustCompile(`^[0-9]{1,5}$`), // Liechtenstein (e.g., 12345) 9 | "en-IN": regexp.MustCompile(`^[A-Z]{2}[0-9]{1,2}[A-Z]{1,2}[0-9]{4}$`), // India (e.g., DL12AB1234) 10 | "en-SG": regexp.MustCompile(`^[A-Z]{1,3}[0-9]{1,4}[A-Z]{1}$`), // Singapore (e.g., SGP1234A) 11 | "en-PK": regexp.MustCompile(`^[A-Z]{2,3}-[0-9]{1,4}$`), // Pakistan (e.g., ABC-1234) 12 | "es-AR": regexp.MustCompile(`^[A-Z]{2}[0-9]{3}[A-Z]{2}$`), // Argentina (e.g., AB123CD) 13 | "hu-HU": regexp.MustCompile(`^[A-Z]{3}-[0-9]{3}$`), // Hungary (e.g., ABC-123) 14 | "pt-BR": regexp.MustCompile(`^[A-Z]{3}-[0-9]{4}$`), // Brazil (e.g., ABC-1234) 15 | "pt-PT": regexp.MustCompile(`^[0-9]{2}-[0-9]{2}-[A-Z]{2}$`), // Portugal (e.g., 12-34-AB) 16 | "sq-AL": regexp.MustCompile(`^[A-Z]{2}-[0-9]{3}-[A-Z]{2}$`), // Albania (e.g., AB-123-CD) 17 | "sv-SE": regexp.MustCompile(`^[A-Z]{3}[0-9]{3}$`), // Sweden (e.g., ABC123) 18 | } 19 | 20 | // A validator that checks if the string matches the format of a country's license plate. 21 | // 22 | // locale is one of ("cs-CZ", "de-DE", "de-LI", "en-IN", "en-SG", "en-PK", "es-AR", "hu-HU", "pt-BR", "pt-PT", "sq-AL", "sv-SE", "any") 23 | // 24 | // ok := validatorgo.IsLength("hello", &validatorgo.IsLengthOpts{Min: 3}) 25 | // fmt.Println(ok) // true 26 | // ok := validatorgo.IsLength("hi", &validatorgo.IsLengthOpts{Min: 3}) 27 | // fmt.Println(ok) // false 28 | func IsLicensePlate(str string, locale string) bool { 29 | re, ok := localeLicensePlateRegex[locale] 30 | 31 | if !ok { 32 | if locale != "any" && locale != "" { 33 | return false 34 | } 35 | 36 | for _, reg := range localeLicensePlateRegex { 37 | match := reg.MatchString(str) 38 | 39 | if match { 40 | return true 41 | } 42 | 43 | } 44 | return false 45 | } 46 | 47 | return re.MatchString(str) 48 | } 49 | -------------------------------------------------------------------------------- /islocale_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsLocale(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid Locales 12 | {name: "Is valid locale", param1: "ca_ES", want: true}, 13 | {name: "Is valid locale", param1: "it_IT", want: true}, 14 | {name: "Is valid locale", param1: "uk_UA", want: true}, 15 | {name: "Is valid locale", param1: "zu_ZA", want: true}, 16 | // Invalid Locales 17 | {name: "Is invalid locale", param1: "en_XY", want: false}, 18 | {name: "Is invalid locale", param1: "fr_QQ", want: false}, 19 | {name: "Is invalid locale", param1: "fs_ZZ", want: false}, 20 | } 21 | 22 | for _, test := range tests { 23 | t.Run(test.name, func(t *testing.T) { 24 | result := IsLocale(test.param1) 25 | 26 | if result != test.want { 27 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 28 | } 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /islowercase.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "strings" 4 | 5 | // A validator that checks if the string is lowercase. 6 | // 7 | // ok := validatorgo.IsLowerCase("hello") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsLowerCase("WORLD") 10 | // fmt.Println(ok) // false 11 | func IsLowerCase(str string) bool { 12 | return str == strings.ToLower(str) && str != strings.ToUpper(str) 13 | } 14 | -------------------------------------------------------------------------------- /islowercase_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsLowerCase(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid Example 12 | {name: "Is lowercase", param1: "hello", want: true}, 13 | // Invalid Example 14 | {name: "Empty string is not lowercase", param1: "", want: false}, 15 | {name: "Is not lowercase", param1: "WORLD", want: false}, 16 | {name: "Only few letter are lowercase", param1: "ExaMPle", want: false}, 17 | } 18 | 19 | for _, test := range tests { 20 | t.Run(test.name, func(t *testing.T) { 21 | result := IsLowerCase(test.param1) 22 | 23 | if result != test.want { 24 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 25 | } 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /isluhnnumber.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "strconv" 4 | 5 | // A validator that checks if the string passes the [Luhn algorithm] check. 6 | // 7 | // ok := validatorgo.IsLuhnNumber("4532015112830366") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsLuhnNumber("4532015112830367") 10 | // fmt.Println(ok) // false 11 | // 12 | // [Luhn algorithm]: https://en.wikipedia.org/wiki/Luhn_algorithm 13 | func IsLuhnNumber(str string) bool { 14 | var ( 15 | len = len(str) 16 | sum = 0 17 | isSecond = false 18 | ) 19 | 20 | for i := len - 1; i >= 0; i-- { 21 | char := str[i] 22 | d, err := strconv.Atoi(string(char)) 23 | 24 | if err != nil { 25 | return false 26 | } 27 | 28 | if isSecond { 29 | d = d * 2 30 | if d > 9 { 31 | d = digitSum(d) 32 | } 33 | } 34 | 35 | sum += d 36 | isSecond = !isSecond 37 | } 38 | 39 | return sum%10 == 0 40 | } 41 | -------------------------------------------------------------------------------- /isluhnnumber_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsLuhnNumber(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid Luhn number 12 | {name: "Valid Visa card number", param1: "4532015112830366", want: true}, 13 | {name: "Valid Discover card number", param1: "6011514433546201", want: true}, 14 | {name: "Valid Visa card number", param1: "4485275742308327", want: true}, 15 | {name: "Valid IMEI number", param1: "490154203237518", want: true}, 16 | {name: "A valid Luhn number", param1: "79927398713", want: true}, 17 | {name: "A valid Luhn number", param1: "1234567812345670", want: true}, 18 | // Invalid Luhn number 19 | {name: "Invalid due to incorrect checksum", param1: "4532015112830367", want: false}, 20 | {name: "Invalid Discover card number", param1: "6011514433546202", want: false}, 21 | {name: "Invalid due to incorrect checksum", param1: "490154203237519", want: false}, 22 | {name: "Invalid Luhn number", param1: "79927398714", want: false}, 23 | {name: "Invalid due to incorrect checksum", param1: "1234567812345671", want: false}, 24 | {name: "Invalid due to incorrect non number included", param1: "12345abc12345671", want: false}, 25 | } 26 | 27 | for _, test := range tests { 28 | t.Run(test.name, func(t *testing.T) { 29 | result := IsLuhnNumber(test.param1) 30 | 31 | if result != test.want { 32 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 33 | } 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ismacaddress.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | var ( 9 | isMacAddressOptsDefault bool = false 10 | isMacAddressOptsType *string = nil 11 | ) 12 | 13 | // IsMacAddressOpts is used to configure IsMacAddress 14 | type IsMacAddressOpts struct { 15 | NoSeparators bool // will not allow separators 16 | Type *string // mac address type 17 | } 18 | 19 | // A validator that checks if the string is a MAC address. 20 | // 21 | // IsMacAddressOpts is a struct which defaults to { NoSeparators: false, Type: nil }. 22 | // 23 | // If NoSeparators is true, the validator will allow MAC addresses without separators. 24 | // 25 | // Also, it allows the use of hyphens, spaces or dots e.g. "01 02 03 04 05 ab", "01-02-03-04-05-ab" or "0102.0304.05ab". 26 | // 27 | // The Type is a pointer to "48" or "64", defaults to "48" 28 | // 29 | // ok := validatorgo.IsMacAddress("00:1A:2B:3C:4D:5E", validatorgo.IsMacAddressOpts{}) 30 | // fmt.Println(ok) // true 31 | // ok := validatorgo.IsMacAddress("00:1A:2B:3C:4D:ZZ", validatorgo.IsMacAddressOpts{}) 32 | // fmt.Println(ok) // false 33 | func IsMacAddress(str string, opts *IsMacAddressOpts) bool { 34 | if opts == nil { 35 | opts = setIsMacAddressOptsToDefault() 36 | } 37 | 38 | eu48, eu64 := "48", "64" 39 | 40 | if opts.Type == nil { 41 | opts.Type = strPtr(eu48) 42 | } 43 | 44 | if *opts.Type != eu48 && *opts.Type != eu64 { 45 | return false 46 | } 47 | 48 | noSepReStr := `[\s:.-]?` 49 | if opts.NoSeparators { 50 | noSepReStr = "" 51 | } 52 | 53 | typeReStr := "5" 54 | if *opts.Type == *strPtr(eu64) { 55 | typeReStr = "7" 56 | } 57 | 58 | re := regexp.MustCompile(fmt.Sprintf(`^([[:xdigit:]]{2}%s){%s}[[:xdigit:]]{2}$`, noSepReStr, typeReStr)) 59 | 60 | return re.MatchString(str) 61 | } 62 | 63 | func setIsMacAddressOptsToDefault() *IsMacAddressOpts { 64 | return &IsMacAddressOpts{ 65 | NoSeparators: isMacAddressOptsDefault, 66 | Type: isMacAddressOptsType, 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ismagneturi.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator that checks if the string is a [Magnet URI] format. 6 | // 7 | // ok := validatorgo.IsMagnetURI("magnet:?xt=urn:btih:123456789abcdef123456789abcdef123456789a") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsMagnetURI("magnet:?dn=Example&tr=http://example.com/announce") 10 | // fmt.Println(ok) // false 11 | // 12 | // [Magnet URI]: https://en.wikipedia.org/wiki/Magnet_URI_scheme 13 | func IsMagnetURI(str string) bool { 14 | return regexp.MustCompile(`(?:^magnet:\?|[^?&]&)xt(?:\.1)?=urn:(?:(?:aich|bitprint|btih|ed2k|ed2khash|kzhash|md5|sha1|tree:tiger):[a-z0-9]{32}(?:[a-z0-9]{8})?|btmh:1220[a-z0-9]{64})(?:$|&)`).MatchString(str) 15 | } 16 | -------------------------------------------------------------------------------- /ismagneturi_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsMagnetURI(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid magnet links 12 | {name: "Simple Magnet Link with hash", param1: "magnet:?xt=urn:btih:123456789abcdef123456789abcdef123456789a", want: true}, 13 | {name: "Magnet Link with multiple parameters", param1: "magnet:?xt=urn:btih:123456789abcdef123456789abcdef123456789a&dn=Example&tr=http://example.com/announce&xl=123456789", want: true}, 14 | {name: "Magnet Link with different hash (ed2k)", param1: "magnet:?xt=urn:ed2k:123456789abcdef123456789abcdef12", want: true}, 15 | {name: "Magnet Link with multiple trackers", param1: "magnet:?xt=urn:btih:123456789abcdef123456789abcdef123456789a&tr=http://example.com/announce&tr=http://example.org/announce", want: true}, 16 | // Invalid magnet links 17 | {name: "Missing xt parameter", param1: "magnet:?dn=Example&tr=http://example.com/announce", want: false}, 18 | {name: "Invalid hash length", param1: "magnet:?xt=urn:btih:12345", want: false}, 19 | {name: "Missing the magnet scheme", param1: "?xt=urn:btih:123456789abcdef123456789abcdef123456789a", want: false}, 20 | {name: "Invalid URN prefix", param1: "magnet:?xt=urn:invalid:123456789abcdef123456789abcdef123456789a", want: false}, 21 | } 22 | 23 | for _, test := range tests { 24 | t.Run(test.name, func(t *testing.T) { 25 | result := IsMagnetURI(test.param1) 26 | 27 | if result != test.want { 28 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ismailtouri.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | // IsMailToURIOpts is used to configure IsMailtoURI 8 | type IsMailToURIOpts struct { 9 | IsEmailOpts 10 | } 11 | 12 | // A validator that checks if the string is a [Mailto URI] format. 13 | // 14 | // IsMailToURIOpts is a struct that directly embeds IsEmailOpts. 15 | // 16 | // IsMailToURIOpts validates emails inside the URI (check IsEmailOpts for details). 17 | // 18 | // ok := validatorgo.IsMailtoURI("mailto:someone@example.com", nil) 19 | // fmt.Println(ok) // true 20 | // ok := validatorgo.IsMailtoURI("someone@example.com", nil) 21 | // fmt.Println(ok) // false 22 | // 23 | // [Mailto URI]: https://en.wikipedia.org/wiki/Mailto 24 | func IsMailtoURI(str string, opts *IsMailToURIOpts) bool { 25 | re := regexp.MustCompile(`^(mailto:)([^\?]+)(\?.*)?$`) 26 | 27 | capGrp := re.FindStringSubmatch(str) 28 | 29 | if capGrp == nil { 30 | return false 31 | } 32 | 33 | email := capGrp[2] 34 | 35 | if opts == nil { 36 | return IsEmail(email, setIsEmailOptsToDefault()) 37 | } 38 | 39 | return IsEmail(email, &IsEmailOpts{ 40 | AllowDisplayName: opts.AllowDisplayName, 41 | RequireDisplayName: opts.RequireDisplayName, 42 | AllowUTF8LocalPart: opts.AllowUTF8LocalPart, 43 | RequireTld: opts.RequireTld, 44 | IgnoreMaxLength: opts.IgnoreMaxLength, 45 | AllowIpDomain: opts.AllowIpDomain, 46 | DomainSpecificValidation: opts.DomainSpecificValidation, 47 | BlacklistedChars: opts.BlacklistedChars, 48 | HostBlacklist: opts.HostBlacklist, 49 | HostWhitelist: opts.HostWhitelist, 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /ismailtouri_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsMailtoURI(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 *IsMailToURIOpts 10 | want bool 11 | }{ 12 | // Valid mailto URIs 13 | {name: "Basic mailto URI nil config", param1: "mailto:someone@example.com", param2: nil, want: true}, 14 | {name: "Basic mailto URI", param1: "mailto:someone@example.com", param2: &IsMailToURIOpts{}, want: true}, 15 | {name: "mailto URI with country code TLD", param1: "mailto:user@domain.co.uk", param2: &IsMailToURIOpts{}, want: true}, 16 | {name: "mailto URI with subject", param1: "mailto:admin@domain.com?subject=Hello", param2: &IsMailToURIOpts{}, want: true}, 17 | {name: "mailto URI with body", param1: "mailto:support@service.com?body=Help", param2: &IsMailToURIOpts{}, want: true}, 18 | {name: "mailto URI with cc and bcc", param1: "mailto:test@domain.com?cc=cc@domain.com&bcc=bcc@domain.com", param2: &IsMailToURIOpts{}, want: true}, 19 | // Invalid mailto URIs 20 | {name: "Missing mailto scheme", param1: "someone@example.com", param2: &IsMailToURIOpts{}, want: false}, 21 | {name: "Invalid email format", param1: "mailto:invalid@domain", param2: &IsMailToURIOpts{}, want: false}, 22 | {name: "Non-mailto URI", param1: "http://example.com", param2: &IsMailToURIOpts{}, want: false}, 23 | {name: "Invalid domain in email", param1: "mailto:user@.com", param2: &IsMailToURIOpts{}, want: false}, 24 | {name: "Missing domain extension", param1: "mailto:admin@domain?subject=Hello", param2: &IsMailToURIOpts{}, want: false}, 25 | } 26 | 27 | for _, test := range tests { 28 | t.Run(test.name, func(t *testing.T) { 29 | result := IsMailtoURI(test.param1, test.param2) 30 | 31 | if result != test.want { 32 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 33 | } 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ismd5str.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | // A validator that checks if the string is a MD5 hash. 4 | // 5 | // Please note that you can also use the isHash(str, "md5") function. 6 | // 7 | // Keep in mind that MD5 has some collision weaknesses compared to other algorithms (e.g., SHA). 8 | // 9 | // ok := validatorgo.IsMD5("9e107d9d372bb6826bd81d3542a419d6") 10 | // fmt.Println(ok) // true 11 | // ok := validatorgo.IsMD5("9e107d9d372bb6826bd81d3542a419d") 12 | // fmt.Println(ok) // false 13 | func IsMD5(str string) bool { 14 | return IsHash(str, "md5") 15 | } 16 | -------------------------------------------------------------------------------- /ismd5str_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsMD5(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid MD5 hashes 12 | {name: "Valid MD5 - all lowercase", param1: "9e107d9d372bb6826bd81d3542a419d6", want: true}, 13 | {name: "Valid MD5 - mixed case", param1: "9E107D9D372BB6826BD81D3542A419D6", want: true}, 14 | {name: "Valid MD5 - numeric", param1: "12345678901234567890123456789012", want: true}, 15 | 16 | // Invalid MD5 hashes 17 | {name: "Invalid MD5 - too short", param1: "9e107d9d372bb6826bd81d3542a419d", want: false}, 18 | {name: "Invalid MD5 - too long", param1: "9e107d9d372bb6826bd81d3542a419d61", want: false}, 19 | {name: "Invalid MD5 - contains non-hex character", param1: "9e107d9d372bb6826bd81d3542a419dz", want: false}, 20 | {name: "Invalid MD5 - empty string", param1: "", want: false}, 21 | {name: "Invalid MD5 - non-hex characters", param1: "g3fcd3d76192e4007dfb496cca67e13b", want: false}, 22 | } 23 | 24 | for _, test := range tests { 25 | t.Run(test.name, func(t *testing.T) { 26 | result := IsMD5(test.param1) 27 | 28 | if result != test.want { 29 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 30 | } 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ismimetype.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator that checks if the string matches to a valid MIME type format. 6 | // 7 | // ok := validatorgo.IsMimeType("text/plain") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsMimeType("textplain") 10 | // fmt.Println(ok) // false 11 | func IsMimeType(str string) bool { 12 | return regexp.MustCompile(`^(application|audio|font|image|message|model|multipart|text|video|x-[\w.+-]+)\/[a-zA-Z][\w.+-]*(\s*;\s*[\w.+-]+=[\w.+-]+)*$`).MatchString(str) 13 | } 14 | -------------------------------------------------------------------------------- /ismimetype_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsMimeType(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid mime types 12 | {name: "Valid text MIME type", param1: "text/plain", want: true}, 13 | {name: "Valid image MIME type", param1: "image/jpeg", want: true}, 14 | {name: "Valid application MIME type", param1: "application/json", want: true}, 15 | {name: "Valid audio MIME type", param1: "audio/mpeg", want: true}, 16 | {name: "Valid video MIME type", param1: "video/mp4", want: true}, 17 | {name: "Vendor-specific MIME type", param1: "application/vnd.ms-excel", want: true}, 18 | {name: "Vendor-specific with personal prefix", param1: "application/x-my-custom", want: true}, 19 | {name: "MIME type with charset", param1: "text/html; charset=UTF-8", want: true}, 20 | {name: "MIME type with boundary", param1: "multipart/form-data; boundary=something", want: true}, 21 | {name: "MIME type with plus symbol", param1: "application/ld+json", want: true}, 22 | {name: "MIME type with dot", param1: "image/vnd.adobe.photoshop", want: true}, 23 | 24 | // Invalid mime types 25 | {name: "Missing slash", param1: "textplain", want: false}, 26 | {name: "Multiple slashes", param1: "text//plain", want: false}, 27 | {name: "Invalid characters in type", param1: "text/pla!n", want: false}, 28 | {name: "Invalid characters in subtype", param1: "image/jp@g", want: false}, 29 | {name: "Missing type", param1: "/plain", want: false}, 30 | {name: "Missing subtype", param1: "image/", want: false}, 31 | {name: "Invalid parameter format", param1: "text/html;charset", want: false}, 32 | {name: "Malformed parameter key-value", param1: "application/json; charset", want: false}, 33 | {name: "Extra semicolon", param1: "application/json;", want: false}, 34 | {name: "Non-alphanumeric subtype", param1: "application/123", want: false}, 35 | } 36 | 37 | for _, test := range tests { 38 | t.Run(test.name, func(t *testing.T) { 39 | result := IsMimeType(test.param1) 40 | 41 | if result != test.want { 42 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 43 | } 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ismongoid.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "unicode/utf8" 4 | 5 | // A validator that checks if the string is a valid hex-encoded representation of a MongoDB ObjectId. 6 | // 7 | // ok := validatorgo.IsMongoID("507f1f77bcf86cd799439011") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsMongoID("507f1f77bcf86cd79943901") 10 | // fmt.Println(ok) // false 11 | func IsMongoID(str string) bool { 12 | return IsHexadecimal(str) && utf8.RuneCountInString(str) == 24 13 | } 14 | -------------------------------------------------------------------------------- /ismongoid_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsMongoID(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid mongo id 12 | {name: "Valid mongo id", param1: "507f1f77bcf86cd799439011", want: true}, 13 | {name: "Valid mongo id", param1: "5f2a6c69e1d7a4e0077b4e6b", want: true}, 14 | {name: "Valid mongo id", param1: "60ad7c6d5f98bf2e6c1453c7", want: true}, 15 | {name: "Valid mongo id", param1: "000000000000000000000000", want: true}, 16 | 17 | // Invalid mongo id 18 | {name: "Invalid mongo id, too short", param1: "507f1f77bcf86cd79943901", want: false}, 19 | {name: "Invalid mongo id, too long", param1: "507f1f77bcf86cd7994390110", want: false}, 20 | {name: "Invalid mongo id, contains invalid characters", param1: "507f1f77bcf86cd79943901G", want: false}, 21 | {name: "Invalid mongo id, invalid format only letters", param1: "ZZZZZZZZZZZZZZZZZZZZZZZZ", want: false}, 22 | {name: "Invalid mongo id, too many digits", param1: "1234567890123456789012345", want: false}, 23 | } 24 | 25 | for _, test := range tests { 26 | t.Run(test.name, func(t *testing.T) { 27 | result := IsMongoID(test.param1) 28 | 29 | if result != test.want { 30 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ismultibyte.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator that checks if the string contains one or more multibyte chars. 6 | // 7 | // ok := validatorgo.IsMultibyte("こんにちは") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsMultibyte("hello") 10 | // fmt.Println(ok) // false 11 | func IsMultibyte(str string) bool { 12 | return regexp.MustCompile(`[^\x00-\x7F]`).MatchString(str) 13 | } 14 | -------------------------------------------------------------------------------- /ismultibyte_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsMultibyte(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid multi byte 12 | {name: "Valid multi byte Japanese characters", param1: "こんにちは", want: true}, 13 | {name: "Valid multi byte chinese characters", param1: "你好", want: true}, 14 | {name: "Valid multi byte Cyrillic characters", param1: "Привет", want: true}, 15 | {name: "Valid multi byte Unicode characters above U+FFF", param1: "𠜎𠜱", want: true}, 16 | {name: "Valid multi byte emoji", param1: "😊🍕", want: true}, 17 | 18 | // Invalid multi byte 19 | {name: "Invalid multi byte ASCII characters only", param1: "hello", want: false}, 20 | {name: "Invalid multi byte numbers only", param1: "12345", want: false}, 21 | {name: "Invalid multi byte single-byte symbol", param1: "!", want: false}, 22 | {name: "Invalid multi byte ASCII letters and numbers", param1: "test123", want: false}, 23 | {name: "Invalid multi byte single-byte symbols", param1: "@#%&", want: false}, 24 | } 25 | 26 | for _, test := range tests { 27 | t.Run(test.name, func(t *testing.T) { 28 | result := IsMultibyte(test.param1) 29 | 30 | if result != test.want { 31 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /isobject.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | var ( 8 | IsObjectOptsDefaultStrict = true 9 | ) 10 | 11 | // IsObjectOpts is used to configure IsObject 12 | type IsObjectOpts struct { 13 | Strict bool // looseness or strictness 14 | } 15 | 16 | // A validator to check that a value is a json object. 17 | // For example, "{}", "{ foo: 'bar' }" would pass this validator. 18 | // 19 | // IsObjectOpts is a struct which defaults to { Strict: true }. 20 | // 21 | // If the Strict option is set to false, then this validator works, where both arrays ("[]") and the null ("null") value are considered objects. 22 | // 23 | // ok := validatorgo.IsObject(`{"name": "John", "age": 30}`, &validatorgo.IsObjectOpts{Strict: true}) 24 | // fmt.Println(ok) // true 25 | // ok := validatorgo.IsObject(`{"name": "John", "age`, &validatorgo.IsObjectOpts{Strict: true}) 26 | // fmt.Println(ok) // false 27 | func IsObject(str string, opts *IsObjectOpts) bool { 28 | if opts == nil { 29 | opts = setIsObjectOptsToDefault() 30 | } 31 | 32 | var obj interface{} 33 | 34 | err := json.Unmarshal([]byte(str), &obj) 35 | if err != nil { 36 | return false 37 | } 38 | 39 | switch obj.(type) { 40 | case map[string]interface{}: 41 | return true 42 | case []interface{}: 43 | return !opts.Strict 44 | default: 45 | if str == "null" && !opts.Strict { 46 | return true 47 | } 48 | return false 49 | } 50 | } 51 | 52 | func setIsObjectOptsToDefault() *IsObjectOpts { 53 | return &IsObjectOpts{ 54 | Strict: IsObjectOptsDefaultStrict, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /isobject_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsObject(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | param2 *IsObjectOpts 12 | want bool 13 | }{ 14 | // Valid json default config with strictness 15 | {name: "Valid JSON object", param1: `{"name": "John", "age": 30}`, param2: nil, want: true}, 16 | {name: "Valid empty JSON", param1: `{}`, param2: nil, want: true}, 17 | // Invalid json default config with strictness 18 | {name: "Invalid JSON object, no arrays", param1: `["item1", "item2"]`, param2: nil, want: false}, 19 | {name: "Invalid JSON object, no null", param1: `null`, param2: nil, want: false}, 20 | {name: "Invalid JSON object", param1: `{"name": "John", "age": 30`, param2: nil, want: false}, 21 | 22 | // Valid json without strictness 23 | {name: "Valid JSON object", param1: `{"name": "John", "age": 30}`, param2: &IsObjectOpts{Strict: false}, want: true}, 24 | {name: "Valid JSON array", param1: `["item1", "item2"]`, param2: &IsObjectOpts{Strict: false}, want: true}, 25 | {name: "Valid JSON null", param1: `null`, param2: &IsObjectOpts{Strict: false}, want: true}, 26 | // Invalid json without strictness 27 | {name: "Valid JSON, is string", param1: `"Just a string"`, param2: &IsObjectOpts{Strict: false}, want: false}, 28 | {name: "Valid JSON format", param1: `{invalid json}`, param2: &IsObjectOpts{Strict: false}, want: false}, 29 | {name: "Valid JSON format misspell null", param1: `nil`, param2: &IsObjectOpts{Strict: false}, want: false}, 30 | } 31 | 32 | for _, test := range tests { 33 | t.Run(test.name, func(t *testing.T) { 34 | result := IsObject(test.param1, test.param2) 35 | 36 | if result != test.want { 37 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /isoctal.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator that checks if the string is a valid octal number. 6 | // 7 | // ok := validatorgo.IsOctal("07") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsOctal("078") 10 | // fmt.Println(ok) // false 11 | func IsOctal(str string) bool { 12 | return regexp.MustCompile(`^[0-7]+$`).MatchString(str) 13 | } 14 | -------------------------------------------------------------------------------- /isoctal_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsOctal(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid octal numbers 12 | {name: "Valid Octal - Simple", param1: "07", want: true}, 13 | {name: "Valid Octal - Complex", param1: "0754321", want: true}, 14 | {name: "Valid Octal - Zero", param1: "0", want: true}, 15 | {name: "Valid Octal - Max", param1: "07777777777", want: true}, 16 | 17 | // Invalid octal numbers 18 | {name: "Invalid Octal - Contains 8", param1: "078", want: false}, 19 | {name: "Invalid Octal - Contains 9", param1: "079", want: false}, 20 | {name: "Invalid Octal - Non-numeric character", param1: "075a321", want: false}, 21 | {name: "Invalid Octal - Negative sign", param1: "-0754", want: false}, 22 | } 23 | 24 | for _, test := range tests { 25 | t.Run(test.name, func(t *testing.T) { 26 | result := IsOctal(test.param1) 27 | 28 | if result != test.want { 29 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 30 | } 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ispassport_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsPassportNumber(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 string 10 | want bool 11 | }{ 12 | // Valid passport number (US) 13 | {name: "Valid US passport number", param1: "123456789", param2: "US", want: true}, 14 | // Invalid passport number (US) 15 | {name: "Invalid US passport number (letters not allowed)", param1: "A12345678", param2: "US", want: false}, 16 | {name: "Invalid US passport number (too short)", param1: "12345678", param2: "US", want: false}, 17 | {name: "Invalid US passport number (too long)", param1: "1234567890", param2: "US", want: false}, 18 | 19 | // Valid passport number (GB) 20 | {name: "Valid GB passport number", param1: "123456789", param2: "GB", want: true}, 21 | // Invalid passport number (GB) 22 | {name: "Invalid GB passport number (too short)", param1: "12345678", param2: "GB", want: false}, 23 | 24 | // Valid passport number (DE) 25 | {name: "Valid DE passport number", param1: "C12345678", param2: "DE", want: true}, 26 | // Invalid passport number (DE) 27 | {name: "Invalid DE passport number (too short)", param1: "C26X0A7", param2: "DE", want: false}, 28 | 29 | // Valid passport number (FR) 30 | {name: "Valid FR passport number", param1: "12AB34567", param2: "FR", want: true}, 31 | // Invalid passport number (FR) 32 | {name: "Invalid FR passport number (invalid characters)", param1: "12AB@4567", param2: "FR", want: false}, 33 | {name: "Valid password but invalid locale", param1: "123456789", param2: "", want: false}, 34 | 35 | // Valid passport number (IN) 36 | {name: "Valid IN passport number", param1: "A1234567", param2: "IN", want: true}, 37 | // Invalid passport number (IN) 38 | {name: "Invalid IN passport number (too long)", param1: "A12345678", param2: "IN", want: false}, 39 | {name: "Invalid IN passport number (missing letter)", param1: "123456789", param2: "IN", want: false}, 40 | } 41 | 42 | for _, test := range tests { 43 | t.Run(test.name, func(t *testing.T) { 44 | result := IsPassportNumber(test.param1, test.param2) 45 | 46 | if result != test.want { 47 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 48 | } 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /isport.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator that checks if the string is a valid port number. 6 | // 7 | // ok := validatorgo.IsPort("07") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsPort("078") 10 | // fmt.Println(ok) // false 11 | func IsPort(str string) bool { 12 | return regexp.MustCompile(`^0*([0-9]|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$`).MatchString(str) 13 | } 14 | -------------------------------------------------------------------------------- /isport_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsPort(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid port numbers 12 | {name: "Valid Port - Minimum", param1: "0", want: true}, 13 | {name: "Valid Port - Common HTTP", param1: "80", want: true}, 14 | {name: "Valid Port - Common HTTPS", param1: "443", want: true}, 15 | {name: "Valid Port - FTP", param1: "21", want: true}, 16 | {name: "Valid Port - SSH", param1: "22", want: true}, 17 | {name: "Valid Port - Custom Port", param1: "8080", want: true}, 18 | {name: "Valid Port - Upper Bound", param1: "65535", want: true}, 19 | 20 | // Invalid port numbers 21 | {name: "Invalid Port - Below Minimum", param1: "-1", want: false}, 22 | {name: "Invalid Port - Above Maximum", param1: "65536", want: false}, 23 | {name: "Invalid Port - Non-numeric characters", param1: "abc123", want: false}, 24 | {name: "Invalid Port - With special characters", param1: "123$", want: false}, 25 | {name: "Invalid Port - Floating point", param1: "8080.1", want: false}, 26 | {name: "Invalid Port - Empty string", param1: "", want: false}, 27 | {name: "Invalid Port - Space character", param1: " 8080", want: false}, 28 | {name: "Invalid Port - Leading zero", param1: "080", want: true}, 29 | {name: "Invalid Port - Extra characters", param1: "443port", want: false}, 30 | } 31 | 32 | for _, test := range tests { 33 | t.Run(test.name, func(t *testing.T) { 34 | result := IsPort(test.param1) 35 | 36 | if result != test.want { 37 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 38 | } 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ispostalcode_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsPostalCode(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 string 10 | want bool 11 | }{ 12 | // Valid postal codes with specific locales 13 | {name: "Valid Postal Code - US", param1: "90210", param2: "US", want: true}, 14 | {name: "Valid Postal Code - Canada", param1: "K1A 0B1", param2: "CA", want: true}, 15 | {name: "Valid Postal Code - UK", param1: "SW1A 1AA", param2: "GB", want: true}, 16 | {name: "Valid Postal Code - Germany", param1: "10115", param2: "DE", want: true}, 17 | {name: "Valid Postal Code - Japan", param1: "100-0001", param2: "JP", want: true}, 18 | 19 | // Valid postal codes with "any" locale 20 | {name: "Valid Postal Code - US (Any Locale)", param1: "90210", param2: "any", want: true}, 21 | {name: "Valid Postal Code - Canada (Any Locale)", param1: "K1A 0B1", param2: "any", want: true}, 22 | {name: "Valid Postal Code - UK (Any Locale)", param1: "SW1A 1AA", param2: "any", want: true}, 23 | 24 | // Valid postal codes with no locale 25 | {name: "Valid Postal Code - Germany (No Locale)", param1: "10115", param2: "", want: true}, 26 | {name: "Valid Postal Code - France (No Locale)", param1: "75001", param2: "", want: true}, 27 | 28 | // Invalid postal codes 29 | {name: "Invalid Postal Code - CN Format in Germany", param1: "902101", param2: "DE", want: false}, 30 | {name: "Invalid Postal Code - Too Short", param1: "12", param2: "any", want: false}, 31 | {name: "Invalid Postal Code - Letters in Number-only Locale", param1: "ABCDE", param2: "DE", want: false}, 32 | {name: "Invalid Postal Code - Wrong Format for Locale", param1: "12345-6789", param2: "GB", want: false}, 33 | 34 | // Invalid postal codes with no locale 35 | {name: "Invalid Postal Code - Gibberish (No Locale)", param1: "XYZ123", param2: "", want: false}, 36 | {name: "Invalid Postal Code - Gibberish (and unrecognizable Locale)", param1: "XYZ123", param2: "ab-XY", want: false}, 37 | {name: "Invalid Postal Code - Empty String (No Locale)", param1: "", param2: "", want: false}, 38 | } 39 | 40 | for _, test := range tests { 41 | t.Run(test.name, func(t *testing.T) { 42 | result := IsPostalCode(test.param1, test.param2) 43 | 44 | if result != test.want { 45 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /isrfc3339.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | // A validator that checks if the string is a valid [RFC 3339] date. 8 | // 9 | // ok := validatorgo.IsRFC3339("2024-09-21T14:30:00Z") 10 | // fmt.Println(ok) // true 11 | // ok := validatorgo.IsRFC3339("2024-09-21 14:30:00Z") 12 | // fmt.Println(ok) // false 13 | // 14 | // [RFC 3339]: https://tools.ietf.org/html/rfc3339 15 | func IsRFC3339(str string) bool { 16 | re := regexp.MustCompile(`^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):[0-5]\d:[0-5]\d(Z|([+-](0[0-9]|1[0-3]):[0-5]\d))$`) 17 | 18 | capGrp := re.FindStringSubmatch(str) 19 | 20 | if len(capGrp) == 0 { 21 | return false 22 | } 23 | 24 | year, month, day := capGrp[1], capGrp[2], capGrp[3] 25 | 26 | return validYearMonthDay(year, month, day) 27 | } 28 | -------------------------------------------------------------------------------- /isrfc3339_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsRFC3339(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid RFC3339 timestamps 12 | {name: "Valid RFC3339 - UTC", param1: "2024-09-21T14:30:00Z", want: true}, 13 | {name: "Valid RFC3339 - Positive Timezone Offset", param1: "2024-09-21T14:30:00+02:00", want: true}, 14 | {name: "Valid RFC3339 - Negative Timezone Offset", param1: "2024-09-21T14:30:00-05:00", want: true}, 15 | {name: "Valid RFC3339 - UTC Midnight", param1: "2024-01-01T00:00:00Z", want: true}, 16 | {name: "Valid RFC3339 - Maximum Valid Time", param1: "2024-12-31T23:59:59+00:00", want: true}, 17 | 18 | // Invalid RFC3339 timestamps 19 | {name: "Invalid RFC3339 - Missing T separator", param1: "2024-09-21 14:30:00Z", want: false}, 20 | {name: "Invalid RFC3339 - Missing Timezone", param1: "2024-09-21T14:30:00", want: false}, 21 | {name: "Invalid RFC3339 - Invalid Date", param1: "2024-02-30T14:30:00Z", want: false}, // Invalid day 22 | {name: "Invalid RFC3339 - Invalid Time", param1: "2024-09-21T25:30:00Z", want: false}, // Invalid hour 23 | {name: "Invalid RFC3339 - Invalid Seconds", param1: "2024-09-21T14:30:61Z", want: false}, // Invalid seconds 24 | {name: "Invalid RFC3339 - Timezone Out of Range", param1: "2024-09-21T14:30:00+14:00", want: false}, // Timezone beyond valid range 25 | {name: "Invalid RFC3339 - Missing Timezone Offset Sign", param1: "2024-09-21T14:30:00 02:00", want: false}, // Missing '+' or '-' in offset 26 | {name: "Invalid RFC3339 - Extra Characters", param1: "2024-09-21T14:30:00Z123", want: false}, // Extra characters at the end 27 | 28 | // Edge cases 29 | {name: "Invalid RFC3339 - Empty String", param1: "", want: false}, 30 | {name: "Invalid RFC3339 - Only Date", param1: "2024-09-21", want: false}, 31 | {name: "Invalid RFC3339 - Only Time", param1: "14:30:00Z", want: false}, 32 | } 33 | 34 | for _, test := range tests { 35 | t.Run(test.name, func(t *testing.T) { 36 | result := IsRFC3339(test.param1) 37 | 38 | if result != test.want { 39 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /isrgbcolor.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | var ( 6 | isRgbOptsDefaultIncludePercentValues bool = false 7 | isRgbOptsDefaultAllowSpaces bool = false 8 | ) 9 | 10 | // IsRgbOpts is used to configure IsRgbColor 11 | type IsRgbOpts struct { 12 | IncludePercentValues bool // must use percent values 90% not 0-255 13 | AllowSpaces bool // whether to include spaces 14 | } 15 | 16 | // A validator that checks if the string is a rgb or rgba color. 17 | // 18 | // IsRgbOpts is a struct with the following properties: 19 | // 20 | // IncludePercentValues defaults to false. If you don't want to allow to set rgb or rgba values with percents, like rgb(5%,5%,5%), or rgba(90%,90%,90%,.3), then set it to false. 21 | // 22 | // AllowSpaces defaults to false, which prohibits whitespace. If set to false, whitespace between color values is allowed, such as rgb(255, 255, 255) or even rgba(255, 128, 0, 0.7). 23 | // 24 | // ok := validatorgo.IsRgbColor("rgb(255,0,0)", validatorgo.IsRgbColor{}) 25 | // fmt.Println(ok) // true 26 | // ok := validatorgo.IsRgbColor("rgb( 255 , 0 , 0 )", validatorgo.IsRgbColor{}) 27 | // fmt.Println(ok) // false 28 | func IsRgbColor(str string, opts *IsRgbOpts) bool { 29 | if opts == nil { 30 | opts = setIsRgbOptsToDefault() 31 | } 32 | 33 | if opts.IncludePercentValues && opts.AllowSpaces { 34 | return regexp.MustCompile(`^rgba?\((\d{0,100}(\.[0-9]*)?%|\d{0,255}),\s*(\d{0,100}(\.[0-9]*)?%|\d{0,255}),\s*(\d{0,100}(\.[0-9]*)?%|\d{0,255})(,\s*(1|0?\.[0-9])?)?\)$`).MatchString(str) 35 | } else if !opts.IncludePercentValues && opts.AllowSpaces { 36 | return regexp.MustCompile(`^rgba?\(\d{0,255},\s*\d{0,255},\s*\d{0,255}(,\s*(1|0?\.[1-9])*)?\)$`).MatchString(str) 37 | } else if opts.IncludePercentValues && !opts.AllowSpaces { 38 | return regexp.MustCompile(`^rgba?\((\d{0,100}(\.[0-9]*)?%|\d{0,255}),(\d{0,100}(\.[0-9]*)?%|\d{0,255}),(\d{0,100}(\.[0-9]*)?%|\d{0,255})(,(1|0?\.[0-9])?)?\)$`).MatchString(str) 39 | } else { 40 | return regexp.MustCompile(`^rgba?\(\d{0,255},\d{0,255},\d{0,255}(,(1|0?\.[1-9])*)?\)$`).MatchString(str) 41 | } 42 | } 43 | 44 | func setIsRgbOptsToDefault() *IsRgbOpts { 45 | return &IsRgbOpts{ 46 | IncludePercentValues: isRgbOptsDefaultIncludePercentValues, 47 | AllowSpaces: isRgbOptsDefaultAllowSpaces, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /issemver.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator that checks if the string is a Semantic Versioning Specification (SemVer). 6 | // 7 | // ok := validatorgo.IsSemVer("1.0.0") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsSemVer("1.0.0.0") 10 | // fmt.Println(ok) // false 11 | func IsSemVer(str string) bool { 12 | return regexp.MustCompile(`^(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-]+)*))?$`).MatchString(str) 13 | } 14 | -------------------------------------------------------------------------------- /issemver_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsSemver(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | { 12 | name: "Valid semver", 13 | param1: "1.0.0", 14 | want: true, 15 | }, 16 | { 17 | name: "Valid semver", 18 | param1: "2.1.3", 19 | want: true, 20 | }, 21 | { 22 | name: "Valid semver", 23 | param1: "0.1.0", 24 | want: true, 25 | }, 26 | { 27 | name: "Valid semver", 28 | param1: "1.0.0-alpha", 29 | want: true, 30 | }, 31 | { 32 | name: "Valid semver", 33 | param1: "1.2.3-beta.1", 34 | want: true, 35 | }, 36 | { 37 | name: "Valid semver", 38 | param1: "3.0.0-rc.1+build.001", 39 | want: true, 40 | }, 41 | { 42 | name: "Valid semver", 43 | param1: "1.0.0+exp.sha.5114f85", 44 | want: true, 45 | }, 46 | { 47 | name: "Invalid semver", 48 | param1: "1.0", 49 | want: false, 50 | }, 51 | // { 52 | // name: "Invalid semver", 53 | // param1: "1.0.0-beta-", 54 | // want: false, 55 | // }, 56 | { 57 | name: "Invalid semver", 58 | param1: "1.0.0.0", 59 | want: false, 60 | }, 61 | { 62 | name: "Invalid semver", 63 | param1: "01.0.0", 64 | want: false, 65 | }, 66 | { 67 | name: "Invalid semver", 68 | param1: "1.0.0+!build.123", 69 | want: false, 70 | }, 71 | { 72 | name: "Invalid semver", 73 | param1: "v1.0.0", 74 | want: false, 75 | }, 76 | { 77 | name: "Invalid semver", 78 | param1: "1.0.0-alpha@001", 79 | want: false, 80 | }, 81 | } 82 | 83 | for _, test := range tests { 84 | t.Run(test.name, func(t *testing.T) { 85 | result := IsSemVer(test.param1) 86 | 87 | if result != test.want { 88 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 89 | } 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /isslug.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator that checks if the string is of type slug. 6 | // 7 | // ok := validatorgo.IsSlug("rgb(255,0,0)") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsSlug("rgb( 255 , 0 , 0 )") 10 | // fmt.Println(ok) // false 11 | func IsSlug(str string) bool { 12 | return regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`).MatchString(str) 13 | } 14 | -------------------------------------------------------------------------------- /isslug_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsSlug(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid slugs 12 | {name: "Valid Slug - Simple", param1: "my-first-blog-post", want: true}, 13 | {name: "Valid Slug - Single Word", param1: "product", want: true}, 14 | {name: "Valid Slug - Numeric", param1: "product-2024", want: true}, 15 | {name: "Valid Slug - All Numeric", param1: "123456", want: true}, 16 | {name: "Valid Slug - Lowercase with Hyphens", param1: "a-slug-with-lowercase", want: true}, 17 | 18 | // Invalid slugs 19 | {name: "Invalid Slug - Contains Uppercase", param1: "My-First-Blog-Post", want: false}, // Uppercase letters 20 | {name: "Invalid Slug - Contains Special Characters", param1: "my@blog#post!", want: false}, // Special characters like @, #, ! 21 | {name: "Invalid Slug - Contains Underscores", param1: "my_first_blog_post", want: false}, // Underscores instead of hyphens 22 | {name: "Invalid Slug - Leading Hyphen", param1: "-leading-hyphen", want: false}, // Leading hyphen 23 | {name: "Invalid Slug - Trailing Hyphen", param1: "trailing-hyphen-", want: false}, // Trailing hyphen 24 | {name: "Invalid Slug - Multiple Consecutive Hyphens", param1: "multiple--hyphens", want: false}, // Multiple consecutive hyphens 25 | {name: "Invalid Slug - Contains Spaces", param1: "slug with spaces", want: false}, // Spaces in slug 26 | {name: "Invalid Slug - Empty String", param1: "", want: false}, // Empty string 27 | {name: "Invalid Slug - Only Hyphens", param1: "---", want: false}, // Only hyphens 28 | } 29 | 30 | for _, test := range tests { 31 | t.Run(test.name, func(t *testing.T) { 32 | result := IsSlug(test.param1) 33 | 34 | if result != test.want { 35 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 36 | } 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /issurrogate_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsSurrogatePair(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid surrogates 12 | {name: "Valid - Single Surrogate Pair", param1: "𝌆", want: true}, // Single surrogate pair character (U+1D306) 13 | {name: "Valid - Multiple Surrogate Pairs", param1: "𝌆𝌇", want: true}, // Multiple surrogate pair characters 14 | {name: "Valid - Surrogate Pair with Regular Chars", param1: "abc𝌆def", want: true}, // Surrogate pair with regular characters 15 | // Invalid surrogates 16 | {name: "Invalid - Regular Characters", param1: "abcdef", want: false}, // No surrogate pairs, only regular characters 17 | {name: "Invalid - Empty String", param1: "", want: false}, // Empty string 18 | {name: "Invalid - High Surrogate Alone", param1: `\uD834`, want: false}, // Unpaired high surrogate 19 | {name: "Invalid - Low Surrogate Alone", param1: `\uDF06`, want: false}, // Unpaired low surrogate 20 | {name: "Invalid - Mixed Regular and Surrogates", param1: `abc\uD834xyz`, want: false}, // High surrogate without matching low surrogate 21 | } 22 | 23 | for _, test := range tests { 24 | t.Run(test.name, func(t *testing.T) { 25 | result := IsSurrogatePair(test.param1) 26 | 27 | if result != test.want { 28 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 29 | } 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /issurrogatepair.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | // A validator that checks if the string contains any surrogate pairs chars. 4 | // 5 | // ok := validatorgo.IsSurrogatePair("") 6 | // fmt.Println(ok) // true 7 | // ok := validatorgo.IsSurrogatePair("") 8 | // fmt.Println(ok) // false 9 | func IsSurrogatePair(str string) bool { 10 | for _, r := range str { 11 | if r > 0xFFFF { 12 | return true 13 | } 14 | } 15 | return false 16 | } 17 | -------------------------------------------------------------------------------- /isulid.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator that checks if the string is a [ULID]. 6 | // 7 | // ok := validatorgo.IsULID("01ARZ3NDEKTSV4RRFFQ69G5FAV") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsULID("01ARZ3NDEKTSV4RRFFQ69G5FA") 10 | // fmt.Println(ok) // false 11 | // 12 | // [ULID]: https://github.com/ulid/spec 13 | func IsULID(str string) bool { 14 | return regexp.MustCompile(`[0-7][0-9A-HJKMNP-TV-Z]{25}`).MatchString(str) 15 | } 16 | -------------------------------------------------------------------------------- /isulid_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsULID(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid ULID 12 | {name: "Valid ULID - All Uppercase", param1: "01ARZ3NDEKTSV4RRFFQ69G5FAV", want: true}, // Proper ULID format 13 | {name: "Valid ULID - Starts with Timestamp", param1: "01B3EAFYJXG24N2H5YAFVYTHZV", want: true}, // Correct ULID structure 14 | {name: "Valid ULID - Mixed with Timestamp and Random", param1: "01BX5ZZKBKACTAV9WEVGEMMVRY", want: true}, // Valid timestamp and random parts 15 | 16 | // Invalid ULID 17 | {name: "Invalid ULID - Too Short", param1: "01ARZ3NDEKTSV4RRFFQ69G5FA", want: false}, // Only 25 characters (1 missing) 18 | {name: "Invalid ULID - Contains Invalid Characters", param1: "01ARZ3NDEKTSV4RRFFQ69G5FA@", want: false}, // Invalid character '@' 19 | {name: "Invalid ULID - Lowercase Letters", param1: "01arz3ndektsv4rrffq69g5fav", want: false}, // Lowercase letters are not allowed 20 | {name: "Invalid ULID - Non-base32 Characters", param1: "01ARZ3NDEKTSV4RRFFQ69G5FAL", want: false}, // Contains 'L', which is not allowed in Crockford's Base32 21 | {name: "Invalid ULID - Empty String", param1: "", want: false}, // Empty string 22 | } 23 | 24 | for _, test := range tests { 25 | t.Run(test.name, func(t *testing.T) { 26 | result := IsULID(test.param1) 27 | 28 | if result != test.want { 29 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 30 | } 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /isuppercase.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "strings" 4 | 5 | // A validator that checks if the string is uppercase. 6 | // 7 | // ok := validatorgo.IsUpperCase("HELLO") 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.IsUpperCase("world") 10 | // fmt.Println(ok) // false 11 | func IsUpperCase(str string) bool { 12 | return str == strings.ToUpper(str) && str != strings.ToLower(str) 13 | } 14 | -------------------------------------------------------------------------------- /isuppercase_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsUpperCase(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid Example 12 | {name: "Is uppercase", param1: "HELLO", want: true}, 13 | // Invalid Example 14 | {name: "Empty string is not uppercase", param1: "", want: false}, 15 | {name: "Is not uppercase", param1: "hello", want: false}, 16 | {name: "Only few letter are uppercase", param1: "ExaMPle", want: false}, 17 | } 18 | 19 | for _, test := range tests { 20 | t.Run(test.name, func(t *testing.T) { 21 | result := IsUpperCase(test.param1) 22 | 23 | if result != test.want { 24 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 25 | } 26 | }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /isuuid.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | var uuidRegex = map[string]*regexp.Regexp{ 6 | "1": regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[1][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`), 7 | "2": regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[2][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`), 8 | "3": regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[3][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`), 9 | "4": regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`), 10 | "5": regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`), 11 | // "6": regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[6][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`), 12 | // "7": regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`), 13 | // "8": regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`), 14 | } 15 | 16 | // A validator that checks if the string is an RFC9562 UUID. 17 | // 18 | // version is one of ("1"-"5"). if none is not provided, it will validate any of them. 19 | // 20 | // ok := validatorgo.IsUUID("550e8400-e29b-11d4-a716-446655440000", "1") 21 | // fmt.Println(ok) // true 22 | // ok := validatorgo.IsUUID("f47ac10b-58cc-4372-a567-0e02b2c3d479", "1") 23 | // fmt.Println(ok) // false 24 | func IsUUID(str, version string) bool { 25 | re, exists := uuidRegex[version] 26 | 27 | if !exists { 28 | if version != "" { 29 | return false 30 | } 31 | 32 | for _, uiRe := range uuidRegex { 33 | match := uiRe.MatchString(str) 34 | 35 | if match { 36 | return true 37 | } 38 | } 39 | 40 | return false 41 | } 42 | 43 | return re.MatchString(str) 44 | } 45 | -------------------------------------------------------------------------------- /isvariablewidth.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | // A validator that checks if the string contains a mixture of full and half-width chars. 4 | // 5 | // ok := validatorgo.IsVariableWidth("abc123") 6 | // fmt.Println(ok) // true 7 | // ok := validatorgo.IsVariableWidth("abc123") 8 | // fmt.Println(ok) // false 9 | func IsVariableWidth(str string) bool { 10 | hasFullWidth := false 11 | hasHalfWidth := false 12 | 13 | for _, char := range str { 14 | if (char >= '\uFF00' && char <= '\uFFEF') || 15 | (char >= '\u4E00' && char <= '\u9FFF') || 16 | (char >= '\u3040' && char <= '\u309F') || 17 | (char >= '\u30A0' && char <= '\u30FF') { 18 | hasFullWidth = true 19 | } 20 | 21 | if char >= '\u0020' && char <= '\u007E' { 22 | hasHalfWidth = true 23 | } 24 | 25 | if hasFullWidth && hasHalfWidth { 26 | return true 27 | } 28 | } 29 | 30 | return false 31 | } 32 | -------------------------------------------------------------------------------- /isvariablewidth_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsVariableWidth(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | want bool 10 | }{ 11 | // Valid cases with both full-width and half-width characters 12 | {name: "Mixed full-width and half-width characters", param1: "abc123", want: true}, // Full-width letters with half-width numbers 13 | {name: "Mixed full-width Japanese and half-width English", param1: "テストabc", want: true}, // Full-width Japanese with half-width English 14 | {name: "Full-width Chinese and half-width punctuation", param1: "你好!!", want: true}, // Full-width Chinese with half-width punctuation 15 | {name: "Mixed full-width numbers and half-width letters", param1: "123abc", want: true}, // Full-width numbers with half-width letters 16 | 17 | // Cases without mixed width 18 | {name: "All full-width characters", param1: "abc123", want: false}, // Only full-width characters 19 | {name: "All half-width characters", param1: "abc123", want: false}, // Only half-width characters 20 | {name: "Full-width punctuation only", param1: "!@#", want: false}, // Only full-width punctuation 21 | {name: "Half-width punctuation only", param1: "!@#", want: false}, // Only half-width punctuation 22 | 23 | // Edge cases 24 | {name: "Empty string", param1: "", want: false}, // Empty string should return false 25 | {name: "Single full-width character", param1: "a", want: false}, // Single full-width character 26 | {name: "Single half-width character", param1: "a", want: false}, // Single half-width character 27 | 28 | // Mixed cases with special characters 29 | {name: "Full-width and half-width special characters", param1: "@a!", want: true}, // Full-width punctuation with half-width letter 30 | {name: "Mixed width including spaces", param1: "123 abc", want: true}, // Full-width numbers with half-width letters and space 31 | } 32 | 33 | for _, test := range tests { 34 | t.Run(test.name, func(t *testing.T) { 35 | result := IsVariableWidth(test.param1) 36 | 37 | if result != test.want { 38 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 39 | } 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /isvat_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsVAT(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 string 10 | want bool 11 | }{ 12 | {name: "Valid VAT Germany", param1: "DE123456789", param2: "DE", want: true}, 13 | {name: "Invalid VAT Germany", param1: "DE12345678", param2: "DE", want: false}, 14 | {name: "Valid VAT Italy", param1: "IT12345678901", param2: "IT", want: true}, 15 | {name: "Invalid VAT Italy", param1: "IT1234567890", param2: "IT", want: false}, 16 | {name: "Valid VAT Netherlands", param1: "NL123456789B01", param2: "NL", want: true}, 17 | {name: "Invalid VAT Netherlands", param1: "NL123456789B", param2: "NL", want: false}, 18 | {name: "Valid VAT Austria", param1: "U12345678", param2: "AT", want: true}, 19 | {name: "Invalid VAT Austria", param1: "U1234567", param2: "AT", want: false}, 20 | {name: "Valid VAT with empty country", param1: "DE123456789", param2: "", want: true}, 21 | {name: "Invalid VAT with empty country", param1: "XYZ123456789", param2: "", want: false}, 22 | 23 | {name: "Valid VAT with invalid country", param1: "DE123456789", param2: "XYX", want: false}, 24 | {name: "Valid VAT with no country", param1: "DE123456789", param2: "", want: true}, 25 | {name: "Valid VAT with any country", param1: "DE123456789", param2: "any", want: true}, 26 | } 27 | 28 | for _, test := range tests { 29 | t.Run(test.name, func(t *testing.T) { 30 | result := IsVAT(test.param1, test.param2) 31 | 32 | if result != test.want { 33 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 34 | } 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /iswhilelisted.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | // A validator that checks if the string consists only of characters that appear in the whitelist chars. 4 | // 5 | // ok := validatorgo.IsWhitelisted("stop", "post") 6 | // fmt.Println(ok) // true 7 | // ok := validatorgo.IsWhitelisted("bang", "take") 8 | // fmt.Println(ok) // false 9 | func IsWhitelisted(str, chars string) bool { 10 | charsM := make(map[string]int) 11 | 12 | for _, char := range chars { 13 | val := string(char) 14 | _, exist := charsM[val] 15 | 16 | if exist { 17 | charsM[val] += 1 18 | } else { 19 | charsM[val] = 1 20 | } 21 | } 22 | 23 | for _, st := range str { 24 | val := string(st) 25 | 26 | _, exist := charsM[val] 27 | 28 | if !exist { 29 | return false 30 | } 31 | 32 | } 33 | 34 | return true 35 | } 36 | -------------------------------------------------------------------------------- /iswhilelisted_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "testing" 4 | 5 | func TestIsWhitelisted(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | param1 string 9 | param2 string 10 | want bool 11 | }{ 12 | {name: "Empty string provided and empty list provided", param1: "", param2: "", want: true}, 13 | {name: "Empty string provided but list provided", param1: "", param2: "thf", want: true}, 14 | 15 | {name: "All characters are present in list", param1: "stop", param2: "post", want: true}, 16 | {name: "All characters are present in list, repasted letters ", param1: "stops", param2: "post", want: true}, 17 | {name: "No characters are present in list", param1: "bang", param2: "take", want: false}, 18 | {name: "At least one character (c) is not present in list", param1: "cake", param2: "take", want: false}, 19 | {name: "Characters present but empty list", param1: "cake", param2: "", want: false}, 20 | } 21 | 22 | for _, test := range tests { 23 | t.Run(test.name, func(t *testing.T) { 24 | result := IsWhitelisted(test.param1, test.param2) 25 | 26 | if result != test.want { 27 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 28 | } 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /matches.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import "regexp" 4 | 5 | // A validator that checks if the string matches the regex. 6 | // 7 | // ok := validatorgo.Matches("foo", regexp.MustCompile(`^foo$`)) 8 | // fmt.Println(ok) // true 9 | // ok := validatorgo.Matches("foo", regexp.MustCompile(`^foobar$`)) 10 | // fmt.Println(ok) // false 11 | func Matches(str string, re *regexp.Regexp) bool { 12 | if re == nil { 13 | return false 14 | } 15 | 16 | return re.MatchString(str) 17 | } 18 | -------------------------------------------------------------------------------- /matches_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | ) 7 | 8 | func TestMatches(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | param1 string 12 | param2 *regexp.Regexp 13 | want bool 14 | }{ 15 | {name: "Matches regex", param1: "foo", param2: regexp.MustCompile(`^foo$`), want: true}, 16 | {name: "Does not match regex", param1: "foo", param2: regexp.MustCompile(`^foobar$`), want: false}, 17 | {name: "nil regex", param1: "foo", param2: nil, want: false}, 18 | } 19 | 20 | for _, test := range tests { 21 | t.Run(test.name, func(t *testing.T) { 22 | result := Matches(test.param1, test.param2) 23 | 24 | if result != test.want { 25 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 26 | } 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /sanitizer/blacklist.go: -------------------------------------------------------------------------------- 1 | // A package of string sanitizers. 2 | package sanitizer 3 | 4 | import "regexp" 5 | 6 | // A sanitizer that remove characters that appear in the blacklist. 7 | // 8 | // The characters are used in a RegExp and will escaped for you. 9 | // 10 | // str := sanitizer.Blacklist("Hello123 World!", "0-9") 11 | // fmt.Println(str) // "Hello World!" 12 | func Blacklist(str, blacklistedChars string) string { 13 | if blacklistedChars == "" { 14 | return str 15 | } 16 | 17 | // If blacklistedChars is ".", it means blacklist all characters 18 | if blacklistedChars == "." { 19 | return "" 20 | } 21 | 22 | re := regexp.MustCompile("[" + regexp.QuoteMeta(blacklistedChars) + "]+") 23 | return re.ReplaceAllString(str, "") 24 | } 25 | -------------------------------------------------------------------------------- /sanitizer/blacklist_test.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBlacklist(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | param2 string 12 | want string 13 | }{ 14 | {name: "Blacklist digits", param1: "Hello123 World!", param2: "0-9", want: "Hello World!"}, 15 | {name: "Blacklist letters", param1: "Hello123 World!", param2: "a-zA-Z", want: "123 !"}, 16 | {name: "Blacklist spaces", param1: "Hello123 World!", param2: " ", want: "Hello123World!"}, 17 | {name: "Blacklist alphanumeric characters", param1: "Hello123 World!", param2: "a-zA-Z0-9", want: " !"}, 18 | {name: "Blacklist specific characters (symbols)", param1: "Hello123@World!", param2: "@!", want: "Hello123World"}, 19 | {name: "Blacklist letters with accents", param1: "Olá123 Mundo!", param2: "áéíóúÁÉÍÓÚ", want: "Ol123 Mundo!"}, 20 | {name: "Blacklist empty string", param1: "Hello World!", param2: "", want: "Hello World!"}, 21 | {name: "Blacklist with no matching characters", param1: "Hello World!", param2: "123", want: "Hello World!"}, 22 | {name: "Blacklist all characters", param1: "Hello World!", param2: ".", want: ""}, 23 | {name: "Blacklist multiple types (letters, digits, spaces)", param1: "Hello123 World!", param2: "a-zA-Z0-9 ", want: "!"}, 24 | } 25 | 26 | for _, test := range tests { 27 | 28 | t.Run(test.name, func(t *testing.T) { 29 | result := Blacklist(test.param1, test.param2) 30 | 31 | if result != test.want { 32 | t.Errorf("got `%s`, wanted `%s`", result, test.want) 33 | } 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /sanitizer/escape.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import ( 4 | "html" 5 | ) 6 | 7 | // A sanitizer that replaces <, >, &, ' and ". with HTML entities. 8 | // 9 | // str := sanitizer.Escape("") 10 | // fmt.Println(str) // "<script>alert('XSS');</script>" 11 | func Escape(str string) string { 12 | return html.EscapeString(str) 13 | } 14 | -------------------------------------------------------------------------------- /sanitizer/escape_test.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestEscape(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | want string 12 | }{ 13 | {name: "Basic case with script tags", param1: "", want: "<script>alert('XSS');</script>"}, 14 | {name: "Basic case with HTML tags", param1: "
Hello World!
", want: "<div>Hello World!</div>"}, 15 | {name: "Case with special characters", param1: "Hello & welcome to !", want: "Hello & welcome to <Validator>!"}, 16 | {name: "Case with quotes", param1: `He said, "Hello World!"`, want: "He said, "Hello World!""}, 17 | {name: "Case with apostrophes", param1: "It's a beautiful day!", want: "It's a beautiful day!"}, 18 | {name: "Empty string case", param1: "", want: ""}, 19 | {name: "Whitespace preservation", param1: " ", want: " <span> "}, 20 | {name: "Complex string with multiple types", param1: "Hello World & Friends", want: "Hello <b>World</b> & <i>Friends</i>"}, 21 | {name: "String with only blacklisted characters", param1: "<>", want: "<>"}, 22 | {name: "String with no characters to escape", param1: "No special chars here.", want: "No special chars here."}, 23 | } 24 | 25 | for _, test := range tests { 26 | 27 | t.Run(test.name, func(t *testing.T) { 28 | result := Escape(test.param1) 29 | 30 | if result != test.want { 31 | t.Errorf("got %s`, wanted %s`", result, test.want) 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sanitizer/stripLow_test.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestStripLow(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | param1 string 12 | param2 bool 13 | want string 14 | }{ 15 | {name: "Remove low ASCII chars", param1: "Hello\x00World", param2: false, want: "HelloWorld"}, 16 | {name: "Keep newlines, remove other low ASCII chars", param1: "Hello\x00\nWorld", param2: true, want: "Hello\nWorld"}, 17 | {name: "No low ASCII chars to strip", param1: "HelloWorld", param2: false, want: "HelloWorld"}, 18 | {name: "Remove low ASCII chars, keep newlines", param1: "Hi\x00\n\x01There", param2: true, want: "Hi\nThere"}, 19 | {name: "Strip all low ASCII chars including newlines", param1: "Text\x00\nWith\x01\x02Low", param2: false, want: "TextWithLow"}, 20 | {name: "Remove only control characters, keep newlines", param1: "\x01Hello\nWorld\x02", param2: true, want: "Hello\nWorld"}, 21 | {name: "String with no low characters", param1: "NormalText123", param2: false, want: "NormalText123"}, 22 | } 23 | 24 | for _, test := range tests { 25 | 26 | t.Run(test.name, func(t *testing.T) { 27 | result := StripLow(test.param1, test.param2) 28 | 29 | // if result != test.want { 30 | if !reflect.DeepEqual(result, test.want) { 31 | t.Errorf("got `%s`, wanted `%s`", result, test.want) 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sanitizer/striplow.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // A sanitizer that removes characters with a numerical value < 32 and 127, mostly control characters. 8 | // 9 | // If keepNewLines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD). 10 | // 11 | // str := sanitizer.StripLow("Hello\x00World", false) 12 | // fmt.Println(str) // "HelloWorld" 13 | func StripLow(str string, keepNewLines bool) string { 14 | var newStr strings.Builder 15 | 16 | for _, r := range str { 17 | if r < 32 || r == 127 { 18 | if keepNewLines && (r == '\n' || r == '\r') { 19 | newStr.WriteRune(r) 20 | } 21 | } else { 22 | newStr.WriteRune(r) 23 | } 24 | } 25 | 26 | return newStr.String() 27 | } 28 | -------------------------------------------------------------------------------- /sanitizer/toboolean.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | // A sanitizer that converts the input string to a boolean. 4 | // 5 | // Everything except for "0", "false" and """ returns true. 6 | // 7 | // In strict mode only "1" and "true" return true. 8 | // 9 | // bool := sanitizer.ToBoolean("", false) 10 | // fmt.Println(bool) // false 11 | func ToBoolean(str string, strict bool) bool { 12 | if strict { 13 | if str == "1" || str == "true" { 14 | return true 15 | } else { 16 | return false 17 | } 18 | } else { 19 | if str == "0" || str == "false" || str == "" { 20 | return false 21 | } else { 22 | return true 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sanitizer/toboolean_test.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestToBoolean(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | param2 bool 12 | want bool 13 | }{ 14 | {name: "Non-strict mode: empty string returns false", param1: "", param2: false, want: false}, 15 | {name: "Non-strict mode: '0' returns false", param1: "0", param2: false, want: false}, 16 | {name: "Non-strict mode: 'false' returns false", param1: "false", param2: false, want: false}, 17 | {name: "Non-strict mode: 'true' returns true", param1: "true", param2: false, want: true}, 18 | {name: "Non-strict mode: '1' returns true", param1: "1", param2: false, want: true}, 19 | {name: "Non-strict mode: random string returns true", param1: "random", param2: false, want: true}, 20 | {name: "Strict mode: '1' returns true", param1: "1", param2: true, want: true}, 21 | {name: "Strict mode: 'true' returns true", param1: "true", param2: true, want: true}, 22 | {name: "Strict mode: '0' returns false", param1: "0", param2: true, want: false}, 23 | {name: "Strict mode: 'false' returns false", param1: "false", param2: true, want: false}, 24 | {name: "Strict mode: random string returns false", param1: "random", param2: true, want: false}, 25 | {name: "Strict mode: empty string returns false", param1: "", param2: true, want: false}, 26 | } 27 | 28 | for _, test := range tests { 29 | t.Run(test.name, func(t *testing.T) { 30 | result := ToBoolean(test.param1, test.param2) 31 | 32 | if result != test.want { 33 | t.Errorf("got `%t`, wanted `%t`", result, test.want) 34 | } 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /sanitizer/todate.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import "time" 4 | 5 | // IsDate formats 6 | const ( 7 | StandardDateLayout = "2006-01-02" 8 | SlashDateLayout = "2006/01/02" 9 | DateTimeLayout = "2006-01-02 15:04:05" 10 | ISO8601Layout = "2006-01-02T15:04:05" 11 | ISO8601ZuluLayout = "2006-01-02T15:04:05Z" 12 | ISO8601WithMillisecondsLayout = "2006-01-02T15:04:05.000Z" 13 | ) 14 | 15 | var dateLayouts = []string{StandardDateLayout, SlashDateLayout, DateTimeLayout, ISO8601Layout, ISO8601ZuluLayout, ISO8601WithMillisecondsLayout} 16 | 17 | var timeLayouts = []string{ 18 | time.ANSIC, 19 | time.UnixDate, 20 | time.RubyDate, 21 | time.RFC822, 22 | time.RFC822Z, 23 | time.RFC850, 24 | time.RFC1123, 25 | time.RFC1123Z, 26 | time.Kitchen, 27 | time.Stamp, 28 | time.StampMilli, 29 | time.StampMicro, 30 | time.StampNano, 31 | time.DateTime, 32 | time.DateOnly, 33 | time.TimeOnly, 34 | } 35 | 36 | // var combinedLayouts = append(timeLayouts,) 37 | 38 | // A sanitizer that converts the input string to a pointer to time.Time, if the input is not of a time layout returns a nil pointer. e.g (Layout, ANSIC, UnixDate, RubyDate, RFC822, RFC822Z, RFC850, RFC1123, RFC1123Z, Kitchen, Stamp, StampMilli, StampMicro, StampNano, DateTime, DateOnly, TimeOnly) 39 | // 40 | // date := sanitizer.ToDate("Mon Jan 2 15:04:05 2006") 41 | // dateAsStr := date.String() 42 | // fmt.Println(dateAsStr) // "2006-01-02 15:04:05 +0000 UTC" 43 | func ToDate(str string) *time.Time { 44 | 45 | for _, layout := range append(timeLayouts, dateLayouts...) { 46 | if t, err := time.Parse(layout, str); err == nil { 47 | return &t 48 | } 49 | } 50 | 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /sanitizer/todate_test.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestToDate(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | want string 12 | }{ 13 | {name: "Valid ANSIC format", param1: "Mon Jan 2 15:04:05 2006", want: "2006-01-02 15:04:05 +0000 UTC"}, 14 | {name: "Valid UnixDate format", param1: "Mon Jan 2 15:04:05 MST 2006", want: "2006-01-02 15:04:05 +0000 MST"}, 15 | {name: "Valid RubyDate format", param1: "Mon Jan 02 15:04:05 -0700 2006", want: "2006-01-02 15:04:05 -0700 -0700"}, 16 | {name: "Valid RFC822 format", param1: "02 Jan 06 15:04 MST", want: "2006-01-02 15:04:00 +0000 MST"}, 17 | {name: "Valid RFC822Z format", param1: "02 Jan 06 15:04 -0700", want: "2006-01-02 15:04:00 -0700 -0700"}, 18 | {name: "Valid RFC850 format", param1: "Monday, 02-Jan-06 15:04:05 MST", want: "2006-01-02 15:04:05 +0000 MST"}, 19 | {name: "Valid RFC1123 format", param1: "Mon, 02 Jan 2006 15:04:05 MST", want: "2006-01-02 15:04:05 +0000 MST"}, 20 | {name: "Valid RFC1123Z format", param1: "Mon, 02 Jan 2006 15:04:05 -0700", want: "2006-01-02 15:04:05 -0700 -0700"}, 21 | {name: "Valid Kitchen format", param1: "3:04PM", want: "0000-01-01 15:04:00 +0000 UTC"}, 22 | {name: "Valid Stamp format", param1: "Jan 2 15:04:05", want: "0000-01-02 15:04:05 +0000 UTC"}, 23 | {name: "Valid StampMilli format", param1: "Jan 2 15:04:05.000", want: "0000-01-02 15:04:05 +0000 UTC"}, 24 | {name: "Valid StampMicro format", param1: "Jan 2 15:04:05.000000", want: "0000-01-02 15:04:05 +0000 UTC"}, 25 | {name: "Valid StampNano format", param1: "Jan 2 15:04:05.000000000", want: "0000-01-02 15:04:05 +0000 UTC"}, 26 | {name: "Valid DateTime format", param1: "2006-01-02 15:04:05", want: "2006-01-02 15:04:05 +0000 UTC"}, 27 | {name: "Valid DateOnly format", param1: "2006-01-02", want: "2006-01-02 00:00:00 +0000 UTC"}, 28 | {name: "Valid TimeOnly format", param1: "15:04:05", want: "0000-01-01 15:04:05 +0000 UTC"}, 29 | {name: "Invalid format", param1: "invalid-date-string", want: ""}, 30 | {name: "Empty string", param1: "", want: ""}, 31 | } 32 | 33 | for _, test := range tests { 34 | t.Run(test.name, func(t *testing.T) { 35 | date := ToDate(test.param1) 36 | 37 | if date == nil && test.want != "" { 38 | if test.want == "" { 39 | return 40 | } 41 | 42 | t.Errorf("got `%v`, wanted `%s`", nil, test.want) 43 | return 44 | } 45 | 46 | dateAsStr := date.String() 47 | 48 | if dateAsStr != test.want { 49 | t.Errorf("got `%s`, wanted `%s`", dateAsStr, test.want) 50 | } 51 | }) 52 | } 53 | } -------------------------------------------------------------------------------- /sanitizer/tofloat.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import "strconv" 4 | 5 | // A sanitizer that converts the input string to a float64 and also returns an error if the input is not a float. 6 | // 7 | // flt := sanitizer.ToFloat("123.45") 8 | // fmt.Println(flt) // 123.45 9 | func ToFloat(str string) (float64, error) { 10 | float, err := strconv.ParseFloat(str, 64) 11 | 12 | return float, err 13 | } -------------------------------------------------------------------------------- /sanitizer/tofloat_test.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestToFloat(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | param1 string 13 | want float64 14 | err error 15 | }{ 16 | {name: "Valid float string", param1: "123.45", want: 123.45, err: nil}, 17 | {name: "Valid negative float string", param1: "-987.65", want: -987.65, err: nil}, 18 | {name: "Valid integer string", param1: "42", want: 42.0, err: nil}, 19 | {name: "Valid float string with scientific notation", param1: "1.23e4", want: 12300.0, err: nil}, 20 | {name: "Valid zero float string", param1: "0.0", want: 0.0, err: nil}, 21 | {name: "Valid negative zero float string", param1: "-0.0", want: -0.0, err: nil}, 22 | {name: "Invalid float string: letters", param1: "abc", want: 0.0, err: fmt.Errorf("invalid syntax")}, 23 | {name: "Invalid float string: symbols", param1: "#$%", want: 0.0, err: fmt.Errorf("invalid syntax")}, 24 | {name: "Empty string", param1: "", want: 0.0, err: fmt.Errorf("invalid syntax")}, 25 | {name: "String with spaces", param1: " 123.45 ", want: 123.45, err: nil}, 26 | {name: "String with commas", param1: "1,234.56", want: 0.0, err: fmt.Errorf("invalid syntax")}, 27 | {name: "String with multiple dots", param1: "12.34.56", want: 0.0, err: fmt.Errorf("invalid syntax")}, 28 | } 29 | 30 | for _, test := range tests { 31 | t.Run(test.name, func(t *testing.T) { 32 | result, err := ToFloat(test.param1) 33 | 34 | if err != nil { 35 | errMsg := err.Error() 36 | 37 | if strings.HasSuffix(errMsg, "invalid syntax") { 38 | return 39 | } 40 | } 41 | 42 | if result != test.want || err != test.err { 43 | t.Errorf("got `%.2f`, wanted `%.2f`", result, test.want) 44 | } 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /sanitizer/toint.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import "strconv" 4 | 5 | // A sanitizer that converts the input string to an int and also returns an error if the input is not a int. (Beware of octals) 6 | // 7 | // num := sanitizer.ToInt("123") 8 | // fmt.Println(num) // 123 9 | func ToInt(str string) (int, error) { 10 | num, err := strconv.Atoi(str) 11 | 12 | return num, err 13 | } -------------------------------------------------------------------------------- /sanitizer/toint_test.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestToInt(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | param1 string 13 | want int 14 | err error 15 | }{ 16 | {name: "Valid integer string", param1: "123", want: 123, err: nil}, 17 | {name: "Valid negative integer string", param1: "-987", want: -987, err: nil}, 18 | {name: "Valid zero integer string", param1: "0", want: 0, err: nil}, 19 | {name: "Valid large integer string", param1: "9223372036854775807", want: 9223372036854775807, err: nil}, // Max int64 value 20 | {name: "Valid negative large integer string", param1: "-9223372036854775808", want: -9223372036854775808, err: nil}, // Min int64 value 21 | {name: "Invalid integer string: letters", param1: "abc", want: 0, err: fmt.Errorf("invalid syntax")}, 22 | {name: "Invalid integer string: symbols", param1: "#$%", want: 0, err: fmt.Errorf("invalid syntax")}, 23 | {name: "Empty string", param1: "", want: 0, err: fmt.Errorf("invalid syntax")}, 24 | {name: "String with spaces", param1: " 456 ", want: 456, err: nil}, 25 | {name: "String with decimal", param1: "123.45", want: 0, err: fmt.Errorf("invalid syntax")}, 26 | {name: "String with commas", param1: "1,234", want: 0, err: fmt.Errorf("invalid syntax")}, 27 | {name: "Overflow large integer", param1: "9223372036854775808", want: 0, err: fmt.Errorf("value out of range")}, 28 | {name: "Underflow large negative integer", param1: "-9223372036854775809", want: 0, err: fmt.Errorf("value out of range")}, 29 | } 30 | 31 | for _, test := range tests { 32 | t.Run(test.name, func(t *testing.T) { 33 | result, err := ToInt(test.param1) 34 | 35 | if err != nil { 36 | errMsg := err.Error() 37 | fmt.Println(errMsg) 38 | 39 | if strings.HasSuffix(errMsg, "invalid syntax") || strings.HasSuffix(errMsg, "value out of range") { 40 | return 41 | } 42 | } 43 | 44 | if result != test.want || err != test.err { 45 | t.Errorf("got `%d`, wanted `%d`", result, test.want) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sanitizer/trim.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // A sanitizer that trim characters (whitespace by default) from both sides of the input. 8 | // 9 | // str := sanitizer.Trim(" Hello World ", "") 10 | // fmt.Println(str) // "Hello World" 11 | func Trim(str, chars string) string { 12 | if chars == "" { 13 | return strings.Trim(str, " ") 14 | } 15 | return strings.Trim(str, chars) 16 | } 17 | 18 | // A sanitizer that trims characters (whitespace by default) from the left-side of the input. 19 | // 20 | // str := sanitizer.LTrim(" Hello World ", "") 21 | // fmt.Println(str) // "Hello World " 22 | func LTrim(str, chars string) string { 23 | if chars == "" { 24 | return strings.TrimLeft(str, " ") 25 | } 26 | return strings.TrimLeft(str, chars) 27 | } 28 | 29 | // A sanitizer that trims characters (whitespace by default) from the right-side of the input. 30 | // 31 | // str := sanitizer.RTrim(" Hello World ", "") 32 | // fmt.Println(str) // " Hello World" 33 | func RTrim(str, chars string) string { 34 | if chars == "" { 35 | return strings.TrimRight(str, " ") 36 | } 37 | return strings.TrimRight(str, chars) 38 | } 39 | -------------------------------------------------------------------------------- /sanitizer/unescape.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import "html" 4 | 5 | // A sanitizer that replaces HTML encoded entities with <, >, &, ', ", `, \ and /. 6 | // 7 | // str := sanitizer.Unescape("&") 8 | // fmt.Println(str) // "&" 9 | func Unescape(str string) string { 10 | return html.UnescapeString(str) 11 | } -------------------------------------------------------------------------------- /sanitizer/unescape_test.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestUnescape(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | want string 12 | }{ 13 | {name: "Unescape HTML entity", param1: "&", want: "&"}, 14 | {name: "Unescape multiple HTML entities", param1: "<>"'", want: "<>\"'"}, 15 | {name: "Unescape numeric entity", param1: "'", want: "'"}, 16 | {name: "Unescape mixed entities and text", param1: "Hello & World!", want: "Hello & World!"}, 17 | {name: "Unescape without entities", param1: "Hello World!", want: "Hello World!"}, 18 | {name: "Unescape string with multiple spaces", param1: "Hello   World", want: "Hello   World"}, 19 | {name: "Unescape with no entities", param1: "Plain text", want: "Plain text"}, 20 | {name: "Unescape hexadecimal entity", param1: "'", want: "'"}, 21 | {name: "Unescape string with special character entities", param1: "© 2024", want: "© 2024"}, 22 | {name: "Unescape empty string", param1: "", want: ""}, 23 | } 24 | 25 | for _, test := range tests { 26 | 27 | t.Run(test.name, func(t *testing.T) { 28 | result := Unescape(test.param1) 29 | 30 | if result != test.want { 31 | t.Errorf("got `%s`, wanted `%s`", result, test.want) 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sanitizer/whitelist.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | // A sanitizer that removes characters that do not appear in the whitelist. 8 | // 9 | // The characters are used in a RegExp and will escaped for you. 10 | // 11 | // str := sanitizer.Whitelist("Hello123 World!", "a-zA-Z") 12 | // fmt.Println(str) // "HelloWorld" 13 | func Whitelist(str, whitelistedChars string) string { 14 | if whitelistedChars == "." { 15 | return str 16 | } 17 | 18 | re := regexp.MustCompile("[^" + regexp.QuoteMeta(whitelistedChars) + "]+") 19 | return re.ReplaceAllString(str, "") 20 | } 21 | -------------------------------------------------------------------------------- /sanitizer/whitelist_test.go: -------------------------------------------------------------------------------- 1 | package sanitizer 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestWhitelist(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | param1 string 11 | param2 string 12 | want string 13 | }{ 14 | {name: "Whitelist only letters", param1: "Hello123 World!", param2: "a-zA-Z", want: "HelloWorld"}, 15 | {name: "Whitelist only digits", param1: "Hello123 World!", param2: "0-9", want: "123"}, 16 | {name: "Whitelist letters and spaces", param1: "Hello123 World!", param2: "a-zA-Z ", want: "Hello World"}, 17 | {name: "Whitelist alphanumeric characters", param1: "Hello123 World!", param2: "a-zA-Z0-9", want: "Hello123World"}, 18 | {name: "Whitelist specific characters (symbols)", param1: "Hello123@World!", param2: "@!", want: "@!"}, 19 | {name: "Whitelist spaces only", param1: "Hello123 World!", param2: " ", want: " "}, 20 | {name: "Whitelist letters with accents", param1: "Olá123 Mundo!", param2: "a-zA-ZáéíóúÁÉÍÓÚ", want: "OláMundo"}, 21 | {name: "Whitelist empty string", param1: "", param2: "a-zA-Z", want: ""}, 22 | {name: "Whitelist with no matching characters", param1: "123456789", param2: "a-zA-Z", want: ""}, 23 | {name: "Whitelist all characters", param1: "Hello World!", param2: `.`, want: "Hello World!"}, 24 | } 25 | 26 | for _, test := range tests { 27 | 28 | t.Run(test.name, func(t *testing.T) { 29 | result := Whitelist(test.param1, test.param2) 30 | 31 | if result != test.want { 32 | t.Errorf("got `%s`, wanted `%s`", result, test.want) 33 | } 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | func ptr(i uint) *uint { 9 | return &i 10 | } 11 | 12 | func intPtr(i int) *int { 13 | return &i 14 | } 15 | 16 | func uintPtr(i uint) *uint { 17 | return &i 18 | } 19 | 20 | func floatPtr(f float64) *float64 { 21 | return &f 22 | } 23 | 24 | func strPtr(f string) *string { 25 | return &f 26 | } 27 | 28 | func stripDashesAndSpaces(str string) string { 29 | strWthOutDashes := strings.ReplaceAll(str, "-", "") 30 | strWthOutSpacesAndDashes := strings.ReplaceAll(strWthOutDashes, " ", "") 31 | 32 | return strWthOutSpacesAndDashes 33 | } 34 | 35 | func stripHyphens(str string) string { 36 | return strings.ReplaceAll(str, "-", "") 37 | } 38 | 39 | // digitSum returns the sum of digits in an int. 40 | func digitSum(i int) (sum int) { 41 | for { 42 | sum += i % 10 43 | i /= 10 44 | if i == 0 { 45 | break 46 | } 47 | } 48 | return 49 | } 50 | 51 | func validYearMonthDay(year, month, day string) bool { 52 | yr, err := strconv.Atoi(year) 53 | 54 | if err != nil { 55 | return false 56 | } 57 | 58 | mn, err := strconv.Atoi(month) 59 | 60 | if err != nil { 61 | return false 62 | } 63 | 64 | dy, err := strconv.Atoi(day) 65 | 66 | if err != nil { 67 | return false 68 | } 69 | 70 | monthLength := []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} 71 | 72 | // Adjust for leap years 73 | if yr%400 == 0 || (yr%100 != 0 && yr%4 == 0) { 74 | monthLength[1] = 29 75 | } 76 | 77 | if !(mn > 0 && mn < 13) { 78 | return false 79 | } 80 | 81 | if dy < 0 || dy > monthLength[mn-1] { 82 | return false 83 | } 84 | 85 | return true 86 | } 87 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package validatorgo 2 | --------------------------------------------------------------------------------