From ff27adbde5bbdb166a99c9c60e6ac5df3c2232dd Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 22 Aug 2024 06:59:07 -0700 Subject: [PATCH 01/41] Config rules. --- checker2.go | 117 +++++++++++++++++++++++++++++++++++++++++++++++ checker2_test.go | 41 +++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 checker2.go create mode 100644 checker2_test.go diff --git a/checker2.go b/checker2.go new file mode 100644 index 0000000..b20a6a4 --- /dev/null +++ b/checker2.go @@ -0,0 +1,117 @@ +package checker + +import ( + "fmt" + "reflect" + "strings" +) + +type Rule struct { + FieldIndex []int + Checkers []CheckFunc +} + +// rulesRegistry stores a collection of rules associated with a specific type. +var rulesRegistry = map[reflect.Type][]*Rule{} + +// checkersCache stores checkers associated with a specific config. +var checkersCache = map[string]CheckFunc{} + +// RegisterRulesFromConfig registers rules based on the provided struct type and config map. +func RegisterRulesFromConfig(structType reflect.Type, nameToConfig map[string]string) error { + rules := make([]*Rule, len(nameToConfig)) + + for name, config := range nameToConfig { + field, ok := structType.FieldByName(name) + if !ok { + return fmt.Errorf("field %s not found", name) + } + + checkers, err := initCheckersFromConfig(config) + if err != nil { + return fmt.Errorf("field %s has errors: %w", name, err) + } + + rules = append(rules, &Rule{ + FieldIndex: field.Index, + Checkers: checkers, + }) + } + + rulesRegistry[structType] = rules + + return nil +} + +// RegisterRulesFromTag registers rules based on the provided struct type and field tags. +func RegisterRulesFromTag(structType reflect.Type) error { + structTypes := []reflect.Type{structType} + + for len(structTypes) > 0 { + structType := structTypes[0] + structTypes = structTypes[1:] + + // Skip already registed structs + _, found := rulesRegistry[structType] + if found { + continue + } + + var rules []*Rule + + for i := range structType.NumField() { + field := structType.Field(i) + + // Queue the nested structs + if field.Type.Kind() == reflect.Struct { + structTypes = append(structTypes, field.Type) + continue + } + + config := field.Tag.Get("checkers") + if config == "" { + continue + } + + checkers, err := initCheckersFromConfig(config) + if err != nil { + return fmt.Errorf("field %s has errors: %w", field.Name, err) + } + + rules = append(rules, &Rule{ + FieldIndex: field.Index, + Checkers: checkers, + }) + } + + rulesRegistry[structType] = rules + } + + return nil +} + +// initCheckersFromConfig parses the given config string and initializes checker instances. +func initCheckersFromConfig(config string) ([]CheckFunc, error) { + items := strings.Fields(config) + checkers := make([]CheckFunc, len(items)) + + for i, item := range items { + checker, ok := checkersCache[item] + if ok { + checkers[i] = checker + continue + } + + name, params, _ := strings.Cut(item, ":") + + maker, ok := makers[name] + if !ok { + return nil, fmt.Errorf("checker %s not found", name) + } + + checkers[i] = maker(params) + checkersCache[item] = checkers[i] + } + + return checkers, nil +} diff --git a/checker2_test.go b/checker2_test.go new file mode 100644 index 0000000..5e8ffcc --- /dev/null +++ b/checker2_test.go @@ -0,0 +1,41 @@ +package checker_test + +import ( + "reflect" + "testing" + + "github.com/cinar/checker" +) + +func TestRegisterRulesFromConfig(t *testing.T) { + type Person struct { + Name string + Email string + } + + err := checker.RegisterRulesFromConfig(reflect.TypeFor[Person](), map[string]string{ + "Name": "trim required", + "Email": "required email", + }) + if err != nil { + t.Fatal(err) + } +} + +func TestRegisterRulesFromTag(t *testing.T) { + type Address struct { + Street string `checkers:"required"` + } + + type Person struct { + Name string `checkers:"trim required"` + Email string `checkers:"required email"` + Home Address + Work Address + } + + err := checker.RegisterRulesFromTag(reflect.TypeFor[Person]()) + if err != nil { + t.Fatal(err) + } +} From 04635fcca1c34a0d39a17f2a38f8997086b3af9a Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Tue, 29 Oct 2024 19:43:20 -0700 Subject: [PATCH 02/41] Copyright message is added to all source files. (#123) # Describe Request Copyright message is added to all source files. # Change Type Code maintenance. --- alphanumeric.go | 5 +++++ alphanumeric_test.go | 5 +++++ ascii.go | 5 +++++ ascii_test.go | 5 +++++ checker.go | 5 ++--- checker_test.go | 5 +++++ cidr.go | 5 +++++ cidr_test.go | 5 +++++ credit_card.go | 5 +++++ credit_card_test.go | 5 +++++ digits.go | 5 +++++ digits_test.go | 5 +++++ email.go | 5 +++++ email_test.go | 5 +++++ fqdn.go | 5 +++++ fqdn_test.go | 5 +++++ html_escape.go | 5 +++++ html_escape_test.go | 5 +++++ html_unescape.go | 5 +++++ html_unescape_test.go | 5 +++++ ip.go | 5 +++++ ip_test.go | 5 +++++ ipv4.go | 5 +++++ ipv4_test.go | 5 +++++ ipv6.go | 5 +++++ ipv6_test.go | 5 +++++ isbn.go | 5 +++++ isbn_test.go | 5 +++++ lower.go | 5 +++++ lower_test.go | 5 +++++ luhn.go | 5 +++++ luhn_test.go | 5 +++++ mac.go | 5 +++++ mac_test.go | 5 +++++ max.go | 5 +++++ max_test.go | 5 +++++ maxlength.go | 5 +++++ maxlength_test.go | 5 +++++ min.go | 5 +++++ min_test.go | 5 +++++ minlenght.go | 5 +++++ minlength_test.go | 5 +++++ regexp.go | 5 +++++ regexp_test.go | 5 +++++ required.go | 5 +++++ required_test.go | 5 +++++ same.go | 5 +++++ same_test.go | 5 +++++ test_helper.go | 5 +++++ test_helper_test.go | 5 +++++ title.go | 5 +++++ title_test.go | 5 +++++ trim.go | 5 +++++ trim_left.go | 5 +++++ trim_left_test.go | 5 +++++ trim_right.go | 5 +++++ trim_right_test.go | 5 +++++ trim_test.go | 5 +++++ upper.go | 5 +++++ upper_test.go | 5 +++++ url.go | 5 +++++ url_escape.go | 5 +++++ url_escape_test.go | 5 +++++ url_test.go | 5 +++++ url_unescape.go | 5 +++++ url_unescape_test.go | 5 +++++ 66 files changed, 327 insertions(+), 3 deletions(-) diff --git a/alphanumeric.go b/alphanumeric.go index bea70d4..9ef41c9 100644 --- a/alphanumeric.go +++ b/alphanumeric.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/alphanumeric_test.go b/alphanumeric_test.go index 00d2107..9e92fa7 100644 --- a/alphanumeric_test.go +++ b/alphanumeric_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/ascii.go b/ascii.go index e06c171..452b075 100644 --- a/ascii.go +++ b/ascii.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/ascii_test.go b/ascii_test.go index d170b87..fa2f5e4 100644 --- a/ascii_test.go +++ b/ascii_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/checker.go b/checker.go index 0865ac2..4e4436f 100644 --- a/checker.go +++ b/checker.go @@ -1,10 +1,9 @@ // Package checker is a Go library for validating user input through struct tags. // -// https://github.com/cinar/checker -// -// Copyright 2023 Onur Cinar. All rights reserved. +// Copyright (c) 2023-2024 Onur Cinar. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. +// https://github.com/cinar/checker package checker import ( diff --git a/checker_test.go b/checker_test.go index 7d85815..1a39146 100644 --- a/checker_test.go +++ b/checker_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/cidr.go b/cidr.go index 8490dc9..4276a8a 100644 --- a/cidr.go +++ b/cidr.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/cidr_test.go b/cidr_test.go index 7b84ad7..47cddc9 100644 --- a/cidr_test.go +++ b/cidr_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/credit_card.go b/credit_card.go index a904ad2..5a4f464 100644 --- a/credit_card.go +++ b/credit_card.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/credit_card_test.go b/credit_card_test.go index f26e544..ad2898d 100644 --- a/credit_card_test.go +++ b/credit_card_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/digits.go b/digits.go index ee16598..3ace687 100644 --- a/digits.go +++ b/digits.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/digits_test.go b/digits_test.go index f685d5e..40c8f02 100644 --- a/digits_test.go +++ b/digits_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/email.go b/email.go index 7a9588d..6238bcf 100644 --- a/email.go +++ b/email.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/email_test.go b/email_test.go index 67dd96e..4460f7b 100644 --- a/email_test.go +++ b/email_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/fqdn.go b/fqdn.go index 4ba200e..002ce19 100644 --- a/fqdn.go +++ b/fqdn.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/fqdn_test.go b/fqdn_test.go index e417e01..4e822f0 100644 --- a/fqdn_test.go +++ b/fqdn_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/html_escape.go b/html_escape.go index ed540ad..2701f56 100644 --- a/html_escape.go +++ b/html_escape.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/html_escape_test.go b/html_escape_test.go index 3ce5ba5..799eba4 100644 --- a/html_escape_test.go +++ b/html_escape_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/html_unescape.go b/html_unescape.go index f348805..f202ae1 100644 --- a/html_unescape.go +++ b/html_unescape.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/html_unescape_test.go b/html_unescape_test.go index a459736..f6f333c 100644 --- a/html_unescape_test.go +++ b/html_unescape_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/ip.go b/ip.go index aacef03..a113269 100644 --- a/ip.go +++ b/ip.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/ip_test.go b/ip_test.go index f2fcfb3..a09bc28 100644 --- a/ip_test.go +++ b/ip_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/ipv4.go b/ipv4.go index 54ca08c..68225fc 100644 --- a/ipv4.go +++ b/ipv4.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/ipv4_test.go b/ipv4_test.go index 234d8b1..f38f542 100644 --- a/ipv4_test.go +++ b/ipv4_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/ipv6.go b/ipv6.go index 10edf45..da6ba34 100644 --- a/ipv6.go +++ b/ipv6.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/ipv6_test.go b/ipv6_test.go index 7d60316..68d9e95 100644 --- a/ipv6_test.go +++ b/ipv6_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/isbn.go b/isbn.go index 11e50db..3b4d4d6 100644 --- a/isbn.go +++ b/isbn.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/isbn_test.go b/isbn_test.go index 2e2f132..d8fe4be 100644 --- a/isbn_test.go +++ b/isbn_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/lower.go b/lower.go index 1a8d99b..6cf6987 100644 --- a/lower.go +++ b/lower.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/lower_test.go b/lower_test.go index 8b68d46..e5f660c 100644 --- a/lower_test.go +++ b/lower_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/luhn.go b/luhn.go index ef12591..4f8808a 100644 --- a/luhn.go +++ b/luhn.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/luhn_test.go b/luhn_test.go index 71f806c..bdd06a5 100644 --- a/luhn_test.go +++ b/luhn_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/mac.go b/mac.go index 56a20c2..7c6fbf2 100644 --- a/mac.go +++ b/mac.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/mac_test.go b/mac_test.go index d8aed1a..79f9729 100644 --- a/mac_test.go +++ b/mac_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/max.go b/max.go index 4583ae3..04a028e 100644 --- a/max.go +++ b/max.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/max_test.go b/max_test.go index 0826ef3..144246f 100644 --- a/max_test.go +++ b/max_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/maxlength.go b/maxlength.go index 44e28ef..910e9f4 100644 --- a/maxlength.go +++ b/maxlength.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/maxlength_test.go b/maxlength_test.go index b76f5a3..5322cfc 100644 --- a/maxlength_test.go +++ b/maxlength_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/min.go b/min.go index 15ea1bd..dcab579 100644 --- a/min.go +++ b/min.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/min_test.go b/min_test.go index 8b1cad9..b518e3c 100644 --- a/min_test.go +++ b/min_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/minlenght.go b/minlenght.go index 15f57c0..376444d 100644 --- a/minlenght.go +++ b/minlenght.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/minlength_test.go b/minlength_test.go index 00cac55..2a95559 100644 --- a/minlength_test.go +++ b/minlength_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/regexp.go b/regexp.go index 5142b53..f4e24ab 100644 --- a/regexp.go +++ b/regexp.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/regexp_test.go b/regexp_test.go index 99cee62..64a443a 100644 --- a/regexp_test.go +++ b/regexp_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/required.go b/required.go index 1f06c5f..bfc8c48 100644 --- a/required.go +++ b/required.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import "reflect" diff --git a/required_test.go b/required_test.go index 0781377..27a43cc 100644 --- a/required_test.go +++ b/required_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/same.go b/same.go index b1c1057..1901041 100644 --- a/same.go +++ b/same.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/same_test.go b/same_test.go index 54e7f09..31e8181 100644 --- a/same_test.go +++ b/same_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/test_helper.go b/test_helper.go index ab3319f..ce78ede 100644 --- a/test_helper.go +++ b/test_helper.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import "testing" diff --git a/test_helper_test.go b/test_helper_test.go index a093409..94ca47f 100644 --- a/test_helper_test.go +++ b/test_helper_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/title.go b/title.go index 68d9944..9a75bac 100644 --- a/title.go +++ b/title.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/title_test.go b/title_test.go index acad9d1..2a56175 100644 --- a/title_test.go +++ b/title_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/trim.go b/trim.go index b5b9f18..36638be 100644 --- a/trim.go +++ b/trim.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/trim_left.go b/trim_left.go index 04b7d87..a0aee1a 100644 --- a/trim_left.go +++ b/trim_left.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/trim_left_test.go b/trim_left_test.go index 181bdb1..bd21696 100644 --- a/trim_left_test.go +++ b/trim_left_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/trim_right.go b/trim_right.go index 6f0bb6c..2790c44 100644 --- a/trim_right.go +++ b/trim_right.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/trim_right_test.go b/trim_right_test.go index da3b114..6adb417 100644 --- a/trim_right_test.go +++ b/trim_right_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/trim_test.go b/trim_test.go index 2e58fb3..876370d 100644 --- a/trim_test.go +++ b/trim_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/upper.go b/upper.go index 85ce3f5..9b9de87 100644 --- a/upper.go +++ b/upper.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/upper_test.go b/upper_test.go index fedd20b..1d52cee 100644 --- a/upper_test.go +++ b/upper_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/url.go b/url.go index 1553d2a..a53d3ff 100644 --- a/url.go +++ b/url.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/url_escape.go b/url_escape.go index 7a0b1c0..a41784e 100644 --- a/url_escape.go +++ b/url_escape.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/url_escape_test.go b/url_escape_test.go index 2b7cd6d..a28ec11 100644 --- a/url_escape_test.go +++ b/url_escape_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/url_test.go b/url_test.go index 644e183..ce97f90 100644 --- a/url_test.go +++ b/url_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( diff --git a/url_unescape.go b/url_unescape.go index 99859c0..a115689 100644 --- a/url_unescape.go +++ b/url_unescape.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker import ( diff --git a/url_unescape_test.go b/url_unescape_test.go index 19427a9..6c4995b 100644 --- a/url_unescape_test.go +++ b/url_unescape_test.go @@ -1,3 +1,8 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + package checker_test import ( From da76950858b3852fab2924969f6d1c47d43c7163 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Tue, 29 Oct 2024 19:46:44 -0700 Subject: [PATCH 03/41] Checker errors are converted from string to error. (#124) # Describe Request Checker errors are converted from string to error. # Change Type New feature. --- alphanumeric.go | 13 +++---- alphanumeric_test.go | 9 +++-- ascii.go | 13 +++---- ascii_test.go | 9 +++-- checker.go | 30 +++++++---------- checker_test.go | 10 +++--- cidr.go | 13 +++---- cidr_test.go | 9 +++-- credit_card.go | 33 +++++++++--------- credit_card_test.go | 80 ++++++++++++++++++++++---------------------- digits.go | 13 +++---- digits_test.go | 9 +++-- email.go | 33 +++++++++--------- email_test.go | 9 +++-- fqdn.go | 25 +++++++------- fqdn_test.go | 23 ++++++------- html_escape.go | 4 +-- html_escape_test.go | 1 - html_unescape.go | 4 +-- ip.go | 13 +++---- ip_test.go | 9 +++-- ipv4.go | 15 +++++---- ipv4_test.go | 11 +++--- ipv6.go | 15 +++++---- ipv6_test.go | 10 +++--- isbn.go | 29 ++++++++-------- isbn_test.go | 61 ++++++++++++++++----------------- lower.go | 4 +-- lower_test.go | 2 +- luhn.go | 13 +++---- luhn_test.go | 7 ++-- mac.go | 13 +++---- mac_test.go | 9 +++-- max.go | 14 ++++---- max_test.go | 7 ++-- maxlength.go | 14 ++++---- maxlength_test.go | 7 ++-- min.go | 14 ++++---- min_test.go | 7 ++-- minlenght.go | 14 ++++---- minlength_test.go | 7 ++-- regexp.go | 23 +++++++------ regexp_test.go | 9 ++--- required.go | 19 ++++++----- required_test.go | 30 ++++++++--------- same.go | 13 +++---- title.go | 4 +-- title_test.go | 2 +- trim.go | 4 +-- trim_left.go | 4 +-- trim_left_test.go | 2 +- trim_right.go | 4 +-- trim_right_test.go | 2 +- trim_test.go | 2 +- upper.go | 4 +-- upper_test.go | 2 +- url.go | 17 +++++----- url_escape.go | 4 +-- url_test.go | 12 +++---- url_unescape.go | 4 +-- 60 files changed, 394 insertions(+), 402 deletions(-) diff --git a/alphanumeric.go b/alphanumeric.go index 9ef41c9..8b87ace 100644 --- a/alphanumeric.go +++ b/alphanumeric.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "reflect" "unicode" ) @@ -13,18 +14,18 @@ import ( // CheckerAlphanumeric is the name of the checker. const CheckerAlphanumeric = "alphanumeric" -// ResultNotAlphanumeric indicates that the given string contains non-alphanumeric characters. -const ResultNotAlphanumeric = "NOT_ALPHANUMERIC" +// ErrNotAlphanumeric indicates that the given string contains non-alphanumeric characters. +var ErrNotAlphanumeric = errors.New("please use only letters and numbers") // IsAlphanumeric checks if the given string consists of only alphanumeric characters. -func IsAlphanumeric(value string) Result { +func IsAlphanumeric(value string) error { for _, c := range value { if !unicode.IsDigit(c) && !unicode.IsLetter(c) { - return ResultNotAlphanumeric + return ErrNotAlphanumeric } } - return ResultValid + return nil } // makeAlphanumeric makes a checker function for the alphanumeric checker. @@ -33,7 +34,7 @@ func makeAlphanumeric(_ string) CheckFunc { } // checkAlphanumeric checks if the given string consists of only alphanumeric characters. -func checkAlphanumeric(value, _ reflect.Value) Result { +func checkAlphanumeric(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } diff --git a/alphanumeric_test.go b/alphanumeric_test.go index 9e92fa7..4fe9661 100644 --- a/alphanumeric_test.go +++ b/alphanumeric_test.go @@ -12,21 +12,20 @@ import ( ) func ExampleIsAlphanumeric() { - result := checker.IsAlphanumeric("ABcd1234") - - if result != checker.ResultValid { + err := checker.IsAlphanumeric("ABcd1234") + if err != nil { // Send the mistakes back to the user } } func TestIsAlphanumericInvalid(t *testing.T) { - if checker.IsAlphanumeric("-/") == checker.ResultValid { + if checker.IsAlphanumeric("-/") == nil { t.Fail() } } func TestIsAlphanumericValid(t *testing.T) { - if checker.IsAlphanumeric("ABcd1234") != checker.ResultValid { + if checker.IsAlphanumeric("ABcd1234") != nil { t.Fail() } } diff --git a/ascii.go b/ascii.go index 452b075..32f68a2 100644 --- a/ascii.go +++ b/ascii.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "reflect" "unicode" ) @@ -13,18 +14,18 @@ import ( // CheckerASCII is the name of the checker. const CheckerASCII = "ascii" -// ResultNotASCII indicates that the given string contains non-ASCII characters. -const ResultNotASCII = "NOT_ASCII" +// ErrNotASCII indicates that the given string contains non-ASCII characters. +var ErrNotASCII = errors.New("please use standard English characters only") // IsASCII checks if the given string consists of only ASCII characters. -func IsASCII(value string) Result { +func IsASCII(value string) error { for _, c := range value { if c > unicode.MaxASCII { - return ResultNotASCII + return ErrNotASCII } } - return ResultValid + return nil } // makeASCII makes a checker function for the ASCII checker. @@ -33,7 +34,7 @@ func makeASCII(_ string) CheckFunc { } // checkASCII checks if the given string consists of only ASCII characters. -func checkASCII(value, _ reflect.Value) Result { +func checkASCII(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } diff --git a/ascii_test.go b/ascii_test.go index fa2f5e4..8685362 100644 --- a/ascii_test.go +++ b/ascii_test.go @@ -12,21 +12,20 @@ import ( ) func ExampleIsASCII() { - result := checker.IsASCII("Checker") - - if result != checker.ResultValid { + err := checker.IsASCII("Checker") + if err != nil { // Send the mistakes back to the user } } func TestIsASCIIInvalid(t *testing.T) { - if checker.IsASCII("𝄞 Music!") == checker.ResultValid { + if checker.IsASCII("𝄞 Music!") == nil { t.Fail() } } func TestIsASCIIValid(t *testing.T) { - if checker.IsASCII("Checker") != checker.ResultValid { + if checker.IsASCII("Checker") != nil { t.Fail() } } diff --git a/checker.go b/checker.go index 4e4436f..afbef50 100644 --- a/checker.go +++ b/checker.go @@ -12,17 +12,14 @@ import ( "strings" ) -// Result is a unique textual identifier for the mistake. -type Result string +// CheckFunc defines the signature for the checker functions. +type CheckFunc func(value, parent reflect.Value) error -// CheckFunc defines the checker function. -type CheckFunc func(value, parent reflect.Value) Result - -// MakeFunc defines the maker function. +// MakeFunc defines the signature for the checker maker functions. type MakeFunc func(params string) CheckFunc -// Mistakes provides mapping to checker result for the invalid fields. -type Mistakes map[string]Result +// Errors provides a mapping of the checker errors keyed by the field names. +type Errors map[string]error type checkerJob struct { Parent reflect.Value @@ -31,10 +28,7 @@ type checkerJob struct { Config string } -// ResultValid result indicates that the user input is valid. -const ResultValid Result = "VALID" - -// makers provides mapping to maker function for the checkers. +// makers provides a mapping of the maker functions keyed by the respective checker names. var makers = map[string]MakeFunc{ CheckerAlphanumeric: makeAlphanumeric, CheckerASCII: makeASCII, @@ -74,14 +68,14 @@ func Register(name string, maker MakeFunc) { makers[name] = maker } -// Check checks the given struct based on the checkers listed in each field's strcut tag named checkers. -func Check(s interface{}) (Mistakes, bool) { +// Check function checks the given struct based on the checkers listed in field tag names. +func Check(s interface{}) (Errors, bool) { root := reflect.Indirect(reflect.ValueOf(s)) if root.Kind() != reflect.Struct { panic("expecting struct") } - mistakes := Mistakes{} + errors := Errors{} jobs := []checkerJob{ { @@ -123,15 +117,15 @@ func Check(s interface{}) (Mistakes, bool) { } } else { for _, checker := range initCheckers(job.Config) { - if result := checker(job.Value, job.Parent); result != ResultValid { - mistakes[job.Name] = result + if err := checker(job.Value, job.Parent); err != nil { + errors[job.Name] = err break } } } } - return mistakes, len(mistakes) == 0 + return errors, len(errors) == 0 } // initCheckers initializes the checkers provided in the config. diff --git a/checker_test.go b/checker_test.go index 1a39146..723a9e6 100644 --- a/checker_test.go +++ b/checker_test.go @@ -30,8 +30,8 @@ func TestInitCheckersKnwon(t *testing.T) { } func TestRegister(t *testing.T) { - var checker CheckFunc = func(_, _ reflect.Value) Result { - return ResultValid + var checker CheckFunc = func(_, _ reflect.Value) error { + return nil } var maker MakeFunc = func(_ string) CheckFunc { @@ -69,7 +69,7 @@ func TestCheckInvalid(t *testing.T) { t.Fail() } - if mistakes["Name"] != ResultRequired { + if mistakes["Name"] != ErrRequired { t.Fail() } } @@ -121,11 +121,11 @@ func TestCheckNestedStruct(t *testing.T) { t.Fail() } - if mistakes["Name"] != ResultRequired { + if mistakes["Name"] != ErrRequired { t.Fail() } - if mistakes["Home.Street"] != ResultRequired { + if mistakes["Home.Street"] != ErrRequired { t.Fail() } } diff --git a/cidr.go b/cidr.go index 4276a8a..275427c 100644 --- a/cidr.go +++ b/cidr.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "net" "reflect" ) @@ -13,17 +14,17 @@ import ( // CheckerCidr is the name of the checker. const CheckerCidr = "cidr" -// ResultNotCidr indicates that the given value is not a valid CIDR. -const ResultNotCidr = "NOT_CIDR" +// ErrNotCidr indicates that the given value is not a valid CIDR. +var ErrNotCidr = errors.New("please enter a valid CIDR") // IsCidr checker checks if the value is a valid CIDR notation IP address and prefix length. -func IsCidr(value string) Result { +func IsCidr(value string) error { _, _, err := net.ParseCIDR(value) if err != nil { - return ResultNotCidr + return ErrNotCidr } - return ResultValid + return nil } // makeCidr makes a checker function for the ip checker. @@ -32,7 +33,7 @@ func makeCidr(_ string) CheckFunc { } // checkCidr checker checks if the value is a valid CIDR notation IP address and prefix length. -func checkCidr(value, _ reflect.Value) Result { +func checkCidr(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } diff --git a/cidr_test.go b/cidr_test.go index 47cddc9..2dd1e38 100644 --- a/cidr_test.go +++ b/cidr_test.go @@ -12,21 +12,20 @@ import ( ) func ExampleIsCidr() { - result := checker.IsCidr("2001:db8::/32") - - if result != checker.ResultValid { + err := checker.IsCidr("2001:db8::/32") + if err != nil { // Send the mistakes back to the user } } func TestIsCidrInvalid(t *testing.T) { - if checker.IsCidr("900.800.200.100//24") == checker.ResultValid { + if checker.IsCidr("900.800.200.100//24") == nil { t.Fail() } } func TestIsCidrValid(t *testing.T) { - if checker.IsCidr("2001:db8::/32") != checker.ResultValid { + if checker.IsCidr("2001:db8::/32") != nil { t.Fail() } } diff --git a/credit_card.go b/credit_card.go index 5a4f464..9f7cc01 100644 --- a/credit_card.go +++ b/credit_card.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "reflect" "regexp" "strings" @@ -14,8 +15,8 @@ import ( // CheckerCreditCard is the name of the checker. const CheckerCreditCard = "credit-card" -// ResultNotCreditCard indicates that the given value is not a valid credit card number. -const ResultNotCreditCard = "NOT_CREDIT_CARD" +// ErrNotCreditCard indicates that the given value is not a valid credit card number. +var ErrNotCreditCard = errors.New("please enter a valid credit card number") // amexExpression is the regexp for the AMEX cards. They start with 34 or 37, and has 15 digits. var amexExpression = "(?:^(?:3[47])[0-9]{13}$)" @@ -68,42 +69,42 @@ var creditCardPatterns = map[string]*regexp.Regexp{ } // IsAnyCreditCard checks if the given value is a valid credit card number. -func IsAnyCreditCard(number string) Result { +func IsAnyCreditCard(number string) error { return isCreditCard(number, anyCreditCardPattern) } // IsAmexCreditCard checks if the given valie is a valid AMEX credit card. -func IsAmexCreditCard(number string) Result { +func IsAmexCreditCard(number string) error { return isCreditCard(number, amexPattern) } // IsDinersCreditCard checks if the given valie is a valid Diners credit card. -func IsDinersCreditCard(number string) Result { +func IsDinersCreditCard(number string) error { return isCreditCard(number, dinersPattern) } // IsDiscoverCreditCard checks if the given valie is a valid Discover credit card. -func IsDiscoverCreditCard(number string) Result { +func IsDiscoverCreditCard(number string) error { return isCreditCard(number, discoverPattern) } // IsJcbCreditCard checks if the given valie is a valid JCB 15 credit card. -func IsJcbCreditCard(number string) Result { +func IsJcbCreditCard(number string) error { return isCreditCard(number, jcbPattern) } // IsMasterCardCreditCard checks if the given valie is a valid MasterCard credit card. -func IsMasterCardCreditCard(number string) Result { +func IsMasterCardCreditCard(number string) error { return isCreditCard(number, masterCardPattern) } // IsUnionPayCreditCard checks if the given valie is a valid UnionPay credit card. -func IsUnionPayCreditCard(number string) Result { +func IsUnionPayCreditCard(number string) error { return isCreditCard(number, unionPayPattern) } // IsVisaCreditCard checks if the given valie is a valid Visa credit card. -func IsVisaCreditCard(number string) Result { +func IsVisaCreditCard(number string) error { return isCreditCard(number, visaPattern) } @@ -124,7 +125,7 @@ func makeCreditCard(config string) CheckFunc { patterns = append(patterns, anyCreditCardPattern) } - return func(value, _ reflect.Value) Result { + return func(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } @@ -132,19 +133,19 @@ func makeCreditCard(config string) CheckFunc { number := value.String() for _, pattern := range patterns { - if isCreditCard(number, pattern) == ResultValid { - return ResultValid + if isCreditCard(number, pattern) == nil { + return nil } } - return ResultNotCreditCard + return ErrNotCreditCard } } // isCreditCard checks if the given number based on the given credit card pattern and the Luhn algorithm check digit. -func isCreditCard(number string, pattern *regexp.Regexp) Result { +func isCreditCard(number string, pattern *regexp.Regexp) error { if !pattern.MatchString(number) { - return ResultNotCreditCard + return ErrNotCreditCard } return IsLuhn(number) diff --git a/credit_card_test.go b/credit_card_test.go index ad2898d..f1905e8 100644 --- a/credit_card_test.go +++ b/credit_card_test.go @@ -35,206 +35,206 @@ func changeToInvalidLuhn(number string) string { } func ExampleIsAnyCreditCard() { - result := checker.IsAnyCreditCard("6011111111111117") + err := checker.IsAnyCreditCard("6011111111111117") - if result != checker.ResultValid { + if err != nil { // Send the mistakes back to the user } } func TestIsAnyCreditCardValid(t *testing.T) { - if checker.IsAnyCreditCard(amexCard) != checker.ResultValid { + if checker.IsAnyCreditCard(amexCard) != nil { t.Fail() } } func TestIsAnyCreditCardInvalidPattern(t *testing.T) { - if checker.IsAnyCreditCard(invalidCard) == checker.ResultValid { + if checker.IsAnyCreditCard(invalidCard) == nil { t.Fail() } } func TestIsAnyCreditCardInvalidLuhn(t *testing.T) { - if checker.IsAnyCreditCard(changeToInvalidLuhn(amexCard)) == checker.ResultValid { + if checker.IsAnyCreditCard(changeToInvalidLuhn(amexCard)) == nil { t.Fail() } } func ExampleIsAmexCreditCard() { - result := checker.IsAmexCreditCard("378282246310005") + err := checker.IsAmexCreditCard("378282246310005") - if result != checker.ResultValid { + if err != nil { // Send the mistakes back to the user } } func TestIsAmexCreditCardValid(t *testing.T) { - if checker.IsAmexCreditCard(amexCard) != checker.ResultValid { + if checker.IsAmexCreditCard(amexCard) != nil { t.Fail() } } func TestIsAmexCreditCardInvalidPattern(t *testing.T) { - if checker.IsAmexCreditCard(invalidCard) == checker.ResultValid { + if checker.IsAmexCreditCard(invalidCard) == nil { t.Fail() } } func TestIsAmexCreditCardInvalidLuhn(t *testing.T) { - if checker.IsAmexCreditCard(changeToInvalidLuhn(amexCard)) == checker.ResultValid { + if checker.IsAmexCreditCard(changeToInvalidLuhn(amexCard)) == nil { t.Fail() } } func ExampleIsDinersCreditCard() { - result := checker.IsDinersCreditCard("36227206271667") + err := checker.IsDinersCreditCard("36227206271667") - if result != checker.ResultValid { + if err != nil { // Send the mistakes back to the user } } func TestIsDinersCreditCardValid(t *testing.T) { - if checker.IsDinersCreditCard(dinersCard) != checker.ResultValid { + if checker.IsDinersCreditCard(dinersCard) != nil { t.Fail() } } func TestIsDinersCreditCardInvalidPattern(t *testing.T) { - if checker.IsDinersCreditCard(invalidCard) == checker.ResultValid { + if checker.IsDinersCreditCard(invalidCard) == nil { t.Fail() } } func TestIsDinersCreditCardInvalidLuhn(t *testing.T) { - if checker.IsDinersCreditCard(changeToInvalidLuhn(dinersCard)) == checker.ResultValid { + if checker.IsDinersCreditCard(changeToInvalidLuhn(dinersCard)) == nil { t.Fail() } } func ExampleIsDiscoverCreditCard() { - result := checker.IsDiscoverCreditCard("6011111111111117") + err := checker.IsDiscoverCreditCard("6011111111111117") - if result != checker.ResultValid { + if err != nil { // Send the mistakes back to the user } } func TestIsDiscoverCreditCardValid(t *testing.T) { - if checker.IsDiscoverCreditCard(discoverCard) != checker.ResultValid { + if checker.IsDiscoverCreditCard(discoverCard) != nil { t.Fail() } } func TestIsDiscoverCreditCardInvalidPattern(t *testing.T) { - if checker.IsDiscoverCreditCard(invalidCard) == checker.ResultValid { + if checker.IsDiscoverCreditCard(invalidCard) == nil { t.Fail() } } func TestIsDiscoverCreditCardInvalidLuhn(t *testing.T) { - if checker.IsDiscoverCreditCard(changeToInvalidLuhn(discoverCard)) == checker.ResultValid { + if checker.IsDiscoverCreditCard(changeToInvalidLuhn(discoverCard)) == nil { t.Fail() } } func ExampleIsJcbCreditCard() { - result := checker.IsJcbCreditCard("3530111333300000") + err := checker.IsJcbCreditCard("3530111333300000") - if result != checker.ResultValid { + if err != nil { // Send the mistakes back to the user } } func TestIsJcbCreditCardValid(t *testing.T) { - if checker.IsJcbCreditCard(jcbCard) != checker.ResultValid { + if checker.IsJcbCreditCard(jcbCard) != nil { t.Fail() } } func TestIsJcbCreditCardInvalidPattern(t *testing.T) { - if checker.IsJcbCreditCard(invalidCard) == checker.ResultValid { + if checker.IsJcbCreditCard(invalidCard) == nil { t.Fail() } } func TestIsJcbCreditCardInvalidLuhn(t *testing.T) { - if checker.IsJcbCreditCard(changeToInvalidLuhn(jcbCard)) == checker.ResultValid { + if checker.IsJcbCreditCard(changeToInvalidLuhn(jcbCard)) == nil { t.Fail() } } func ExampleIsMasterCardCreditCard() { - result := checker.IsMasterCardCreditCard("5555555555554444") + err := checker.IsMasterCardCreditCard("5555555555554444") - if result != checker.ResultValid { + if err != nil { // Send the mistakes back to the user } } func TestIsMasterCardCreditCardValid(t *testing.T) { - if checker.IsMasterCardCreditCard(masterCard) != checker.ResultValid { + if checker.IsMasterCardCreditCard(masterCard) != nil { t.Fail() } } func TestIsMasterCardCreditCardInvalidPattern(t *testing.T) { - if checker.IsMasterCardCreditCard(invalidCard) == checker.ResultValid { + if checker.IsMasterCardCreditCard(invalidCard) == nil { t.Fail() } } func TestIsMasterCardCreditCardInvalidLuhn(t *testing.T) { - if checker.IsMasterCardCreditCard(changeToInvalidLuhn(masterCard)) == checker.ResultValid { + if checker.IsMasterCardCreditCard(changeToInvalidLuhn(masterCard)) == nil { t.Fail() } } func ExampleIsUnionPayCreditCard() { - result := checker.IsUnionPayCreditCard("6200000000000005") + err := checker.IsUnionPayCreditCard("6200000000000005") - if result != checker.ResultValid { + if err != nil { // Send the mistakes back to the user } } func TestIsUnionPayCreditCardValid(t *testing.T) { - if checker.IsUnionPayCreditCard(unionPayCard) != checker.ResultValid { + if checker.IsUnionPayCreditCard(unionPayCard) != nil { t.Fail() } } func TestIsUnionPayCreditCardInvalidPattern(t *testing.T) { - if checker.IsUnionPayCreditCard(invalidCard) == checker.ResultValid { + if checker.IsUnionPayCreditCard(invalidCard) == nil { t.Fail() } } func TestIsUnionPayCreditCardInvalidLuhn(t *testing.T) { - if checker.IsUnionPayCreditCard(changeToInvalidLuhn(unionPayCard)) == checker.ResultValid { + if checker.IsUnionPayCreditCard(changeToInvalidLuhn(unionPayCard)) == nil { t.Fail() } } func ExampleIsVisaCreditCard() { - result := checker.IsVisaCreditCard("4111111111111111") + err := checker.IsVisaCreditCard("4111111111111111") - if result != checker.ResultValid { + if err != nil { // Send the mistakes back to the user } } func TestIsVisaCreditCardValid(t *testing.T) { - if checker.IsVisaCreditCard(visaCard) != checker.ResultValid { + if checker.IsVisaCreditCard(visaCard) != nil { t.Fail() } } func TestIsVisaCreditCardInvalidPattern(t *testing.T) { - if checker.IsVisaCreditCard(invalidCard) == checker.ResultValid { + if checker.IsVisaCreditCard(invalidCard) == nil { t.Fail() } } func TestIsVisaCreditCardInvalidLuhn(t *testing.T) { - if checker.IsVisaCreditCard(changeToInvalidLuhn(visaCard)) == checker.ResultValid { + if checker.IsVisaCreditCard(changeToInvalidLuhn(visaCard)) == nil { t.Fail() } } diff --git a/digits.go b/digits.go index 3ace687..9098622 100644 --- a/digits.go +++ b/digits.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "reflect" "unicode" ) @@ -13,18 +14,18 @@ import ( // CheckerDigits is the name of the checker. const CheckerDigits = "digits" -// ResultNotDigits indicates that the given string contains non-digit characters. -const ResultNotDigits = "NOT_DIGITS" +// ErrNotDigits indicates that the given string contains non-digit characters. +var ErrNotDigits = errors.New("please enter a valid number") // IsDigits checks if the given string consists of only digit characters. -func IsDigits(value string) Result { +func IsDigits(value string) error { for _, c := range value { if !unicode.IsDigit(c) { - return ResultNotDigits + return ErrNotDigits } } - return ResultValid + return nil } // makeDigits makes a checker function for the digits checker. @@ -33,7 +34,7 @@ func makeDigits(_ string) CheckFunc { } // checkDigits checks if the given string consists of only digit characters. -func checkDigits(value, _ reflect.Value) Result { +func checkDigits(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } diff --git a/digits_test.go b/digits_test.go index 40c8f02..86830c1 100644 --- a/digits_test.go +++ b/digits_test.go @@ -12,21 +12,20 @@ import ( ) func ExampleIsDigits() { - result := checker.IsDigits("1234") - - if result != checker.ResultValid { + err := checker.IsDigits("1234") + if err != nil { // Send the mistakes back to the user } } func TestIsDigitsInvalid(t *testing.T) { - if checker.IsDigits("checker") == checker.ResultValid { + if checker.IsDigits("checker") == nil { t.Fail() } } func TestIsDigitsValid(t *testing.T) { - if checker.IsDigits("1234") != checker.ResultValid { + if checker.IsDigits("1234") != nil { t.Fail() } } diff --git a/email.go b/email.go index 6238bcf..b0ffc8f 100644 --- a/email.go +++ b/email.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "reflect" "regexp" "strings" @@ -14,8 +15,8 @@ import ( // CheckerEmail is the name of the checker. const CheckerEmail = "email" -// ResultNotEmail indicates that the given string is not a valid email. -const ResultNotEmail = "NOT_EMAIL" +// ErrNotEmail indicates that the given string is not a valid email. +var ErrNotEmail = errors.New("please enter a valid email address") // ipV6Prefix is the IPv6 prefix for the domain. const ipV6Prefix = "[IPv6:" @@ -24,15 +25,15 @@ const ipV6Prefix = "[IPv6:" var notQuotedChars = regexp.MustCompile("[a-zA-Z0-9!#$%&'*\\+\\-/=?^_`{|}~]") // IsEmail checks if the given string is an email address. -func IsEmail(email string) Result { +func IsEmail(email string) error { atIndex := strings.LastIndex(email, "@") if atIndex == -1 || atIndex == len(email)-1 { - return ResultNotEmail + return ErrNotEmail } domain := email[atIndex+1:] - if isValidEmailDomain(domain) != ResultValid { - return ResultNotEmail + if isValidEmailDomain(domain) != nil { + return ErrNotEmail } return isValidEmailUser(email[:atIndex]) @@ -44,7 +45,7 @@ func makeEmail(_ string) CheckFunc { } // checkEmail checks if the given string is an email address. -func checkEmail(value, _ reflect.Value) Result { +func checkEmail(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } @@ -53,9 +54,9 @@ func checkEmail(value, _ reflect.Value) Result { } // isValidEmailDomain checks if the email domain is a IPv4 or IPv6 address, or a FQDN. -func isValidEmailDomain(domain string) Result { +func isValidEmailDomain(domain string) error { if len(domain) > 255 { - return ResultNotEmail + return ErrNotEmail } if domain[0] == '[' { @@ -72,22 +73,22 @@ func isValidEmailDomain(domain string) Result { } // isValidEmailUser checks if the email user is valid. -func isValidEmailUser(user string) Result { +func isValidEmailUser(user string) error { // Cannot be empty user if user == "" || len(user) > 64 { - return ResultNotEmail + return ErrNotEmail } // Cannot start or end with dot if user[0] == '.' || user[len(user)-1] == '.' { - return ResultNotEmail + return ErrNotEmail } return isValidEmailUserCharacters(user) } // isValidEmailUserCharacters if the email user characters are valid. -func isValidEmailUserCharacters(user string) Result { +func isValidEmailUserCharacters(user string) error { quoted := false start := true prev := ' ' @@ -95,7 +96,7 @@ func isValidEmailUserCharacters(user string) Result { for _, c := range user { // Cannot have a double dot unless quoted if !quoted && c == '.' && prev == '.' { - return ResultNotEmail + return ErrNotEmail } if start { @@ -112,7 +113,7 @@ func isValidEmailUserCharacters(user string) Result { if c == '.' { start = true } else if !notQuotedChars.MatchString(string(c)) { - return ResultNotEmail + return ErrNotEmail } } else { if c == '"' && prev != '\\' { @@ -123,5 +124,5 @@ func isValidEmailUserCharacters(user string) Result { prev = c } - return ResultValid + return nil } diff --git a/email_test.go b/email_test.go index 4460f7b..862e2ef 100644 --- a/email_test.go +++ b/email_test.go @@ -12,9 +12,8 @@ import ( ) func ExampleIsEmail() { - result := checker.IsEmail("user@zdo.com") - - if result != checker.ResultValid { + err := checker.IsEmail("user@zdo.com") + if err != nil { // Send the mistakes back to the user } } @@ -69,7 +68,7 @@ func TestIsEmailValid(t *testing.T) { } for _, email := range validEmails { - if checker.IsEmail(email) != checker.ResultValid { + if checker.IsEmail(email) != nil { t.Fatal(email) } } @@ -93,7 +92,7 @@ func TestIsEmailInvalid(t *testing.T) { } for _, email := range validEmails { - if checker.IsEmail(email) == checker.ResultValid { + if checker.IsEmail(email) == nil { t.Fatal(email) } } diff --git a/fqdn.go b/fqdn.go index 002ce19..e227260 100644 --- a/fqdn.go +++ b/fqdn.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "reflect" "regexp" "strings" @@ -14,51 +15,51 @@ import ( // CheckerFqdn is the name of the checker. const CheckerFqdn = "fqdn" -// ResultNotFqdn indicates that the given string is not a valid FQDN. -const ResultNotFqdn = "NOT_FQDN" +// ErrNotFqdn indicates that the given string is not a valid FQDN. +var ErrNotFqdn = errors.New("please enter a valid domain name") // Valid characters excluding full-width characters. var fqdnValidChars = regexp.MustCompile("^[a-z0-9\u00a1-\uff00\uff06-\uffff\\-]+$") // IsFqdn checks if the given string is a fully qualified domain name. -func IsFqdn(domain string) Result { +func IsFqdn(domain string) error { parts := strings.Split(domain, ".") // Require TLD if len(parts) < 2 { - return ResultNotFqdn + return ErrNotFqdn } tld := parts[len(parts)-1] // Should be all numeric TLD - if IsDigits(tld) == ResultValid { - return ResultNotFqdn + if IsDigits(tld) == nil { + return ErrNotFqdn } // Short TLD if len(tld) < 2 { - return ResultNotFqdn + return ErrNotFqdn } for _, part := range parts { // Cannot be more than 63 characters if len(part) > 63 { - return ResultNotFqdn + return ErrNotFqdn } // Check for valid characters if !fqdnValidChars.MatchString(part) { - return ResultNotFqdn + return ErrNotFqdn } // Should not start or end with a hyphen (-) character. if part[0] == '-' || part[len(part)-1] == '-' { - return ResultNotFqdn + return ErrNotFqdn } } - return ResultValid + return nil } // makeFqdn makes a checker function for the fqdn checker. @@ -67,7 +68,7 @@ func makeFqdn(_ string) CheckFunc { } // checkFqdn checks if the given string is a fully qualified domain name. -func checkFqdn(value, _ reflect.Value) Result { +func checkFqdn(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } diff --git a/fqdn_test.go b/fqdn_test.go index 4e822f0..82edbfd 100644 --- a/fqdn_test.go +++ b/fqdn_test.go @@ -12,63 +12,62 @@ import ( ) func ExampleIsFqdn() { - result := checker.IsFqdn("zdo.com") - - if result != checker.ResultValid { + err := checker.IsFqdn("zdo.com") + if err != nil { // Send the mistakes back to the user } } func TestCheckFdqnWithoutTld(t *testing.T) { - if checker.IsFqdn("abcd") != checker.ResultNotFqdn { + if checker.IsFqdn("abcd") == nil { t.Fail() } } func TestCheckFdqnShortTld(t *testing.T) { - if checker.IsFqdn("abcd.c") != checker.ResultNotFqdn { + if checker.IsFqdn("abcd.c") == nil { t.Fail() } } func TestCheckFdqnNumericTld(t *testing.T) { - if checker.IsFqdn("abcd.1234") != checker.ResultNotFqdn { + if checker.IsFqdn("abcd.1234") == nil { t.Fail() } } func TestCheckFdqnLong(t *testing.T) { - if checker.IsFqdn("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd.com") != checker.ResultNotFqdn { + if checker.IsFqdn("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd.com") == nil { t.Fail() } } func TestCheckFdqnInvalidCharacters(t *testing.T) { - if checker.IsFqdn("ab_cd.com") != checker.ResultNotFqdn { + if checker.IsFqdn("ab_cd.com") == nil { t.Fail() } } func TestCheckFdqnStaringWithHyphen(t *testing.T) { - if checker.IsFqdn("-abcd.com") != checker.ResultNotFqdn { + if checker.IsFqdn("-abcd.com") == nil { t.Fail() } } func TestCheckFdqnStaringEndingWithHyphen(t *testing.T) { - if checker.IsFqdn("abcd-.com") != checker.ResultNotFqdn { + if checker.IsFqdn("abcd-.com") == nil { t.Fail() } } func TestCheckFdqnStartingWithDot(t *testing.T) { - if checker.IsFqdn(".abcd.com") != checker.ResultNotFqdn { + if checker.IsFqdn(".abcd.com") == nil { t.Fail() } } func TestCheckFdqnEndingWithDot(t *testing.T) { - if checker.IsFqdn("abcd.com.") != checker.ResultNotFqdn { + if checker.IsFqdn("abcd.com.") == nil { t.Fail() } } diff --git a/html_escape.go b/html_escape.go index 2701f56..012448d 100644 --- a/html_escape.go +++ b/html_escape.go @@ -20,12 +20,12 @@ func makeHTMLEscape(_ string) CheckFunc { // normalizeHTMLEscape applies HTML escaping to special characters. // Uses html.EscapeString for the actual escape operation. -func normalizeHTMLEscape(value, _ reflect.Value) Result { +func normalizeHTMLEscape(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } value.SetString(html.EscapeString(value.String())) - return ResultValid + return nil } diff --git a/html_escape_test.go b/html_escape_test.go index 799eba4..f823afc 100644 --- a/html_escape_test.go +++ b/html_escape_test.go @@ -13,7 +13,6 @@ import ( func TestNormalizeHTMLEscapeNonString(t *testing.T) { defer checker.FailIfNoPanic(t) - type Comment struct { Body int `checkers:"html-escape"` } diff --git a/html_unescape.go b/html_unescape.go index f202ae1..3c8a93f 100644 --- a/html_unescape.go +++ b/html_unescape.go @@ -20,12 +20,12 @@ func makeHTMLUnescape(_ string) CheckFunc { // normalizeHTMLUnescape applies HTML unescaping to special characters. // Uses html.UnescapeString for the actual unescape operation. -func normalizeHTMLUnescape(value, _ reflect.Value) Result { +func normalizeHTMLUnescape(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } value.SetString(html.UnescapeString(value.String())) - return ResultValid + return nil } diff --git a/ip.go b/ip.go index a113269..9fa240b 100644 --- a/ip.go +++ b/ip.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "net" "reflect" ) @@ -13,17 +14,17 @@ import ( // CheckerIP is the name of the checker. const CheckerIP = "ip" -// ResultNotIP indicates that the given value is not an IP address. -const ResultNotIP = "NOT_IP" +// ErrNotIP indicates that the given value is not an IP address. +var ErrNotIP = errors.New("please enter a valid IP address") // IsIP checks if the given value is an IP address. -func IsIP(value string) Result { +func IsIP(value string) error { ip := net.ParseIP(value) if ip == nil { - return ResultNotIP + return ErrNotIP } - return ResultValid + return nil } // makeIP makes a checker function for the ip checker. @@ -32,7 +33,7 @@ func makeIP(_ string) CheckFunc { } // checkIP checks if the given value is an IP address. -func checkIP(value, _ reflect.Value) Result { +func checkIP(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } diff --git a/ip_test.go b/ip_test.go index a09bc28..4995d8f 100644 --- a/ip_test.go +++ b/ip_test.go @@ -12,21 +12,20 @@ import ( ) func ExampleIsIP() { - result := checker.IsIP("2001:db8::68") - - if result != checker.ResultValid { + err := checker.IsIP("2001:db8::68") + if err != nil { // Send the mistakes back to the user } } func TestIsIPInvalid(t *testing.T) { - if checker.IsIP("900.800.200.100") == checker.ResultValid { + if checker.IsIP("900.800.200.100") == nil { t.Fail() } } func TestIsIPValid(t *testing.T) { - if checker.IsIP("2001:db8::68") != checker.ResultValid { + if checker.IsIP("2001:db8::68") != nil { t.Fail() } } diff --git a/ipv4.go b/ipv4.go index 68225fc..93d28ca 100644 --- a/ipv4.go +++ b/ipv4.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "net" "reflect" ) @@ -13,21 +14,21 @@ import ( // CheckerIPV4 is the name of the checker. const CheckerIPV4 = "ipv4" -// ResultNotIPV4 indicates that the given value is not an IPv4 address. -const ResultNotIPV4 = "NOT_IP_V4" +// ErrNotIPV4 indicates that the given value is not an IPv4 address. +var ErrNotIPV4 = errors.New("please enter a valid IPv4 address") // IsIPV4 checks if the given value is an IPv4 address. -func IsIPV4(value string) Result { +func IsIPV4(value string) error { ip := net.ParseIP(value) if ip == nil { - return ResultNotIPV4 + return ErrNotIPV4 } if ip.To4() == nil { - return ResultNotIPV4 + return ErrNotIPV4 } - return ResultValid + return nil } // makeIPV4 makes a checker function for the ipV4 checker. @@ -36,7 +37,7 @@ func makeIPV4(_ string) CheckFunc { } // checkIPV4 checks if the given value is an IPv4 address. -func checkIPV4(value, _ reflect.Value) Result { +func checkIPV4(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } diff --git a/ipv4_test.go b/ipv4_test.go index f38f542..fba513d 100644 --- a/ipv4_test.go +++ b/ipv4_test.go @@ -12,27 +12,26 @@ import ( ) func ExampleIsIPV4() { - result := checker.IsIPV4("192.168.1.1") - - if result != checker.ResultValid { + err := checker.IsIPV4("192.168.1.1") + if err != nil { // Send the mistakes back to the user } } func TestIsIPV4Invalid(t *testing.T) { - if checker.IsIPV4("900.800.200.100") == checker.ResultValid { + if checker.IsIPV4("900.800.200.100") == nil { t.Fail() } } func TestIsIPV4InvalidV6(t *testing.T) { - if checker.IsIPV4("2001:db8::68") == checker.ResultValid { + if checker.IsIPV4("2001:db8::68") == nil { t.Fail() } } func TestIsIPV4Valid(t *testing.T) { - if checker.IsIPV4("192.168.1.1") != checker.ResultValid { + if checker.IsIPV4("192.168.1.1") != nil { t.Fail() } } diff --git a/ipv6.go b/ipv6.go index da6ba34..8fe9a07 100644 --- a/ipv6.go +++ b/ipv6.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "net" "reflect" ) @@ -13,21 +14,21 @@ import ( // CheckerIPV6 is the name of the checker. const CheckerIPV6 = "ipv6" -// ResultNotIPV6 indicates that the given value is not an IPv6 address. -const ResultNotIPV6 = "NOT_IP_V6" +// ErrNotIPV6 indicates that the given value is not an IPv6 address. +var ErrNotIPV6 = errors.New("please enter a valid IPv6 address") // IsIPV6 checks if the given value is an IPv6 address. -func IsIPV6(value string) Result { +func IsIPV6(value string) error { ip := net.ParseIP(value) if ip == nil { - return ResultNotIPV6 + return ErrNotIPV6 } if ip.To4() != nil { - return ResultNotIPV6 + return ErrNotIPV6 } - return ResultValid + return nil } // makeIPV6 makes a checker function for the ipV6 checker. @@ -36,7 +37,7 @@ func makeIPV6(_ string) CheckFunc { } // checkIPV6 checks if the given value is an IPv6 address. -func checkIPV6(value, _ reflect.Value) Result { +func checkIPV6(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } diff --git a/ipv6_test.go b/ipv6_test.go index 68d9e95..c8d8d4e 100644 --- a/ipv6_test.go +++ b/ipv6_test.go @@ -12,27 +12,27 @@ import ( ) func ExampleIsIPV6() { - result := checker.IsIPV6("2001:db8::68") + err := checker.IsIPV6("2001:db8::68") - if result != checker.ResultValid { + if err != nil { // Send the mistakes back to the user } } func TestIsIPV6Invalid(t *testing.T) { - if checker.IsIPV6("900.800.200.100") == checker.ResultValid { + if checker.IsIPV6("900.800.200.100") == nil { t.Fail() } } func TestIsIPV6InvalidV4(t *testing.T) { - if checker.IsIPV6("192.168.1.1") == checker.ResultValid { + if checker.IsIPV6("192.168.1.1") == nil { t.Fail() } } func TestIsIPV6Valid(t *testing.T) { - if checker.IsIPV6("2001:db8::68") != checker.ResultValid { + if checker.IsIPV6("2001:db8::68") != nil { t.Fail() } } diff --git a/isbn.go b/isbn.go index 3b4d4d6..67fbb10 100644 --- a/isbn.go +++ b/isbn.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "reflect" "strings" ) @@ -19,15 +20,15 @@ import ( // CheckerISBN is the name of the checker. const CheckerISBN = "isbn" -// ResultNotISBN indicates that the given value is not a valid ISBN. -const ResultNotISBN = "NOT_ISBN" +// ErrNotISBN indicates that the given value is not a valid ISBN. +var ErrNotISBN = errors.New("please enter a valid ISBN number") // IsISBN10 checks if the given value is a valid ISBN-10 number. -func IsISBN10(value string) Result { +func IsISBN10(value string) error { value = strings.ReplaceAll(value, "-", "") if len(value) != 10 { - return ResultNotISBN + return ErrNotISBN } digits := []rune(value) @@ -39,18 +40,18 @@ func IsISBN10(value string) Result { } if sum%11 != 0 { - return ResultNotISBN + return ErrNotISBN } - return ResultValid + return nil } // IsISBN13 checks if the given value is a valid ISBN-13 number. -func IsISBN13(value string) Result { +func IsISBN13(value string) error { value = strings.ReplaceAll(value, "-", "") if len(value) != 13 { - return ResultNotISBN + return ErrNotISBN } digits := []rune(value) @@ -66,14 +67,14 @@ func IsISBN13(value string) Result { } if sum%10 != 0 { - return ResultNotISBN + return ErrNotISBN } - return ResultValid + return nil } // IsISBN checks if the given value is a valid ISBN number. -func IsISBN(value string) Result { +func IsISBN(value string) error { value = strings.ReplaceAll(value, "-", "") if len(value) == 10 { @@ -82,7 +83,7 @@ func IsISBN(value string) Result { return IsISBN13(value) } - return ResultNotISBN + return ErrNotISBN } // isbnDigitToInt returns the integer value of given ISBN digit. @@ -100,13 +101,13 @@ func makeISBN(config string) CheckFunc { panic("invalid format") } - return func(value, parent reflect.Value) Result { + return func(value, parent reflect.Value) error { return checkISBN(value, parent, config) } } // checkISBN checks if the given value is a valid ISBN number. -func checkISBN(value, _ reflect.Value, mode string) Result { +func checkISBN(value, _ reflect.Value, mode string) error { if value.Kind() != reflect.String { panic("string expected") } diff --git a/isbn_test.go b/isbn_test.go index d8fe4be..f173981 100644 --- a/isbn_test.go +++ b/isbn_test.go @@ -6,112 +6,113 @@ package checker_test import ( + "errors" "testing" "github.com/cinar/checker" ) func ExampleIsISBN10() { - result := checker.IsISBN10("1430248270") - if result != checker.ResultValid { + err := checker.IsISBN10("1430248270") + if err != nil { // Send the mistakes back to the user } } func TestIsISBN10Valid(t *testing.T) { - result := checker.IsISBN10("1430248270") - if result != checker.ResultValid { + err := checker.IsISBN10("1430248270") + if err != nil { t.Fail() } } func TestIsISBN10ValidX(t *testing.T) { - result := checker.IsISBN10("007462542X") - if result != checker.ResultValid { + err := checker.IsISBN10("007462542X") + if err != nil { t.Fail() } } func TestIsISBN10ValidWithDashes(t *testing.T) { - result := checker.IsISBN10("1-4302-4827-0") - if result != checker.ResultValid { + err := checker.IsISBN10("1-4302-4827-0") + if err != nil { t.Fail() } } func TestIsISBN10InvalidLength(t *testing.T) { - result := checker.IsISBN10("143024827") - if result != checker.ResultNotISBN { + err := checker.IsISBN10("143024827") + if !errors.Is(err, checker.ErrNotISBN) { t.Fail() } } func TestIsISBN10InvalidCheck(t *testing.T) { - result := checker.IsISBN10("1430248272") - if result != checker.ResultNotISBN { + err := checker.IsISBN10("1430248272") + if !errors.Is(err, checker.ErrNotISBN) { t.Fail() } } func ExampleIsISBN13() { - result := checker.IsISBN13("9781430248279") - if result != checker.ResultValid { + err := checker.IsISBN13("9781430248279") + if err != nil { // Send the mistakes back to the user } } func TestIsISBN13Valid(t *testing.T) { - result := checker.IsISBN13("9781430248279") - if result != checker.ResultValid { + err := checker.IsISBN13("9781430248279") + if err != nil { t.Fail() } } func TestIsISBN13ValidWithDashes(t *testing.T) { - result := checker.IsISBN13("978-1-4302-4827-9") - if result != checker.ResultValid { + err := checker.IsISBN13("978-1-4302-4827-9") + if err != nil { t.Fail() } } func TestIsISBN13InvalidLength(t *testing.T) { - result := checker.IsISBN13("978143024827") - if result != checker.ResultNotISBN { + err := checker.IsISBN13("978143024827") + if !errors.Is(err, checker.ErrNotISBN) { t.Fail() } } func TestIsISBN13InvalidCheck(t *testing.T) { - result := checker.IsISBN13("9781430248272") - if result != checker.ResultNotISBN { + err := checker.IsISBN13("9781430248272") + if !errors.Is(err, checker.ErrNotISBN) { t.Fail() } } func ExampleIsISBN() { - result := checker.IsISBN("1430248270") - if result != checker.ResultValid { + err := checker.IsISBN("1430248270") + if err != nil { // Send the mistakes back to the user } } func TestIsISBNValid10(t *testing.T) { - result := checker.IsISBN("1430248270") - if result != checker.ResultValid { + err := checker.IsISBN("1430248270") + if err != nil { t.Fail() } } func TestIsISBNValid13(t *testing.T) { - result := checker.IsISBN("9781430248279") - if result != checker.ResultValid { + err := checker.IsISBN("9781430248279") + if err != nil { t.Fail() } } func TestIsISBNInvalidLenght(t *testing.T) { - result := checker.IsISBN("978143024827") - if result != checker.ResultNotISBN { + err := checker.IsISBN("978143024827") + if err != checker.ErrNotISBN { t.Fail() } } diff --git a/lower.go b/lower.go index 6cf6987..3843c33 100644 --- a/lower.go +++ b/lower.go @@ -19,12 +19,12 @@ func makeLower(_ string) CheckFunc { } // normalizeLower maps all Unicode letters in the given value to their lower case. -func normalizeLower(value, _ reflect.Value) Result { +func normalizeLower(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } value.SetString(strings.ToLower(value.String())) - return ResultValid + return nil } diff --git a/lower_test.go b/lower_test.go index e5f660c..112fbd8 100644 --- a/lower_test.go +++ b/lower_test.go @@ -23,7 +23,7 @@ func TestNormalizeLowerNonString(t *testing.T) { checker.Check(user) } -func TestNormalizeLowerResultValid(t *testing.T) { +func TestNormalizeLowerErrValid(t *testing.T) { type User struct { Username string `checkers:"lower"` } diff --git a/luhn.go b/luhn.go index 4f8808a..6e222d3 100644 --- a/luhn.go +++ b/luhn.go @@ -6,28 +6,29 @@ package checker import ( + "errors" "reflect" ) // CheckerLuhn is the name of the checker. const CheckerLuhn = "luhn" -// ResultNotLuhn indicates that the given number is not valid based on the Luhn algorithm. -const ResultNotLuhn = "NOT_LUHN" +// ErrNotLuhn indicates that the given number is not valid based on the Luhn algorithm. +var ErrNotLuhn = errors.New("please enter a valid LUHN") // doubleTable is the values for the last digits of doubled digits added. var doubleTable = [10]int{0, 2, 4, 6, 8, 1, 3, 5, 7, 9} // IsLuhn checks if the given number is valid based on the Luhn algorithm. -func IsLuhn(number string) Result { +func IsLuhn(number string) error { digits := number[:len(number)-1] check := rune(number[len(number)-1]) if calculateLuhnCheckDigit(digits) != check { - return ResultNotLuhn + return ErrNotLuhn } - return ResultValid + return nil } // makeLuhn makes a checker function for the Luhn algorithm. @@ -36,7 +37,7 @@ func makeLuhn(_ string) CheckFunc { } // checkLuhn checks if the given number is valid based on the Luhn algorithm. -func checkLuhn(value, _ reflect.Value) Result { +func checkLuhn(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } diff --git a/luhn_test.go b/luhn_test.go index bdd06a5..e193621 100644 --- a/luhn_test.go +++ b/luhn_test.go @@ -12,9 +12,8 @@ import ( ) func ExampleIsLuhn() { - result := checker.IsLuhn("4012888888881881") - - if result != checker.ResultValid { + err := checker.IsLuhn("4012888888881881") + if err != nil { // Send the mistakes back to the user } } @@ -28,7 +27,7 @@ func TestIsLuhnValid(t *testing.T) { } for _, number := range numbers { - if checker.IsLuhn(number) != checker.ResultValid { + if checker.IsLuhn(number) != nil { t.Fail() } } diff --git a/mac.go b/mac.go index 7c6fbf2..509ecc3 100644 --- a/mac.go +++ b/mac.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "net" "reflect" ) @@ -13,17 +14,17 @@ import ( // CheckerMac is the name of the checker. const CheckerMac = "mac" -// ResultNotMac indicates that the given value is not an MAC address. -const ResultNotMac = "NOT_MAC" +// ErrNotMac indicates that the given value is not an MAC address. +var ErrNotMac = errors.New("please enter a valid MAC address") // IsMac checks if the given value is a valid an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet IP over InfiniBand link-layer address. -func IsMac(value string) Result { +func IsMac(value string) error { _, err := net.ParseMAC(value) if err != nil { - return ResultNotMac + return ErrNotMac } - return ResultValid + return nil } // makeMac makes a checker function for the ip checker. @@ -32,7 +33,7 @@ func makeMac(_ string) CheckFunc { } // checkMac checks if the given value is a valid an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet IP over InfiniBand link-layer address. -func checkMac(value, _ reflect.Value) Result { +func checkMac(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } diff --git a/mac_test.go b/mac_test.go index 79f9729..e966765 100644 --- a/mac_test.go +++ b/mac_test.go @@ -12,21 +12,20 @@ import ( ) func ExampleIsMac() { - result := checker.IsMac("00:00:5e:00:53:01") - - if result != checker.ResultValid { + err := checker.IsMac("00:00:5e:00:53:01") + if err != nil { // Send the mistakes back to the user } } func TestIsMacInvalid(t *testing.T) { - if checker.IsMac("1234") == checker.ResultValid { + if checker.IsMac("1234") == nil { t.Fail() } } func TestIsMacValid(t *testing.T) { - if checker.IsMac("00:00:5e:00:53:01") != checker.ResultValid { + if checker.IsMac("00:00:5e:00:53:01") != nil { t.Fail() } } diff --git a/max.go b/max.go index 04a028e..ed8c2d3 100644 --- a/max.go +++ b/max.go @@ -6,6 +6,7 @@ package checker import ( + "fmt" "reflect" "strconv" ) @@ -13,11 +14,8 @@ import ( // CheckerMax is the name of the checker. const CheckerMax = "max" -// ResultNotMax indicates that the given value is above the defined maximum. -const ResultNotMax = "NOT_MIN" - // IsMax checks if the given value is below than the given maximum. -func IsMax(value interface{}, max float64) Result { +func IsMax(value interface{}, max float64) error { return checkMax(reflect.Indirect(reflect.ValueOf(value)), reflect.ValueOf(nil), max) } @@ -28,18 +26,18 @@ func makeMax(config string) CheckFunc { panic("unable to parse max") } - return func(value, parent reflect.Value) Result { + return func(value, parent reflect.Value) error { return checkMax(value, parent, max) } } // checkMax checks if the given value is less than the given maximum. -func checkMax(value, _ reflect.Value, max float64) Result { +func checkMax(value, _ reflect.Value, max float64) error { n := numberOf(value) if n > max { - return ResultNotMax + return fmt.Errorf("please enter a number less than %g", max) } - return ResultValid + return nil } diff --git a/max_test.go b/max_test.go index 144246f..c6f3adf 100644 --- a/max_test.go +++ b/max_test.go @@ -14,9 +14,8 @@ import ( func ExampleIsMax() { quantity := 5 - result := checker.IsMax(quantity, 10) - - if result != checker.ResultValid { + err := checker.IsMax(quantity, 10) + if err != nil { // Send the mistakes back to the user } } @@ -24,7 +23,7 @@ func ExampleIsMax() { func TestIsMaxValid(t *testing.T) { n := 5 - if checker.IsMax(n, 10) != checker.ResultValid { + if checker.IsMax(n, 10) != nil { t.Fail() } } diff --git a/maxlength.go b/maxlength.go index 910e9f4..63a0f0a 100644 --- a/maxlength.go +++ b/maxlength.go @@ -6,6 +6,7 @@ package checker import ( + "fmt" "reflect" "strconv" ) @@ -13,11 +14,8 @@ import ( // CheckerMaxLength is the name of the checker. const CheckerMaxLength = "max-length" -// ResultNotMaxLength indicates that the length of the given value is above the defined number. -const ResultNotMaxLength = "NOT_MAX_LENGTH" - // IsMaxLength checks if the length of the given value is less than the given maximum length. -func IsMaxLength(value interface{}, maxLength int) Result { +func IsMaxLength(value interface{}, maxLength int) error { return checkMaxLength(reflect.Indirect(reflect.ValueOf(value)), reflect.ValueOf(nil), maxLength) } @@ -28,17 +26,17 @@ func makeMaxLength(config string) CheckFunc { panic("unable to parse max length value") } - return func(value, parent reflect.Value) Result { + return func(value, parent reflect.Value) error { return checkMaxLength(value, parent, maxLength) } } // checkMaxLength checks if the length of the given value is less than the given maximum length. // The function uses the reflect.Value.Len() function to determaxe the length of the value. -func checkMaxLength(value, _ reflect.Value, maxLength int) Result { +func checkMaxLength(value, _ reflect.Value, maxLength int) error { if value.Len() > maxLength { - return ResultNotMaxLength + return fmt.Errorf("please enter %d characters or less", maxLength-1) } - return ResultValid + return nil } diff --git a/maxlength_test.go b/maxlength_test.go index 5322cfc..e2e6d3b 100644 --- a/maxlength_test.go +++ b/maxlength_test.go @@ -14,9 +14,8 @@ import ( func ExampleIsMaxLength() { s := "1234" - result := checker.IsMaxLength(s, 4) - - if result != checker.ResultValid { + err := checker.IsMaxLength(s, 4) + if err != nil { // Send the mistakes back to the user } } @@ -24,7 +23,7 @@ func ExampleIsMaxLength() { func TestIsMaxLengthValid(t *testing.T) { s := "1234" - if checker.IsMaxLength(s, 4) != checker.ResultValid { + if checker.IsMaxLength(s, 4) != nil { t.Fail() } } diff --git a/min.go b/min.go index dcab579..1807b1f 100644 --- a/min.go +++ b/min.go @@ -6,6 +6,7 @@ package checker import ( + "fmt" "reflect" "strconv" ) @@ -13,11 +14,8 @@ import ( // CheckerMin is the name of the checker. const CheckerMin = "min" -// ResultNotMin indicates that the given value is below the defined minimum. -const ResultNotMin = "NOT_MIN" - // IsMin checks if the given value is above than the given minimum. -func IsMin(value interface{}, min float64) Result { +func IsMin(value interface{}, min float64) error { return checkMin(reflect.Indirect(reflect.ValueOf(value)), reflect.ValueOf(nil), min) } @@ -28,18 +26,18 @@ func makeMin(config string) CheckFunc { panic("unable to parse min") } - return func(value, parent reflect.Value) Result { + return func(value, parent reflect.Value) error { return checkMin(value, parent, min) } } // checkMin checks if the given value is greather than the given minimum. -func checkMin(value, _ reflect.Value, min float64) Result { +func checkMin(value, _ reflect.Value, min float64) error { n := numberOf(value) if n < min { - return ResultNotMin + return fmt.Errorf("please enter a number less than %g", min) } - return ResultValid + return nil } diff --git a/min_test.go b/min_test.go index b518e3c..40efbe7 100644 --- a/min_test.go +++ b/min_test.go @@ -14,9 +14,8 @@ import ( func ExampleIsMin() { age := 45 - result := checker.IsMin(age, 21) - - if result != checker.ResultValid { + err := checker.IsMin(age, 21) + if err != nil { // Send the mistakes back to the user } } @@ -24,7 +23,7 @@ func ExampleIsMin() { func TestIsMinValid(t *testing.T) { n := 45 - if checker.IsMin(n, 21) != checker.ResultValid { + if checker.IsMin(n, 21) != nil { t.Fail() } } diff --git a/minlenght.go b/minlenght.go index 376444d..b8bf0e1 100644 --- a/minlenght.go +++ b/minlenght.go @@ -6,6 +6,7 @@ package checker import ( + "fmt" "reflect" "strconv" ) @@ -13,11 +14,8 @@ import ( // CheckerMinLength is the name of the checker. const CheckerMinLength = "min-length" -// ResultNotMinLength indicates that the length of the given value is below the defined number. -const ResultNotMinLength = "NOT_MIN_LENGTH" - // IsMinLength checks if the length of the given value is greather than the given minimum length. -func IsMinLength(value interface{}, minLength int) Result { +func IsMinLength(value interface{}, minLength int) error { return checkMinLength(reflect.Indirect(reflect.ValueOf(value)), reflect.ValueOf(nil), minLength) } @@ -28,17 +26,17 @@ func makeMinLength(config string) CheckFunc { panic("unable to parse min length value") } - return func(value, parent reflect.Value) Result { + return func(value, parent reflect.Value) error { return checkMinLength(value, parent, minLength) } } // checkMinLength checks if the length of the given value is greather than the given minimum length. // The function uses the reflect.Value.Len() function to determine the length of the value. -func checkMinLength(value, _ reflect.Value, minLength int) Result { +func checkMinLength(value, _ reflect.Value, minLength int) error { if value.Len() < minLength { - return ResultNotMinLength + return fmt.Errorf("please enter at least %d characters", minLength) } - return ResultValid + return nil } diff --git a/minlength_test.go b/minlength_test.go index 2a95559..045e958 100644 --- a/minlength_test.go +++ b/minlength_test.go @@ -14,9 +14,8 @@ import ( func ExampleIsMinLength() { s := "1234" - result := checker.IsMinLength(s, 4) - - if result != checker.ResultValid { + err := checker.IsMinLength(s, 4) + if err != nil { // Send the mistakes back to the user } } @@ -24,7 +23,7 @@ func ExampleIsMinLength() { func TestIsMinLengthValid(t *testing.T) { s := "1234" - if checker.IsMinLength(s, 4) != checker.ResultValid { + if checker.IsMinLength(s, 4) != nil { t.Fail() } } diff --git a/regexp.go b/regexp.go index f4e24ab..b48ff18 100644 --- a/regexp.go +++ b/regexp.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "reflect" "regexp" ) @@ -13,39 +14,39 @@ import ( // CheckerRegexp is the name of the checker. const CheckerRegexp = "regexp" -// ResultNotMatch indicates that the given string does not match the regexp pattern. -const ResultNotMatch = "NOT_MATCH" +// ErrNotMatch indicates that the given string does not match the regexp pattern. +var ErrNotMatch = errors.New("please enter a valid input") // MakeRegexpMaker makes a regexp checker maker for the given regexp expression with the given invalid result. -func MakeRegexpMaker(expression string, invalidResult Result) MakeFunc { +func MakeRegexpMaker(expression string, invalidError error) MakeFunc { return func(_ string) CheckFunc { - return MakeRegexpChecker(expression, invalidResult) + return MakeRegexpChecker(expression, invalidError) } } // MakeRegexpChecker makes a regexp checker for the given regexp expression with the given invalid result. -func MakeRegexpChecker(expression string, invalidResult Result) CheckFunc { +func MakeRegexpChecker(expression string, invalidError error) CheckFunc { pattern := regexp.MustCompile(expression) - return func(value, parent reflect.Value) Result { - return checkRegexp(value, pattern, invalidResult) + return func(value, parent reflect.Value) error { + return checkRegexp(value, pattern, invalidError) } } // makeRegexp makes a checker function for the regexp. func makeRegexp(config string) CheckFunc { - return MakeRegexpChecker(config, ResultNotMatch) + return MakeRegexpChecker(config, ErrNotMatch) } // checkRegexp checks if the given string matches the regexp pattern. -func checkRegexp(value reflect.Value, pattern *regexp.Regexp, invalidResult Result) Result { +func checkRegexp(value reflect.Value, pattern *regexp.Regexp, invalidError error) error { if value.Kind() != reflect.String { panic("string expected") } if !pattern.MatchString(value.String()) { - return invalidResult + return invalidError } - return ResultValid + return nil } diff --git a/regexp_test.go b/regexp_test.go index 64a443a..58fa217 100644 --- a/regexp_test.go +++ b/regexp_test.go @@ -6,6 +6,7 @@ package checker_test import ( + "errors" "reflect" "testing" @@ -55,16 +56,16 @@ func TestCheckRegexpValid(t *testing.T) { } func TestMakeRegexpChecker(t *testing.T) { - checkHex := checker.MakeRegexpChecker("^[A-Fa-f0-9]+$", "NOT_HEX") + checkHex := checker.MakeRegexpChecker("^[A-Fa-f0-9]+$", errors.New("Not Hex")) - result := checkHex(reflect.ValueOf("f0f0f0"), reflect.ValueOf(nil)) - if result != checker.ResultValid { + err := checkHex(reflect.ValueOf("f0f0f0"), reflect.ValueOf(nil)) + if err != nil { t.Fail() } } func TestMakeRegexpMaker(t *testing.T) { - checker.Register("hex", checker.MakeRegexpMaker("^[A-Fa-f0-9]+$", "NOT_HEX")) + checker.Register("hex", checker.MakeRegexpMaker("^[A-Fa-f0-9]+$", errors.New("Not Hex"))) type Theme struct { Color string `checkers:"hex"` diff --git a/required.go b/required.go index bfc8c48..16c092e 100644 --- a/required.go +++ b/required.go @@ -5,16 +5,19 @@ package checker -import "reflect" +import ( + "errors" + "reflect" +) // CheckerRequired is the name of the checker. const CheckerRequired = "required" -// ResultRequired indicates that the required value is missing. -const ResultRequired Result = "REQUIRED" +// ErrRequired indicates that the required value is missing. +var ErrRequired = errors.New("is required") // IsRequired checks if the given required value is present. -func IsRequired(v interface{}) Result { +func IsRequired(v interface{}) error { return checkRequired(reflect.ValueOf(v), reflect.ValueOf(nil)) } @@ -24,16 +27,16 @@ func makeRequired(_ string) CheckFunc { } // checkRequired checks if the required value is present. -func checkRequired(value, _ reflect.Value) Result { +func checkRequired(value, _ reflect.Value) error { if value.IsZero() { - return ResultRequired + return ErrRequired } k := value.Kind() if (k == reflect.Array || k == reflect.Map || k == reflect.Slice) && value.Len() == 0 { - return ResultRequired + return ErrRequired } - return ResultValid + return nil } diff --git a/required_test.go b/required_test.go index 27a43cc..958aff9 100644 --- a/required_test.go +++ b/required_test.go @@ -14,16 +14,16 @@ import ( func ExampleIsRequired() { var name string - result := checker.IsRequired(name) - if result != checker.ResultValid { - // Send the result back to the user + err := checker.IsRequired(name) + if err != nil { + // Send the err back to the user } } func TestIsRequired(t *testing.T) { s := "valid" - if checker.IsRequired(s) != checker.ResultValid { + if checker.IsRequired(s) != nil { t.Fail() } } @@ -31,7 +31,7 @@ func TestIsRequired(t *testing.T) { func TestIsRequiredUninitializedString(t *testing.T) { var s string - if checker.IsRequired(s) != checker.ResultRequired { + if checker.IsRequired(s) != checker.ErrRequired { t.Fail() } } @@ -39,7 +39,7 @@ func TestIsRequiredUninitializedString(t *testing.T) { func TestIsRequiredEmptyString(t *testing.T) { s := "" - if checker.IsRequired(s) == checker.ResultValid { + if checker.IsRequired(s) == nil { t.Fail() } } @@ -47,7 +47,7 @@ func TestIsRequiredEmptyString(t *testing.T) { func TestIsRequiredUninitializedNumber(t *testing.T) { var n int - if checker.IsRequired(n) != checker.ResultRequired { + if checker.IsRequired(n) != checker.ErrRequired { t.Fail() } } @@ -55,7 +55,7 @@ func TestIsRequiredUninitializedNumber(t *testing.T) { func TestIsRequiredValidSlice(t *testing.T) { s := []int{1} - if checker.IsRequired(s) != checker.ResultValid { + if checker.IsRequired(s) != nil { t.Fail() } } @@ -63,7 +63,7 @@ func TestIsRequiredValidSlice(t *testing.T) { func TestIsRequiredUninitializedSlice(t *testing.T) { var s []int - if checker.IsRequired(s) != checker.ResultRequired { + if checker.IsRequired(s) != checker.ErrRequired { t.Fail() } } @@ -71,7 +71,7 @@ func TestIsRequiredUninitializedSlice(t *testing.T) { func TestIsRequiredEmptySlice(t *testing.T) { s := make([]int, 0) - if checker.IsRequired(s) != checker.ResultRequired { + if checker.IsRequired(s) != checker.ErrRequired { t.Fail() } } @@ -79,7 +79,7 @@ func TestIsRequiredEmptySlice(t *testing.T) { func TestIsRequiredValidArray(t *testing.T) { s := [1]int{1} - if checker.IsRequired(s) != checker.ResultValid { + if checker.IsRequired(s) != nil { t.Fail() } } @@ -87,7 +87,7 @@ func TestIsRequiredValidArray(t *testing.T) { func TestIsRequiredEmptyArray(t *testing.T) { s := [1]int{} - if checker.IsRequired(s) != checker.ResultRequired { + if checker.IsRequired(s) != checker.ErrRequired { t.Fail() } } @@ -97,7 +97,7 @@ func TestIsRequiredValidMap(t *testing.T) { "a": "b", } - if checker.IsRequired(m) != checker.ResultValid { + if checker.IsRequired(m) != nil { t.Fail() } } @@ -105,7 +105,7 @@ func TestIsRequiredValidMap(t *testing.T) { func TestIsRequiredUninitializedMap(t *testing.T) { var m map[string]string - if checker.IsRequired(m) != checker.ResultRequired { + if checker.IsRequired(m) != checker.ErrRequired { t.Fail() } } @@ -113,7 +113,7 @@ func TestIsRequiredUninitializedMap(t *testing.T) { func TestCheckRequiredEmptyMap(t *testing.T) { m := map[string]string{} - if checker.IsRequired(m) != checker.ResultRequired { + if checker.IsRequired(m) != checker.ErrRequired { t.Fail() } } diff --git a/same.go b/same.go index 1901041..10b67d2 100644 --- a/same.go +++ b/same.go @@ -6,24 +6,25 @@ package checker import ( + "errors" "reflect" ) // CheckerSame is the name of the checker. const CheckerSame = "same" -// ResultNotSame indicates that the given two values are not equal to each other. -const ResultNotSame = "NOT_SAME" +// ErrNotSame indicates that the given two values are not equal to each other. +var ErrNotSame = errors.New("does not match the other") // makeSame makes a checker function for the same checker. func makeSame(config string) CheckFunc { - return func(value, parent reflect.Value) Result { + return func(value, parent reflect.Value) error { return checkSame(value, parent, config) } } // checkSame checks if the given value is equal to the value of the field with the given name. -func checkSame(value, parent reflect.Value, name string) Result { +func checkSame(value, parent reflect.Value, name string) error { other := parent.FieldByName(name) if !other.IsValid() { @@ -33,8 +34,8 @@ func checkSame(value, parent reflect.Value, name string) Result { other = reflect.Indirect(other) if !value.Equal(other) { - return ResultNotSame + return ErrNotSame } - return ResultValid + return nil } diff --git a/title.go b/title.go index 9a75bac..90d2b6b 100644 --- a/title.go +++ b/title.go @@ -20,7 +20,7 @@ func makeTitle(_ string) CheckFunc { } // normalizeTitle maps the first letter of each word to their upper case. -func normalizeTitle(value, _ reflect.Value) Result { +func normalizeTitle(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } @@ -45,5 +45,5 @@ func normalizeTitle(value, _ reflect.Value) Result { value.SetString(sb.String()) - return ResultValid + return nil } diff --git a/title_test.go b/title_test.go index 2a56175..bf6900c 100644 --- a/title_test.go +++ b/title_test.go @@ -23,7 +23,7 @@ func TestNormalizeTitleNonString(t *testing.T) { checker.Check(book) } -func TestNormalizeTitleResultValid(t *testing.T) { +func TestNormalizeTitleErrValid(t *testing.T) { type Book struct { Chapter string `checkers:"title"` } diff --git a/trim.go b/trim.go index 36638be..88cbc82 100644 --- a/trim.go +++ b/trim.go @@ -19,12 +19,12 @@ func makeTrim(_ string) CheckFunc { } // normalizeTrim removes the whitespaces at the beginning and at the end of the given value. -func normalizeTrim(value, _ reflect.Value) Result { +func normalizeTrim(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } value.SetString(strings.Trim(value.String(), " \t")) - return ResultValid + return nil } diff --git a/trim_left.go b/trim_left.go index a0aee1a..e3e53ba 100644 --- a/trim_left.go +++ b/trim_left.go @@ -19,12 +19,12 @@ func makeTrimLeft(_ string) CheckFunc { } // normalizeTrim removes the whitespaces at the beginning of the given value. -func normalizeTrimLeft(value, _ reflect.Value) Result { +func normalizeTrimLeft(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } value.SetString(strings.TrimLeft(value.String(), " \t")) - return ResultValid + return nil } diff --git a/trim_left_test.go b/trim_left_test.go index bd21696..2f93471 100644 --- a/trim_left_test.go +++ b/trim_left_test.go @@ -23,7 +23,7 @@ func TestNormalizeTrimLeftNonString(t *testing.T) { checker.Check(user) } -func TestNormalizeTrimLeftResultValid(t *testing.T) { +func TestNormalizeTrimLeftErrValid(t *testing.T) { type User struct { Username string `checkers:"trim-left"` } diff --git a/trim_right.go b/trim_right.go index 2790c44..82d4115 100644 --- a/trim_right.go +++ b/trim_right.go @@ -19,12 +19,12 @@ func makeTrimRight(_ string) CheckFunc { } // normalizeTrimRight removes the whitespaces at the end of the given value. -func normalizeTrimRight(value, _ reflect.Value) Result { +func normalizeTrimRight(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } value.SetString(strings.TrimRight(value.String(), " \t")) - return ResultValid + return nil } diff --git a/trim_right_test.go b/trim_right_test.go index 6adb417..88fda32 100644 --- a/trim_right_test.go +++ b/trim_right_test.go @@ -23,7 +23,7 @@ func TestNormalizeTrimRightNonString(t *testing.T) { checker.Check(user) } -func TestNormalizeTrimRightResultValid(t *testing.T) { +func TestNormalizeTrimRightErrValid(t *testing.T) { type User struct { Username string `checkers:"trim-right"` } diff --git a/trim_test.go b/trim_test.go index 876370d..7648840 100644 --- a/trim_test.go +++ b/trim_test.go @@ -23,7 +23,7 @@ func TestNormalizeTrimNonString(t *testing.T) { checker.Check(user) } -func TestNormalizeTrimResultValid(t *testing.T) { +func TestNormalizeTrimErrValid(t *testing.T) { type User struct { Username string `checkers:"trim"` } diff --git a/upper.go b/upper.go index 9b9de87..a0a101e 100644 --- a/upper.go +++ b/upper.go @@ -19,12 +19,12 @@ func makeUpper(_ string) CheckFunc { } // normalizeUpper maps all Unicode letters in the given value to their upper case. -func normalizeUpper(value, _ reflect.Value) Result { +func normalizeUpper(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } value.SetString(strings.ToUpper(value.String())) - return ResultValid + return nil } diff --git a/upper_test.go b/upper_test.go index 1d52cee..6e0fb8f 100644 --- a/upper_test.go +++ b/upper_test.go @@ -23,7 +23,7 @@ func TestNormalizeUpperNonString(t *testing.T) { checker.Check(user) } -func TestNormalizeUpperResultValid(t *testing.T) { +func TestNormalizeUpperErrValid(t *testing.T) { type User struct { Username string `checkers:"upper"` } diff --git a/url.go b/url.go index a53d3ff..b43efb3 100644 --- a/url.go +++ b/url.go @@ -6,6 +6,7 @@ package checker import ( + "errors" "net/url" "reflect" ) @@ -13,25 +14,25 @@ import ( // CheckerURL is the name of the checker. const CheckerURL = "url" -// ResultNotURL indicates that the given value is not a valid URL. -const ResultNotURL = "NOT_URL" +// ErrNotURL indicates that the given value is not a valid URL. +var ErrNotURL = errors.New("please enter a valid URL") // IsURL checks if the given value is a valid URL. -func IsURL(value string) Result { +func IsURL(value string) error { url, err := url.ParseRequestURI(value) if err != nil { - return ResultNotURL + return ErrNotURL } if url.Scheme == "" { - return ResultNotURL + return ErrNotURL } if url.Host == "" { - return ResultNotURL + return ErrNotURL } - return ResultValid + return nil } // makeURL makes a checker function for the URL checker. @@ -40,7 +41,7 @@ func makeURL(_ string) CheckFunc { } // checkURL checks if the given value is a valid URL. -func checkURL(value, _ reflect.Value) Result { +func checkURL(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } diff --git a/url_escape.go b/url_escape.go index a41784e..f78abe2 100644 --- a/url_escape.go +++ b/url_escape.go @@ -20,12 +20,12 @@ func makeURLEscape(_ string) CheckFunc { // normalizeURLEscape applies URL escaping to special characters. // Uses net.url.QueryEscape for the actual escape operation. -func normalizeURLEscape(value, _ reflect.Value) Result { +func normalizeURLEscape(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } value.SetString(url.QueryEscape(value.String())) - return ResultValid + return nil } diff --git a/url_test.go b/url_test.go index ce97f90..883eb0a 100644 --- a/url_test.go +++ b/url_test.go @@ -12,22 +12,22 @@ import ( ) func ExampleIsURL() { - result := checker.IsURL("https://zdo.com") - if result != checker.ResultValid { + err := checker.IsURL("https://zdo.com") + if err != nil { // Send the mistakes back to the user } } func TestIsURLValid(t *testing.T) { - result := checker.IsURL("https://zdo.com") - if result != checker.ResultValid { + err := checker.IsURL("https://zdo.com") + if err != nil { t.Fail() } } func TestIsURLInvalid(t *testing.T) { - result := checker.IsURL("https:://index.html") - if result == checker.ResultValid { + err := checker.IsURL("https:://index.html") + if err == nil { t.Fail() } } diff --git a/url_unescape.go b/url_unescape.go index a115689..d10e602 100644 --- a/url_unescape.go +++ b/url_unescape.go @@ -20,7 +20,7 @@ func makeURLUnescape(_ string) CheckFunc { // normalizeURLUnescape applies URL unescaping to special characters. // Uses url.QueryUnescape for the actual unescape operation. -func normalizeURLUnescape(value, _ reflect.Value) Result { +func normalizeURLUnescape(value, _ reflect.Value) error { if value.Kind() != reflect.String { panic("string expected") } @@ -30,5 +30,5 @@ func normalizeURLUnescape(value, _ reflect.Value) Result { value.SetString(unescaped) } - return ResultValid + return nil } From 0c362a17e9db6d9c10260c647d019e5f33183b66 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Tue, 29 Oct 2024 20:06:47 -0700 Subject: [PATCH 04/41] Fix documentation. (#125) # Describe Request Fix documentation for the new error return types. # Change Type Documentation update. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4c2fbb0..6f209e4 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,8 @@ If you do not want to validate user input stored in a struct, you can individual ```golang var name -result := checker.IsRequired(name) -if result != ResultValid { +err := checker.IsRequired(name) +if err != nil { // Send the result back to the user } ``` @@ -114,8 +114,8 @@ This package currently provides the following normalizers. They can be mixed wit To define a custom checker, you need to create a new function with the following parameters: ```golang -func CustomChecker(value, parent reflect.Value) Result { - return ResultValid +func CustomChecker(value, parent reflect.Value) error { + return nil } ``` type MakeFunc From 4a77e34c927654c141256a9108367b321caedaf1 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Tue, 29 Oct 2024 21:10:40 -0700 Subject: [PATCH 05/41] Doc automation. (#126) # Describe Request Doc automation. # Change Type Doc update. --- .gomarkdoc.yml | 3 + README.md | 1213 +++++++++++++++++++++++++++++- alphanumeric.go | 4 +- ascii.go | 4 +- checker.go | 62 +- cidr.go | 4 +- credit_card.go | 4 +- digits.go | 4 +- doc/checkers/alphanumeric.md | 28 - doc/checkers/ascii.md | 28 - doc/checkers/cidr.md | 28 - doc/checkers/credit_card.md | 62 -- doc/checkers/digits.md | 28 - doc/checkers/email.md | 28 - doc/checkers/fqdn.md | 28 - doc/checkers/ip.md | 28 - doc/checkers/ipv4.md | 28 - doc/checkers/ipv6.md | 28 - doc/checkers/isbn.md | 52 -- doc/checkers/luhn.md | 28 - doc/checkers/mac.md | 28 - doc/checkers/max.md | 30 - doc/checkers/maxlength.md | 32 - doc/checkers/min.md | 30 - doc/checkers/minlength.md | 32 - doc/checkers/regexp.md | 48 -- doc/checkers/required.md | 27 - doc/checkers/same.md | 20 - doc/checkers/url.md | 29 - doc/normalizers/html_escape.md | 19 - doc/normalizers/html_unescape.md | 22 - doc/normalizers/lower.md | 17 - doc/normalizers/title.md | 17 - doc/normalizers/trim.md | 17 - doc/normalizers/trim_left.md | 17 - doc/normalizers/trim_right.md | 17 - doc/normalizers/upper.md | 15 - doc/normalizers/url_escape.md | 22 - doc/normalizers/url_unescape.md | 26 - doc/optimization.md | 15 - email.go | 4 +- fqdn.go | 4 +- html_escape.go | 4 +- html_unescape.go | 4 +- ip.go | 4 +- ipv4.go | 4 +- ipv6.go | 4 +- isbn.go | 4 +- lower.go | 4 +- luhn.go | 4 +- mac.go | 4 +- max.go | 4 +- maxlength.go | 4 +- min.go | 4 +- minlenght.go | 4 +- regexp.go | 4 +- required.go | 4 +- same.go | 4 +- taskfile.yml | 5 + title.go | 4 +- trim.go | 4 +- trim_left.go | 4 +- trim_right.go | 4 +- upper.go | 4 +- url.go | 4 +- url_escape.go | 4 +- url_unescape.go | 4 +- 67 files changed, 1283 insertions(+), 998 deletions(-) create mode 100644 .gomarkdoc.yml delete mode 100644 doc/checkers/alphanumeric.md delete mode 100644 doc/checkers/ascii.md delete mode 100644 doc/checkers/cidr.md delete mode 100644 doc/checkers/credit_card.md delete mode 100644 doc/checkers/digits.md delete mode 100644 doc/checkers/email.md delete mode 100644 doc/checkers/fqdn.md delete mode 100644 doc/checkers/ip.md delete mode 100644 doc/checkers/ipv4.md delete mode 100644 doc/checkers/ipv6.md delete mode 100644 doc/checkers/isbn.md delete mode 100644 doc/checkers/luhn.md delete mode 100644 doc/checkers/mac.md delete mode 100644 doc/checkers/max.md delete mode 100644 doc/checkers/maxlength.md delete mode 100644 doc/checkers/min.md delete mode 100644 doc/checkers/minlength.md delete mode 100644 doc/checkers/regexp.md delete mode 100644 doc/checkers/required.md delete mode 100644 doc/checkers/same.md delete mode 100644 doc/checkers/url.md delete mode 100644 doc/normalizers/html_escape.md delete mode 100644 doc/normalizers/html_unescape.md delete mode 100644 doc/normalizers/lower.md delete mode 100644 doc/normalizers/title.md delete mode 100644 doc/normalizers/trim.md delete mode 100644 doc/normalizers/trim_left.md delete mode 100644 doc/normalizers/trim_right.md delete mode 100644 doc/normalizers/upper.md delete mode 100644 doc/normalizers/url_escape.md delete mode 100644 doc/normalizers/url_unescape.md delete mode 100644 doc/optimization.md diff --git a/.gomarkdoc.yml b/.gomarkdoc.yml new file mode 100644 index 0000000..86a76b1 --- /dev/null +++ b/.gomarkdoc.yml @@ -0,0 +1,3 @@ +output: "{{.Dir}}/README.md" +repository: + url: https://github.com/cinar/checker diff --git a/README.md b/README.md index 6f209e4..d2e4aeb 100644 --- a/README.md +++ b/README.md @@ -72,42 +72,42 @@ type Person struct { This package currently provides the following checkers: -- [alphanumeric](doc/checkers/alphanumeric.md) checks if the given string consists of only alphanumeric characters. -- [ascii](doc/checkers/ascii.md) checks if the given string consists of only ASCII characters. -- [cidr](doc/checkers/cidr.md) checker checks if the value is a valid CIDR notation IP address and prefix length. -- [credit-card](doc/checkers/credit_card.md) checks if the given value is a valid credit card number. -- [digits](doc/checkers/digits.md) checks if the given string consists of only digit characters. -- [email](doc/checkers/email.md) checks if the given string is an email address. -- [fqdn](doc/checkers/fqdn.md) checks if the given string is a fully qualified domain name. -- [ip](doc/checkers/ip.md) checks if the given value is an IP address. -- [ipv4](doc/checkers/ipv4.md) checks if the given value is an IPv4 address. -- [ipv6](doc/checkers/ipv6.md) checks if the given value is an IPv6 address. -- [isbn](doc/checkers/isbn.md) checks if the given value is a valid ISBN number. -- [luhn](doc/checkers/luhn.md) checks if the given number is valid based on the Luhn algorithm. -- [mac](doc/checkers/mac.md) checks if the given value is a valid an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet IP over InfiniBand link-layer address. -- [max](doc/checkers/max.md) checks if the given value is less than the given maximum. -- [max-length](doc/checkers/maxlength.md) checks if the length of the given value is less than the given maximum length. -- [min](doc/checkers/min.md) checks if the given value is greather than the given minimum. -- [min-length](doc/checkers/minlength.md) checks if the length of the given value is greather than the given minimum length. -- [regexp](doc/checkers/regexp.md) checks if the given string matches the regexp pattern. -- [required](doc/checkers/required.md) checks if the required value is provided. -- [same](doc/checkers/same.md) checks if the given value is equal to the value of the field with the given name. -- [url](doc/checkers/url.md) checks if the given value is a valid URL. +- `alphanumeric` checks if the given string consists of only alphanumeric characters. +- `ascii` checks if the given string consists of only ASCII characters. +- `cidr` checker checks if the value is a valid CIDR notation IP address and prefix length. +- `credit-card` checks if the given value is a valid credit card number. +- `digits` checks if the given string consists of only digit characters. +- `email` checks if the given string is an email address. +- `fqdn` checks if the given string is a fully qualified domain name. +- `ip` checks if the given value is an IP address. +- `ipv4` checks if the given value is an IPv4 address. +- `ipv6` checks if the given value is an IPv6 address. +- `isbn` checks if the given value is a valid ISBN number. +- `luhn` checks if the given number is valid based on the Luhn algorithm. +- `mac` checks if the given value is a valid an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet IP over InfiniBand link-layer address. +- `max` checks if the given value is less than the given maximum. +- `max-length` checks if the length of the given value is less than the given maximum length. +- `min` checks if the given value is greather than the given minimum. +- `min-length` checks if the length of the given value is greather than the given minimum length. +- `regexp` checks if the given string matches the regexp pattern. +- `required` checks if the required value is provided. +- `same` checks if the given value is equal to the value of the field with the given name. +- `url` checks if the given value is a valid URL. # Normalizers Provided This package currently provides the following normalizers. They can be mixed with the checkers when defining the validation steps for user data. -- [html-escape](doc/normalizers/html_escape.md) applies HTML escaping to special characters. -- [html-unescape](doc/normalizers/html_unescape.md) applies HTML unescaping to special characters. -- [lower](doc/normalizers/lower.md) maps all Unicode letters in the given value to their lower case. -- [upper](doc/normalizers/upper.md) maps all Unicode letters in the given value to their upper case. -- [title](doc/normalizers/title.md) maps the first letter of each word to their upper case. -- [trim](doc/normalizers/trim.md) removes the whitespaces at the beginning and at the end of the given value. -- [trim-left](doc/normalizers/trim_left.md) removes the whitespaces at the beginning of the given value. -- [trim-right](doc/normalizers/trim_right.md) removes the whitespaces at the end of the given value. -- [url-escape](doc/normalizers/url_escape.md) applies URL escaping to special characters. -- [url-unescape](doc/normalizers/url_unescape.md) applies URL unescaping to special characters. +- `html-escape` applies HTML escaping to special characters. +- `html-unescape` applies HTML unescaping to special characters. +- `lower` maps all Unicode letters in the given value to their lower case. +- `upper` maps all Unicode letters in the given value to their upper case. +- `title` maps the first letter of each word to their upper case. +- `trim` removes the whitespaces at the beginning and at the end of the given value. +- `trim-left` removes the whitespaces at the beginning of the given value. +- `trim-right` removes the whitespaces at the end of the given value. +- `url-escape` applies URL escaping to special characters. +- `url-unescape` applies URL unescaping to special characters. # Custom Checkers @@ -141,6 +141,1157 @@ type User struct { } ``` + + + + +# checker + +```go +import "github.com/cinar/checker" +``` + +Package checker is a Go library for validating user input through struct tags. + +Copyright \(c\) 2023\-2024 Onur Cinar. Use of this source code is governed by a MIT\-style license that can be found in the LICENSE file. https://github.com/cinar/checker + +## Index + +- [Variables](<#variables>) +- [func FailIfNoPanic\(t \*testing.T\)](<#FailIfNoPanic>) +- [func IsASCII\(value string\) error](<#IsASCII>) +- [func IsAlphanumeric\(value string\) error](<#IsAlphanumeric>) +- [func IsAmexCreditCard\(number string\) error](<#IsAmexCreditCard>) +- [func IsAnyCreditCard\(number string\) error](<#IsAnyCreditCard>) +- [func IsCidr\(value string\) error](<#IsCidr>) +- [func IsDigits\(value string\) error](<#IsDigits>) +- [func IsDinersCreditCard\(number string\) error](<#IsDinersCreditCard>) +- [func IsDiscoverCreditCard\(number string\) error](<#IsDiscoverCreditCard>) +- [func IsEmail\(email string\) error](<#IsEmail>) +- [func IsFqdn\(domain string\) error](<#IsFqdn>) +- [func IsIP\(value string\) error](<#IsIP>) +- [func IsIPV4\(value string\) error](<#IsIPV4>) +- [func IsIPV6\(value string\) error](<#IsIPV6>) +- [func IsISBN\(value string\) error](<#IsISBN>) +- [func IsISBN10\(value string\) error](<#IsISBN10>) +- [func IsISBN13\(value string\) error](<#IsISBN13>) +- [func IsJcbCreditCard\(number string\) error](<#IsJcbCreditCard>) +- [func IsLuhn\(number string\) error](<#IsLuhn>) +- [func IsMac\(value string\) error](<#IsMac>) +- [func IsMasterCardCreditCard\(number string\) error](<#IsMasterCardCreditCard>) +- [func IsMax\(value interface\{\}, max float64\) error](<#IsMax>) +- [func IsMaxLength\(value interface\{\}, maxLength int\) error](<#IsMaxLength>) +- [func IsMin\(value interface\{\}, min float64\) error](<#IsMin>) +- [func IsMinLength\(value interface\{\}, minLength int\) error](<#IsMinLength>) +- [func IsRequired\(v interface\{\}\) error](<#IsRequired>) +- [func IsURL\(value string\) error](<#IsURL>) +- [func IsUnionPayCreditCard\(number string\) error](<#IsUnionPayCreditCard>) +- [func IsVisaCreditCard\(number string\) error](<#IsVisaCreditCard>) +- [func Register\(name string, maker MakeFunc\)](<#Register>) +- [type CheckFunc](<#CheckFunc>) + - [func MakeRegexpChecker\(expression string, invalidError error\) CheckFunc](<#MakeRegexpChecker>) +- [type Errors](<#Errors>) + - [func Check\(s interface\{\}\) \(Errors, bool\)](<#Check>) +- [type MakeFunc](<#MakeFunc>) + - [func MakeRegexpMaker\(expression string, invalidError error\) MakeFunc](<#MakeRegexpMaker>) + + +## Variables + +ErrNotASCII indicates that the given string contains non\-ASCII characters. + +```go +var ErrNotASCII = errors.New("please use standard English characters only") +``` + +ErrNotAlphanumeric indicates that the given string contains non\-alphanumeric characters. + +```go +var ErrNotAlphanumeric = errors.New("please use only letters and numbers") +``` + +ErrNotCidr indicates that the given value is not a valid CIDR. + +```go +var ErrNotCidr = errors.New("please enter a valid CIDR") +``` + +ErrNotCreditCard indicates that the given value is not a valid credit card number. + +```go +var ErrNotCreditCard = errors.New("please enter a valid credit card number") +``` + +ErrNotDigits indicates that the given string contains non\-digit characters. + +```go +var ErrNotDigits = errors.New("please enter a valid number") +``` + +ErrNotEmail indicates that the given string is not a valid email. + +```go +var ErrNotEmail = errors.New("please enter a valid email address") +``` + +ErrNotFqdn indicates that the given string is not a valid FQDN. + +```go +var ErrNotFqdn = errors.New("please enter a valid domain name") +``` + +ErrNotIP indicates that the given value is not an IP address. + +```go +var ErrNotIP = errors.New("please enter a valid IP address") +``` + +ErrNotIPV4 indicates that the given value is not an IPv4 address. + +```go +var ErrNotIPV4 = errors.New("please enter a valid IPv4 address") +``` + +ErrNotIPV6 indicates that the given value is not an IPv6 address. + +```go +var ErrNotIPV6 = errors.New("please enter a valid IPv6 address") +``` + +ErrNotISBN indicates that the given value is not a valid ISBN. + +```go +var ErrNotISBN = errors.New("please enter a valid ISBN number") +``` + +ErrNotLuhn indicates that the given number is not valid based on the Luhn algorithm. + +```go +var ErrNotLuhn = errors.New("please enter a valid LUHN") +``` + +ErrNotMac indicates that the given value is not an MAC address. + +```go +var ErrNotMac = errors.New("please enter a valid MAC address") +``` + +ErrNotMatch indicates that the given string does not match the regexp pattern. + +```go +var ErrNotMatch = errors.New("please enter a valid input") +``` + +ErrNotSame indicates that the given two values are not equal to each other. + +```go +var ErrNotSame = errors.New("does not match the other") +``` + +ErrNotURL indicates that the given value is not a valid URL. + +```go +var ErrNotURL = errors.New("please enter a valid URL") +``` + +ErrRequired indicates that the required value is missing. + +```go +var ErrRequired = errors.New("is required") +``` + + +## func [FailIfNoPanic]() + +```go +func FailIfNoPanic(t *testing.T) +``` + +FailIfNoPanic fails if test didn't panic. Use this function with the defer. + + +## func [IsASCII]() + +```go +func IsASCII(value string) error +``` + +IsASCII checks if the given string consists of only ASCII characters. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsASCII("Checker") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsAlphanumeric]() + +```go +func IsAlphanumeric(value string) error +``` + +IsAlphanumeric checks if the given string consists of only alphanumeric characters. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsAlphanumeric("ABcd1234") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsAmexCreditCard]() + +```go +func IsAmexCreditCard(number string) error +``` + +IsAmexCreditCard checks if the given valie is a valid AMEX credit card. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsAmexCreditCard("378282246310005") + + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsAnyCreditCard]() + +```go +func IsAnyCreditCard(number string) error +``` + +IsAnyCreditCard checks if the given value is a valid credit card number. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsAnyCreditCard("6011111111111117") + + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsCidr]() + +```go +func IsCidr(value string) error +``` + +IsCidr checker checks if the value is a valid CIDR notation IP address and prefix length. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsCidr("2001:db8::/32") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsDigits]() + +```go +func IsDigits(value string) error +``` + +IsDigits checks if the given string consists of only digit characters. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsDigits("1234") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsDinersCreditCard]() + +```go +func IsDinersCreditCard(number string) error +``` + +IsDinersCreditCard checks if the given valie is a valid Diners credit card. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsDinersCreditCard("36227206271667") + + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsDiscoverCreditCard]() + +```go +func IsDiscoverCreditCard(number string) error +``` + +IsDiscoverCreditCard checks if the given valie is a valid Discover credit card. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsDiscoverCreditCard("6011111111111117") + + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsEmail]() + +```go +func IsEmail(email string) error +``` + +IsEmail checks if the given string is an email address. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsEmail("user@zdo.com") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsFqdn]() + +```go +func IsFqdn(domain string) error +``` + +IsFqdn checks if the given string is a fully qualified domain name. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsFqdn("zdo.com") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsIP]() + +```go +func IsIP(value string) error +``` + +IsIP checks if the given value is an IP address. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsIP("2001:db8::68") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsIPV4]() + +```go +func IsIPV4(value string) error +``` + +IsIPV4 checks if the given value is an IPv4 address. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsIPV4("192.168.1.1") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsIPV6]() + +```go +func IsIPV6(value string) error +``` + +IsIPV6 checks if the given value is an IPv6 address. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsIPV6("2001:db8::68") + + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsISBN]() + +```go +func IsISBN(value string) error +``` + +IsISBN checks if the given value is a valid ISBN number. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsISBN("1430248270") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsISBN10]() + +```go +func IsISBN10(value string) error +``` + +IsISBN10 checks if the given value is a valid ISBN\-10 number. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsISBN10("1430248270") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsISBN13]() + +```go +func IsISBN13(value string) error +``` + +IsISBN13 checks if the given value is a valid ISBN\-13 number. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsISBN13("9781430248279") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsJcbCreditCard]() + +```go +func IsJcbCreditCard(number string) error +``` + +IsJcbCreditCard checks if the given valie is a valid JCB 15 credit card. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsJcbCreditCard("3530111333300000") + + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsLuhn]() + +```go +func IsLuhn(number string) error +``` + +IsLuhn checks if the given number is valid based on the Luhn algorithm. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsLuhn("4012888888881881") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsMac]() + +```go +func IsMac(value string) error +``` + +IsMac checks if the given value is a valid an IEEE 802 MAC\-48, EUI\-48, EUI\-64, or a 20\-octet IP over InfiniBand link\-layer address. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsMac("00:00:5e:00:53:01") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsMasterCardCreditCard]() + +```go +func IsMasterCardCreditCard(number string) error +``` + +IsMasterCardCreditCard checks if the given valie is a valid MasterCard credit card. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsMasterCardCreditCard("5555555555554444") + + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsMax]() + +```go +func IsMax(value interface{}, max float64) error +``` + +IsMax checks if the given value is below than the given maximum. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + quantity := 5 + + err := checker.IsMax(quantity, 10) + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsMaxLength]() + +```go +func IsMaxLength(value interface{}, maxLength int) error +``` + +IsMaxLength checks if the length of the given value is less than the given maximum length. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + s := "1234" + + err := checker.IsMaxLength(s, 4) + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsMin]() + +```go +func IsMin(value interface{}, min float64) error +``` + +IsMin checks if the given value is above than the given minimum. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + age := 45 + + err := checker.IsMin(age, 21) + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsMinLength]() + +```go +func IsMinLength(value interface{}, minLength int) error +``` + +IsMinLength checks if the length of the given value is greather than the given minimum length. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + s := "1234" + + err := checker.IsMinLength(s, 4) + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsRequired]() + +```go +func IsRequired(v interface{}) error +``` + +IsRequired checks if the given required value is present. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + var name string + + err := checker.IsRequired(name) + if err != nil { + // Send the err back to the user + } +} +``` + +

+
+ + +## func [IsURL]() + +```go +func IsURL(value string) error +``` + +IsURL checks if the given value is a valid URL. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsURL("https://zdo.com") + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsUnionPayCreditCard]() + +```go +func IsUnionPayCreditCard(number string) error +``` + +IsUnionPayCreditCard checks if the given valie is a valid UnionPay credit card. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsUnionPayCreditCard("6200000000000005") + + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [IsVisaCreditCard]() + +```go +func IsVisaCreditCard(number string) error +``` + +IsVisaCreditCard checks if the given valie is a valid Visa credit card. + +
Example +

+ + + +```go +package main + +import ( + "github.com/cinar/checker" +) + +func main() { + err := checker.IsVisaCreditCard("4111111111111111") + + if err != nil { + // Send the mistakes back to the user + } +} +``` + +

+
+ + +## func [Register]() + +```go +func Register(name string, maker MakeFunc) +``` + +Register registers the given checker name and the maker function. + + +## type [CheckFunc]() + +CheckFunc defines the signature for the checker functions. + +```go +type CheckFunc func(value, parent reflect.Value) error +``` + + +### func [MakeRegexpChecker]() + +```go +func MakeRegexpChecker(expression string, invalidError error) CheckFunc +``` + +MakeRegexpChecker makes a regexp checker for the given regexp expression with the given invalid result. + + +## type [Errors]() + +Errors provides a mapping of the checker errors keyed by the field names. + +```go +type Errors map[string]error +``` + + +### func [Check]() + +```go +func Check(s interface{}) (Errors, bool) +``` + +Check function checks the given struct based on the checkers listed in field tag names. + + +## type [MakeFunc]() + +MakeFunc defines the signature for the checker maker functions. + +```go +type MakeFunc func(params string) CheckFunc +``` + + +### func [MakeRegexpMaker]() + +```go +func MakeRegexpMaker(expression string, invalidError error) MakeFunc +``` + +MakeRegexpMaker makes a regexp checker maker for the given regexp expression with the given invalid result. + +Generated by [gomarkdoc]() + + + + # Contributing to the Project Anyone can contribute to Checkers library. Please make sure to read our [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md) guide first. Follow the [How to Contribute to Checker](./CONTRIBUTING.md) to contribute. diff --git a/alphanumeric.go b/alphanumeric.go index 8b87ace..0eb4f52 100644 --- a/alphanumeric.go +++ b/alphanumeric.go @@ -11,8 +11,8 @@ import ( "unicode" ) -// CheckerAlphanumeric is the name of the checker. -const CheckerAlphanumeric = "alphanumeric" +// tagAlphanumeric is the tag of the checker. +const tagAlphanumeric = "alphanumeric" // ErrNotAlphanumeric indicates that the given string contains non-alphanumeric characters. var ErrNotAlphanumeric = errors.New("please use only letters and numbers") diff --git a/ascii.go b/ascii.go index 32f68a2..b9b4318 100644 --- a/ascii.go +++ b/ascii.go @@ -11,8 +11,8 @@ import ( "unicode" ) -// CheckerASCII is the name of the checker. -const CheckerASCII = "ascii" +// tagASCII is the tag of the checker. +const tagASCII = "ascii" // ErrNotASCII indicates that the given string contains non-ASCII characters. var ErrNotASCII = errors.New("please use standard English characters only") diff --git a/checker.go b/checker.go index afbef50..595649c 100644 --- a/checker.go +++ b/checker.go @@ -30,37 +30,37 @@ type checkerJob struct { // makers provides a mapping of the maker functions keyed by the respective checker names. var makers = map[string]MakeFunc{ - CheckerAlphanumeric: makeAlphanumeric, - CheckerASCII: makeASCII, - CheckerCreditCard: makeCreditCard, - CheckerCidr: makeCidr, - CheckerDigits: makeDigits, - CheckerEmail: makeEmail, - CheckerFqdn: makeFqdn, - CheckerIP: makeIP, - CheckerIPV4: makeIPV4, - CheckerIPV6: makeIPV6, - CheckerISBN: makeISBN, - CheckerLuhn: makeLuhn, - CheckerMac: makeMac, - CheckerMax: makeMax, - CheckerMaxLength: makeMaxLength, - CheckerMin: makeMin, - CheckerMinLength: makeMinLength, - CheckerRegexp: makeRegexp, - CheckerRequired: makeRequired, - CheckerSame: makeSame, - CheckerURL: makeURL, - NormalizerHTMLEscape: makeHTMLEscape, - NormalizerHTMLUnescape: makeHTMLUnescape, - NormalizerLower: makeLower, - NormalizerUpper: makeUpper, - NormalizerTitle: makeTitle, - NormalizerTrim: makeTrim, - NormalizerTrimLeft: makeTrimLeft, - NormalizerTrimRight: makeTrimRight, - NormalizerURLEscape: makeURLEscape, - NormalizerURLUnescape: makeURLUnescape, + tagAlphanumeric: makeAlphanumeric, + tagASCII: makeASCII, + tagCreditCard: makeCreditCard, + tagCidr: makeCidr, + tagDigits: makeDigits, + tagEmail: makeEmail, + tagFqdn: makeFqdn, + tagIP: makeIP, + tagIPV4: makeIPV4, + tagIPV6: makeIPV6, + tagISBN: makeISBN, + tagLuhn: makeLuhn, + tagMac: makeMac, + tagMax: makeMax, + tagMaxLength: makeMaxLength, + tagMin: makeMin, + tagMinLength: makeMinLength, + tagRegexp: makeRegexp, + tagRequired: makeRequired, + tagSame: makeSame, + tagURL: makeURL, + tagHTMLEscape: makeHTMLEscape, + tagHTMLUnescape: makeHTMLUnescape, + tagLower: makeLower, + tagUpper: makeUpper, + tagTitle: makeTitle, + tagTrim: makeTrim, + tagTrimLeft: makeTrimLeft, + tagTrimRight: makeTrimRight, + tagURLEscape: makeURLEscape, + tagURLUnescape: makeURLUnescape, } // Register registers the given checker name and the maker function. diff --git a/cidr.go b/cidr.go index 275427c..0bad38d 100644 --- a/cidr.go +++ b/cidr.go @@ -11,8 +11,8 @@ import ( "reflect" ) -// CheckerCidr is the name of the checker. -const CheckerCidr = "cidr" +// tagCidr is the tag of the checker. +const tagCidr = "cidr" // ErrNotCidr indicates that the given value is not a valid CIDR. var ErrNotCidr = errors.New("please enter a valid CIDR") diff --git a/credit_card.go b/credit_card.go index 9f7cc01..5016153 100644 --- a/credit_card.go +++ b/credit_card.go @@ -12,8 +12,8 @@ import ( "strings" ) -// CheckerCreditCard is the name of the checker. -const CheckerCreditCard = "credit-card" +// tagCreditCard is the tag of the checker. +const tagCreditCard = "credit-card" // ErrNotCreditCard indicates that the given value is not a valid credit card number. var ErrNotCreditCard = errors.New("please enter a valid credit card number") diff --git a/digits.go b/digits.go index 9098622..83e41b7 100644 --- a/digits.go +++ b/digits.go @@ -11,8 +11,8 @@ import ( "unicode" ) -// CheckerDigits is the name of the checker. -const CheckerDigits = "digits" +// tagDigits is the tag of the checker. +const tagDigits = "digits" // ErrNotDigits indicates that the given string contains non-digit characters. var ErrNotDigits = errors.New("please enter a valid number") diff --git a/doc/checkers/alphanumeric.md b/doc/checkers/alphanumeric.md deleted file mode 100644 index ace92b0..0000000 --- a/doc/checkers/alphanumeric.md +++ /dev/null @@ -1,28 +0,0 @@ -# Alphanumeric Checker - -The `alphanumeric` checker checks if the given string consists of only alphanumeric characters. If the string contains non-alphanumeric characters, the checker will return the `NOT_ALPHANUMERIC` result. Here is an example: - -```golang -type User struct { - Username string `checkers:"alphanumeric"` -} - -user := &User{ - Username: "ABcd1234", -} - -_, valid := checker.Check(user) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `alphanumeric` checker function [`IsAlphanumeric`](https://pkg.go.dev/github.com/cinar/checker#IsAlphanumeric) to validate the user input. Here is an example: - -```golang -result := checker.IsAlphanumeric("ABcd1234") - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/ascii.md b/doc/checkers/ascii.md deleted file mode 100644 index cb5b9f8..0000000 --- a/doc/checkers/ascii.md +++ /dev/null @@ -1,28 +0,0 @@ -# ASCII Checker - -The `ascii` checker checks if the given string consists of only ASCII characters. If the string contains non-ASCII characters, the checker will return the `NOT_ASCII` result. Here is an example: - -```golang -type User struct { - Username string `checkers:"ascii"` -} - -user := &User{ - Username: "checker", -} - -_, valid := checker.Check(user) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `ascii` checker function [`IsASCII`](https://pkg.go.dev/github.com/cinar/checker#IsASCII) to validate the user input. Here is an example: - -```golang -result := checker.IsASCII("Checker") - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/cidr.md b/doc/checkers/cidr.md deleted file mode 100644 index d496f29..0000000 --- a/doc/checkers/cidr.md +++ /dev/null @@ -1,28 +0,0 @@ -# CIDR Checker - -The `cidr` checker checks if the value is a valid CIDR notation IP address and prefix length, like `192.0.2.0/24` or `2001:db8::/32`, as defined in [RFC 4632](https://rfc-editor.org/rfc/rfc4632.html) and [RFC 4291](https://rfc-editor.org/rfc/rfc4291.html). If the value is not a valid CIDR notation, the checker will return the `NOT_CIDR` result. Here is an example: - -```golang -type Network struct { - Subnet string `checkers:"cidr"` -} - -network := &Network{ - Subnet: "192.0.2.0/24", -} - -_, valid := checker.Check(network) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `cidr` checker function [`IsCidr`](https://pkg.go.dev/github.com/cinar/checker#IsASCII) to validate the user input. Here is an example: - -```golang -result := checker.IsCidr("2001:db8::/32") - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/credit_card.md b/doc/checkers/credit_card.md deleted file mode 100644 index 19989d2..0000000 --- a/doc/checkers/credit_card.md +++ /dev/null @@ -1,62 +0,0 @@ -# Credit Card Checker - -The `credit-card` checker checks if the given value is a valid credit card number. If the given value is not a valid credit card number, the checker will return the `NOT_CREDIT_CARD` result. - -Here is an example: - -```golang -type Order struct { - CreditCard string `checkers:"credit-card"` -} - -order := &Order{ - CreditCard: invalidCard, -} - -_, valid := checker.Check(order) -if valid { - // Send the mistakes back to the user -} -``` - -The checker currently knows about AMEX, Diners, Discover, JCB, MasterCard, UnionPay, and VISA credit card numbers. - -If you would like to check for a subset of those credit cards, you can specify them through the checker config parameter. Here is an example: - -```golang -type Order struct { - CreditCard string `checkers:"credit-card:amex,visa"` -} - -order := &Order{ - CreditCard: "6011111111111117", -} - -_, valid := checker.Check(order) -if valid { - // Send the mistakes back to the user -} -``` - -If you would like to verify a credit card that is not listed here, please use the [luhn](luhn.md) checker to use the Luhn Algorithm to verify the check digit. - -In your custom checkers, you can call the `credit-card` checker functions below to validate the user input. - -- [`IsAnyCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsAnyCreditCard): checks if the given value is a valid credit card number. -- [`IsAmexCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsAmexCreditCard): checks if the given value is a valid AMEX credit card number. -- [`IsDinersCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsDinersCreditCard): checks if the given value is a valid Diners credit card number. -- [`IsDiscoverCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsDiscoverCreditCard): checks if the given value is a valid Discover credit card number. -- [`IsJcbCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsJcbCreditCard): checks if the given value is a valid JCB credit card number. -- [`IsMasterCardCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsMasterCardCreditCard): checks if the given value is a valid MasterCard credit card number. -- [`IsUnionPayCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsUnionPayCreditCard): checks if the given value is a valid UnionPay credit card number. -- [`IsVisaCreditCard`](https://pkg.go.dev/github.com/cinar/checker#IsVisaCreditCard): checks if the given value is a valid VISA credit card number. - -Here is an example: - -```golang -result := checker.IsAnyCreditCard("6011111111111117") - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/digits.md b/doc/checkers/digits.md deleted file mode 100644 index 03fb89a..0000000 --- a/doc/checkers/digits.md +++ /dev/null @@ -1,28 +0,0 @@ -# Digits Checker - -The `digits` checker checks if the given string consists of only digit characters. If the string contains non-digit characters, the checker will return the `NOT_DIGITS` result. Here is an example: - -```golang -type User struct { - Id string `checkers:"digits"` -} - -user := &User{ - Id: "1234", -} - -_, valid := checker.Check(user) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `digits` checker function [`IsDigits`](https://pkg.go.dev/github.com/cinar/checker#IsDigits) to validate the user input. Here is an example: - -```golang -result := checker.IsDigits("1234") - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/email.md b/doc/checkers/email.md deleted file mode 100644 index db41c6a..0000000 --- a/doc/checkers/email.md +++ /dev/null @@ -1,28 +0,0 @@ -# Email Checker - -The `email` checker checks if the given string is an email address. If the given string is not a valid email address, the checker will return the `NOT_EMAIL` result. Here is an example: - -```golang -type User struct { - Email string `checkers:"email"` -} - -user := &User{ - Email: "user@zdo.com", -} - -_, valid := checker.Check(user) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `email` checker function [`IsEmail`](https://pkg.go.dev/github.com/cinar/checker#IsEmail) to validate the user input. Here is an example: - -```golang -result := checker.IsEmail("user@zdo.com") - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/fqdn.md b/doc/checkers/fqdn.md deleted file mode 100644 index e63565f..0000000 --- a/doc/checkers/fqdn.md +++ /dev/null @@ -1,28 +0,0 @@ -# FQDN Checker - -The Full Qualified Domain Name (FQDN) is the complete domain name for a computer or host on the internet. The `fqdn` checker checks if the given string consists of a FQDN. If the string is not a valid FQDN, the checker will return the `NOT_FQDN` result. Here is an example: - -```golang -type Request struct { - Domain string `checkers:"fqdn"` -} - -request := &Request{ - Domain: "zdo.com", -} - -_, valid := checker.Check(request) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `fqdn` checker function [`IsFqdn`](https://pkg.go.dev/github.com/cinar/checker#IsFqdn) to validate the user input. Here is an example: - -```golang -result := checker.IsFqdn("zdo.com") - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/ip.md b/doc/checkers/ip.md deleted file mode 100644 index 4b01a51..0000000 --- a/doc/checkers/ip.md +++ /dev/null @@ -1,28 +0,0 @@ -# IP Checker - -The `ip` checker checks if the value is an IP address. If the value is not an IP address, the checker will return the `NOT_IP` result. Here is an example: - -```golang -type Request struct { - RemoteIP string `checkers:"ip"` -} - -request := &Request{ - RemoteIP: "192.168.1.1", -} - -_, valid := checker.Check(request) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `ip` checker function [`IsIP`](https://pkg.go.dev/github.com/cinar/checker#IsIP) to validate the user input. Here is an example: - -```golang -result := checker.IsIP("2001:db8::68") - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/ipv4.md b/doc/checkers/ipv4.md deleted file mode 100644 index d3ac8a5..0000000 --- a/doc/checkers/ipv4.md +++ /dev/null @@ -1,28 +0,0 @@ -# IPv4 Checker - -The `ipv4` checker checks if the value is an IPv4 address. If the value is not an IPv4 address, the checker will return the `NOT_IP_V4` result. Here is an example: - -```golang -type Request struct { - RemoteIP string `checkers:"ipv4"` -} - -request := &Request{ - RemoteIP: "192.168.1.1", -} - -_, valid := checker.Check(request) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `ipv4` checker function [`IsIPV4`](https://pkg.go.dev/github.com/cinar/checker#IsIPV4) to validate the user input. Here is an example: - -```golang -result := checker.IsIPV4("192.168.1.1") - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/ipv6.md b/doc/checkers/ipv6.md deleted file mode 100644 index 66626e2..0000000 --- a/doc/checkers/ipv6.md +++ /dev/null @@ -1,28 +0,0 @@ -# IPv6 Checker - -The `ipv6` checker checks if the value is an IPv6 address. If the value is not an IPv6 address, the checker will return the `NOT_IP_V6` result. Here is an example: - -```golang -type Request struct { - RemoteIP string `checkers:"ipv6"` -} - -request := &Request{ - RemoteIP: "2001:db8::68", -} - -_, valid := checker.Check(request) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `ipv6` checker function [`IsIPV6`](https://pkg.go.dev/github.com/cinar/checker#IsIPV6) to validate the user input. Here is an example: - -```golang -result := checker.IsIPV6("2001:db8::68") - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/isbn.md b/doc/checkers/isbn.md deleted file mode 100644 index 40a936f..0000000 --- a/doc/checkers/isbn.md +++ /dev/null @@ -1,52 +0,0 @@ -# ISBN Checker - -An [ISBN (International Standard Book Number)](https://en.wikipedia.org/wiki/International_Standard_Book_Number) is a 10 or 13 digit number that is used to identify a book. - -The `isbn` checker checks if the given value is a valid ISBN. If the given value is not a valid ISBN, the checker will return the `NOT_ISBN` result. Here is an example: - -```golang -type Book struct { - ISBN string `checkers:"isbn"` -} - -book := &Book{ - ISBN: "1430248270", -} - -_, valid := checker.Check(book) -if !valid { - // Send the mistakes back to the user -} -``` - -The `isbn` checker can also be configured to check for a 10 digit or a 13 digit number. Here is an example: - -```golang -type Book struct { - ISBN string `checkers:"isbn:13"` -} - -book := &Book{ - ISBN: "9781430248279", -} - -_, valid := checker.Check(book) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `isbn` checker functions below to validate the user input. - -- [`IsISBN`](https://pkg.go.dev/github.com/cinar/checker#IsISBN) checks if the given value is a valid ISBN number. -- [`IsISBN10`](https://pkg.go.dev/github.com/cinar/checker#IsISBN10) checks if the given value is a valid ISBN 10 number. -- [`IsISBN13`](https://pkg.go.dev/github.com/cinar/checker#IsISBN13) checks if the given value is a valid ISBN 13 number. - -Here is an example: - -```golang -result := checker.IsISBN("1430248270") -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/luhn.md b/doc/checkers/luhn.md deleted file mode 100644 index 3148e43..0000000 --- a/doc/checkers/luhn.md +++ /dev/null @@ -1,28 +0,0 @@ -# Luhn Checker - -The `luhn` checker checks if the given number is valid based on the Luhn Algorithm. If the given number is not valid, it will return the `NOT_LUHN` result. Here is an example: - -```golang -type Order struct { - CreditCard string `checkers:"luhn"` -} - -order := &Order{ - CreditCard: "4012888888881881", -} - -_, valid := checker.Check(order) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `luhn` checker function [`IsLuhn`](https://pkg.go.dev/github.com/cinar/checker#IsLuhn) to validate the user input. Here is an example: - -```golang -result := checker.IsLuhn("4012888888881881") - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/mac.md b/doc/checkers/mac.md deleted file mode 100644 index ac50fdd..0000000 --- a/doc/checkers/mac.md +++ /dev/null @@ -1,28 +0,0 @@ -# MAC Checker - -The `mac` checker checks if the value is a valid an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet IP over InfiniBand link-layer address. If the value is not a valid MAC address, the checker will return the `NOT_MAC` result. Here is an example: - -```golang -type Network struct { - HardwareAddress string `checkers:"mac"` -} - -network := &Network{ - HardwareAddress: "00:00:5e:00:53:01", -} - -_, valid := checker.Check(network) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `mac` checker function [`IsMac`](https://pkg.go.dev/github.com/cinar/checker#IsMac) to validate the user input. Here is an example: - -```golang -result := checker.IsMac("00:00:5e:00:53:01") - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/max.md b/doc/checkers/max.md deleted file mode 100644 index b26e714..0000000 --- a/doc/checkers/max.md +++ /dev/null @@ -1,30 +0,0 @@ -# Max Checker - -The `max` checker checks if the given ```int``` or ```float``` value is less than the given maximum. If the value is above the maximum, the checker will return the `NOT_MAX` result. Here is an example: - -```golang -type Order struct { - Quantity int `checkers:"max:10"` -} - -order := &Order{ - Quantity: 5, -} - -mistakes, valid := checker.Check(order) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `max` checker function [`IsMax`](https://pkg.go.dev/github.com/cinar/checker#IsMax) to validate the user input. Here is an example: - -```golang -quantity := 5 - -result := checker.IsMax(quantity, 10) - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/maxlength.md b/doc/checkers/maxlength.md deleted file mode 100644 index af008aa..0000000 --- a/doc/checkers/maxlength.md +++ /dev/null @@ -1,32 +0,0 @@ -# Max Length Checker - -The `max-length` checker checks if the length of the given value is less than the given maximum length. If the length of the value is above the maximum length, the checker will return the `NOT_MAX_LENGTH` result. Here is an example: - -```golang -type User struct { - Password string `checkers:"max-length:4"` -} - -user := &User{ - Password: "1234", -} - -mistakes, valid := checker.Check(user) -if !valid { - // Send the mistakes back to the user -} -``` - -The checker can be applied to all types that are supported by the [reflect.Value.Len()](https://pkg.go.dev/reflect#Value.Len) function. - -If you do not want to validate user input stored in a struct, you can individually call the `max-length` checker function [`IsMaxLength`](https://pkg.go.dev/github.com/cinar/checker#IsMaxLength) to validate the user input. Here is an example: - -```golang -s := "1234" - -result := checker.IsMaxLength(s, 4) - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/min.md b/doc/checkers/min.md deleted file mode 100644 index 8520655..0000000 --- a/doc/checkers/min.md +++ /dev/null @@ -1,30 +0,0 @@ -# Min Checker - -The `min` checker checks if the given ```int``` or ```float``` value is greather than the given minimum. If the value is below the minimum, the checker will return the `NOT_MIN` result. Here is an example: - -```golang -type User struct { - Age int `checkers:"min:21"` -} - -user := &User{ - Age: 45, -} - -mistakes, valid := checker.Check(user) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `min` checker function [`IsMin`](https://pkg.go.dev/github.com/cinar/checker#IsMin) to validate the user input. Here is an example: - -```golang -age := 45 - -result := checker.IsMin(age, 21) - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/minlength.md b/doc/checkers/minlength.md deleted file mode 100644 index 7a9a680..0000000 --- a/doc/checkers/minlength.md +++ /dev/null @@ -1,32 +0,0 @@ -# Min Length Checker - -The `min-length` checker checks if the length of the given value is greather than the given minimum length. If the length of the value is below the minimum length, the checker will return the `NOT_MIN_LENGTH` result. Here is an example: - -```golang -type User struct { - Password string `checkers:"min-length:4"` -} - -user := &User{ - Password: "1234", -} - -mistakes, valid := checker.Check(user) -if !valid { - // Send the mistakes back to the user -} -``` - -The checker can be applied to all types that are supported by the [reflect.Value.Len()](https://pkg.go.dev/reflect#Value.Len) function. - -If you do not want to validate user input stored in a struct, you can individually call the `min-length` checker function [`IsMinLength`](https://pkg.go.dev/github.com/cinar/checker#IsMinLength) to validate the user input. Here is an example: - -```golang -s := "1234" - -result := checker.IsMinLength(s, 4) - -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/regexp.md b/doc/checkers/regexp.md deleted file mode 100644 index f97a4cb..0000000 --- a/doc/checkers/regexp.md +++ /dev/null @@ -1,48 +0,0 @@ -# Regexp Checker - -The `regexp` checker checks if the given string matches the given regexp. If the given string does not match, the checker will return the `NOT_MATCH` result. Here is an example: - -```golang -type User struct { - Username string `checkers:"regexp:^[A-Za-z]+$"` -} - -user := &User{ - Username: "abcd", -} - -_, valid := checker.Check(user) -if !valid { - // Send the mistakes back to the user -} -``` - -The `regexp` checker can be used to build other checkers for other regexp patterns. In order to do that, you can use the [`MakeRegexpChecker`](https://pkg.go.dev/github.com/cinar/checker#MakeRegexpChecker) function. The function takes an expression and a result to return when the the given string is not a match. Here is an example: - -```golang -checkHex := checker.MakeRegexpChecker("^[A-Fa-f0-9]+$", "NOT_HEX") - -result := checkHex(reflect.ValueOf("f0f0f0"), reflect.ValueOf(nil)) -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` - -To register the new regexp checker to validate user input in struct, [`Register`](https://pkg.go.dev/github.com/cinar/checker#Register) function can be used. Here is an example: - -```golang -checker.Register("hex", checker.MakeRegexpMaker("^[A-Fa-f0-9]+$", "NOT_HEX")) - -type Theme struct { - Color string `checkers:hex` -} - -theme := &Theme{ - Color: "f0f0f0", -} - -_, valid := checker.Check(theme) -if !valid { - // Send the mistakes back to the user -} -``` diff --git a/doc/checkers/required.md b/doc/checkers/required.md deleted file mode 100644 index 5b4638b..0000000 --- a/doc/checkers/required.md +++ /dev/null @@ -1,27 +0,0 @@ -# Required Checker - -The `required` checker checks for the presence of required input. If the input is not present, the checker will return the `REQUIRED` result. Here is an example: - -```golang -type Person struct { - Name string `checkers:"required"` -} - -person := &Person{} - -mistakes, valid := checker.Check(person) -if !valid { - // Send the mistakes back to the user -} -``` - -If you do not want to validate user input stored in a struct, you can individually call the `required` checker function [`IsRequired`](https://pkg.go.dev/github.com/cinar/checker#IsRequired) to validate the user input. Here is an example: - -```golang -var name string - -result := checker.IsRequired(name) -if result != checker.ResultValid { - // Send the result back to the user -} -``` diff --git a/doc/checkers/same.md b/doc/checkers/same.md deleted file mode 100644 index f974dd1..0000000 --- a/doc/checkers/same.md +++ /dev/null @@ -1,20 +0,0 @@ -# Same Checker - -The `same` checker checks if the given value is equal to the value of the other field specified by its name. If they are not equal, the checker will return the `NOT_SAME` result. In the example below, the `same` checker ensures that the value in the `Confirm` field matches the value in the `Password` field. - -```golang -type User struct { - Password string - Confirm string `checkers:"same:Password"` -} - -user := &User{ - Password: "1234", - Confirm: "1234", -} - -mistakes, valid := checker.Check(user) -if !valid { - // Send the mistakes back to the user -} -``` \ No newline at end of file diff --git a/doc/checkers/url.md b/doc/checkers/url.md deleted file mode 100644 index c41b551..0000000 --- a/doc/checkers/url.md +++ /dev/null @@ -1,29 +0,0 @@ -# URL Checker - -The `url` checker checks if the given value is a valid URL. If the given value is not a valid URL, the checker will return the `NOT_URL` result. The checker uses [ParseRequestURI](https://pkg.go.dev/net/url#ParseRequestURI) function to parse the URL, and then checks if the schema or the host are both set. - -Here is an example: - -```golang -type Bookmark struct { - URL string `checkers:"url"` -} - -bookmark := &Bookmark{ - URL: "https://zdo.com", -} - -_, valid := checker.Check(bookmark) -if !valid { - // Send the mistakes back to the user -} -``` - -In your custom checkers, you can call the `url` checker function [`IsURL`](https://pkg.go.dev/github.com/cinar/checker#IsURL) to validate the user input. Here is an example: - -```golang -result := checker.IsURL("https://zdo.com") -if result != checker.ResultValid { - // Send the mistakes back to the user -} -``` diff --git a/doc/normalizers/html_escape.md b/doc/normalizers/html_escape.md deleted file mode 100644 index a8f9f9f..0000000 --- a/doc/normalizers/html_escape.md +++ /dev/null @@ -1,19 +0,0 @@ -# HTML Escape Normalizer - -The `html-escape` normalizer uses [html.EscapeString](https://pkg.go.dev/html#EscapeString) to escape special characters like "<" to become "<". It escapes only five such characters: <, >, &, ' and ". - -```golang -type Comment struct { - Body string `checkers:"html-escape"` -} - -comment := &Comment{ - Body: " \"Checker\" & 'Library' ", -} - -checker.Check(comment) - -// Outputs: -// <tag> "Checker" & 'Library' </tag> -fmt.Println(comment.Body) -``` diff --git a/doc/normalizers/html_unescape.md b/doc/normalizers/html_unescape.md deleted file mode 100644 index ae64303..0000000 --- a/doc/normalizers/html_unescape.md +++ /dev/null @@ -1,22 +0,0 @@ -# HTML Unescape Normalizer - -The `html-unescape` normalizer uses [html.UnescapeString](https://pkg.go.dev/html#UnescapeString) to unescape entities like "<" to become "<". It unescapes a larger range of entities than EscapeString escapes. For example, "á" unescapes to "á", as does "á" and "á". - -```golang -type Comment struct { - Body string `checkers:"html-unescape"` -} - -comment := &Comment{ - Body: "<tag> "Checker" & 'Library' </tag>", -} - -_, valid := checker.Check(comment) -if !valid { - t.Fail() -} - -// Outputs: -// \"Checker\" & 'Library' -fmt.Println(comment.Body) -``` diff --git a/doc/normalizers/lower.md b/doc/normalizers/lower.md deleted file mode 100644 index 0c8d041..0000000 --- a/doc/normalizers/lower.md +++ /dev/null @@ -1,17 +0,0 @@ -# Lower Case Normalizer - -The `lower` normalizer maps all Unicode letters in the given value to their lower case. It can be mixed with checkers and other normalizers when defining the validation steps for user data. - -```golang -type User struct { - Username string `checkers:"lower"` -} - -user := &User{ - Username: "chECker", -} - -checker.Check(user) - -fmt.Println(user.Username) // checker -``` diff --git a/doc/normalizers/title.md b/doc/normalizers/title.md deleted file mode 100644 index 7eb4710..0000000 --- a/doc/normalizers/title.md +++ /dev/null @@ -1,17 +0,0 @@ -# Title Case Normalizer - -The `title` normalizer maps the first letter of each word to their upper case. It can be mixed with checkers and other normalizers when defining the validation steps for user data. - -```golang -type Book struct { - Chapter string `checkers:"title"` -} - -book := &Book{ - Chapter: "THE checker", -} - -checker.Check(book) - -fmt.Println(book.Chapter) // The Checker -``` diff --git a/doc/normalizers/trim.md b/doc/normalizers/trim.md deleted file mode 100644 index e99f112..0000000 --- a/doc/normalizers/trim.md +++ /dev/null @@ -1,17 +0,0 @@ -# Trim Normalizer - -The `trim` normalizer removes the whitespaces at the beginning and at the end of the given value. It can be mixed with checkers and other normalizers when defining the validation steps for user data. - -```golang -type User struct { - Username string `checkers:"trim"` -} - -user := &User{ - Username: " normalizer ", -} - -checker.Check(user) - -fmt.Println(user.Username) // CHECKER -``` diff --git a/doc/normalizers/trim_left.md b/doc/normalizers/trim_left.md deleted file mode 100644 index 7c49338..0000000 --- a/doc/normalizers/trim_left.md +++ /dev/null @@ -1,17 +0,0 @@ -# Trim Left Normalizer - -The `trim-left` normalizer removes the whitespaces at the beginning of the given value. It can be mixed with checkers and other normalizers when defining the validation steps for user data. - -```golang -type User struct { - Username string `checkers:"trim-left"` -} - -user := &User{ - Username: " normalizer", -} - -checker.Check(user) - -fmt.Println(user.Username) // normalizer -``` diff --git a/doc/normalizers/trim_right.md b/doc/normalizers/trim_right.md deleted file mode 100644 index d2e0ca0..0000000 --- a/doc/normalizers/trim_right.md +++ /dev/null @@ -1,17 +0,0 @@ -# Trim Right Normalizer - -The `trim-right` normalizer removes the whitespaces at the end of the given value. It can be mixed with checkers and other normalizers when defining the validation steps for user data. - -```golang -type User struct { - Username string `checkers:"trim-right"` -} - -user := &User{ - Username: "normalizer ", -} - -checker.Check(user) - -fmt.Println(user.Username) // CHECKER -``` diff --git a/doc/normalizers/upper.md b/doc/normalizers/upper.md deleted file mode 100644 index c57ed01..0000000 --- a/doc/normalizers/upper.md +++ /dev/null @@ -1,15 +0,0 @@ -# Upper Case Normalizer - -The `upper` normalizer maps all Unicode letters in the given value to their upper case. It can be mixed with checkers and other normalizers when defining the validation steps for user data. - -```golang -type User struct { - Username string `checkers:"upper"` -} - -user := &User{ - Username: "chECker", -} - -fmt.Println(user.Username) // CHECKER -``` diff --git a/doc/normalizers/url_escape.md b/doc/normalizers/url_escape.md deleted file mode 100644 index 0d229d5..0000000 --- a/doc/normalizers/url_escape.md +++ /dev/null @@ -1,22 +0,0 @@ -# URL Escape Normalizer - -The `url-escape` normalizer uses [net.url.QueryEscape](https://pkg.go.dev/net/url#QueryEscape) to escape the string so it can be safely placed inside a URL query. - -```golang -type Request struct { - Query string `checkers:"url-escape"` -} - -request := &Request{ - Query: "param1/param2 = 1 + 2 & 3 + 4", -} - -_, valid := checker.Check(request) -if !valid { - t.Fail() -} - -// Outputs: -// param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4 -fmt.Println(request.Query) -``` diff --git a/doc/normalizers/url_unescape.md b/doc/normalizers/url_unescape.md deleted file mode 100644 index 8982a49..0000000 --- a/doc/normalizers/url_unescape.md +++ /dev/null @@ -1,26 +0,0 @@ -# URL Unescape Normalizer - -The `url-unescape` normalizer uses [net.url.QueryUnescape](https://pkg.go.dev/net/url#QueryUnescape) to converte each 3-byte encoded substring of the form "%AB" into the hex-decoded byte 0xAB. - -```golang -type Request struct { - Query string `checkers:"url-unescape"` -} - -request := &Request{ - Query: "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4", -} - -_, valid := checker.Check(request) -if !valid { - t.Fail() -} - -if request.Query != "param1/param2 = 1 + 2 & 3 + 4" { - t.Fail() -} - -// Outputs: -// param1/param2 = 1 + 2 & 3 + 4 -fmt.Println(comment.Body) -``` diff --git a/doc/optimization.md b/doc/optimization.md deleted file mode 100644 index d0e6dbf..0000000 --- a/doc/optimization.md +++ /dev/null @@ -1,15 +0,0 @@ -# Optimization Guide - -The ```BenchmarkCheck``` function helps profiling the library. Generate a ```profile.out``` my issuing the command below. - -``` -go test -bench=. -benchmem -cpuprofile profile.out -``` - -View the analysis through the ```pprof``` by issuing the command below. - -``` -go tool pprof -http localhost:9000 profile.out -``` - -Use the web interface for further optimization work. diff --git a/email.go b/email.go index b0ffc8f..2c456c4 100644 --- a/email.go +++ b/email.go @@ -12,8 +12,8 @@ import ( "strings" ) -// CheckerEmail is the name of the checker. -const CheckerEmail = "email" +// tagEmail is the tag of the checker. +const tagEmail = "email" // ErrNotEmail indicates that the given string is not a valid email. var ErrNotEmail = errors.New("please enter a valid email address") diff --git a/fqdn.go b/fqdn.go index e227260..c6c816d 100644 --- a/fqdn.go +++ b/fqdn.go @@ -12,8 +12,8 @@ import ( "strings" ) -// CheckerFqdn is the name of the checker. -const CheckerFqdn = "fqdn" +// tagFqdn is the tag of the checker. +const tagFqdn = "fqdn" // ErrNotFqdn indicates that the given string is not a valid FQDN. var ErrNotFqdn = errors.New("please enter a valid domain name") diff --git a/html_escape.go b/html_escape.go index 012448d..0a08372 100644 --- a/html_escape.go +++ b/html_escape.go @@ -10,8 +10,8 @@ import ( "reflect" ) -// NormalizerHTMLEscape is the name of the normalizer. -const NormalizerHTMLEscape = "html-escape" +// tagHTMLEscape is the tag of the normalizer. +const tagHTMLEscape = "html-escape" // makeHTMLEscape makes a normalizer function for the HTML escape normalizer. func makeHTMLEscape(_ string) CheckFunc { diff --git a/html_unescape.go b/html_unescape.go index 3c8a93f..df78575 100644 --- a/html_unescape.go +++ b/html_unescape.go @@ -10,8 +10,8 @@ import ( "reflect" ) -// NormalizerHTMLUnescape is the name of the normalizer. -const NormalizerHTMLUnescape = "html-unescape" +// tagHTMLUnescape is the tag of the normalizer. +const tagHTMLUnescape = "html-unescape" // makeHTMLUnescape makes a normalizer function for the HTML unscape normalizer. func makeHTMLUnescape(_ string) CheckFunc { diff --git a/ip.go b/ip.go index 9fa240b..023dc6c 100644 --- a/ip.go +++ b/ip.go @@ -11,8 +11,8 @@ import ( "reflect" ) -// CheckerIP is the name of the checker. -const CheckerIP = "ip" +// tagIP is the tag of the checker. +const tagIP = "ip" // ErrNotIP indicates that the given value is not an IP address. var ErrNotIP = errors.New("please enter a valid IP address") diff --git a/ipv4.go b/ipv4.go index 93d28ca..4168626 100644 --- a/ipv4.go +++ b/ipv4.go @@ -11,8 +11,8 @@ import ( "reflect" ) -// CheckerIPV4 is the name of the checker. -const CheckerIPV4 = "ipv4" +// tagIPV4 is the tag of the checker. +const tagIPV4 = "ipv4" // ErrNotIPV4 indicates that the given value is not an IPv4 address. var ErrNotIPV4 = errors.New("please enter a valid IPv4 address") diff --git a/ipv6.go b/ipv6.go index 8fe9a07..49c7533 100644 --- a/ipv6.go +++ b/ipv6.go @@ -11,8 +11,8 @@ import ( "reflect" ) -// CheckerIPV6 is the name of the checker. -const CheckerIPV6 = "ipv6" +// tagIPV6 is the tag of the checker. +const tagIPV6 = "ipv6" // ErrNotIPV6 indicates that the given value is not an IPv6 address. var ErrNotIPV6 = errors.New("please enter a valid IPv6 address") diff --git a/isbn.go b/isbn.go index 67fbb10..11ebf28 100644 --- a/isbn.go +++ b/isbn.go @@ -17,8 +17,8 @@ import ( // How to Verify an ISBN // https://www.instructables.com/How-to-verify-a-ISBN/ -// CheckerISBN is the name of the checker. -const CheckerISBN = "isbn" +// tagISBN is the tag of the checker. +const tagISBN = "isbn" // ErrNotISBN indicates that the given value is not a valid ISBN. var ErrNotISBN = errors.New("please enter a valid ISBN number") diff --git a/lower.go b/lower.go index 3843c33..585792d 100644 --- a/lower.go +++ b/lower.go @@ -10,8 +10,8 @@ import ( "strings" ) -// NormalizerLower is the name of the normalizer. -const NormalizerLower = "lower" +// tagLower is the tag of the normalizer. +const tagLower = "lower" // makeLower makes a normalizer function for the lower normalizer. func makeLower(_ string) CheckFunc { diff --git a/luhn.go b/luhn.go index 6e222d3..d1543ef 100644 --- a/luhn.go +++ b/luhn.go @@ -10,8 +10,8 @@ import ( "reflect" ) -// CheckerLuhn is the name of the checker. -const CheckerLuhn = "luhn" +// tagLuhn is the tag of the checker. +const tagLuhn = "luhn" // ErrNotLuhn indicates that the given number is not valid based on the Luhn algorithm. var ErrNotLuhn = errors.New("please enter a valid LUHN") diff --git a/mac.go b/mac.go index 509ecc3..58f6284 100644 --- a/mac.go +++ b/mac.go @@ -11,8 +11,8 @@ import ( "reflect" ) -// CheckerMac is the name of the checker. -const CheckerMac = "mac" +// tagMac is the tag of the checker. +const tagMac = "mac" // ErrNotMac indicates that the given value is not an MAC address. var ErrNotMac = errors.New("please enter a valid MAC address") diff --git a/max.go b/max.go index ed8c2d3..f26246d 100644 --- a/max.go +++ b/max.go @@ -11,8 +11,8 @@ import ( "strconv" ) -// CheckerMax is the name of the checker. -const CheckerMax = "max" +// tagMax is the tag of the checker. +const tagMax = "max" // IsMax checks if the given value is below than the given maximum. func IsMax(value interface{}, max float64) error { diff --git a/maxlength.go b/maxlength.go index 63a0f0a..3b12f35 100644 --- a/maxlength.go +++ b/maxlength.go @@ -11,8 +11,8 @@ import ( "strconv" ) -// CheckerMaxLength is the name of the checker. -const CheckerMaxLength = "max-length" +// tagMaxLength is the tag of the checker. +const tagMaxLength = "max-length" // IsMaxLength checks if the length of the given value is less than the given maximum length. func IsMaxLength(value interface{}, maxLength int) error { diff --git a/min.go b/min.go index 1807b1f..9531db1 100644 --- a/min.go +++ b/min.go @@ -11,8 +11,8 @@ import ( "strconv" ) -// CheckerMin is the name of the checker. -const CheckerMin = "min" +// tagMin is the tag of the checker. +const tagMin = "min" // IsMin checks if the given value is above than the given minimum. func IsMin(value interface{}, min float64) error { diff --git a/minlenght.go b/minlenght.go index b8bf0e1..f1cefac 100644 --- a/minlenght.go +++ b/minlenght.go @@ -11,8 +11,8 @@ import ( "strconv" ) -// CheckerMinLength is the name of the checker. -const CheckerMinLength = "min-length" +// tagMinLength is the tag of the checker. +const tagMinLength = "min-length" // IsMinLength checks if the length of the given value is greather than the given minimum length. func IsMinLength(value interface{}, minLength int) error { diff --git a/regexp.go b/regexp.go index b48ff18..1e95a9b 100644 --- a/regexp.go +++ b/regexp.go @@ -11,8 +11,8 @@ import ( "regexp" ) -// CheckerRegexp is the name of the checker. -const CheckerRegexp = "regexp" +// tagRegexp is the tag of the checker. +const tagRegexp = "regexp" // ErrNotMatch indicates that the given string does not match the regexp pattern. var ErrNotMatch = errors.New("please enter a valid input") diff --git a/required.go b/required.go index 16c092e..01a35f8 100644 --- a/required.go +++ b/required.go @@ -10,8 +10,8 @@ import ( "reflect" ) -// CheckerRequired is the name of the checker. -const CheckerRequired = "required" +// tagRequired is the tag of the checker. +const tagRequired = "required" // ErrRequired indicates that the required value is missing. var ErrRequired = errors.New("is required") diff --git a/same.go b/same.go index 10b67d2..f4a3e06 100644 --- a/same.go +++ b/same.go @@ -10,8 +10,8 @@ import ( "reflect" ) -// CheckerSame is the name of the checker. -const CheckerSame = "same" +// tagSame is the tag of the checker. +const tagSame = "same" // ErrNotSame indicates that the given two values are not equal to each other. var ErrNotSame = errors.New("does not match the other") diff --git a/taskfile.yml b/taskfile.yml index eb18359..c5d8020 100644 --- a/taskfile.yml +++ b/taskfile.yml @@ -7,6 +7,7 @@ tasks: - task: fmt - task: lint - task: test + - task: docs action: deps: [lint, test] @@ -26,3 +27,7 @@ tasks: cmds: - go test -cover -coverprofile=coverage.out ./... + docs: + cmds: + - go run github.com/princjef/gomarkdoc/cmd/gomarkdoc@v1.1.0 -e ./... + diff --git a/title.go b/title.go index 90d2b6b..604ca2c 100644 --- a/title.go +++ b/title.go @@ -11,8 +11,8 @@ import ( "unicode" ) -// NormalizerTitle is the name of the normalizer. -const NormalizerTitle = "title" +// tagTitle is the tag of the normalizer. +const tagTitle = "title" // makeTitle makes a normalizer function for the title normalizer. func makeTitle(_ string) CheckFunc { diff --git a/trim.go b/trim.go index 88cbc82..749cb22 100644 --- a/trim.go +++ b/trim.go @@ -10,8 +10,8 @@ import ( "strings" ) -// NormalizerTrim is the name of the normalizer. -const NormalizerTrim = "trim" +// tagTrim is the tag of the normalizer. +const tagTrim = "trim" // makeTrim makes a normalizer function for the trim normalizer. func makeTrim(_ string) CheckFunc { diff --git a/trim_left.go b/trim_left.go index e3e53ba..f17c699 100644 --- a/trim_left.go +++ b/trim_left.go @@ -10,8 +10,8 @@ import ( "strings" ) -// NormalizerTrimLeft is the name of the normalizer. -const NormalizerTrimLeft = "trim-left" +// tagTrimLeft is the tag of the normalizer. +const tagTrimLeft = "trim-left" // makeTrimLeft makes a normalizer function for the trim left normalizer. func makeTrimLeft(_ string) CheckFunc { diff --git a/trim_right.go b/trim_right.go index 82d4115..e2edb9e 100644 --- a/trim_right.go +++ b/trim_right.go @@ -10,8 +10,8 @@ import ( "strings" ) -// NormalizerTrimRight is the name of the normalizer. -const NormalizerTrimRight = "trim-right" +// tagTrimRight is the tag of the normalizer. +const tagTrimRight = "trim-right" // makeTrimRight makes a normalizer function for the trim right normalizer. func makeTrimRight(_ string) CheckFunc { diff --git a/upper.go b/upper.go index a0a101e..7bef24b 100644 --- a/upper.go +++ b/upper.go @@ -10,8 +10,8 @@ import ( "strings" ) -// NormalizerUpper is the name of the normalizer. -const NormalizerUpper = "upper" +// tagUpper is the tag of the normalizer. +const tagUpper = "upper" // makeUpper makes a normalizer function for the upper normalizer. func makeUpper(_ string) CheckFunc { diff --git a/url.go b/url.go index b43efb3..a27b85a 100644 --- a/url.go +++ b/url.go @@ -11,8 +11,8 @@ import ( "reflect" ) -// CheckerURL is the name of the checker. -const CheckerURL = "url" +// tagURL is the tag of the checker. +const tagURL = "url" // ErrNotURL indicates that the given value is not a valid URL. var ErrNotURL = errors.New("please enter a valid URL") diff --git a/url_escape.go b/url_escape.go index f78abe2..d7ab2d7 100644 --- a/url_escape.go +++ b/url_escape.go @@ -10,8 +10,8 @@ import ( "reflect" ) -// NormalizerURLEscape is the name of the normalizer. -const NormalizerURLEscape = "url-escape" +// tagURLEscape is the tag of the normalizer. +const tagURLEscape = "url-escape" // makeURLEscape makes a normalizer function for the URL escape normalizer. func makeURLEscape(_ string) CheckFunc { diff --git a/url_unescape.go b/url_unescape.go index d10e602..dc6a807 100644 --- a/url_unescape.go +++ b/url_unescape.go @@ -10,8 +10,8 @@ import ( "reflect" ) -// NormalizerURLUnescape is the name of the normalizer. -const NormalizerURLUnescape = "url-unescape" +// tagURLUnescape is the tag of the normalizer. +const tagURLUnescape = "url-unescape" // makeURLUnescape makes a normalizer function for the URL unscape normalizer. func makeURLUnescape(_ string) CheckFunc { From b88aed2335ef13c754c0b86be9bc38919e07eb01 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Tue, 29 Oct 2024 21:13:45 -0700 Subject: [PATCH 06/41] Fixing the CI badge. (#127) # Describe Request Fixing the CI badge. # Change Type Documentation fix. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2e4aeb..78360bc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![GoDoc](https://godoc.org/github.com/cinar/checker?status.svg)](https://godoc.org/github.com/cinar/checker) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Go Report Card](https://goreportcard.com/badge/github.com/cinar/checker)](https://goreportcard.com/report/github.com/cinar/checker) -![Go CI](https://github.com/cinar/checker/actions/workflows/go.yml/badge.svg) +![Go CI](https://github.com/cinar/checker/actions/workflows/ci.yml/badge.svg) [![codecov](https://codecov.io/gh/cinar/checker/branch/main/graph/badge.svg?token=VO9BYBHJHE)](https://codecov.io/gh/cinar/checker) # Checker From 80ba6aa927e9cbe44aba269e852fa0de9b218239 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Tue, 29 Oct 2024 21:18:56 -0700 Subject: [PATCH 07/41] Change mistakes to errors. (#128) # Describe Request Change mistakes to errors. # Change Type Code maintenance. --- CODE_OF_CONDUCT.md | 2 +- README.md | 58 ++++++++++++++++++++++---------------------- alphanumeric_test.go | 2 +- ascii_test.go | 2 +- checker.go | 6 ++--- checker_test.go | 18 +++++++------- cidr_test.go | 2 +- credit_card_test.go | 16 ++++++------ digits_test.go | 2 +- email_test.go | 2 +- fqdn_test.go | 2 +- ip_test.go | 2 +- ipv4_test.go | 2 +- ipv6_test.go | 2 +- isbn_test.go | 6 ++--- luhn_test.go | 2 +- mac_test.go | 2 +- max_test.go | 2 +- maxlength_test.go | 2 +- min_test.go | 2 +- minlength_test.go | 2 +- url_test.go | 2 +- 22 files changed, 69 insertions(+), 69 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 9f6c995..c99a682 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -20,7 +20,7 @@ community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, +* Accepting responsibility and apologizing to those affected by our errors, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community diff --git a/README.md b/README.md index 78360bc..8fe9c09 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,9 @@ type Person struct { person := &Person{} -mistakes, valid := checker.Check(person) +errors, valid := checker.Check(person) if !valid { - // Send the mistakes back to the user + // Send the errors back to the user } ``` @@ -333,7 +333,7 @@ import ( func main() { err := checker.IsASCII("Checker") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -365,7 +365,7 @@ import ( func main() { err := checker.IsAlphanumeric("ABcd1234") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -398,7 +398,7 @@ func main() { err := checker.IsAmexCreditCard("378282246310005") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -431,7 +431,7 @@ func main() { err := checker.IsAnyCreditCard("6011111111111117") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -463,7 +463,7 @@ import ( func main() { err := checker.IsCidr("2001:db8::/32") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -495,7 +495,7 @@ import ( func main() { err := checker.IsDigits("1234") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -528,7 +528,7 @@ func main() { err := checker.IsDinersCreditCard("36227206271667") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -561,7 +561,7 @@ func main() { err := checker.IsDiscoverCreditCard("6011111111111117") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -593,7 +593,7 @@ import ( func main() { err := checker.IsEmail("user@zdo.com") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -625,7 +625,7 @@ import ( func main() { err := checker.IsFqdn("zdo.com") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -657,7 +657,7 @@ import ( func main() { err := checker.IsIP("2001:db8::68") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -689,7 +689,7 @@ import ( func main() { err := checker.IsIPV4("192.168.1.1") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -722,7 +722,7 @@ func main() { err := checker.IsIPV6("2001:db8::68") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -754,7 +754,7 @@ import ( func main() { err := checker.IsISBN("1430248270") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -786,7 +786,7 @@ import ( func main() { err := checker.IsISBN10("1430248270") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -818,7 +818,7 @@ import ( func main() { err := checker.IsISBN13("9781430248279") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -851,7 +851,7 @@ func main() { err := checker.IsJcbCreditCard("3530111333300000") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -883,7 +883,7 @@ import ( func main() { err := checker.IsLuhn("4012888888881881") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -915,7 +915,7 @@ import ( func main() { err := checker.IsMac("00:00:5e:00:53:01") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -948,7 +948,7 @@ func main() { err := checker.IsMasterCardCreditCard("5555555555554444") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -982,7 +982,7 @@ func main() { err := checker.IsMax(quantity, 10) if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -1016,7 +1016,7 @@ func main() { err := checker.IsMaxLength(s, 4) if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -1050,7 +1050,7 @@ func main() { err := checker.IsMin(age, 21) if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -1084,7 +1084,7 @@ func main() { err := checker.IsMinLength(s, 4) if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -1150,7 +1150,7 @@ import ( func main() { err := checker.IsURL("https://zdo.com") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -1183,7 +1183,7 @@ func main() { err := checker.IsUnionPayCreditCard("6200000000000005") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` @@ -1216,7 +1216,7 @@ func main() { err := checker.IsVisaCreditCard("4111111111111111") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } ``` diff --git a/alphanumeric_test.go b/alphanumeric_test.go index 4fe9661..e94768c 100644 --- a/alphanumeric_test.go +++ b/alphanumeric_test.go @@ -14,7 +14,7 @@ import ( func ExampleIsAlphanumeric() { err := checker.IsAlphanumeric("ABcd1234") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/ascii_test.go b/ascii_test.go index 8685362..238f60b 100644 --- a/ascii_test.go +++ b/ascii_test.go @@ -14,7 +14,7 @@ import ( func ExampleIsASCII() { err := checker.IsASCII("Checker") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/checker.go b/checker.go index 595649c..b5ef498 100644 --- a/checker.go +++ b/checker.go @@ -75,7 +75,7 @@ func Check(s interface{}) (Errors, bool) { panic("expecting struct") } - errors := Errors{} + errs := Errors{} jobs := []checkerJob{ { @@ -118,14 +118,14 @@ func Check(s interface{}) (Errors, bool) { } else { for _, checker := range initCheckers(job.Config) { if err := checker(job.Value, job.Parent); err != nil { - errors[job.Name] = err + errs[job.Name] = err break } } } } - return errors, len(errors) == 0 + return errs, len(errs) == 0 } // initCheckers initializes the checkers provided in the config. diff --git a/checker_test.go b/checker_test.go index 723a9e6..540bca5 100644 --- a/checker_test.go +++ b/checker_test.go @@ -60,16 +60,16 @@ func TestCheckInvalid(t *testing.T) { person := &Person{} - mistakes, valid := Check(person) + errors, valid := Check(person) if valid { t.Fail() } - if len(mistakes) != 1 { + if len(errors) != 1 { t.Fail() } - if mistakes["Name"] != ErrRequired { + if errors["Name"] != ErrRequired { t.Fail() } } @@ -83,12 +83,12 @@ func TestCheckValid(t *testing.T) { Name: "Onur", } - mistakes, valid := Check(person) + errors, valid := Check(person) if !valid { t.Fail() } - if len(mistakes) != 0 { + if len(errors) != 0 { t.Fail() } } @@ -112,20 +112,20 @@ func TestCheckNestedStruct(t *testing.T) { person := &Person{} - mistakes, valid := Check(person) + errors, valid := Check(person) if valid { t.Fail() } - if len(mistakes) != 2 { + if len(errors) != 2 { t.Fail() } - if mistakes["Name"] != ErrRequired { + if errors["Name"] != ErrRequired { t.Fail() } - if mistakes["Home.Street"] != ErrRequired { + if errors["Home.Street"] != ErrRequired { t.Fail() } } diff --git a/cidr_test.go b/cidr_test.go index 2dd1e38..2d7852d 100644 --- a/cidr_test.go +++ b/cidr_test.go @@ -14,7 +14,7 @@ import ( func ExampleIsCidr() { err := checker.IsCidr("2001:db8::/32") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/credit_card_test.go b/credit_card_test.go index f1905e8..fc81997 100644 --- a/credit_card_test.go +++ b/credit_card_test.go @@ -38,7 +38,7 @@ func ExampleIsAnyCreditCard() { err := checker.IsAnyCreditCard("6011111111111117") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } @@ -64,7 +64,7 @@ func ExampleIsAmexCreditCard() { err := checker.IsAmexCreditCard("378282246310005") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } @@ -90,7 +90,7 @@ func ExampleIsDinersCreditCard() { err := checker.IsDinersCreditCard("36227206271667") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } func TestIsDinersCreditCardValid(t *testing.T) { @@ -115,7 +115,7 @@ func ExampleIsDiscoverCreditCard() { err := checker.IsDiscoverCreditCard("6011111111111117") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } func TestIsDiscoverCreditCardValid(t *testing.T) { @@ -140,7 +140,7 @@ func ExampleIsJcbCreditCard() { err := checker.IsJcbCreditCard("3530111333300000") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } @@ -166,7 +166,7 @@ func ExampleIsMasterCardCreditCard() { err := checker.IsMasterCardCreditCard("5555555555554444") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } @@ -192,7 +192,7 @@ func ExampleIsUnionPayCreditCard() { err := checker.IsUnionPayCreditCard("6200000000000005") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } @@ -218,7 +218,7 @@ func ExampleIsVisaCreditCard() { err := checker.IsVisaCreditCard("4111111111111111") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } func TestIsVisaCreditCardValid(t *testing.T) { diff --git a/digits_test.go b/digits_test.go index 86830c1..4246713 100644 --- a/digits_test.go +++ b/digits_test.go @@ -14,7 +14,7 @@ import ( func ExampleIsDigits() { err := checker.IsDigits("1234") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/email_test.go b/email_test.go index 862e2ef..c200ed0 100644 --- a/email_test.go +++ b/email_test.go @@ -14,7 +14,7 @@ import ( func ExampleIsEmail() { err := checker.IsEmail("user@zdo.com") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/fqdn_test.go b/fqdn_test.go index 82edbfd..0a8d76e 100644 --- a/fqdn_test.go +++ b/fqdn_test.go @@ -14,7 +14,7 @@ import ( func ExampleIsFqdn() { err := checker.IsFqdn("zdo.com") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/ip_test.go b/ip_test.go index 4995d8f..a9dd74d 100644 --- a/ip_test.go +++ b/ip_test.go @@ -14,7 +14,7 @@ import ( func ExampleIsIP() { err := checker.IsIP("2001:db8::68") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/ipv4_test.go b/ipv4_test.go index fba513d..1e2509c 100644 --- a/ipv4_test.go +++ b/ipv4_test.go @@ -14,7 +14,7 @@ import ( func ExampleIsIPV4() { err := checker.IsIPV4("192.168.1.1") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/ipv6_test.go b/ipv6_test.go index c8d8d4e..97be750 100644 --- a/ipv6_test.go +++ b/ipv6_test.go @@ -15,7 +15,7 @@ func ExampleIsIPV6() { err := checker.IsIPV6("2001:db8::68") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/isbn_test.go b/isbn_test.go index f173981..8d12e60 100644 --- a/isbn_test.go +++ b/isbn_test.go @@ -15,7 +15,7 @@ import ( func ExampleIsISBN10() { err := checker.IsISBN10("1430248270") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } @@ -57,7 +57,7 @@ func TestIsISBN10InvalidCheck(t *testing.T) { func ExampleIsISBN13() { err := checker.IsISBN13("9781430248279") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } @@ -92,7 +92,7 @@ func TestIsISBN13InvalidCheck(t *testing.T) { func ExampleIsISBN() { err := checker.IsISBN("1430248270") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/luhn_test.go b/luhn_test.go index e193621..3ddb5e1 100644 --- a/luhn_test.go +++ b/luhn_test.go @@ -14,7 +14,7 @@ import ( func ExampleIsLuhn() { err := checker.IsLuhn("4012888888881881") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/mac_test.go b/mac_test.go index e966765..7e8ba63 100644 --- a/mac_test.go +++ b/mac_test.go @@ -14,7 +14,7 @@ import ( func ExampleIsMac() { err := checker.IsMac("00:00:5e:00:53:01") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/max_test.go b/max_test.go index c6f3adf..6f5ce65 100644 --- a/max_test.go +++ b/max_test.go @@ -16,7 +16,7 @@ func ExampleIsMax() { err := checker.IsMax(quantity, 10) if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/maxlength_test.go b/maxlength_test.go index e2e6d3b..cb9b395 100644 --- a/maxlength_test.go +++ b/maxlength_test.go @@ -16,7 +16,7 @@ func ExampleIsMaxLength() { err := checker.IsMaxLength(s, 4) if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/min_test.go b/min_test.go index 40efbe7..dc12888 100644 --- a/min_test.go +++ b/min_test.go @@ -16,7 +16,7 @@ func ExampleIsMin() { err := checker.IsMin(age, 21) if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/minlength_test.go b/minlength_test.go index 045e958..bac0898 100644 --- a/minlength_test.go +++ b/minlength_test.go @@ -16,7 +16,7 @@ func ExampleIsMinLength() { err := checker.IsMinLength(s, 4) if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } diff --git a/url_test.go b/url_test.go index 883eb0a..ed5334e 100644 --- a/url_test.go +++ b/url_test.go @@ -14,7 +14,7 @@ import ( func ExampleIsURL() { err := checker.IsURL("https://zdo.com") if err != nil { - // Send the mistakes back to the user + // Send the errors back to the user } } From 33c6896213f641c182f86d2422ef05e768481f9a Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Mon, 2 Dec 2024 18:53:28 -0800 Subject: [PATCH 08/41] V2 (#129) # Describe Request Version 2 of the library. # Change Type New feature. --- v2/alphanumeric.go | 43 +++++++++ v2/alphanumeric_test.go | 76 +++++++++++++++ v2/check_error.go | 24 +++++ v2/check_error_test.go | 22 +++++ v2/check_func.go | 11 +++ v2/checker.go | 153 +++++++++++++++++++++++++++++++ v2/checker_test.go | 198 ++++++++++++++++++++++++++++++++++++++++ v2/go.mod | 3 + v2/helper_test.go | 17 ++++ v2/maker.go | 44 +++++++++ v2/maker_test.go | 26 ++++++ v2/max_len.go | 51 +++++++++++ v2/max_len_test.go | 93 +++++++++++++++++++ v2/min_len.go | 51 +++++++++++ v2/min_len_test.go | 93 +++++++++++++++++++ v2/required.go | 42 +++++++++ v2/required_test.go | 41 +++++++++ v2/trim_space.go | 32 +++++++ v2/trim_space_test.go | 47 ++++++++++ 19 files changed, 1067 insertions(+) create mode 100644 v2/alphanumeric.go create mode 100644 v2/alphanumeric_test.go create mode 100644 v2/check_error.go create mode 100644 v2/check_error_test.go create mode 100644 v2/check_func.go create mode 100644 v2/checker.go create mode 100644 v2/checker_test.go create mode 100644 v2/go.mod create mode 100644 v2/helper_test.go create mode 100644 v2/maker.go create mode 100644 v2/maker_test.go create mode 100644 v2/max_len.go create mode 100644 v2/max_len_test.go create mode 100644 v2/min_len.go create mode 100644 v2/min_len_test.go create mode 100644 v2/required.go create mode 100644 v2/required_test.go create mode 100644 v2/trim_space.go create mode 100644 v2/trim_space_test.go diff --git a/v2/alphanumeric.go b/v2/alphanumeric.go new file mode 100644 index 0000000..35ecaec --- /dev/null +++ b/v2/alphanumeric.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "unicode" +) + +const ( + // nameAlphanumeric is the name of the alphanumeric check. + nameAlphanumeric = "alphanumeric" +) + +var ( + // ErrNotAlphanumeric indicates that the given string contains non-alphanumeric characters. + ErrNotAlphanumeric = NewCheckError("ALPHANUMERIC") +) + +// IsAlphanumeric checks if the given string consists of only alphanumeric characters. +func IsAlphanumeric(value string) (string, error) { + for _, c := range value { + if !unicode.IsDigit(c) && !unicode.IsLetter(c) { + return value, ErrNotAlphanumeric + } + } + + return value, nil +} + +// checkAlphanumeric checks if the given string consists of only alphanumeric characters. +func isAlphanumeric(value reflect.Value) (reflect.Value, error) { + _, err := IsAlphanumeric(value.Interface().(string)) + return value, err +} + +// makeAlphanumeric makes a checker function for the alphanumeric checker. +func makeAlphanumeric(_ string) CheckFunc[reflect.Value] { + return isAlphanumeric +} diff --git a/v2/alphanumeric_test.go b/v2/alphanumeric_test.go new file mode 100644 index 0000000..5d339b3 --- /dev/null +++ b/v2/alphanumeric_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsAlphanumeric() { + _, err := v2.IsAlphanumeric("ABcd1234") + if err != nil { + fmt.Println(err) + } +} + +func TestIsAlphanumericInvalid(t *testing.T) { + _, err := v2.IsAlphanumeric("-/") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsAlphanumericValid(t *testing.T) { + _, err := v2.IsAlphanumeric("ABcd1234") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckAlphanumericNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Name int `checkers:"alphanumeric"` + } + + person := &Person{} + + v2.CheckStruct(person) +} + +func TestCheckAlphanumericInvalid(t *testing.T) { + type Person struct { + Name string `checkers:"alphanumeric"` + } + + person := &Person{ + Name: "name-/", + } + + _, ok := v2.CheckStruct(person) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckAlphanumericValid(t *testing.T) { + type Person struct { + Name string `checkers:"alphanumeric"` + } + + person := &Person{ + Name: "ABcd1234", + } + + errs, ok := v2.CheckStruct(person) + if !ok { + t.Fatal(errs) + } +} diff --git a/v2/check_error.go b/v2/check_error.go new file mode 100644 index 0000000..c523177 --- /dev/null +++ b/v2/check_error.go @@ -0,0 +1,24 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +// CheckError defines the check error. +type CheckError struct { + // code is the error code. + code string +} + +// NewCheckError creates a new check error with the specified error code. +func NewCheckError(code string) *CheckError { + return &CheckError{ + code: code, + } +} + +// Error returns the error message for the check. +func (c *CheckError) Error() string { + return c.code +} diff --git a/v2/check_error_test.go b/v2/check_error_test.go new file mode 100644 index 0000000..672a5e1 --- /dev/null +++ b/v2/check_error_test.go @@ -0,0 +1,22 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestCheckErrorError(t *testing.T) { + code := "CODE" + + err := v2.NewCheckError(code) + + if err.Error() != code { + t.Fatalf("actaul %s expected %s", err.Error(), code) + } +} diff --git a/v2/check_func.go b/v2/check_func.go new file mode 100644 index 0000000..fca1bf0 --- /dev/null +++ b/v2/check_func.go @@ -0,0 +1,11 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +// CheckFunc is a function that takes a value of type T and performs +// a check on it. It returns the resulting value and any error that +// occurred during the check. +type CheckFunc[T any] func(value T) (T, error) diff --git a/v2/checker.go b/v2/checker.go new file mode 100644 index 0000000..e651348 --- /dev/null +++ b/v2/checker.go @@ -0,0 +1,153 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "fmt" + "reflect" + "strings" +) + +const ( + // checkerTag is the name of the field tag used for checker. + checkerTag = "checkers" + + // sliceConfigPrefix is the prefix used to distinguish slice-level checks from item-level checks. + sliceConfigPrefix = "@" +) + +// checkStructJob defines a check strcut job. +type checkStructJob struct { + Name string + Value reflect.Value + Config string +} + +// Check applies the given check functions to a value sequentially. +// It returns the final value and the first encountered error, if any. +func Check[T any](value T, checks ...CheckFunc[T]) (T, error) { + var err error + + for _, check := range checks { + value, err = check(value) + if err != nil { + break + } + } + + return value, err +} + +// CheckWithConfig applies the check functions specified by the config string to the given value. +// It returns the modified value and the first encountered error, if any. +func CheckWithConfig[T any](value T, config string) (T, error) { + newValue, err := ReflectCheckWithConfig(reflect.Indirect(reflect.ValueOf(value)), config) + return newValue.Interface().(T), err +} + +// ReflectCheckWithConfig applies the check functions specified by the config string +// to the given reflect.Value. It returns the modified reflect.Value and the first +// encountered error, if any. +func ReflectCheckWithConfig(value reflect.Value, config string) (reflect.Value, error) { + return Check(value, makeChecks(config)...) +} + +// CheckStruct checks the given struct based on the validation rules specified in the +// "checker" tag of each struct field. It returns a map of field names to their +// corresponding errors, and a boolean indicating if all checks passed. +func CheckStruct(st any) (map[string]error, bool) { + errs := make(map[string]error) + + jobs := []*checkStructJob{ + { + Name: "", + Value: reflect.Indirect(reflect.ValueOf(st)), + }, + } + + for len(jobs) > 0 { + job := jobs[0] + jobs = jobs[1:] + + switch job.Value.Kind() { + case reflect.Struct: + for i := 0; i < job.Value.NumField(); i++ { + field := job.Value.Type().Field(i) + + name := fieldName(job.Name, field) + value := reflect.Indirect(job.Value.FieldByIndex(field.Index)) + + jobs = append(jobs, &checkStructJob{ + Name: name, + Value: value, + Config: field.Tag.Get(checkerTag), + }) + } + + case reflect.Slice: + sliceConfig, itemConfig := splitSliceConfig(job.Config) + job.Config = sliceConfig + + for i := 0; i < job.Value.Len(); i++ { + name := fmt.Sprintf("%s[%d]", job.Name, i) + value := reflect.Indirect(job.Value.Index(i)) + + jobs = append(jobs, &checkStructJob{ + Name: name, + Value: value, + Config: itemConfig, + }) + } + } + + if job.Config != "" { + newValue, err := ReflectCheckWithConfig(job.Value, job.Config) + if err != nil { + errs[job.Name] = err + } + + job.Value.Set(newValue) + } + } + + return errs, len(errs) == 0 +} + +// fieldName returns the field name. If a "json" tag is present, it uses the +// tag value instead. It also prepends the parent struct's name (if any) to +// create a fully qualified field name. +func fieldName(prefix string, field reflect.StructField) string { + // Default to field name + name := field.Name + + // Use json tag if present + if jsonTag, ok := field.Tag.Lookup("json"); ok { + name = jsonTag + } + + // Prepend parent name + if prefix != "" { + name = prefix + "." + name + } + + return name +} + +// splitSliceConfig splits config string into slice and item-level configurations. +func splitSliceConfig(config string) (string, string) { + sliceFileds := make([]string, 0) + itemFields := make([]string, 0) + + for _, configField := range strings.Fields(config) { + if strings.HasPrefix(configField, sliceConfigPrefix) { + sliceFileds = append(sliceFileds, strings.TrimPrefix(configField, sliceConfigPrefix)) + } else { + itemFields = append(itemFields, configField) + } + } + + return strings.Join(sliceFileds, " "), strings.Join(itemFields, " ") +} diff --git a/v2/checker_test.go b/v2/checker_test.go new file mode 100644 index 0000000..32c8b96 --- /dev/null +++ b/v2/checker_test.go @@ -0,0 +1,198 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "errors" + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleCheck() { + name := " Onur Cinar " + + name, err := v2.Check(name, v2.TrimSpace, v2.Required) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(name) + // Output: Onur Cinar +} + +func ExampleCheckStruct() { + type Person struct { + Name string `checkers:"trim required"` + } + + person := &Person{ + Name: " Onur Cinar ", + } + + errs, ok := v2.CheckStruct(person) + if !ok { + fmt.Println(errs) + return + } + + fmt.Println(person.Name) + // Output: Onur Cinar +} + +func TestCheckTrimSpaceRequiredSuccess(t *testing.T) { + input := " test " + expected := "test" + + actual, err := v2.Check(input, v2.TrimSpace, v2.Required) + if err != nil { + t.Fatal(err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestCheckTrimSpaceRequiredMissing(t *testing.T) { + input := " " + expected := "" + + actual, err := v2.Check(input, v2.TrimSpace, v2.Required) + if !errors.Is(err, v2.ErrRequired) { + t.Fatalf("got unexpected error %v", err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestCheckWithConfigSuccess(t *testing.T) { + input := " test " + expected := "test" + + actual, err := v2.CheckWithConfig(input, "trim required") + if err != nil { + t.Fatal(err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestCheckWithConfigRequiredMissing(t *testing.T) { + input := " " + expected := "" + + actual, err := v2.CheckWithConfig(input, "trim required") + if !errors.Is(err, v2.ErrRequired) { + t.Fatalf("got unexpected error %v", err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestCheckStructSuccess(t *testing.T) { + type Address struct { + Street string `checkers:"required"` + } + + type Person struct { + Name string `checkers:"required"` + Address *Address + } + + person := &Person{ + Name: "Onur Cinar", + Address: &Address{ + Street: "1234 Main", + }, + } + + errors, ok := v2.CheckStruct(person) + if !ok { + t.Fatalf("got unexpected errors %v", errors) + } +} + +func TestCheckStructRequiredMissing(t *testing.T) { + type Address struct { + Street string `checkers:"required"` + } + + type Person struct { + Name string `checkers:"required"` + Address *Address + } + + person := &Person{ + Name: "", + Address: &Address{ + Street: "", + }, + } + + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatal("expected errors") + } + + if !errors.Is(errs["Name"], v2.ErrRequired) { + t.Fatalf("expected name required %v", errs) + } + + if !errors.Is(errs["Address.Street"], v2.ErrRequired) { + t.Fatalf("expected streed required %v", errs) + } +} + +func TestCheckStructCustomName(t *testing.T) { + type Person struct { + Name string `json:"name" checkers:"required"` + } + + person := &Person{ + Name: "", + } + + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatal("expected errors") + } + + if !errors.Is(errs["name"], v2.ErrRequired) { + t.Fatalf("expected name required %v", errs) + } +} + +func TestCheckStructSlice(t *testing.T) { + type Person struct { + Name string `checkers:"required"` + Emails []string `checkers:"@max-len:1 max-len:4"` + } + + person := &Person{ + Name: "Onur Cinar", + Emails: []string{ + "onur.cinar", + }, + } + + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatal("expected errors") + } + + if !errors.Is(errs["Emails[0]"], v2.ErrMaxLen) { + t.Fatalf("expected email max len") + } +} diff --git a/v2/go.mod b/v2/go.mod new file mode 100644 index 0000000..4a6df3a --- /dev/null +++ b/v2/go.mod @@ -0,0 +1,3 @@ +module github.com/cinar/checker/v2 + +go 1.23.2 diff --git a/v2/helper_test.go b/v2/helper_test.go new file mode 100644 index 0000000..a82b83a --- /dev/null +++ b/v2/helper_test.go @@ -0,0 +1,17 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import "testing" + +// FailIfNoPanic fails the test if there were no panic. +func FailIfNoPanic(t *testing.T, message string) { + t.Helper() + + if r := recover(); r == nil { + t.Fatal(message) + } +} diff --git a/v2/maker.go b/v2/maker.go new file mode 100644 index 0000000..bec2435 --- /dev/null +++ b/v2/maker.go @@ -0,0 +1,44 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "fmt" + "reflect" + "strings" +) + +// MakeCheckFunc is a function that returns a check function using the given params. +type MakeCheckFunc func(params string) CheckFunc[reflect.Value] + +// makers provides a mapping of maker functions keyed by the check name. +var makers = map[string]MakeCheckFunc{ + nameAlphanumeric: makeAlphanumeric, + nameMaxLen: makeMaxLen, + nameMinLen: makeMinLen, + nameRequired: makeRequired, + nameTrimSpace: makeTrimSpace, +} + +// makeChecks take a checker config and returns the check functions. +func makeChecks(config string) []CheckFunc[reflect.Value] { + fields := strings.Fields(config) + + checks := make([]CheckFunc[reflect.Value], len(fields)) + + for i, field := range fields { + name, params, _ := strings.Cut(field, ":") + + maker, ok := makers[name] + if !ok { + panic(fmt.Sprintf("check %s not found", name)) + } + + checks[i] = maker(params) + } + + return checks +} diff --git a/v2/maker_test.go b/v2/maker_test.go new file mode 100644 index 0000000..931183c --- /dev/null +++ b/v2/maker_test.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestMakeCheckersUnknown(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Name string `checkers:"unknown"` + } + + person := &Person{ + Name: "Onur", + } + + v2.CheckStruct(person) +} diff --git a/v2/max_len.go b/v2/max_len.go new file mode 100644 index 0000000..8abad39 --- /dev/null +++ b/v2/max_len.go @@ -0,0 +1,51 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "strconv" +) + +const ( + // nameMaxLen is the name of the maximum length check. + nameMaxLen = "max-len" +) + +var ( + // ErrMaxLen indicates that the value's length is greater than the specified maximum. + ErrMaxLen = NewCheckError("MAX_LEN") +) + +// MaxLen checks if the length of the given value (string, slice, or map) is at most n. +// Returns an error if the length is greater than n. +func MaxLen[T any](n int) CheckFunc[T] { + return func(value T) (T, error) { + v, ok := any(value).(reflect.Value) + if !ok { + v = reflect.ValueOf(value) + } + + v = reflect.Indirect(v) + + if v.Len() > n { + return value, ErrMaxLen + } + + return value, nil + } +} + +// makeMaxLen creates a maximum length check function from a string parameter. +// Panics if the parameter cannot be parsed as an integer. +func makeMaxLen(params string) CheckFunc[reflect.Value] { + n, err := strconv.Atoi(params) + if err != nil { + panic("unable to parse max length") + } + + return MaxLen[reflect.Value](n) +} diff --git a/v2/max_len_test.go b/v2/max_len_test.go new file mode 100644 index 0000000..97d0087 --- /dev/null +++ b/v2/max_len_test.go @@ -0,0 +1,93 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "errors" + "log" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestMaxLenSuccess(t *testing.T) { + value := "test" + + check := v2.MaxLen[string](4) + + result, err := check(value) + if result != value { + t.Fatalf("result (%s) is not the original value (%s)", result, value) + } + + if err != nil { + t.Fatal(err) + } +} + +func TestMaxLenError(t *testing.T) { + value := "test test" + + check := v2.MaxLen[string](5) + + result, err := check(value) + if result != value { + t.Fatalf("result (%s) is not the original value (%s)", result, value) + } + + if !errors.Is(err, v2.ErrMaxLen) { + t.Fatalf("got unexpected error %v", err) + } + + log.Println(err) +} + +func TestReflectMaxLenError(t *testing.T) { + type Person struct { + Name string `checkers:"max-len:2"` + } + + person := &Person{ + Name: "Onur", + } + + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatalf("expected errors") + } + + if errs["Name"] == nil { + t.Fatalf("expected maximum length error") + } +} + +func TestReflectMaxLenInvalidMaxLen(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Name string `checkers:"max-len:abcd"` + } + + person := &Person{ + Name: "Onur", + } + + v2.CheckStruct(person) +} + +func TestReflectMaxLenInvalidType(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Name int `checkers:"max-len:8"` + } + + person := &Person{ + Name: 1, + } + + v2.CheckStruct(person) +} diff --git a/v2/min_len.go b/v2/min_len.go new file mode 100644 index 0000000..c996a0e --- /dev/null +++ b/v2/min_len.go @@ -0,0 +1,51 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "strconv" +) + +const ( + // nameMinLen is the name of the minimum length check. + nameMinLen = "min-len" +) + +var ( + // ErrMinLen indicates that the value's length is less than the specified minimum. + ErrMinLen = NewCheckError("MIN_LEN") +) + +// MinLen checks if the length of the given value (string, slice, or map) is at least n. +// Returns an error if the length is less than n. +func MinLen[T any](n int) CheckFunc[T] { + return func(value T) (T, error) { + v, ok := any(value).(reflect.Value) + if !ok { + v = reflect.ValueOf(value) + } + + v = reflect.Indirect(v) + + if v.Len() < n { + return value, ErrMinLen + } + + return value, nil + } +} + +// makeMinLen creates a minimum length check function from a string parameter. +// Panics if the parameter cannot be parsed as an integer. +func makeMinLen(params string) CheckFunc[reflect.Value] { + n, err := strconv.Atoi(params) + if err != nil { + panic("unable to parse min length") + } + + return MinLen[reflect.Value](n) +} diff --git a/v2/min_len_test.go b/v2/min_len_test.go new file mode 100644 index 0000000..6a7b6a0 --- /dev/null +++ b/v2/min_len_test.go @@ -0,0 +1,93 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "errors" + "log" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestMinLenSuccess(t *testing.T) { + value := "test" + + check := v2.MinLen[string](4) + + result, err := check(value) + if result != value { + t.Fatalf("result (%s) is not the original value (%s)", result, value) + } + + if err != nil { + t.Fatal(err) + } +} + +func TestMinLenError(t *testing.T) { + value := "test" + + check := v2.MinLen[string](5) + + result, err := check(value) + if result != value { + t.Fatalf("result (%s) is not the original value (%s)", result, value) + } + + if !errors.Is(err, v2.ErrMinLen) { + t.Fatalf("got unexpected error %v", err) + } + + log.Println(err) +} + +func TestReflectMinLenError(t *testing.T) { + type Person struct { + Name string `checkers:"trim min-len:8"` + } + + person := &Person{ + Name: " Onur ", + } + + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatalf("expected errors") + } + + if errs["Name"] == nil { + t.Fatalf("expected minimum length error") + } +} + +func TestReflectMinLenInvalidMinLen(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Name string `checkers:"min-len:abcd"` + } + + person := &Person{ + Name: "Onur", + } + + v2.CheckStruct(person) +} + +func TestReflectMinLenInvalidType(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Name int `checkers:"min-len:8"` + } + + person := &Person{ + Name: 1, + } + + v2.CheckStruct(person) +} diff --git a/v2/required.go b/v2/required.go new file mode 100644 index 0000000..70c5d79 --- /dev/null +++ b/v2/required.go @@ -0,0 +1,42 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import "reflect" + +const ( + // nameRequired is the name of the required check. + nameRequired = "required" +) + +var ( + // ErrRequired indicates that a required value was missing. + ErrRequired = NewCheckError("REQUIRED") +) + +// Required checks if the given value of type T is its zero value. It +// returns an error if the value is zero. +func Required[T any](value T) (T, error) { + _, err := reflectRequired(reflect.ValueOf(value)) + return value, err +} + +// reflectRequired checks if the given value is its zero value. It +// returns an error if the value is zero. +func reflectRequired(value reflect.Value) (reflect.Value, error) { + var err error + + if value.IsZero() { + err = ErrRequired + } + + return value, err +} + +// makeRequired returns the required check function. +func makeRequired(_ string) CheckFunc[reflect.Value] { + return reflectRequired +} diff --git a/v2/required_test.go b/v2/required_test.go new file mode 100644 index 0000000..3231aff --- /dev/null +++ b/v2/required_test.go @@ -0,0 +1,41 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "errors" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestRequiredSuccess(t *testing.T) { + value := "test" + + result, err := v2.Required(value) + + if result != value { + t.Fatalf("result (%s) is not the original value (%s)", result, value) + } + + if err != nil { + t.Fatal(err) + } +} + +func TestRequiredMissing(t *testing.T) { + var value string + + result, err := v2.Required(value) + + if result != value { + t.Fatalf("result (%s) is not the original value (%s)", result, value) + } + + if !errors.Is(err, v2.ErrRequired) { + t.Fatalf("got unexpected error %v", err) + } +} diff --git a/v2/trim_space.go b/v2/trim_space.go new file mode 100644 index 0000000..20bca37 --- /dev/null +++ b/v2/trim_space.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "strings" +) + +const ( + // nameTrimSpace is the name of the trim normalizer. + nameTrimSpace = "trim" +) + +// TrimSpace returns the value of the string with whitespace removed from both ends. +func TrimSpace(value string) (string, error) { + return strings.TrimSpace(value), nil +} + +// reflectTrimSpace returns the value of the string with whitespace removed from both ends. +func reflectTrimSpace(value reflect.Value) (reflect.Value, error) { + newValue, err := TrimSpace(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeTrimSpace returns the trim space normalizer function. +func makeTrimSpace(_ string) CheckFunc[reflect.Value] { + return reflectTrimSpace +} diff --git a/v2/trim_space_test.go b/v2/trim_space_test.go new file mode 100644 index 0000000..b326239 --- /dev/null +++ b/v2/trim_space_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestTrimSpace(t *testing.T) { + input := " test " + expected := "test" + + actual, err := v2.TrimSpace(input) + if err != nil { + t.Fatal(err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestReflectTrimSpace(t *testing.T) { + type Person struct { + Name string `checkers:"trim"` + } + + person := &Person{ + Name: " test ", + } + + expected := "test" + + errs, ok := v2.CheckStruct(person) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if person.Name != expected { + t.Fatalf("actual %s expected %s", person.Name, expected) + } +} From 8a7b88acbed64ebe582c14dbab5f353a173a50ca Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 26 Dec 2024 12:07:17 -0800 Subject: [PATCH 09/41] Update dev container to use the latest Go version. (#130) # Describe Request Update the dev container configuration to use the latest version of Go. # Change Type Code maintenance. --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 5619323..baa453a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,7 +3,7 @@ { "name": "Go", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/go:0-1.20-bullseye", + "image": "mcr.microsoft.com/devcontainers/go", // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, From af8e0e9fbf27a6d9e41bc167a012947ca9b335bb Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 26 Dec 2024 16:04:46 -0800 Subject: [PATCH 10/41] ASCII checker moved to v2 version. (#131) # Describe Request ASCII checker moved to v2 version. # Change Type New code. --- v2/ascii.go | 43 +++++++++++++++++++++++++++ v2/ascii_test.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ v2/maker.go | 1 + 3 files changed, 120 insertions(+) create mode 100644 v2/ascii.go create mode 100644 v2/ascii_test.go diff --git a/v2/ascii.go b/v2/ascii.go new file mode 100644 index 0000000..5bb7946 --- /dev/null +++ b/v2/ascii.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "unicode" +) + +const ( + // nameASCII is the name of the ASCII check. + nameASCII = "ascii" +) + +var ( + // ErrNotASCII indicates that the given string contains non-ASCII characters. + ErrNotASCII = NewCheckError("ASCII") +) + +// IsASCII checks if the given string consists of only ASCII characters. +func IsASCII(value string) (string, error) { + for _, c := range value { + if c > unicode.MaxASCII { + return value, ErrNotASCII + } + } + + return value, nil +} + +// checkASCII checks if the given string consists of only ASCII characters. +func isASCII(value reflect.Value) (reflect.Value, error) { + _, err := IsASCII(value.Interface().(string)) + return value, err +} + +// makeASCII makes a checker function for the ASCII checker. +func makeASCII(_ string) CheckFunc[reflect.Value] { + return isASCII +} diff --git a/v2/ascii_test.go b/v2/ascii_test.go new file mode 100644 index 0000000..cffca70 --- /dev/null +++ b/v2/ascii_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsASCII() { + _, err := v2.IsASCII("Checker") + if err != nil { + fmt.Println(err) + } +} + +func TestIsASCIIInvalid(t *testing.T) { + _, err := v2.IsASCII("𝄞 Music!") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsASCIIValid(t *testing.T) { + _, err := v2.IsASCII("Checker") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckASCIINonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type User struct { + Username int `checkers:"ascii"` + } + + user := &User{} + + v2.CheckStruct(user) +} + +func TestCheckASCIIInvalid(t *testing.T) { + type User struct { + Username string `checkers:"ascii"` + } + + user := &User{ + Username: "𝄞 Music!", + } + + _, ok := v2.CheckStruct(user) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckASCIIValid(t *testing.T) { + type User struct { + Username string `checkers:"ascii"` + } + + user := &User{ + Username: "checker", + } + + _, ok := v2.CheckStruct(user) + if !ok { + t.Fatal("expected valid") + } +} diff --git a/v2/maker.go b/v2/maker.go index bec2435..631363a 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -17,6 +17,7 @@ type MakeCheckFunc func(params string) CheckFunc[reflect.Value] // makers provides a mapping of maker functions keyed by the check name. var makers = map[string]MakeCheckFunc{ nameAlphanumeric: makeAlphanumeric, + nameASCII: makeASCII, nameMaxLen: makeMaxLen, nameMinLen: makeMinLen, nameRequired: makeRequired, From 9e75deef976455814374092895574557eb00b1c8 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 26 Dec 2024 16:09:58 -0800 Subject: [PATCH 11/41] Add CIDR validation checker and tests to v2. (#132) # Describe Request Add CIDR validation checker and tests to v2. # Change Type New code. --- v2/cidr.go | 42 +++++++++++++++++++++++++++ v2/cidr_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ v2/maker.go | 1 + 3 files changed, 119 insertions(+) create mode 100644 v2/cidr.go create mode 100644 v2/cidr_test.go diff --git a/v2/cidr.go b/v2/cidr.go new file mode 100644 index 0000000..c2dd44d --- /dev/null +++ b/v2/cidr.go @@ -0,0 +1,42 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "net" + "reflect" +) + +const ( + // nameCIDR is the name of the CIDR check. + nameCIDR = "cidr" +) + +var ( + // ErrNotCIDR indicates that the given value is not a valid CIDR. + ErrNotCIDR = NewCheckError("CIDR") +) + +// IsCIDR checks if the value is a valid CIDR notation IP address and prefix length. +func IsCIDR(value string) (string, error) { + _, _, err := net.ParseCIDR(value) + if err != nil { + return value, ErrNotCIDR + } + + return value, nil +} + +// checkCIDR checks if the value is a valid CIDR notation IP address and prefix length. +func checkCIDR(value reflect.Value) (reflect.Value, error) { + _, err := IsCIDR(value.Interface().(string)) + return value, err +} + +// makeCIDR makes a checker function for the CIDR checker. +func makeCIDR(_ string) CheckFunc[reflect.Value] { + return checkCIDR +} diff --git a/v2/cidr_test.go b/v2/cidr_test.go new file mode 100644 index 0000000..d402c23 --- /dev/null +++ b/v2/cidr_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsCIDR() { + _, err := v2.IsCIDR("2001:db8::/32") + if err != nil { + fmt.Println(err) + } +} + +func TestIsCIDRInvalid(t *testing.T) { + _, err := v2.IsCIDR("900.800.200.100//24") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsCIDRValid(t *testing.T) { + _, err := v2.IsCIDR("2001:db8::/32") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckCIDRNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Network struct { + Subnet int `checkers:"cidr"` + } + + network := &Network{} + + v2.CheckStruct(network) +} + +func TestCheckCIDRInvalid(t *testing.T) { + type Network struct { + Subnet string `checkers:"cidr"` + } + + network := &Network{ + Subnet: "900.800.200.100//24", + } + + _, ok := v2.CheckStruct(network) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckCIDRValid(t *testing.T) { + type Network struct { + Subnet string `checkers:"cidr"` + } + + network := &Network{ + Subnet: "192.0.2.0/24", + } + + _, ok := v2.CheckStruct(network) + if !ok { + t.Fatal("expected valid") + } +} diff --git a/v2/maker.go b/v2/maker.go index 631363a..14772c7 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -18,6 +18,7 @@ type MakeCheckFunc func(params string) CheckFunc[reflect.Value] var makers = map[string]MakeCheckFunc{ nameAlphanumeric: makeAlphanumeric, nameASCII: makeASCII, + nameCIDR: makeCIDR, nameMaxLen: makeMaxLen, nameMinLen: makeMinLen, nameRequired: makeRequired, From b0096cbb12892cc18ec4d6b38053c3126dea6082 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 26 Dec 2024 18:36:48 -0800 Subject: [PATCH 12/41] Add Digits validation checker and tests to v2. (#133) # Describe Request Add Digits validation checker and tests to v2. # Change Type New code. --- v2/digits.go | 42 ++++++++++++++++++++++++++ v2/digits_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++ v2/maker.go | 1 + 3 files changed, 119 insertions(+) create mode 100644 v2/digits.go create mode 100644 v2/digits_test.go diff --git a/v2/digits.go b/v2/digits.go new file mode 100644 index 0000000..adcddb8 --- /dev/null +++ b/v2/digits.go @@ -0,0 +1,42 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "unicode" +) + +const ( + // nameDigits is the name of the digits check. + nameDigits = "digits" +) + +var ( + // ErrNotDigits indicates that the given value is not a valid digits string. + ErrNotDigits = NewCheckError("Digits") +) + +// IsDigits checks if the value contains only digit characters. +func IsDigits(value string) (string, error) { + for _, r := range value { + if !unicode.IsDigit(r) { + return value, ErrNotDigits + } + } + return value, nil +} + +// checkDigits checks if the value contains only digit characters. +func checkDigits(value reflect.Value) (reflect.Value, error) { + _, err := IsDigits(value.Interface().(string)) + return value, err +} + +// makeDigits makes a checker function for the digits checker. +func makeDigits(_ string) CheckFunc[reflect.Value] { + return checkDigits +} \ No newline at end of file diff --git a/v2/digits_test.go b/v2/digits_test.go new file mode 100644 index 0000000..6858ca6 --- /dev/null +++ b/v2/digits_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsDigits() { + _, err := v2.IsDigits("123456") + if err != nil { + fmt.Println(err) + } +} + +func TestIsDigitsInvalid(t *testing.T) { + _, err := v2.IsDigits("123a456") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsDigitsValid(t *testing.T) { + _, err := v2.IsDigits("123456") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckDigitsNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Code struct { + Value int `checkers:"digits"` + } + + code := &Code{} + + v2.CheckStruct(code) +} + +func TestCheckDigitsInvalid(t *testing.T) { + type Code struct { + Value string `checkers:"digits"` + } + + code := &Code{ + Value: "123a456", + } + + _, ok := v2.CheckStruct(code) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckDigitsValid(t *testing.T) { + type Code struct { + Value string `checkers:"digits"` + } + + code := &Code{ + Value: "123456", + } + + _, ok := v2.CheckStruct(code) + if !ok { + t.Fatal("expected valid") + } +} diff --git a/v2/maker.go b/v2/maker.go index 14772c7..6806ed3 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -19,6 +19,7 @@ var makers = map[string]MakeCheckFunc{ nameAlphanumeric: makeAlphanumeric, nameASCII: makeASCII, nameCIDR: makeCIDR, + nameDigits: makeDigits, nameMaxLen: makeMaxLen, nameMinLen: makeMinLen, nameRequired: makeRequired, From c47eeb64519f7731fb940d9d7b0adfb2d06925a1 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 26 Dec 2024 18:42:21 -0800 Subject: [PATCH 13/41] Add Email validation checker and tests to v2. (#134) # Describe Request Add Email validation checker and tests to v2. # Change Type New code. --- v2/email.go | 41 ++++++++++++++++++++++++++ v2/email_test.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ v2/maker.go | 1 + 3 files changed, 118 insertions(+) create mode 100644 v2/email.go create mode 100644 v2/email_test.go diff --git a/v2/email.go b/v2/email.go new file mode 100644 index 0000000..7251850 --- /dev/null +++ b/v2/email.go @@ -0,0 +1,41 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "net/mail" + "reflect" +) + +const ( + // nameEmail is the name of the email check. + nameEmail = "email" +) + +var ( + // ErrNotEmail indicates that the given value is not a valid email address. + ErrNotEmail = NewCheckError("Email") +) + +// IsEmail checks if the value is a valid email address. +func IsEmail(value string) (string, error) { + _, err := mail.ParseAddress(value) + if err != nil { + return value, ErrNotEmail + } + return value, nil +} + +// checkEmail checks if the value is a valid email address. +func checkEmail(value reflect.Value) (reflect.Value, error) { + _, err := IsEmail(value.Interface().(string)) + return value, err +} + +// makeEmail makes a checker function for the email checker. +func makeEmail(_ string) CheckFunc[reflect.Value] { + return checkEmail +} diff --git a/v2/email_test.go b/v2/email_test.go new file mode 100644 index 0000000..20e3665 --- /dev/null +++ b/v2/email_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsEmail() { + _, err := v2.IsEmail("test@example.com") + if err != nil { + fmt.Println(err) + } +} + +func TestIsEmailInvalid(t *testing.T) { + _, err := v2.IsEmail("invalid-email") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsEmailValid(t *testing.T) { + _, err := v2.IsEmail("test@example.com") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckEmailNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type User struct { + Email int `checkers:"email"` + } + + user := &User{} + + v2.CheckStruct(user) +} + +func TestCheckEmailInvalid(t *testing.T) { + type User struct { + Email string `checkers:"email"` + } + + user := &User{ + Email: "invalid-email", + } + + _, ok := v2.CheckStruct(user) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckEmailValid(t *testing.T) { + type User struct { + Email string `checkers:"email"` + } + + user := &User{ + Email: "test@example.com", + } + + _, ok := v2.CheckStruct(user) + if !ok { + t.Fatal("expected valid") + } +} diff --git a/v2/maker.go b/v2/maker.go index 6806ed3..6841aea 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -20,6 +20,7 @@ var makers = map[string]MakeCheckFunc{ nameASCII: makeASCII, nameCIDR: makeCIDR, nameDigits: makeDigits, + nameEmail: makeEmail, nameMaxLen: makeMaxLen, nameMinLen: makeMinLen, nameRequired: makeRequired, From dac7298ca769c6eaad6c2e09429fb4a9cc8b9ecc Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 26 Dec 2024 18:46:58 -0800 Subject: [PATCH 14/41] Add FQDN validation checker and tests to v2. (#135) # Describe Request Add FQDN validation checker and tests to v2. # Change Type New code. --- v2/fqdn.go | 43 ++++++++++++++++++++++++++++ v2/fqdn_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ v2/maker.go | 1 + 3 files changed, 120 insertions(+) create mode 100644 v2/fqdn.go create mode 100644 v2/fqdn_test.go diff --git a/v2/fqdn.go b/v2/fqdn.go new file mode 100644 index 0000000..899edec --- /dev/null +++ b/v2/fqdn.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "regexp" +) + +const ( + // nameFQDN is the name of the FQDN check. + nameFQDN = "fqdn" +) + +var ( + // ErrNotFQDN indicates that the given value is not a valid FQDN. + ErrNotFQDN = NewCheckError("FQDN") + + // fqdnRegex is the regular expression for validating FQDN. + fqdnRegex = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`) +) + +// IsFQDN checks if the value is a valid fully qualified domain name (FQDN). +func IsFQDN(value string) (string, error) { + if !fqdnRegex.MatchString(value) { + return value, ErrNotFQDN + } + return value, nil +} + +// checkFQDN checks if the value is a valid fully qualified domain name (FQDN). +func checkFQDN(value reflect.Value) (reflect.Value, error) { + _, err := IsFQDN(value.Interface().(string)) + return value, err +} + +// makeFQDN makes a checker function for the FQDN checker. +func makeFQDN(_ string) CheckFunc[reflect.Value] { + return checkFQDN +} \ No newline at end of file diff --git a/v2/fqdn_test.go b/v2/fqdn_test.go new file mode 100644 index 0000000..8ffc8ed --- /dev/null +++ b/v2/fqdn_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsFQDN() { + _, err := v2.IsFQDN("example.com") + if err != nil { + fmt.Println(err) + } +} + +func TestIsFQDNInvalid(t *testing.T) { + _, err := v2.IsFQDN("invalid_fqdn") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsFQDNValid(t *testing.T) { + _, err := v2.IsFQDN("example.com") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckFQDNNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Domain struct { + Name int `checkers:"fqdn"` + } + + domain := &Domain{} + + v2.CheckStruct(domain) +} + +func TestCheckFQDNInvalid(t *testing.T) { + type Domain struct { + Name string `checkers:"fqdn"` + } + + domain := &Domain{ + Name: "invalid_fqdn", + } + + _, ok := v2.CheckStruct(domain) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckFQDNValid(t *testing.T) { + type Domain struct { + Name string `checkers:"fqdn"` + } + + domain := &Domain{ + Name: "example.com", + } + + _, ok := v2.CheckStruct(domain) + if !ok { + t.Fatal("expected valid") + } +} diff --git a/v2/maker.go b/v2/maker.go index 6841aea..e4df7fb 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -21,6 +21,7 @@ var makers = map[string]MakeCheckFunc{ nameCIDR: makeCIDR, nameDigits: makeDigits, nameEmail: makeEmail, + nameFQDN: makeFQDN, nameMaxLen: makeMaxLen, nameMinLen: makeMinLen, nameRequired: makeRequired, From be35612582dbe6cbe9b6e8ac5ea0a88991f8e6ec Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 26 Dec 2024 18:56:06 -0800 Subject: [PATCH 15/41] Add IP, IPv4, IPv6 validation checkers and tests to v2. (#136) # Describe Request Add IP, IPv4, IPv6 validation checkers and tests to v2. # Change Type New code. --- v2/ip.go | 40 ++++++++++++++++++++++++++ v2/ip_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ v2/ipv4.go | 41 ++++++++++++++++++++++++++ v2/ipv4_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ v2/ipv6.go | 40 ++++++++++++++++++++++++++ v2/ipv6_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ v2/maker.go | 3 ++ 7 files changed, 352 insertions(+) create mode 100644 v2/ip.go create mode 100644 v2/ip_test.go create mode 100644 v2/ipv4.go create mode 100644 v2/ipv4_test.go create mode 100644 v2/ipv6.go create mode 100644 v2/ipv6_test.go diff --git a/v2/ip.go b/v2/ip.go new file mode 100644 index 0000000..09e1d6b --- /dev/null +++ b/v2/ip.go @@ -0,0 +1,40 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "net" + "reflect" +) + +const ( + // nameIP is the name of the IP check. + nameIP = "ip" +) + +var ( + // ErrNotIP indicates that the given value is not a valid IP address. + ErrNotIP = NewCheckError("IP") +) + +// IsIP checks if the value is a valid IP address. +func IsIP(value string) (string, error) { + if net.ParseIP(value) == nil { + return value, ErrNotIP + } + return value, nil +} + +// checkIP checks if the value is a valid IP address. +func checkIP(value reflect.Value) (reflect.Value, error) { + _, err := IsIP(value.Interface().(string)) + return value, err +} + +// makeIP makes a checker function for the IP checker. +func makeIP(_ string) CheckFunc[reflect.Value] { + return checkIP +} diff --git a/v2/ip_test.go b/v2/ip_test.go new file mode 100644 index 0000000..5bd23c5 --- /dev/null +++ b/v2/ip_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsIP() { + _, err := v2.IsIP("192.168.1.1") + if err != nil { + fmt.Println(err) + } +} + +func TestIsIPInvalid(t *testing.T) { + _, err := v2.IsIP("invalid-ip") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsIPValid(t *testing.T) { + _, err := v2.IsIP("192.168.1.1") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckIPNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Network struct { + Address int `checkers:"ip"` + } + + network := &Network{} + + v2.CheckStruct(network) +} + +func TestCheckIPInvalid(t *testing.T) { + type Network struct { + Address string `checkers:"ip"` + } + + network := &Network{ + Address: "invalid-ip", + } + + _, ok := v2.CheckStruct(network) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckIPValid(t *testing.T) { + type Network struct { + Address string `checkers:"ip"` + } + + network := &Network{ + Address: "192.168.1.1", + } + + _, ok := v2.CheckStruct(network) + if !ok { + t.Fatal("expected valid") + } +} diff --git a/v2/ipv4.go b/v2/ipv4.go new file mode 100644 index 0000000..791bcf4 --- /dev/null +++ b/v2/ipv4.go @@ -0,0 +1,41 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "net" + "reflect" +) + +const ( + // nameIPv4 is the name of the IPv4 check. + nameIPv4 = "ipv4" +) + +var ( + // ErrNotIPv4 indicates that the given value is not a valid IPv4 address. + ErrNotIPv4 = NewCheckError("IPv4") +) + +// IsIPv4 checks if the value is a valid IPv4 address. +func IsIPv4(value string) (string, error) { + ip := net.ParseIP(value) + if ip == nil || ip.To4() == nil { + return value, ErrNotIPv4 + } + return value, nil +} + +// checkIPv4 checks if the value is a valid IPv4 address. +func checkIPv4(value reflect.Value) (reflect.Value, error) { + _, err := IsIPv4(value.Interface().(string)) + return value, err +} + +// makeIPv4 makes a checker function for the IPv4 checker. +func makeIPv4(_ string) CheckFunc[reflect.Value] { + return checkIPv4 +} diff --git a/v2/ipv4_test.go b/v2/ipv4_test.go new file mode 100644 index 0000000..ba34feb --- /dev/null +++ b/v2/ipv4_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsIPv4() { + _, err := v2.IsIPv4("192.168.1.1") + if err != nil { + fmt.Println(err) + } +} + +func TestIsIPv4Invalid(t *testing.T) { + _, err := v2.IsIPv4("2001:db8::1") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsIPv4Valid(t *testing.T) { + _, err := v2.IsIPv4("192.168.1.1") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckIPv4NonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Network struct { + Address int `checkers:"ipv4"` + } + + network := &Network{} + + v2.CheckStruct(network) +} + +func TestCheckIPv4Invalid(t *testing.T) { + type Network struct { + Address string `checkers:"ipv4"` + } + + network := &Network{ + Address: "2001:db8::1", + } + + _, ok := v2.CheckStruct(network) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckIPv4Valid(t *testing.T) { + type Network struct { + Address string `checkers:"ipv4"` + } + + network := &Network{ + Address: "192.168.1.1", + } + + _, ok := v2.CheckStruct(network) + if !ok { + t.Fatal("expected valid") + } +} diff --git a/v2/ipv6.go b/v2/ipv6.go new file mode 100644 index 0000000..2856166 --- /dev/null +++ b/v2/ipv6.go @@ -0,0 +1,40 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "net" + "reflect" +) + +const ( + // nameIPv6 is the name of the IPv6 check. + nameIPv6 = "ipv6" +) + +var ( + // ErrNotIPv6 indicates that the given value is not a valid IPv6 address. + ErrNotIPv6 = NewCheckError("IPv6") +) + +// IsIPv6 checks if the value is a valid IPv6 address. +func IsIPv6(value string) (string, error) { + if net.ParseIP(value) == nil || net.ParseIP(value).To4() != nil { + return value, ErrNotIPv6 + } + return value, nil +} + +// checkIPv6 checks if the value is a valid IPv6 address. +func checkIPv6(value reflect.Value) (reflect.Value, error) { + _, err := IsIPv6(value.Interface().(string)) + return value, err +} + +// makeIPv6 makes a checker function for the IPv6 checker. +func makeIPv6(_ string) CheckFunc[reflect.Value] { + return checkIPv6 +} diff --git a/v2/ipv6_test.go b/v2/ipv6_test.go new file mode 100644 index 0000000..2a7ea58 --- /dev/null +++ b/v2/ipv6_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsIPv6() { + _, err := v2.IsIPv6("2001:db8::1") + if err != nil { + fmt.Println(err) + } +} + +func TestIsIPv6Invalid(t *testing.T) { + _, err := v2.IsIPv6("192.168.1.1") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsIPv6Valid(t *testing.T) { + _, err := v2.IsIPv6("2001:db8::1") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckIPv6NonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Network struct { + Address int `checkers:"ipv6"` + } + + network := &Network{} + + v2.CheckStruct(network) +} + +func TestCheckIPv6Invalid(t *testing.T) { + type Network struct { + Address string `checkers:"ipv6"` + } + + network := &Network{ + Address: "192.168.1.1", + } + + _, ok := v2.CheckStruct(network) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckIPv6Valid(t *testing.T) { + type Network struct { + Address string `checkers:"ipv6"` + } + + network := &Network{ + Address: "2001:db8::1", + } + + _, ok := v2.CheckStruct(network) + if !ok { + t.Fatal("expected valid") + } +} diff --git a/v2/maker.go b/v2/maker.go index e4df7fb..f5ff1b8 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -22,6 +22,9 @@ var makers = map[string]MakeCheckFunc{ nameDigits: makeDigits, nameEmail: makeEmail, nameFQDN: makeFQDN, + nameIP: makeIP, + nameIPv4: makeIPv4, + nameIPv6: makeIPv6, nameMaxLen: makeMaxLen, nameMinLen: makeMinLen, nameRequired: makeRequired, From aca9811584db552d628bc73f8715b585adb8f22a Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 26 Dec 2024 19:03:30 -0800 Subject: [PATCH 16/41] Add ISBN validation checker and tests to v2. (#137) # Describe Request Add ISBN validation checker and tests to v2. # Change Type New code. --- v2/isbn.go | 43 ++++++++++++++++++++++++++++ v2/isbn_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ v2/maker.go | 1 + 3 files changed, 120 insertions(+) create mode 100644 v2/isbn.go create mode 100644 v2/isbn_test.go diff --git a/v2/isbn.go b/v2/isbn.go new file mode 100644 index 0000000..84cf832 --- /dev/null +++ b/v2/isbn.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "regexp" +) + +const ( + // nameISBN is the name of the ISBN check. + nameISBN = "isbn" +) + +var ( + // ErrNotISBN indicates that the given value is not a valid ISBN. + ErrNotISBN = NewCheckError("ISBN") + + // isbnRegex is the regular expression for validating ISBN-10 and ISBN-13. + isbnRegex = regexp.MustCompile(`^(97(8|9))?\d{9}(\d|X)$`) +) + +// IsISBN checks if the value is a valid ISBN-10 or ISBN-13. +func IsISBN(value string) (string, error) { + if !isbnRegex.MatchString(value) { + return value, ErrNotISBN + } + return value, nil +} + +// checkISBN checks if the value is a valid ISBN-10 or ISBN-13. +func checkISBN(value reflect.Value) (reflect.Value, error) { + _, err := IsISBN(value.Interface().(string)) + return value, err +} + +// makeISBN makes a checker function for the ISBN checker. +func makeISBN(_ string) CheckFunc[reflect.Value] { + return checkISBN +} diff --git a/v2/isbn_test.go b/v2/isbn_test.go new file mode 100644 index 0000000..2f763cf --- /dev/null +++ b/v2/isbn_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsISBN() { + _, err := v2.IsISBN("9783161484100") + if err != nil { + fmt.Println(err) + } +} + +func TestIsISBNInvalid(t *testing.T) { + _, err := v2.IsISBN("invalid-isbn") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsISBNValid(t *testing.T) { + _, err := v2.IsISBN("9783161484100") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckISBNNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Book struct { + ISBN int `checkers:"isbn"` + } + + book := &Book{} + + v2.CheckStruct(book) +} + +func TestCheckISBNInvalid(t *testing.T) { + type Book struct { + ISBN string `checkers:"isbn"` + } + + book := &Book{ + ISBN: "invalid-isbn", + } + + _, ok := v2.CheckStruct(book) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckISBNValid(t *testing.T) { + type Book struct { + ISBN string `checkers:"isbn"` + } + + book := &Book{ + ISBN: "9783161484100", + } + + _, ok := v2.CheckStruct(book) + if !ok { + t.Fatal("expected valid") + } +} diff --git a/v2/maker.go b/v2/maker.go index f5ff1b8..5943eef 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -25,6 +25,7 @@ var makers = map[string]MakeCheckFunc{ nameIP: makeIP, nameIPv4: makeIPv4, nameIPv6: makeIPv6, + nameISBN: makeISBN, nameMaxLen: makeMaxLen, nameMinLen: makeMinLen, nameRequired: makeRequired, From c6c47adc2b305d342c25a5e9bfe26ef878face90 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 26 Dec 2024 19:06:56 -0800 Subject: [PATCH 17/41] Add LUHN validation checker and tests to v2. (#138) # Describe Request Add LUHN validation checker and tests to v2. # Change Type New code. --- v2/luhn.go | 61 +++++++++++++++++++++++++++++++++++++++ v2/luhn_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ v2/maker.go | 1 + 3 files changed, 138 insertions(+) create mode 100644 v2/luhn.go create mode 100644 v2/luhn_test.go diff --git a/v2/luhn.go b/v2/luhn.go new file mode 100644 index 0000000..9b3f5e4 --- /dev/null +++ b/v2/luhn.go @@ -0,0 +1,61 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "unicode" +) + +const ( + // nameLUHN is the name of the LUHN check. + nameLUHN = "luhn" +) + +var ( + // ErrNotLUHN indicates that the given value is not a valid LUHN number. + ErrNotLUHN = NewCheckError("LUHN") +) + +// IsLUHN checks if the value is a valid LUHN number. +func IsLUHN(value string) (string, error) { + var sum int + var alt bool + + for i := len(value) - 1; i >= 0; i-- { + r := rune(value[i]) + if !unicode.IsDigit(r) { + return value, ErrNotLUHN + } + + n := int(r - '0') + if alt { + n *= 2 + if n > 9 { + n -= 9 + } + } + sum += n + alt = !alt + } + + if sum%10 != 0 { + return value, ErrNotLUHN + } + + return value, nil +} + +// checkLUHN checks if the value is a valid LUHN number. +func checkLUHN(value reflect.Value) (reflect.Value, error) { + _, err := IsLUHN(value.Interface().(string)) + return value, err +} + +// makeLUHN makes a checker function for the LUHN checker. +func makeLUHN(_ string) CheckFunc[reflect.Value] { + return checkLUHN +} diff --git a/v2/luhn_test.go b/v2/luhn_test.go new file mode 100644 index 0000000..2dae79d --- /dev/null +++ b/v2/luhn_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsLUHN() { + _, err := v2.IsLUHN("79927398713") + if err != nil { + fmt.Println(err) + } +} + +func TestIsLUHNInvalid(t *testing.T) { + _, err := v2.IsLUHN("123456789") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsLUHNValid(t *testing.T) { + _, err := v2.IsLUHN("79927398713") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckLUHNNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Card struct { + Number int `checkers:"luhn"` + } + + card := &Card{} + + v2.CheckStruct(card) +} + +func TestCheckLUHNInvalid(t *testing.T) { + type Card struct { + Number string `checkers:"luhn"` + } + + card := &Card{ + Number: "123456789", + } + + _, ok := v2.CheckStruct(card) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckLUHNValid(t *testing.T) { + type Card struct { + Number string `checkers:"luhn"` + } + + card := &Card{ + Number: "79927398713", + } + + _, ok := v2.CheckStruct(card) + if !ok { + t.Fatal("expected valid") + } +} diff --git a/v2/maker.go b/v2/maker.go index 5943eef..1a49c6e 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -26,6 +26,7 @@ var makers = map[string]MakeCheckFunc{ nameIPv4: makeIPv4, nameIPv6: makeIPv6, nameISBN: makeISBN, + nameLUHN: makeLUHN, nameMaxLen: makeMaxLen, nameMinLen: makeMinLen, nameRequired: makeRequired, From 29a1a9ba0dffd3a32f6fc873a47746ecf487dd84 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 26 Dec 2024 19:10:33 -0800 Subject: [PATCH 18/41] Add MAC validation checker and tests to v2. (#139) # Describe Request Add MAC validation checker and tests to v2. # Change Type New code. --- v2/mac.go | 41 +++++++++++++++++++++++++++ v2/mac_test.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++ v2/maker.go | 1 + 3 files changed, 118 insertions(+) create mode 100644 v2/mac.go create mode 100644 v2/mac_test.go diff --git a/v2/mac.go b/v2/mac.go new file mode 100644 index 0000000..33312a3 --- /dev/null +++ b/v2/mac.go @@ -0,0 +1,41 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "net" + "reflect" +) + +const ( + // nameMAC is the name of the MAC check. + nameMAC = "mac" +) + +var ( + // ErrNotMAC indicates that the given value is not a valid MAC address. + ErrNotMAC = NewCheckError("MAC") +) + +// IsMAC checks if the value is a valid MAC address. +func IsMAC(value string) (string, error) { + _, err := net.ParseMAC(value) + if err != nil { + return value, ErrNotMAC + } + return value, nil +} + +// checkMAC checks if the value is a valid MAC address. +func checkMAC(value reflect.Value) (reflect.Value, error) { + _, err := IsMAC(value.Interface().(string)) + return value, err +} + +// makeMAC makes a checker function for the MAC checker. +func makeMAC(_ string) CheckFunc[reflect.Value] { + return checkMAC +} diff --git a/v2/mac_test.go b/v2/mac_test.go new file mode 100644 index 0000000..23ec403 --- /dev/null +++ b/v2/mac_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsMAC() { + _, err := v2.IsMAC("00:1A:2B:3C:4D:5E") + if err != nil { + fmt.Println(err) + } +} + +func TestIsMACInvalid(t *testing.T) { + _, err := v2.IsMAC("invalid-mac") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsMACValid(t *testing.T) { + _, err := v2.IsMAC("00:1A:2B:3C:4D:5E") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckMACNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Device struct { + MAC int `checkers:"mac"` + } + + device := &Device{} + + v2.CheckStruct(device) +} + +func TestCheckMACInvalid(t *testing.T) { + type Device struct { + MAC string `checkers:"mac"` + } + + device := &Device{ + MAC: "invalid-mac", + } + + _, ok := v2.CheckStruct(device) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckMACValid(t *testing.T) { + type Device struct { + MAC string `checkers:"mac"` + } + + device := &Device{ + MAC: "00:1A:2B:3C:4D:5E", + } + + _, ok := v2.CheckStruct(device) + if !ok { + t.Fatal("expected valid") + } +} diff --git a/v2/maker.go b/v2/maker.go index 1a49c6e..b20feb6 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -27,6 +27,7 @@ var makers = map[string]MakeCheckFunc{ nameIPv6: makeIPv6, nameISBN: makeISBN, nameLUHN: makeLUHN, + nameMAC: makeMAC, nameMaxLen: makeMaxLen, nameMinLen: makeMinLen, nameRequired: makeRequired, From b709836dede603d710f78f24a5eb7908392d8167 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Thu, 26 Dec 2024 19:12:46 -0800 Subject: [PATCH 19/41] Add URL validation checker and tests to v2. (#140) # Describe Request Add URL validation checker and tests to v2. # Change Type New code. --- v2/maker.go | 1 + v2/url.go | 41 +++++++++++++++++++++++++++ v2/url_test.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 v2/url.go create mode 100644 v2/url_test.go diff --git a/v2/maker.go b/v2/maker.go index b20feb6..a056507 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -32,6 +32,7 @@ var makers = map[string]MakeCheckFunc{ nameMinLen: makeMinLen, nameRequired: makeRequired, nameTrimSpace: makeTrimSpace, + nameURL: makeURL, } // makeChecks take a checker config and returns the check functions. diff --git a/v2/url.go b/v2/url.go new file mode 100644 index 0000000..6c32ff8 --- /dev/null +++ b/v2/url.go @@ -0,0 +1,41 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "net/url" + "reflect" +) + +const ( + // nameURL is the name of the URL check. + nameURL = "url" +) + +var ( + // ErrNotURL indicates that the given value is not a valid URL. + ErrNotURL = NewCheckError("URL") +) + +// IsURL checks if the value is a valid URL. +func IsURL(value string) (string, error) { + _, err := url.ParseRequestURI(value) + if err != nil { + return value, ErrNotURL + } + return value, nil +} + +// checkURL checks if the value is a valid URL. +func checkURL(value reflect.Value) (reflect.Value, error) { + _, err := IsURL(value.Interface().(string)) + return value, err +} + +// makeURL makes a checker function for the URL checker. +func makeURL(_ string) CheckFunc[reflect.Value] { + return checkURL +} diff --git a/v2/url_test.go b/v2/url_test.go new file mode 100644 index 0000000..d3777c1 --- /dev/null +++ b/v2/url_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsURL() { + _, err := v2.IsURL("https://example.com") + if err != nil { + fmt.Println(err) + } +} + +func TestIsURLInvalid(t *testing.T) { + _, err := v2.IsURL("invalid-url") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsURLValid(t *testing.T) { + _, err := v2.IsURL("https://example.com") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckURLNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Website struct { + Link int `checkers:"url"` + } + + website := &Website{} + + v2.CheckStruct(website) +} + +func TestCheckURLInvalid(t *testing.T) { + type Website struct { + Link string `checkers:"url"` + } + + website := &Website{ + Link: "invalid-url", + } + + _, ok := v2.CheckStruct(website) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckURLValid(t *testing.T) { + type Website struct { + Link string `checkers:"url"` + } + + website := &Website{ + Link: "https://example.com", + } + + _, ok := v2.CheckStruct(website) + if !ok { + t.Fatal("expected valid") + } +} From 48d005ce710f1210c165eb3addf6d16907f8f02f Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 27 Dec 2024 06:14:17 -0800 Subject: [PATCH 20/41] Add Credit Card validation checker and tests to v2. (#141) # Describe Request Add Credit Card validation checker and tests to v2. # Change Type New code. --- v2/credit_card.go | 156 +++++++++++++++++++++ v2/credit_card_test.go | 312 +++++++++++++++++++++++++++++++++++++++++ v2/isbn_test.go | 4 +- v2/luhn_test.go | 4 +- v2/maker.go | 1 + 5 files changed, 473 insertions(+), 4 deletions(-) create mode 100644 v2/credit_card.go create mode 100644 v2/credit_card_test.go diff --git a/v2/credit_card.go b/v2/credit_card.go new file mode 100644 index 0000000..32c9230 --- /dev/null +++ b/v2/credit_card.go @@ -0,0 +1,156 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "regexp" + "strings" +) + +const ( + // nameCreditCard is the name of the credit card check. + nameCreditCard = "credit-card" +) + +var ( + // ErrNotCreditCard indicates that the given value is not a valid credit card number. + ErrNotCreditCard = NewCheckError("CreditCard") + + // amexExpression is the regexp for the AMEX cards. They start with 34 or 37, and has 15 digits. + amexExpression = "(?:^(?:3[47])[0-9]{13}$)" + amexPattern = regexp.MustCompile(amexExpression) + + // dinersExpression is the regexp for the Diners cards. They start with 305, 36, 38, and has 14 digits. + dinersExpression = "(?:^3(?:(?:05[0-9]{11})|(?:[68][0-9]{12}))$)" + dinersPattern = regexp.MustCompile(dinersExpression) + + // discoverExpression is the regexp for the Discover cards. They start with 6011 and has 16 digits. + discoverExpression = "(?:^6011[0-9]{12}$)" + discoverPattern = regexp.MustCompile(discoverExpression) + + // jcbExpression is the regexp for the JCB 15 cards. They start with 2131, 1800, and has 15 digits, or start with 35 and has 16 digits. + jcbExpression = "(?:^(?:(?:2131)|(?:1800)|(?:35[0-9]{3}))[0-9]{11}$)" + jcbPattern = regexp.MustCompile(jcbExpression) + + // masterCardExpression is the regexp for the MasterCard cards. They start with 51, 52, 53, 54, or 55, and has 15 digits. + masterCardExpression = "(?:^5[12345][0-9]{14}$)" + masterCardPattern = regexp.MustCompile(masterCardExpression) + + // unionPayExpression is the regexp for the UnionPay cards. They start either with 62 or 67, and has 16 digits, or they start with 81 and has 16 to 19 digits. + unionPayExpression = "(?:(?:6[27][0-9]{14})|(?:81[0-9]{14,17})^$)" + unionPayPattern = regexp.MustCompile(unionPayExpression) + + // visaExpression is the regexp for the Visa cards. They start with 4 and has 13 or 16 digits. + visaExpression = "(?:^4[0-9]{12}(?:[0-9]{3})?$)" + visaPattern = regexp.MustCompile(visaExpression) + + // anyCreditCardPattern is the regexp for any credit card. + anyCreditCardPattern = regexp.MustCompile(strings.Join([]string{ + amexExpression, + dinersExpression, + discoverExpression, + jcbExpression, + masterCardExpression, + unionPayExpression, + visaExpression, + }, "|")) + + // creditCardPatterns is the mapping for credit card names to patterns. + creditCardPatterns = map[string]*regexp.Regexp{ + "amex": amexPattern, + "diners": dinersPattern, + "discover": discoverPattern, + "jcb": jcbPattern, + "mastercard": masterCardPattern, + "unionpay": unionPayPattern, + "visa": visaPattern, + } +) + +// IsAnyCreditCard checks if the given value is a valid credit card number. +func IsAnyCreditCard(number string) (string, error) { + return isCreditCard(number, anyCreditCardPattern) +} + +// IsAmexCreditCard checks if the given valie is a valid AMEX credit card. +func IsAmexCreditCard(number string) (string, error) { + return isCreditCard(number, amexPattern) +} + +// IsDinersCreditCard checks if the given valie is a valid Diners credit card. +func IsDinersCreditCard(number string) (string, error) { + return isCreditCard(number, dinersPattern) +} + +// IsDiscoverCreditCard checks if the given valie is a valid Discover credit card. +func IsDiscoverCreditCard(number string) (string, error) { + return isCreditCard(number, discoverPattern) +} + +// IsJcbCreditCard checks if the given valie is a valid JCB 15 credit card. +func IsJcbCreditCard(number string) (string, error) { + return isCreditCard(number, jcbPattern) +} + +// IsMasterCardCreditCard checks if the given valie is a valid MasterCard credit card. +func IsMasterCardCreditCard(number string) (string, error) { + return isCreditCard(number, masterCardPattern) +} + +// IsUnionPayCreditCard checks if the given valie is a valid UnionPay credit card. +func IsUnionPayCreditCard(number string) (string, error) { + return isCreditCard(number, unionPayPattern) +} + +// IsVisaCreditCard checks if the given valie is a valid Visa credit card. +func IsVisaCreditCard(number string) (string, error) { + return isCreditCard(number, visaPattern) +} + +// makeCreditCard makes a checker function for the credit card checker. +func makeCreditCard(config string) CheckFunc[reflect.Value] { + patterns := []*regexp.Regexp{} + + if config != "" { + for _, card := range strings.Split(config, ",") { + pattern, ok := creditCardPatterns[card] + if !ok { + panic("unknown credit card name") + } + + patterns = append(patterns, pattern) + } + } else { + patterns = append(patterns, anyCreditCardPattern) + } + + return func(value reflect.Value) (reflect.Value, error) { + if value.Kind() != reflect.String { + panic("string expected") + } + + number := value.String() + + for _, pattern := range patterns { + _, err := isCreditCard(number, pattern) + if err == nil { + return value, nil + } + } + + return value, ErrNotCreditCard + } +} + +// isCreditCard checks if the given number based on the given credit card pattern and the Luhn algorithm check digit. +func isCreditCard(number string, pattern *regexp.Regexp) (string, error) { + if !pattern.MatchString(number) { + return number, ErrNotCreditCard + } + + return IsLUHN(number) +} diff --git a/v2/credit_card_test.go b/v2/credit_card_test.go new file mode 100644 index 0000000..efedc17 --- /dev/null +++ b/v2/credit_card_test.go @@ -0,0 +1,312 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "strconv" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +// Test numbers from https://stripe.com/docs/testing +var invalidCard = "1234123412341234" +var amexCard = "378282246310005" +var dinersCard = "36227206271667" +var discoverCard = "6011111111111117" +var jcbCard = "3530111333300000" +var masterCard = "5555555555554444" +var unionPayCard = "6200000000000005" +var visaCard = "4111111111111111" + +// changeToInvalidLuhn increments the luhn digit to make the number invalid. It assumes that the given number is valid. +func changeToInvalidLuhn(number string) string { + luhn, err := strconv.Atoi(number[len(number)-1:]) + if err != nil { + panic(err) + } + + luhn = (luhn + 1) % 10 + + return number[:len(number)-1] + strconv.Itoa(luhn) +} + +func ExampleIsAnyCreditCard() { + _, err := v2.IsAnyCreditCard("6011111111111117") + + if err != nil { + // Send the errors back to the user + } +} + +func TestIsAnyCreditCardValid(t *testing.T) { + _, err := v2.IsAnyCreditCard(amexCard) + if err != nil { + t.Error(err) + } +} + +func TestIsAnyCreditCardInvalidPattern(t *testing.T) { + if _, err := v2.IsAnyCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") + } +} + +func TestIsAnyCreditCardInvalidLuhn(t *testing.T) { + if _, err := v2.IsAnyCreditCard(changeToInvalidLuhn(amexCard)); err == nil { + t.Error("expected error for invalid Luhn") + } +} + +func ExampleIsAmexCreditCard() { + _, err := v2.IsAmexCreditCard("378282246310005") + + if err != nil { + // Send the errors back to the user + } +} + +func TestIsAmexCreditCardValid(t *testing.T) { + if _, err := v2.IsAmexCreditCard(amexCard); err != nil { + t.Error(err) + } +} + +func TestIsAmexCreditCardInvalidPattern(t *testing.T) { + if _, err := v2.IsAmexCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") + } +} + +func TestIsAmexCreditCardInvalidLuhn(t *testing.T) { + if _, err := v2.IsAmexCreditCard(changeToInvalidLuhn(amexCard)); err == nil { + t.Error("expected error for invalid Luhn") + } +} + +func ExampleIsDinersCreditCard() { + _, err := v2.IsDinersCreditCard("36227206271667") + + if err != nil { + // Send the errors back to the user + } +} +func TestIsDinersCreditCardValid(t *testing.T) { + if _, err := v2.IsDinersCreditCard(dinersCard); err != nil { + t.Error(err) + } +} + +func TestIsDinersCreditCardInvalidPattern(t *testing.T) { + if _, err := v2.IsDinersCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") + } +} + +func TestIsDinersCreditCardInvalidLuhn(t *testing.T) { + if _, err := v2.IsDinersCreditCard(changeToInvalidLuhn(dinersCard)); err == nil { + t.Error("expected error for invalid Luhn") + } +} + +func ExampleIsDiscoverCreditCard() { + _, err := v2.IsDiscoverCreditCard("6011111111111117") + + if err != nil { + // Send the errors back to the user + } +} +func TestIsDiscoverCreditCardValid(t *testing.T) { + if _, err := v2.IsDiscoverCreditCard(discoverCard); err != nil { + t.Error(err) + } +} + +func TestIsDiscoverCreditCardInvalidPattern(t *testing.T) { + if _, err := v2.IsDiscoverCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") + } +} + +func TestIsDiscoverCreditCardInvalidLuhn(t *testing.T) { + if _, err := v2.IsDiscoverCreditCard(changeToInvalidLuhn(discoverCard)); err == nil { + t.Error("expected error for invalid Luhn") + } +} + +func ExampleIsJcbCreditCard() { + _, err := v2.IsJcbCreditCard("3530111333300000") + + if err != nil { + // Send the errors back to the user + } +} + +func TestIsJcbCreditCardValid(t *testing.T) { + if _, err := v2.IsJcbCreditCard(jcbCard); err != nil { + t.Error(err) + } +} + +func TestIsJcbCreditCardInvalidPattern(t *testing.T) { + if _, err := v2.IsJcbCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") + } +} + +func TestIsJcbCreditCardInvalidLuhn(t *testing.T) { + if _, err := v2.IsJcbCreditCard(changeToInvalidLuhn(jcbCard)); err == nil { + t.Error("expected error for invalid Luhn") + } +} + +func ExampleIsMasterCardCreditCard() { + _, err := v2.IsMasterCardCreditCard("5555555555554444") + + if err != nil { + // Send the errors back to the user + } +} + +func TestIsMasterCardCreditCardValid(t *testing.T) { + if _, err := v2.IsMasterCardCreditCard(masterCard); err != nil { + t.Error(err) + } +} + +func TestIsMasterCardCreditCardInvalidPattern(t *testing.T) { + if _, err := v2.IsMasterCardCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") + } +} + +func TestIsMasterCardCreditCardInvalidLuhn(t *testing.T) { + if _, err := v2.IsMasterCardCreditCard(changeToInvalidLuhn(masterCard)); err == nil { + t.Error("expected error for invalid Luhn") + } +} + +func ExampleIsUnionPayCreditCard() { + _, err := v2.IsUnionPayCreditCard("6200000000000005") + + if err != nil { + // Send the errors back to the user + } +} + +func TestIsUnionPayCreditCardValid(t *testing.T) { + if _, err := v2.IsUnionPayCreditCard(unionPayCard); err != nil { + t.Error(err) + } +} + +func TestIsUnionPayCreditCardInvalidPattern(t *testing.T) { + if _, err := v2.IsUnionPayCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") + } +} + +func TestIsUnionPayCreditCardInvalidLuhn(t *testing.T) { + if _, err := v2.IsUnionPayCreditCard(changeToInvalidLuhn(unionPayCard)); err == nil { + t.Error("expected error for invalid Luhn") + } +} + +func ExampleIsVisaCreditCard() { + _, err := v2.IsVisaCreditCard("4111111111111111") + + if err != nil { + // Send the errors back to the user + } +} +func TestIsVisaCreditCardValid(t *testing.T) { + if _, err := v2.IsVisaCreditCard(visaCard); err != nil { + t.Error(err) + } +} + +func TestIsVisaCreditCardInvalidPattern(t *testing.T) { + if _, err := v2.IsVisaCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") + } +} + +func TestIsVisaCreditCardInvalidLuhn(t *testing.T) { + if _, err := v2.IsVisaCreditCard(changeToInvalidLuhn(visaCard)); err == nil { + t.Error("expected error for invalid Luhn") + } +} + +func TestCheckCreditCardNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic for non-string credit card") + + type Order struct { + CreditCard int `checkers:"credit-card"` + } + + order := &Order{} + + v2.CheckStruct(order) +} + +func TestCheckCreditCardValid(t *testing.T) { + type Order struct { + CreditCard string `checkers:"credit-card"` + } + + order := &Order{ + CreditCard: amexCard, + } + + _, valid := v2.CheckStruct(order) + if !valid { + t.Fail() + } +} + +func TestCheckCreditCardInvalid(t *testing.T) { + type Order struct { + CreditCard string `checkers:"credit-card"` + } + + order := &Order{ + CreditCard: invalidCard, + } + + _, valid := v2.CheckStruct(order) + if valid { + t.Fail() + } +} + +func TestCheckCreditCardMultipleUnknown(t *testing.T) { + defer FailIfNoPanic(t, "expected panic for unknown credit card") + + type Order struct { + CreditCard string `checkers:"credit-card:amex,unknown"` + } + + order := &Order{ + CreditCard: amexCard, + } + + v2.CheckStruct(order) +} + +func TestCheckCreditCardMultipleInvalid(t *testing.T) { + type Order struct { + CreditCard string `checkers:"credit-card:amex,visa"` + } + + order := &Order{ + CreditCard: discoverCard, + } + + _, valid := v2.CheckStruct(order) + if valid { + t.Fail() + } +} diff --git a/v2/isbn_test.go b/v2/isbn_test.go index 2f763cf..cb8c48c 100644 --- a/v2/isbn_test.go +++ b/v2/isbn_test.go @@ -13,7 +13,7 @@ import ( ) func ExampleIsISBN() { - _, err := v2.IsISBN("9783161484100") + _, err := v2.IsISBN("1430248270") if err != nil { fmt.Println(err) } @@ -27,7 +27,7 @@ func TestIsISBNInvalid(t *testing.T) { } func TestIsISBNValid(t *testing.T) { - _, err := v2.IsISBN("9783161484100") + _, err := v2.IsISBN("1430248270") if err != nil { t.Fatal(err) } diff --git a/v2/luhn_test.go b/v2/luhn_test.go index 2dae79d..7831ccc 100644 --- a/v2/luhn_test.go +++ b/v2/luhn_test.go @@ -13,7 +13,7 @@ import ( ) func ExampleIsLUHN() { - _, err := v2.IsLUHN("79927398713") + _, err := v2.IsLUHN("4012888888881881") if err != nil { fmt.Println(err) } @@ -27,7 +27,7 @@ func TestIsLUHNInvalid(t *testing.T) { } func TestIsLUHNValid(t *testing.T) { - _, err := v2.IsLUHN("79927398713") + _, err := v2.IsLUHN("4012888888881881") if err != nil { t.Fatal(err) } diff --git a/v2/maker.go b/v2/maker.go index a056507..b5db335 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -19,6 +19,7 @@ var makers = map[string]MakeCheckFunc{ nameAlphanumeric: makeAlphanumeric, nameASCII: makeASCII, nameCIDR: makeCIDR, + nameCreditCard: makeCreditCard, nameDigits: makeDigits, nameEmail: makeEmail, nameFQDN: makeFQDN, From 34b664525008948e53b48aed3571106dee3e3e09 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 27 Dec 2024 06:21:28 -0800 Subject: [PATCH 21/41] Add URL escape normalizer and tests to v2. (#142) # Describe Request Add URL escape normalizer and tests to v2. # Change Type New code. --- v2/maker.go | 1 + v2/url_escape.go | 37 +++++++++++++++++++++++++++++++++++++ v2/url_escape_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 v2/url_escape.go create mode 100644 v2/url_escape_test.go diff --git a/v2/maker.go b/v2/maker.go index b5db335..cc27992 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -34,6 +34,7 @@ var makers = map[string]MakeCheckFunc{ nameRequired: makeRequired, nameTrimSpace: makeTrimSpace, nameURL: makeURL, + nameURLEscape: makeURLEscape, } // makeChecks take a checker config and returns the check functions. diff --git a/v2/url_escape.go b/v2/url_escape.go new file mode 100644 index 0000000..9792281 --- /dev/null +++ b/v2/url_escape.go @@ -0,0 +1,37 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "net/url" + "reflect" +) + +const ( + // nameURLEscape is the name of the URL escape normalizer. + nameURLEscape = "url-escape" +) + +// normalizeURLEscape applies URL escaping to special characters. +// Uses net.url.QueryEscape for the actual escape operation. +func normalizeURLEscape(value string) (string, error) { + return url.QueryEscape(value), nil +} + +// checkURLEscape checks if the value is a valid URL escape string. +func checkURLEscape(value reflect.Value) (reflect.Value, error) { + escaped, err := normalizeURLEscape(value.Interface().(string)) + if err != nil { + return value, err + } + value.SetString(escaped) + return value, nil +} + +// makeURLEscape makes a normalizer function for the URL escape normalizer. +func makeURLEscape(_ string) CheckFunc[reflect.Value] { + return checkURLEscape +} diff --git a/v2/url_escape_test.go b/v2/url_escape_test.go new file mode 100644 index 0000000..9d64352 --- /dev/null +++ b/v2/url_escape_test.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestNormalizeURLEscapeNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Request struct { + Query int `checkers:"url-escape"` + } + + request := &Request{} + + v2.CheckStruct(request) +} + +func TestNormalizeURLEscape(t *testing.T) { + type Request struct { + Query string `checkers:"url-escape"` + } + + request := &Request{ + Query: "param1/param2 = 1 + 2 & 3 + 4", + } + + _, valid := v2.CheckStruct(request) + if !valid { + t.Fail() + } + + if request.Query != "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4" { + t.Fail() + } +} From 6779e95f12ec0c87c52ac19a312c31f4037a66bb Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 27 Dec 2024 06:39:57 -0800 Subject: [PATCH 22/41] Add URL escape and unespace normalizers and tests to v2. (#143) # Describe Request Add URL escape and unespace normalizers and tests to v2. # Change Type New code. --- v2/maker.go | 1 + v2/url_escape.go | 27 +++++++++-------------- v2/url_escape_test.go | 30 ++++++++++++++------------ v2/url_unescape.go | 31 +++++++++++++++++++++++++++ v2/url_unescape_test.go | 47 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+), 30 deletions(-) create mode 100644 v2/url_unescape.go create mode 100644 v2/url_unescape_test.go diff --git a/v2/maker.go b/v2/maker.go index cc27992..cbc3fd4 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -35,6 +35,7 @@ var makers = map[string]MakeCheckFunc{ nameTrimSpace: makeTrimSpace, nameURL: makeURL, nameURLEscape: makeURLEscape, + nameURLUnescape: makeURLUnescape, } // makeChecks take a checker config and returns the check functions. diff --git a/v2/url_escape.go b/v2/url_escape.go index 9792281..439d91b 100644 --- a/v2/url_escape.go +++ b/v2/url_escape.go @@ -10,28 +10,21 @@ import ( "reflect" ) -const ( - // nameURLEscape is the name of the URL escape normalizer. - nameURLEscape = "url-escape" -) +// nameURLEscape is the name of the URL escape normalizer. +const nameURLEscape = "url-escape" -// normalizeURLEscape applies URL escaping to special characters. -// Uses net.url.QueryEscape for the actual escape operation. -func normalizeURLEscape(value string) (string, error) { +// URLEscape applies URL escaping to special characters. +func URLEscape(value string) (string, error) { return url.QueryEscape(value), nil } -// checkURLEscape checks if the value is a valid URL escape string. -func checkURLEscape(value reflect.Value) (reflect.Value, error) { - escaped, err := normalizeURLEscape(value.Interface().(string)) - if err != nil { - return value, err - } - value.SetString(escaped) - return value, nil +// reflectURLEscape applies URL escaping to special characters. +func reflectURLEscape(value reflect.Value) (reflect.Value, error) { + newValue, err := URLEscape(value.Interface().(string)) + return reflect.ValueOf(newValue), err } -// makeURLEscape makes a normalizer function for the URL escape normalizer. +// makeURLEscape returns the URL escape normalizer function. func makeURLEscape(_ string) CheckFunc[reflect.Value] { - return checkURLEscape + return reflectURLEscape } diff --git a/v2/url_escape_test.go b/v2/url_escape_test.go index 9d64352..badc16c 100644 --- a/v2/url_escape_test.go +++ b/v2/url_escape_test.go @@ -11,19 +11,21 @@ import ( v2 "github.com/cinar/checker/v2" ) -func TestNormalizeURLEscapeNonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") +func TestURLEscape(t *testing.T) { + input := "param1/param2 = 1 + 2 & 3 + 4" + expected := "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4" - type Request struct { - Query int `checkers:"url-escape"` + actual, err := v2.URLEscape(input) + if err != nil { + t.Fatal(err) } - request := &Request{} - - v2.CheckStruct(request) + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } } -func TestNormalizeURLEscape(t *testing.T) { +func TestReflectURLEscape(t *testing.T) { type Request struct { Query string `checkers:"url-escape"` } @@ -32,12 +34,14 @@ func TestNormalizeURLEscape(t *testing.T) { Query: "param1/param2 = 1 + 2 & 3 + 4", } - _, valid := v2.CheckStruct(request) - if !valid { - t.Fail() + expected := "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4" + + errs, ok := v2.CheckStruct(request) + if !ok { + t.Fatalf("got unexpected errors %v", errs) } - if request.Query != "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4" { - t.Fail() + if request.Query != expected { + t.Fatalf("actual %s expected %s", request.Query, expected) } } diff --git a/v2/url_unescape.go b/v2/url_unescape.go new file mode 100644 index 0000000..c48045c --- /dev/null +++ b/v2/url_unescape.go @@ -0,0 +1,31 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "net/url" + "reflect" +) + +// nameURLUnescape is the name of the URL unescape normalizer. +const nameURLUnescape = "url-unescape" + +// URLUnescape applies URL unescaping to special characters. +func URLUnescape(value string) (string, error) { + unescaped, err := url.QueryUnescape(value) + return unescaped, err +} + +// reflectURLUnescape applies URL unescaping to special characters. +func reflectURLUnescape(value reflect.Value) (reflect.Value, error) { + newValue, err := URLUnescape(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeURLUnescape returns the URL unescape normalizer function. +func makeURLUnescape(_ string) CheckFunc[reflect.Value] { + return reflectURLUnescape +} diff --git a/v2/url_unescape_test.go b/v2/url_unescape_test.go new file mode 100644 index 0000000..21b2c43 --- /dev/null +++ b/v2/url_unescape_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestURLUnescape(t *testing.T) { + input := "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4" + expected := "param1/param2 = 1 + 2 & 3 + 4" + + actual, err := v2.URLUnescape(input) + if err != nil { + t.Fatal(err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestReflectURLUnescape(t *testing.T) { + type Request struct { + Query string `checkers:"url-unescape"` + } + + request := &Request{ + Query: "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4", + } + + expected := "param1/param2 = 1 + 2 & 3 + 4" + + errs, ok := v2.CheckStruct(request) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if request.Query != expected { + t.Fatalf("actual %s expected %s", request.Query, expected) + } +} From 1cff930c51eafeaab9d8d15ca62c2bc8335c599d Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 27 Dec 2024 06:49:58 -0800 Subject: [PATCH 23/41] Add HTML escape normalizer and tests to v2. (#144) # Describe Request Add HTML escape normalizer and tests to v2. # Change Type New code. --- v2/html_escape.go | 32 +++++++++++++++++++++++++++ v2/html_escape_test.go | 47 ++++++++++++++++++++++++++++++++++++++++ v2/html_unescape.go | 30 +++++++++++++++++++++++++ v2/html_unescape_test.go | 47 ++++++++++++++++++++++++++++++++++++++++ v2/maker.go | 2 ++ 5 files changed, 158 insertions(+) create mode 100644 v2/html_escape.go create mode 100644 v2/html_escape_test.go create mode 100644 v2/html_unescape.go create mode 100644 v2/html_unescape_test.go diff --git a/v2/html_escape.go b/v2/html_escape.go new file mode 100644 index 0000000..e209d5b --- /dev/null +++ b/v2/html_escape.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "html" + "reflect" +) + +const ( + // nameHTMLEscape is the name of the HTML escape normalizer. + nameHTMLEscape = "html-escape" +) + +// HTMLEscape applies HTML escaping to special characters. +func HTMLEscape(value string) (string, error) { + return html.EscapeString(value), nil +} + +// reflectHTMLEscape applies HTML escaping to special characters. +func reflectHTMLEscape(value reflect.Value) (reflect.Value, error) { + newValue, err := HTMLEscape(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeHTMLEscape returns the HTML escape normalizer function. +func makeHTMLEscape(_ string) CheckFunc[reflect.Value] { + return reflectHTMLEscape +} diff --git a/v2/html_escape_test.go b/v2/html_escape_test.go new file mode 100644 index 0000000..8a60010 --- /dev/null +++ b/v2/html_escape_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestHTMLEscape(t *testing.T) { + input := " \"Checker\" & 'Library' " + expected := "<tag> "Checker" & 'Library' </tag>" + + actual, err := v2.HTMLEscape(input) + if err != nil { + t.Fatal(err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestReflectHTMLEscape(t *testing.T) { + type Comment struct { + Body string `checkers:"html-escape"` + } + + comment := &Comment{ + Body: " \"Checker\" & 'Library' ", + } + + expected := "<tag> "Checker" & 'Library' </tag>" + + errs, ok := v2.CheckStruct(comment) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if comment.Body != expected { + t.Fatalf("actual %s expected %s", comment.Body, expected) + } +} diff --git a/v2/html_unescape.go b/v2/html_unescape.go new file mode 100644 index 0000000..27ba376 --- /dev/null +++ b/v2/html_unescape.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "html" + "reflect" +) + +// nameHTMLUnescape is the name of the HTML unescape normalizer. +const nameHTMLUnescape = "html-unescape" + +// HTMLUnescape applies HTML unescaping to special characters. +func HTMLUnescape(value string) (string, error) { + return html.UnescapeString(value), nil +} + +// reflectHTMLUnescape applies HTML unescaping to special characters. +func reflectHTMLUnescape(value reflect.Value) (reflect.Value, error) { + newValue, err := HTMLUnescape(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeHTMLUnescape returns the HTML unescape normalizer function. +func makeHTMLUnescape(_ string) CheckFunc[reflect.Value] { + return reflectHTMLUnescape +} diff --git a/v2/html_unescape_test.go b/v2/html_unescape_test.go new file mode 100644 index 0000000..1bf735b --- /dev/null +++ b/v2/html_unescape_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestHTMLUnescape(t *testing.T) { + input := "<tag> "Checker" & 'Library' </tag>" + expected := " \"Checker\" & 'Library' " + + actual, err := v2.HTMLUnescape(input) + if err != nil { + t.Fatal(err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestReflectHTMLUnescape(t *testing.T) { + type Comment struct { + Body string `checkers:"html-unescape"` + } + + comment := &Comment{ + Body: "<tag> "Checker" & 'Library' </tag>", + } + + expected := " \"Checker\" & 'Library' " + + errs, ok := v2.CheckStruct(comment) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if comment.Body != expected { + t.Fatalf("actual %s expected %s", comment.Body, expected) + } +} diff --git a/v2/maker.go b/v2/maker.go index cbc3fd4..038abf0 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -22,6 +22,8 @@ var makers = map[string]MakeCheckFunc{ nameCreditCard: makeCreditCard, nameDigits: makeDigits, nameEmail: makeEmail, + nameHTMLEscape: makeHTMLEscape, + nameHTMLUnescape: makeHTMLUnescape, nameFQDN: makeFQDN, nameIP: makeIP, nameIPv4: makeIPv4, From e24eb024317b8fc1d456b88d41396474834cb2e5 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 27 Dec 2024 06:55:23 -0800 Subject: [PATCH 24/41] Add trim left and right normalizers and tests to v2. (#145) # Describe Request Add trim left and right normalizers and tests to v2. # Change Type New code. --- v2/maker.go | 2 ++ v2/trim_left.go | 32 +++++++++++++++++++++++++++++ v2/trim_left_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++ v2/trim_right.go | 32 +++++++++++++++++++++++++++++ v2/trim_right_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 160 insertions(+) create mode 100644 v2/trim_left.go create mode 100644 v2/trim_left_test.go create mode 100644 v2/trim_right.go create mode 100644 v2/trim_right_test.go diff --git a/v2/maker.go b/v2/maker.go index 038abf0..e65bcb3 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -34,6 +34,8 @@ var makers = map[string]MakeCheckFunc{ nameMaxLen: makeMaxLen, nameMinLen: makeMinLen, nameRequired: makeRequired, + nameTrimLeft: makeTrimLeft, + nameTrimRight: makeTrimRight, nameTrimSpace: makeTrimSpace, nameURL: makeURL, nameURLEscape: makeURLEscape, diff --git a/v2/trim_left.go b/v2/trim_left.go new file mode 100644 index 0000000..20e41d2 --- /dev/null +++ b/v2/trim_left.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "strings" +) + +const ( + // nameTrimLeft is the name of the trim left normalizer. + nameTrimLeft = "trim-left" +) + +// TrimLeft returns the value of the string with whitespace removed from the beginning. +func TrimLeft(value string) (string, error) { + return strings.TrimLeft(value, " \t"), nil +} + +// reflectTrimLeft returns the value of the string with whitespace removed from the beginning. +func reflectTrimLeft(value reflect.Value) (reflect.Value, error) { + newValue, err := TrimLeft(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeTrimLeft returns the trim left normalizer function. +func makeTrimLeft(_ string) CheckFunc[reflect.Value] { + return reflectTrimLeft +} diff --git a/v2/trim_left_test.go b/v2/trim_left_test.go new file mode 100644 index 0000000..3989789 --- /dev/null +++ b/v2/trim_left_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestTrimLeft(t *testing.T) { + input := " test " + expected := "test " + + actual, err := v2.TrimLeft(input) + if err != nil { + t.Fatal(err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestReflectTrimLeft(t *testing.T) { + type Person struct { + Name string `checkers:"trim-left"` + } + + person := &Person{ + Name: " test ", + } + + expected := "test " + + errs, ok := v2.CheckStruct(person) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if person.Name != expected { + t.Fatalf("actual %s expected %s", person.Name, expected) + } +} diff --git a/v2/trim_right.go b/v2/trim_right.go new file mode 100644 index 0000000..aeae386 --- /dev/null +++ b/v2/trim_right.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "strings" +) + +const ( + // nameTrimRight is the name of the trim right normalizer. + nameTrimRight = "trim-right" +) + +// TrimRight returns the value of the string with whitespace removed from the end. +func TrimRight(value string) (string, error) { + return strings.TrimRight(value, " \t"), nil +} + +// reflectTrimRight returns the value of the string with whitespace removed from the end. +func reflectTrimRight(value reflect.Value) (reflect.Value, error) { + newValue, err := TrimRight(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeTrimRight returns the trim right normalizer function. +func makeTrimRight(_ string) CheckFunc[reflect.Value] { + return reflectTrimRight +} diff --git a/v2/trim_right_test.go b/v2/trim_right_test.go new file mode 100644 index 0000000..e75eb45 --- /dev/null +++ b/v2/trim_right_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestTrimRight(t *testing.T) { + input := " test " + expected := " test" + + actual, err := v2.TrimRight(input) + if err != nil { + t.Fatal(err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestReflectTrimRight(t *testing.T) { + type Person struct { + Name string `checkers:"trim-right"` + } + + person := &Person{ + Name: " test ", + } + + expected := " test" + + errs, ok := v2.CheckStruct(person) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if person.Name != expected { + t.Fatalf("actual %s expected %s", person.Name, expected) + } +} From 36a112c2c195a69c25742a846f687599168c2d63 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 27 Dec 2024 07:00:53 -0800 Subject: [PATCH 25/41] Add upper and lower normalizers and tests to v2. (#146) # Describe Request Add upper and lower normalizers and tests to v2. # Change Type New code. --- v2/lower.go | 32 ++++++++++++++++++++++++++++++++ v2/lower_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ v2/maker.go | 4 +++- v2/upper.go | 32 ++++++++++++++++++++++++++++++++ v2/upper_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 v2/lower.go create mode 100644 v2/lower_test.go create mode 100644 v2/upper.go create mode 100644 v2/upper_test.go diff --git a/v2/lower.go b/v2/lower.go new file mode 100644 index 0000000..803ca99 --- /dev/null +++ b/v2/lower.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "strings" +) + +const ( + // nameLower is the name of the lower normalizer. + nameLower = "lower" +) + +// Lower maps all Unicode letters in the given value to their lower case. +func Lower(value string) (string, error) { + return strings.ToLower(value), nil +} + +// reflectLower maps all Unicode letters in the given value to their lower case. +func reflectLower(value reflect.Value) (reflect.Value, error) { + newValue, err := Lower(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeLower returns the lower normalizer function. +func makeLower(_ string) CheckFunc[reflect.Value] { + return reflectLower +} diff --git a/v2/lower_test.go b/v2/lower_test.go new file mode 100644 index 0000000..491e671 --- /dev/null +++ b/v2/lower_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestLower(t *testing.T) { + input := "CHECKER" + expected := "checker" + + actual, err := v2.Lower(input) + if err != nil { + t.Fatal(err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestReflectLower(t *testing.T) { + type Person struct { + Name string `checkers:"lower"` + } + + person := &Person{ + Name: "CHECKER", + } + + expected := "checker" + + errs, ok := v2.CheckStruct(person) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if person.Name != expected { + t.Fatalf("actual %s expected %s", person.Name, expected) + } +} diff --git a/v2/maker.go b/v2/maker.go index e65bcb3..24b26e9 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -22,13 +22,14 @@ var makers = map[string]MakeCheckFunc{ nameCreditCard: makeCreditCard, nameDigits: makeDigits, nameEmail: makeEmail, + nameFQDN: makeFQDN, nameHTMLEscape: makeHTMLEscape, nameHTMLUnescape: makeHTMLUnescape, - nameFQDN: makeFQDN, nameIP: makeIP, nameIPv4: makeIPv4, nameIPv6: makeIPv6, nameISBN: makeISBN, + nameLower: makeLower, nameLUHN: makeLUHN, nameMAC: makeMAC, nameMaxLen: makeMaxLen, @@ -37,6 +38,7 @@ var makers = map[string]MakeCheckFunc{ nameTrimLeft: makeTrimLeft, nameTrimRight: makeTrimRight, nameTrimSpace: makeTrimSpace, + nameUpper: makeUpper, nameURL: makeURL, nameURLEscape: makeURLEscape, nameURLUnescape: makeURLUnescape, diff --git a/v2/upper.go b/v2/upper.go new file mode 100644 index 0000000..cd8ea86 --- /dev/null +++ b/v2/upper.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "strings" +) + +const ( + // nameUpper is the name of the upper normalizer. + nameUpper = "upper" +) + +// Upper maps all Unicode letters in the given value to their upper case. +func Upper(value string) (string, error) { + return strings.ToUpper(value), nil +} + +// reflectUpper maps all Unicode letters in the given value to their upper case. +func reflectUpper(value reflect.Value) (reflect.Value, error) { + newValue, err := Upper(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeUpper returns the upper normalizer function. +func makeUpper(_ string) CheckFunc[reflect.Value] { + return reflectUpper +} diff --git a/v2/upper_test.go b/v2/upper_test.go new file mode 100644 index 0000000..d7fa127 --- /dev/null +++ b/v2/upper_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestUpper(t *testing.T) { + input := "checker" + expected := "CHECKER" + + actual, err := v2.Upper(input) + if err != nil { + t.Fatal(err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestReflectUpper(t *testing.T) { + type Person struct { + Name string `checkers:"upper"` + } + + person := &Person{ + Name: "checker", + } + + expected := "CHECKER" + + errs, ok := v2.CheckStruct(person) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if person.Name != expected { + t.Fatalf("actual %s expected %s", person.Name, expected) + } +} From 08498b79292346e8345cbe6a64d89764f2f2e373 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 27 Dec 2024 07:06:05 -0800 Subject: [PATCH 26/41] Add title normalizer and tests to v2. (#147) # Describe Request Add title normalizer and tests to v2. # Change Type New code. --- v2/maker.go | 1 + v2/title.go | 51 ++++++++++++++++++++++++++++++++++++++++++++++++ v2/title_test.go | 47 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 v2/title.go create mode 100644 v2/title_test.go diff --git a/v2/maker.go b/v2/maker.go index 24b26e9..2f60597 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -35,6 +35,7 @@ var makers = map[string]MakeCheckFunc{ nameMaxLen: makeMaxLen, nameMinLen: makeMinLen, nameRequired: makeRequired, + nameTitle: makeTitle, nameTrimLeft: makeTrimLeft, nameTrimRight: makeTrimRight, nameTrimSpace: makeTrimSpace, diff --git a/v2/title.go b/v2/title.go new file mode 100644 index 0000000..5d4fd54 --- /dev/null +++ b/v2/title.go @@ -0,0 +1,51 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "strings" + "unicode" +) + +const ( + // nameTitle is the name of the title normalizer. + nameTitle = "title" +) + +// Title returns the value of the string with the first letter of each word in upper case. +func Title(value string) (string, error) { + var sb strings.Builder + begin := true + + for _, c := range value { + if unicode.IsLetter(c) { + if begin { + c = unicode.ToUpper(c) + begin = false + } else { + c = unicode.ToLower(c) + } + } else { + begin = true + } + + sb.WriteRune(c) + } + + return sb.String(), nil +} + +// reflectTitle returns the value of the string with the first letter of each word in upper case. +func reflectTitle(value reflect.Value) (reflect.Value, error) { + newValue, err := Title(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeTitle returns the title normalizer function. +func makeTitle(_ string) CheckFunc[reflect.Value] { + return reflectTitle +} diff --git a/v2/title_test.go b/v2/title_test.go new file mode 100644 index 0000000..ad278d1 --- /dev/null +++ b/v2/title_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestTitle(t *testing.T) { + input := "the checker" + expected := "The Checker" + + actual, err := v2.Title(input) + if err != nil { + t.Fatal(err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestReflectTitle(t *testing.T) { + type Book struct { + Chapter string `checkers:"title"` + } + + book := &Book{ + Chapter: "the checker", + } + + expected := "The Checker" + + errs, ok := v2.CheckStruct(book) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if book.Chapter != expected { + t.Fatalf("actual %s expected %s", book.Chapter, expected) + } +} From 23f30648057e3f649915888c6ce997e129c9f3ee Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 27 Dec 2024 07:17:09 -0800 Subject: [PATCH 27/41] Add Regexp checker and tests to v2. (#148) # Describe Request Add Regexp checker and tests to v2. # Change Type New code. --- v2/maker.go | 1 + v2/regexp.go | 47 ++++++++++++++++++++++++++++++++++ v2/regexp_test.go | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 v2/regexp.go create mode 100644 v2/regexp_test.go diff --git a/v2/maker.go b/v2/maker.go index 2f60597..2b1276e 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -34,6 +34,7 @@ var makers = map[string]MakeCheckFunc{ nameMAC: makeMAC, nameMaxLen: makeMaxLen, nameMinLen: makeMinLen, + nameRegexp: makeRegexp, nameRequired: makeRequired, nameTitle: makeTitle, nameTrimLeft: makeTrimLeft, diff --git a/v2/regexp.go b/v2/regexp.go new file mode 100644 index 0000000..426a6dd --- /dev/null +++ b/v2/regexp.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "regexp" +) + +// nameRegexp is the name of the regexp check. +const nameRegexp = "regexp" + +// ErrNotMatch indicates that the given string does not match the regexp pattern. +var ErrNotMatch = NewCheckError("REGEXP") + +// MakeRegexpChecker makes a regexp checker for the given regexp expression with the given invalid result. +func MakeRegexpChecker(expression string, invalidError error) CheckFunc[reflect.Value] { + return func(value reflect.Value) (reflect.Value, error) { + if value.Kind() != reflect.String { + panic("string expected") + } + + matched, err := regexp.MatchString(expression, value.String()) + if err != nil { + return value, err + } + + if !matched { + return value, invalidError + } + + return value, nil + } +} + +// makeRegexp makes a checker function for the regexp. +func makeRegexp(config string) CheckFunc[reflect.Value] { + return MakeRegexpChecker(config, ErrNotMatch) +} + +// checkRegexp checks if the given string matches the regexp pattern. +func checkRegexp(value reflect.Value) (reflect.Value, error) { + return makeRegexp(value.String())(value) +} diff --git a/v2/regexp_test.go b/v2/regexp_test.go new file mode 100644 index 0000000..e66a760 --- /dev/null +++ b/v2/regexp_test.go @@ -0,0 +1,64 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "reflect" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestCheckRegexpNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type User struct { + Username int `checkers:"regexp:^[A-Za-z]$"` + } + + user := &User{} + + v2.CheckStruct(user) +} + +func TestCheckRegexpInvalid(t *testing.T) { + type User struct { + Username string `checkers:"regexp:^[A-Za-z]+$"` + } + + user := &User{ + Username: "abcd1234", + } + + _, ok := v2.CheckStruct(user) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckRegexpValid(t *testing.T) { + type User struct { + Username string `checkers:"regexp:^[A-Za-z]+$"` + } + + user := &User{ + Username: "abcd", + } + + _, ok := v2.CheckStruct(user) + if !ok { + t.Fatal("expected valid") + } +} + +func TestMakeRegexpChecker(t *testing.T) { + checkHex := v2.MakeRegexpChecker("^[A-Fa-f0-9]+$", v2.ErrNotMatch) + + _, err := checkHex(reflect.ValueOf("f0f0f0")) + if err != nil { + t.Fail() + } +} From b9d2edb3cd781dc37e0ec77730526de2ecd59622 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 27 Dec 2024 07:44:26 -0800 Subject: [PATCH 28/41] Register maker is added. (#149) # Describe Request Register maker is added. # Change Type New code. --- v2/maker.go | 5 +++++ v2/maker_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/v2/maker.go b/v2/maker.go index 2b1276e..81866db 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -46,6 +46,11 @@ var makers = map[string]MakeCheckFunc{ nameURLUnescape: makeURLUnescape, } +// RegisterMaker registers a new maker function with the given name. +func RegisterMaker(name string, maker MakeCheckFunc) { + makers[name] = maker +} + // makeChecks take a checker config and returns the check functions. func makeChecks(config string) []CheckFunc[reflect.Value] { fields := strings.Fields(config) diff --git a/v2/maker_test.go b/v2/maker_test.go index 931183c..428bd3b 100644 --- a/v2/maker_test.go +++ b/v2/maker_test.go @@ -6,6 +6,8 @@ package v2_test import ( + "fmt" + "reflect" "testing" v2 "github.com/cinar/checker/v2" @@ -24,3 +26,51 @@ func TestMakeCheckersUnknown(t *testing.T) { v2.CheckStruct(person) } + +func ExampleRegisterMaker() { + v2.RegisterMaker("is-fruit", func(params string) v2.CheckFunc[reflect.Value] { + return func(value reflect.Value) (reflect.Value, error) { + stringValue := value.Interface().(string) + + if stringValue == "apple" || stringValue == "banana" { + return value, nil + } + + return value, v2.NewCheckError("NOT_FRUIT") + } + }) + + type Item struct { + Name string `checkers:"is-fruit"` + } + + person := &Item{ + Name: "banana", + } + + err, ok := v2.CheckStruct(person) + if !ok { + fmt.Println(err) + } +} + +func TestRegisterMaker(t *testing.T) { + v2.RegisterMaker("unknown", func(params string) v2.CheckFunc[reflect.Value] { + return func(value reflect.Value) (reflect.Value, error) { + return value, nil + } + }) + + type Person struct { + Name string `checkers:"unknown"` + } + + person := &Person{ + Name: "Onur", + } + + _, ok := v2.CheckStruct(person) + if !ok { + t.Fatal("expected valid") + } +} From d9516d6b82bb4d4a5292b00ed3b8dcde562f0fd9 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 27 Dec 2024 08:10:57 -0800 Subject: [PATCH 29/41] Readme updated. (#150) # Describe Request Readme updated. # Change Type Documentation update. --- v2/.gomarkdoc.yml | 3 + v2/DOC.md | 1217 +++++++++++++++++++++++++++++++++++++++++++++ v2/README.md | 169 +++++++ 3 files changed, 1389 insertions(+) create mode 100644 v2/.gomarkdoc.yml create mode 100644 v2/DOC.md create mode 100644 v2/README.md diff --git a/v2/.gomarkdoc.yml b/v2/.gomarkdoc.yml new file mode 100644 index 0000000..729da91 --- /dev/null +++ b/v2/.gomarkdoc.yml @@ -0,0 +1,3 @@ +output: "{{.Dir}}/DOC.md" +repository: + url: https://github.com/cinar/checker diff --git a/v2/DOC.md b/v2/DOC.md new file mode 100644 index 0000000..28e079c --- /dev/null +++ b/v2/DOC.md @@ -0,0 +1,1217 @@ + + + + +# v2 + +```go +import "github.com/cinar/checker/v2" +``` + +## Index + +- [Variables](<#variables>) +- [func Check\[T any\]\(value T, checks ...CheckFunc\[T\]\) \(T, error\)](<#Check>) +- [func CheckStruct\(st any\) \(map\[string\]error, bool\)](<#CheckStruct>) +- [func CheckWithConfig\[T any\]\(value T, config string\) \(T, error\)](<#CheckWithConfig>) +- [func HTMLEscape\(value string\) \(string, error\)](<#HTMLEscape>) +- [func HTMLUnescape\(value string\) \(string, error\)](<#HTMLUnescape>) +- [func IsASCII\(value string\) \(string, error\)](<#IsASCII>) +- [func IsAlphanumeric\(value string\) \(string, error\)](<#IsAlphanumeric>) +- [func IsAmexCreditCard\(number string\) \(string, error\)](<#IsAmexCreditCard>) +- [func IsAnyCreditCard\(number string\) \(string, error\)](<#IsAnyCreditCard>) +- [func IsCIDR\(value string\) \(string, error\)](<#IsCIDR>) +- [func IsDigits\(value string\) \(string, error\)](<#IsDigits>) +- [func IsDinersCreditCard\(number string\) \(string, error\)](<#IsDinersCreditCard>) +- [func IsDiscoverCreditCard\(number string\) \(string, error\)](<#IsDiscoverCreditCard>) +- [func IsEmail\(value string\) \(string, error\)](<#IsEmail>) +- [func IsFQDN\(value string\) \(string, error\)](<#IsFQDN>) +- [func IsIP\(value string\) \(string, error\)](<#IsIP>) +- [func IsIPv4\(value string\) \(string, error\)](<#IsIPv4>) +- [func IsIPv6\(value string\) \(string, error\)](<#IsIPv6>) +- [func IsISBN\(value string\) \(string, error\)](<#IsISBN>) +- [func IsJcbCreditCard\(number string\) \(string, error\)](<#IsJcbCreditCard>) +- [func IsLUHN\(value string\) \(string, error\)](<#IsLUHN>) +- [func IsMAC\(value string\) \(string, error\)](<#IsMAC>) +- [func IsMasterCardCreditCard\(number string\) \(string, error\)](<#IsMasterCardCreditCard>) +- [func IsURL\(value string\) \(string, error\)](<#IsURL>) +- [func IsUnionPayCreditCard\(number string\) \(string, error\)](<#IsUnionPayCreditCard>) +- [func IsVisaCreditCard\(number string\) \(string, error\)](<#IsVisaCreditCard>) +- [func Lower\(value string\) \(string, error\)](<#Lower>) +- [func ReflectCheckWithConfig\(value reflect.Value, config string\) \(reflect.Value, error\)](<#ReflectCheckWithConfig>) +- [func Required\[T any\]\(value T\) \(T, error\)](<#Required>) +- [func Title\(value string\) \(string, error\)](<#Title>) +- [func TrimLeft\(value string\) \(string, error\)](<#TrimLeft>) +- [func TrimRight\(value string\) \(string, error\)](<#TrimRight>) +- [func TrimSpace\(value string\) \(string, error\)](<#TrimSpace>) +- [func URLEscape\(value string\) \(string, error\)](<#URLEscape>) +- [func URLUnescape\(value string\) \(string, error\)](<#URLUnescape>) +- [func Upper\(value string\) \(string, error\)](<#Upper>) +- [type CheckError](<#CheckError>) + - [func NewCheckError\(code string\) \*CheckError](<#NewCheckError>) + - [func \(c \*CheckError\) Error\(\) string](<#CheckError.Error>) +- [type CheckFunc](<#CheckFunc>) + - [func MakeRegexpChecker\(expression string, invalidError error\) CheckFunc\[reflect.Value\]](<#MakeRegexpChecker>) + - [func MaxLen\[T any\]\(n int\) CheckFunc\[T\]](<#MaxLen>) + - [func MinLen\[T any\]\(n int\) CheckFunc\[T\]](<#MinLen>) +- [type MakeCheckFunc](<#MakeCheckFunc>) + + +## Variables + + + +```go +var ( + // ErrMaxLen indicates that the value's length is greater than the specified maximum. + ErrMaxLen = NewCheckError("MAX_LEN") +) +``` + + + +```go +var ( + // ErrMinLen indicates that the value's length is less than the specified minimum. + ErrMinLen = NewCheckError("MIN_LEN") +) +``` + + + +```go +var ( + // ErrNotASCII indicates that the given string contains non-ASCII characters. + ErrNotASCII = NewCheckError("ASCII") +) +``` + + + +```go +var ( + // ErrNotAlphanumeric indicates that the given string contains non-alphanumeric characters. + ErrNotAlphanumeric = NewCheckError("ALPHANUMERIC") +) +``` + + + +```go +var ( + // ErrNotCIDR indicates that the given value is not a valid CIDR. + ErrNotCIDR = NewCheckError("CIDR") +) +``` + + + +```go +var ( + // ErrNotCreditCard indicates that the given value is not a valid credit card number. + ErrNotCreditCard = NewCheckError("CreditCard") +) +``` + + + +```go +var ( + // ErrNotDigits indicates that the given value is not a valid digits string. + ErrNotDigits = NewCheckError("Digits") +) +``` + + + +```go +var ( + // ErrNotEmail indicates that the given value is not a valid email address. + ErrNotEmail = NewCheckError("Email") +) +``` + + + +```go +var ( + // ErrNotFQDN indicates that the given value is not a valid FQDN. + ErrNotFQDN = NewCheckError("FQDN") +) +``` + + + +```go +var ( + // ErrNotIP indicates that the given value is not a valid IP address. + ErrNotIP = NewCheckError("IP") +) +``` + + + +```go +var ( + // ErrNotIPv4 indicates that the given value is not a valid IPv4 address. + ErrNotIPv4 = NewCheckError("IPv4") +) +``` + + + +```go +var ( + // ErrNotIPv6 indicates that the given value is not a valid IPv6 address. + ErrNotIPv6 = NewCheckError("IPv6") +) +``` + + + +```go +var ( + // ErrNotISBN indicates that the given value is not a valid ISBN. + ErrNotISBN = NewCheckError("ISBN") +) +``` + + + +```go +var ( + // ErrNotLUHN indicates that the given value is not a valid LUHN number. + ErrNotLUHN = NewCheckError("LUHN") +) +``` + + + +```go +var ( + // ErrNotMAC indicates that the given value is not a valid MAC address. + ErrNotMAC = NewCheckError("MAC") +) +``` + +ErrNotMatch indicates that the given string does not match the regexp pattern. + +```go +var ErrNotMatch = NewCheckError("REGEXP") +``` + + + +```go +var ( + // ErrNotURL indicates that the given value is not a valid URL. + ErrNotURL = NewCheckError("URL") +) +``` + + + +```go +var ( + // ErrRequired indicates that a required value was missing. + ErrRequired = NewCheckError("REQUIRED") +) +``` + + +## func [Check]() + +```go +func Check[T any](value T, checks ...CheckFunc[T]) (T, error) +``` + +Check applies the given check functions to a value sequentially. It returns the final value and the first encountered error, if any. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + name := " Onur Cinar " + + name, err := v2.Check(name, v2.TrimSpace, v2.Required) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(name) +} +``` + +#### Output + +``` +Onur Cinar +``` + +

+
+ + +## func [CheckStruct]() + +```go +func CheckStruct(st any) (map[string]error, bool) +``` + +CheckStruct checks the given struct based on the validation rules specified in the "checker" tag of each struct field. It returns a map of field names to their corresponding errors, and a boolean indicating if all checks passed. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + type Person struct { + Name string `checkers:"trim required"` + } + + person := &Person{ + Name: " Onur Cinar ", + } + + errs, ok := v2.CheckStruct(person) + if !ok { + fmt.Println(errs) + return + } + + fmt.Println(person.Name) +} +``` + +#### Output + +``` +Onur Cinar +``` + +

+
+ + +## func [CheckWithConfig]() + +```go +func CheckWithConfig[T any](value T, config string) (T, error) +``` + +CheckWithConfig applies the check functions specified by the config string to the given value. It returns the modified value and the first encountered error, if any. + + +## func [HTMLEscape]() + +```go +func HTMLEscape(value string) (string, error) +``` + +HTMLEscape applies HTML escaping to special characters. + + +## func [HTMLUnescape]() + +```go +func HTMLUnescape(value string) (string, error) +``` + +HTMLUnescape applies HTML unescaping to special characters. + + +## func [IsASCII]() + +```go +func IsASCII(value string) (string, error) +``` + +IsASCII checks if the given string consists of only ASCII characters. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsASCII("Checker") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ + +## func [IsAlphanumeric]() + +```go +func IsAlphanumeric(value string) (string, error) +``` + +IsAlphanumeric checks if the given string consists of only alphanumeric characters. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsAlphanumeric("ABcd1234") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ + +## func [IsAmexCreditCard]() + +```go +func IsAmexCreditCard(number string) (string, error) +``` + +IsAmexCreditCard checks if the given valie is a valid AMEX credit card. + +
Example +

+ + + +```go +package main + +import ( + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsAmexCreditCard("378282246310005") + + if err != nil { + // Send the errors back to the user + } +} +``` + +

+
+ + +## func [IsAnyCreditCard]() + +```go +func IsAnyCreditCard(number string) (string, error) +``` + +IsAnyCreditCard checks if the given value is a valid credit card number. + +
Example +

+ + + +```go +package main + +import ( + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsAnyCreditCard("6011111111111117") + + if err != nil { + // Send the errors back to the user + } +} +``` + +

+
+ + +## func [IsCIDR]() + +```go +func IsCIDR(value string) (string, error) +``` + +IsCIDR checks if the value is a valid CIDR notation IP address and prefix length. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsCIDR("2001:db8::/32") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ + +## func [IsDigits]() + +```go +func IsDigits(value string) (string, error) +``` + +IsDigits checks if the value contains only digit characters. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsDigits("123456") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ + +## func [IsDinersCreditCard]() + +```go +func IsDinersCreditCard(number string) (string, error) +``` + +IsDinersCreditCard checks if the given valie is a valid Diners credit card. + +
Example +

+ + + +```go +package main + +import ( + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsDinersCreditCard("36227206271667") + + if err != nil { + // Send the errors back to the user + } +} +``` + +

+
+ + +## func [IsDiscoverCreditCard]() + +```go +func IsDiscoverCreditCard(number string) (string, error) +``` + +IsDiscoverCreditCard checks if the given valie is a valid Discover credit card. + +
Example +

+ + + +```go +package main + +import ( + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsDiscoverCreditCard("6011111111111117") + + if err != nil { + // Send the errors back to the user + } +} +``` + +

+
+ + +## func [IsEmail]() + +```go +func IsEmail(value string) (string, error) +``` + +IsEmail checks if the value is a valid email address. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsEmail("test@example.com") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ + +## func [IsFQDN]() + +```go +func IsFQDN(value string) (string, error) +``` + +IsFQDN checks if the value is a valid fully qualified domain name \(FQDN\). + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsFQDN("example.com") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ + +## func [IsIP]() + +```go +func IsIP(value string) (string, error) +``` + +IsIP checks if the value is a valid IP address. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsIP("192.168.1.1") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ + +## func [IsIPv4]() + +```go +func IsIPv4(value string) (string, error) +``` + +IsIPv4 checks if the value is a valid IPv4 address. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsIPv4("192.168.1.1") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ + +## func [IsIPv6]() + +```go +func IsIPv6(value string) (string, error) +``` + +IsIPv6 checks if the value is a valid IPv6 address. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsIPv6("2001:db8::1") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ + +## func [IsISBN]() + +```go +func IsISBN(value string) (string, error) +``` + +IsISBN checks if the value is a valid ISBN\-10 or ISBN\-13. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsISBN("1430248270") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ + +## func [IsJcbCreditCard]() + +```go +func IsJcbCreditCard(number string) (string, error) +``` + +IsJcbCreditCard checks if the given valie is a valid JCB 15 credit card. + +
Example +

+ + + +```go +package main + +import ( + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsJcbCreditCard("3530111333300000") + + if err != nil { + // Send the errors back to the user + } +} +``` + +

+
+ + +## func [IsLUHN]() + +```go +func IsLUHN(value string) (string, error) +``` + +IsLUHN checks if the value is a valid LUHN number. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsLUHN("4012888888881881") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ + +## func [IsMAC]() + +```go +func IsMAC(value string) (string, error) +``` + +IsMAC checks if the value is a valid MAC address. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsMAC("00:1A:2B:3C:4D:5E") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ + +## func [IsMasterCardCreditCard]() + +```go +func IsMasterCardCreditCard(number string) (string, error) +``` + +IsMasterCardCreditCard checks if the given valie is a valid MasterCard credit card. + +
Example +

+ + + +```go +package main + +import ( + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsMasterCardCreditCard("5555555555554444") + + if err != nil { + // Send the errors back to the user + } +} +``` + +

+
+ + +## func [IsURL]() + +```go +func IsURL(value string) (string, error) +``` + +IsURL checks if the value is a valid URL. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsURL("https://example.com") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ + +## func [IsUnionPayCreditCard]() + +```go +func IsUnionPayCreditCard(number string) (string, error) +``` + +IsUnionPayCreditCard checks if the given valie is a valid UnionPay credit card. + +
Example +

+ + + +```go +package main + +import ( + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsUnionPayCreditCard("6200000000000005") + + if err != nil { + // Send the errors back to the user + } +} +``` + +

+
+ + +## func [IsVisaCreditCard]() + +```go +func IsVisaCreditCard(number string) (string, error) +``` + +IsVisaCreditCard checks if the given valie is a valid Visa credit card. + +
Example +

+ + + +```go +package main + +import ( + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsVisaCreditCard("4111111111111111") + + if err != nil { + // Send the errors back to the user + } +} +``` + +

+
+ + +## func [Lower]() + +```go +func Lower(value string) (string, error) +``` + +Lower maps all Unicode letters in the given value to their lower case. + + +## func [ReflectCheckWithConfig]() + +```go +func ReflectCheckWithConfig(value reflect.Value, config string) (reflect.Value, error) +``` + +ReflectCheckWithConfig applies the check functions specified by the config string to the given reflect.Value. It returns the modified reflect.Value and the first encountered error, if any. + + +## func [Required]() + +```go +func Required[T any](value T) (T, error) +``` + +Required checks if the given value of type T is its zero value. It returns an error if the value is zero. + + +## func [Title]() + +```go +func Title(value string) (string, error) +``` + +Title returns the value of the string with the first letter of each word in upper case. + + +## func [TrimLeft]() + +```go +func TrimLeft(value string) (string, error) +``` + +TrimLeft returns the value of the string with whitespace removed from the beginning. + + +## func [TrimRight]() + +```go +func TrimRight(value string) (string, error) +``` + +TrimRight returns the value of the string with whitespace removed from the end. + + +## func [TrimSpace]() + +```go +func TrimSpace(value string) (string, error) +``` + +TrimSpace returns the value of the string with whitespace removed from both ends. + + +## func [URLEscape]() + +```go +func URLEscape(value string) (string, error) +``` + +URLEscape applies URL escaping to special characters. + + +## func [URLUnescape]() + +```go +func URLUnescape(value string) (string, error) +``` + +URLUnescape applies URL unescaping to special characters. + + +## func [Upper]() + +```go +func Upper(value string) (string, error) +``` + +Upper maps all Unicode letters in the given value to their upper case. + + +## type [CheckError]() + +CheckError defines the check error. + +```go +type CheckError struct { + // contains filtered or unexported fields +} +``` + + +### func [NewCheckError]() + +```go +func NewCheckError(code string) *CheckError +``` + +NewCheckError creates a new check error with the specified error code. + + +### func \(\*CheckError\) [Error]() + +```go +func (c *CheckError) Error() string +``` + +Error returns the error message for the check. + + +## type [CheckFunc]() + +CheckFunc is a function that takes a value of type T and performs a check on it. It returns the resulting value and any error that occurred during the check. + +```go +type CheckFunc[T any] func(value T) (T, error) +``` + + +### func [MakeRegexpChecker]() + +```go +func MakeRegexpChecker(expression string, invalidError error) CheckFunc[reflect.Value] +``` + +MakeRegexpChecker makes a regexp checker for the given regexp expression with the given invalid result. + + +### func [MaxLen]() + +```go +func MaxLen[T any](n int) CheckFunc[T] +``` + +MaxLen checks if the length of the given value \(string, slice, or map\) is at most n. Returns an error if the length is greater than n. + + +### func [MinLen]() + +```go +func MinLen[T any](n int) CheckFunc[T] +``` + +MinLen checks if the length of the given value \(string, slice, or map\) is at least n. Returns an error if the length is less than n. + + +## type [MakeCheckFunc]() + +MakeCheckFunc is a function that returns a check function using the given params. + +```go +type MakeCheckFunc func(params string) CheckFunc[reflect.Value] +``` + +Generated by [gomarkdoc]() + + + \ No newline at end of file diff --git a/v2/README.md b/v2/README.md new file mode 100644 index 0000000..5949d09 --- /dev/null +++ b/v2/README.md @@ -0,0 +1,169 @@ +[![GoDoc](https://godoc.org/github.com/cinar/checker?status.svg)](https://godoc.org/github.com/cinar/checker) +[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) +[![Go Report Card](https://goreportcard.com/badge/github.com/cinar/checker)](https://goreportcard.com/report/github.com/cinar/checker) +![Go CI](https://github.com/cinar/checker/actions/workflows/ci.yml/badge.svg) +[![codecov](https://codecov.io/gh/cinar/checker/branch/main/graph/badge.svg?token=VO9BYBHJHE)](https://codecov.io/gh/cinar/checker) + +# Checker + +Checker is a lightweight Go library designed to validate user input efficiently. It supports validation of both struct fields and individual input values. + +While there are numerous validation libraries available, Checker stands out due to its simplicity and lack of external dependencies. This makes it an ideal choice for developers who prefer to minimize dependencies and maintain control over their tools. Checker is straightforward to use and effectively meets your validation needs. + +## Usage + +To begin using the Checker library, install it with the following command: + +```bash +go get github.com/cinar/checker/v2 +``` + +Then, import the library into your source file as shown below: + +```golang +import ( + checker "github.com/cinar/checker/v2" +) +``` + +### Validating User Input Stored in a Struct + +Checker can validate user input stored in a struct by listing the checkers in the struct tags for each field. Here is an example: + +```golang +type Person struct { + Name string `checkers:"trim required"` +} + +person := &Person{ + Name: " Onur Cinar ", +} + +errors, valid := checker.CheckStruct(person) +if !valid { + // Handle validation errors +} +``` + +### Validating Individual User Input with Multiple Checkers + +You can also validate individual user input by calling checker functions directly. Here is an example: + +```golang +name := " Onur Cinar " + +name, err := checker.Check(name, checker.Trim, checker.Required) +if err != nil { + // Handle validation error +} +``` + +### Validating Individual User Input + +For simpler validation, you can call individual checker functions. Here is an example: + +```golang +name := "John Doe" + +err := checker.IsRequired(name) +if err != nil { + // Handle validation error +} +``` + +## Normalizers and Checkers + +Checkers validate user input, while normalizers transform it into a preferred format. For example, a normalizer can trim spaces from a string or convert it to title case. + +Although combining checkers and normalizers into a single library might seem unconventional, using them together can be beneficial. They can be mixed in any order when defining validation steps. For instance, you can use the `trim` normalizer with the `required` checker to first trim the input and then ensure it is provided. Here is an example: + +```golang +type Person struct { + Name string `checkers:"trim required"` +} +``` + +# Checkers Provided + +- [`ascii`](DOC.md#IsASCII): Ensures the string contains only ASCII characters. +- [`alphanumeric`](DOC.md#IsAlphanumeric): Ensures the string contains only letters and numbers. +- [`credit-card`](DOC.md#IsAnyCreditCard): Ensures the string is a valid credit card number. +- [`cidr`](DOC.md#IsCIDR): Ensures the string is a valid CIDR notation. +- [`digits`](DOC.md#IsDigits): Ensures the string contains only digits. +- [`email`](DOC.md#IsEmail): Ensures the string is a valid email address. +- [`fqdn`](DOC.md#IsFQDN): Ensures the string is a valid fully qualified domain name. +- [`ip`](DOC.md#IsIP): Ensures the string is a valid IP address. +- [`ipv4`](DOC.md#IsIPv4): Ensures the string is a valid IPv4 address. +- [`ipv6`](DOC.md#IsIPv6): Ensures the string is a valid IPv6 address. +- [`isbn`](DOC.md#IsISBN): Ensures the string is a valid ISBN. +- [`luhn`](DOC.md#IsLUHN): Ensures the string is a valid LUHN number. +- [`mac`](DOC.md#IsMAC): Ensures the string is a valid MAC address. +- [`required`](DOC.md#func-required) Ensures the value is provided. +- [`regexp`](DOC.md#func-makeregexpchecker) Ensured the string matches the pattern. +- [`url`](DOC.md#IsURL): Ensures the string is a valid URL. + +# Normalizers Provided + +- [`lower`](DOC.md#Lower): Converts the string to lowercase. +- [`title`](DOC.md#Title): Converts the string to title case. +- [`trim-left`](DOC.md#TrimLeft): Trims whitespace from the left side of the string. +- [`trim-right`](DOC.md#TrimRight): Trims whitespace from the right side of the string. +- [`trim`](DOC.md#TrimSpace): Trims whitespace from both sides of the string. +- [`upper`](DOC.md#Upper): Converts the string to uppercase. +- [`html-escape`](DOC.md#HTMLEscape): Escapes special characters in the string for HTML. +- [`html-unescape`](DOC.md#HTMLUnescape): Unescapes special characters in the string for HTML. +- [`url-escape`](DOC.md#URLEscape): Escapes special characters in the string for URLs. +- [`url-unescape`](DOC.md#URLUnescape): Unescapes special characters in the string for URLs. + +# Custom Checkers + +## Custom Checkers and Normalizers + +You can define custom checkers or normalizers and register them for use in your validation logic. Here is an example of how to create and register a custom checker: + +```golang +checker.RegisterMaker("is-fruit", func(params string) v2.CheckFunc[reflect.Value] { + return func(value reflect.Value) (reflect.Value, error) { + stringValue := value.Interface().(string) + + if stringValue == "apple" || stringValue == "banana" { + return value, nil + } + + return value, v2.NewCheckError("NOT_FRUIT") + } +}) +``` + +In this example, the custom checker `is-fruit` checks if the input value is either "apple" or "banana". If the value is not one of these, it returns an error. + +Once registered, you can use your custom checker in struct tags just like the built-in checkers: + +```golang +type Item struct { + Name string `checkers:"is-fruit"` +} + +item := &Item{ + Name: "banana", +} + +errors, valid := v2.CheckStruct(item) +if !valid { + fmt.Println(errors) +} +``` + +In this example, the `is-fruit` checker is used to validate that the `Name` field of the `Item` struct is either "apple" or "banana". + +# Contributing to the Project + +Anyone can contribute to Checkers library. Please make sure to read our [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md) guide first. Follow the [How to Contribute to Checker](./CONTRIBUTING.md) to contribute. + +# License + +This library is free to use, modify, and distribute under the terms of the MIT license. The full license text can be found in the [LICENSE](./LICENSE) file. + +The MIT license is a permissive license that allows you to do almost anything with the library, as long as you retain the copyright notice and the license text. This means that you can use the library in commercial products, modify it, and redistribute it without having to ask for permission from the authors. + +The [LICENSE](./LICENSE) file is located in the root directory of the library. You can open it in a text editor to read the full license text. From 1b73162675e4cfc9796fb4a970049563194caee8 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 27 Dec 2024 14:30:05 -0800 Subject: [PATCH 30/41] Hex check is added. (#151) # Describe Request Hex check is added. Fixes #105 # Change Type New check. --- v2/hex.go | 36 ++++++++++++++++++++++ v2/hex_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++ v2/luhn_test.go | 7 +++++ v2/maker.go | 1 + v2/regexp.go | 20 ++++++------- v2/regexp_test.go | 32 +++++++++++++------- 6 files changed, 152 insertions(+), 20 deletions(-) create mode 100644 v2/hex.go create mode 100644 v2/hex_test.go diff --git a/v2/hex.go b/v2/hex.go new file mode 100644 index 0000000..0b6144d --- /dev/null +++ b/v2/hex.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" +) + +const ( + // nameHex is the name of the hex check. + nameHex = "hex" +) + +var ( + // ErrNotHex indicates that the given string contains hex characters. + ErrNotHex = NewCheckError("HEX") +) + +// IsHex checks if the given string consists of only hex characters. +func IsHex(value string) (string, error) { + return IsRegexp("^[0-9a-fA-F]+$", value) +} + +// isHex checks if the given string consists of only hex characters. +func isHex(value reflect.Value) (reflect.Value, error) { + _, err := IsHex(value.Interface().(string)) + return value, err +} + +// makeAlphanumeric makes a checker function for the alphanumeric checker. +func makeHex(_ string) CheckFunc[reflect.Value] { + return isHex +} diff --git a/v2/hex_test.go b/v2/hex_test.go new file mode 100644 index 0000000..304d777 --- /dev/null +++ b/v2/hex_test.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "fmt" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsHex() { + _, err := v2.IsHex("0123456789abcdefABCDEF") + if err != nil { + fmt.Println(err) + } +} + +func TestIsHexInvalid(t *testing.T) { + _, err := v2.IsHex("ONUR") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsHexValid(t *testing.T) { + _, err := v2.IsHex("0123456789abcdefABCDEF") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckHexNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Car struct { + Color int `checkers:"hex"` + } + + car := &Car{} + + v2.CheckStruct(car) +} + +func TestCheckHexInvalid(t *testing.T) { + type Car struct { + Color string `checkers:"hex"` + } + + car := &Car{ + Color: "red", + } + + _, ok := v2.CheckStruct(car) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckHexValid(t *testing.T) { + type Car struct { + Color string `checkers:"hex"` + } + + car := &Car{ + Color: "ABcd1234", + } + + errs, ok := v2.CheckStruct(car) + if !ok { + t.Fatal(errs) + } +} diff --git a/v2/luhn_test.go b/v2/luhn_test.go index 7831ccc..877d5c5 100644 --- a/v2/luhn_test.go +++ b/v2/luhn_test.go @@ -26,6 +26,13 @@ func TestIsLUHNInvalid(t *testing.T) { } } +func TestIsLUHNInvalidDigits(t *testing.T) { + _, err := v2.IsLUHN("ABCD") + if err == nil { + t.Fatal("expected error") + } +} + func TestIsLUHNValid(t *testing.T) { _, err := v2.IsLUHN("4012888888881881") if err != nil { diff --git a/v2/maker.go b/v2/maker.go index 81866db..935d04b 100644 --- a/v2/maker.go +++ b/v2/maker.go @@ -23,6 +23,7 @@ var makers = map[string]MakeCheckFunc{ nameDigits: makeDigits, nameEmail: makeEmail, nameFQDN: makeFQDN, + nameHex: makeHex, nameHTMLEscape: makeHTMLEscape, nameHTMLUnescape: makeHTMLUnescape, nameIP: makeIP, diff --git a/v2/regexp.go b/v2/regexp.go index 426a6dd..8ae5663 100644 --- a/v2/regexp.go +++ b/v2/regexp.go @@ -16,6 +16,15 @@ const nameRegexp = "regexp" // ErrNotMatch indicates that the given string does not match the regexp pattern. var ErrNotMatch = NewCheckError("REGEXP") +// IsRegexp checks if the given string matches the given regexp expression. +func IsRegexp(expression, value string) (string, error) { + if !regexp.MustCompile(expression).MatchString(value) { + return value, ErrNotMatch + } + + return value, nil +} + // MakeRegexpChecker makes a regexp checker for the given regexp expression with the given invalid result. func MakeRegexpChecker(expression string, invalidError error) CheckFunc[reflect.Value] { return func(value reflect.Value) (reflect.Value, error) { @@ -23,12 +32,8 @@ func MakeRegexpChecker(expression string, invalidError error) CheckFunc[reflect. panic("string expected") } - matched, err := regexp.MatchString(expression, value.String()) + _, err := IsRegexp(expression, value.String()) if err != nil { - return value, err - } - - if !matched { return value, invalidError } @@ -40,8 +45,3 @@ func MakeRegexpChecker(expression string, invalidError error) CheckFunc[reflect. func makeRegexp(config string) CheckFunc[reflect.Value] { return MakeRegexpChecker(config, ErrNotMatch) } - -// checkRegexp checks if the given string matches the regexp pattern. -func checkRegexp(value reflect.Value) (reflect.Value, error) { - return makeRegexp(value.String())(value) -} diff --git a/v2/regexp_test.go b/v2/regexp_test.go index e66a760..cdb78dd 100644 --- a/v2/regexp_test.go +++ b/v2/regexp_test.go @@ -6,12 +6,33 @@ package v2_test import ( - "reflect" + "fmt" "testing" v2 "github.com/cinar/checker/v2" ) +func ExampleIsRegexp() { + _, err := v2.IsRegexp("^[0-9a-fA-F]+$", "ABcd1234") + if err != nil { + fmt.Println(err) + } +} + +func TestIsRegexpInvalid(t *testing.T) { + _, err := v2.IsRegexp("^[0-9a-fA-F]+$", "Onur") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsRegexpValid(t *testing.T) { + _, err := v2.IsRegexp("^[0-9a-fA-F]+$", "ABcd1234") + if err != nil { + t.Fatal(err) + } +} + func TestCheckRegexpNonString(t *testing.T) { defer FailIfNoPanic(t, "expected panic") @@ -53,12 +74,3 @@ func TestCheckRegexpValid(t *testing.T) { t.Fatal("expected valid") } } - -func TestMakeRegexpChecker(t *testing.T) { - checkHex := v2.MakeRegexpChecker("^[A-Fa-f0-9]+$", v2.ErrNotMatch) - - _, err := checkHex(reflect.ValueOf("f0f0f0")) - if err != nil { - t.Fail() - } -} From c88ed08d905831bde0d26f161b2f4c56eb04e609 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 27 Dec 2024 14:36:11 -0800 Subject: [PATCH 31/41] Documentation update. (#152) # Describe Request Documentation update. --- v2/README.md | 3 ++- v2/checker.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/v2/README.md b/v2/README.md index 5949d09..683f6db 100644 --- a/v2/README.md +++ b/v2/README.md @@ -63,7 +63,7 @@ if err != nil { For simpler validation, you can call individual checker functions. Here is an example: ```golang -name := "John Doe" +name := "Onur Cinar" err := checker.IsRequired(name) if err != nil { @@ -92,6 +92,7 @@ type Person struct { - [`digits`](DOC.md#IsDigits): Ensures the string contains only digits. - [`email`](DOC.md#IsEmail): Ensures the string is a valid email address. - [`fqdn`](DOC.md#IsFQDN): Ensures the string is a valid fully qualified domain name. +- [`hex`](DOC.md#IsHex): Ensures the string contains only hex digits. - [`ip`](DOC.md#IsIP): Ensures the string is a valid IP address. - [`ipv4`](DOC.md#IsIPv4): Ensures the string is a valid IPv4 address. - [`ipv6`](DOC.md#IsIPv6): Ensures the string is a valid IPv6 address. diff --git a/v2/checker.go b/v2/checker.go index e651348..3616767 100644 --- a/v2/checker.go +++ b/v2/checker.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker +// Package v2 Checker is a Go library for validating user input through checker rules provided in struct tags. package v2 import ( From 45bb608b3acd825a325e72cfc78c2ba21ca86956 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Sat, 28 Dec 2024 22:20:59 -0800 Subject: [PATCH 32/41] Localized error message support is added. (#153) # Describe Request Localized error message support is added. # Change Type New feature. --- .gomarkdoc.yml | 1 + taskfile.yml | 2 +- v2/DOC.md | 243 ++++++++++++++++++++++++++++++++++++----- v2/README.md | 16 ++- v2/alphanumeric.go | 2 +- v2/ascii.go | 2 +- v2/check_error.go | 85 +++++++++++++- v2/check_error_test.go | 127 ++++++++++++++++++++- v2/cidr.go | 2 +- v2/credit_card.go | 2 +- v2/digits.go | 32 +++--- v2/email.go | 2 +- v2/hex.go | 2 +- v2/ip.go | 2 +- v2/ipv4.go | 2 +- v2/ipv6.go | 2 +- v2/isbn.go | 2 +- v2/locales/DOC.md | 60 ++++++++++ v2/locales/en_us.go | 28 +++++ v2/locales/locales.go | 2 + v2/luhn.go | 2 +- v2/mac.go | 2 +- v2/max_len.go | 14 ++- v2/max_len_test.go | 10 +- v2/min_len.go | 14 ++- v2/min_len_test.go | 10 +- v2/revive.toml | 30 +++++ v2/taskfile.yml | 34 ++++++ v2/url.go | 2 +- 29 files changed, 652 insertions(+), 82 deletions(-) create mode 100644 v2/locales/DOC.md create mode 100644 v2/locales/en_us.go create mode 100644 v2/locales/locales.go create mode 100644 v2/revive.toml create mode 100644 v2/taskfile.yml diff --git a/.gomarkdoc.yml b/.gomarkdoc.yml index 86a76b1..dee4a39 100644 --- a/.gomarkdoc.yml +++ b/.gomarkdoc.yml @@ -1,3 +1,4 @@ output: "{{.Dir}}/README.md" repository: url: https://github.com/cinar/checker +exclude-dirs: "./v2/..." \ No newline at end of file diff --git a/taskfile.yml b/taskfile.yml index c5d8020..158205c 100644 --- a/taskfile.yml +++ b/taskfile.yml @@ -19,7 +19,7 @@ tasks: lint: cmds: - go vet ./... - - go run github.com/securego/gosec/v2/cmd/gosec@v2.20.0 ./... + - go run github.com/securego/gosec/v2/cmd/gosec@v2.20.0 -exclude-dir=v2 ./... - go run honnef.co/go/tools/cmd/staticcheck@v0.5.1 ./... - go run github.com/mgechev/revive@v1.3.4 -config=revive.toml ./... diff --git a/v2/DOC.md b/v2/DOC.md index 28e079c..ceea0fe 100644 --- a/v2/DOC.md +++ b/v2/DOC.md @@ -8,8 +8,11 @@ import "github.com/cinar/checker/v2" ``` +Package v2 Checker is a Go library for validating user input through checker rules provided in struct tags. + ## Index +- [Constants](<#constants>) - [Variables](<#variables>) - [func Check\[T any\]\(value T, checks ...CheckFunc\[T\]\) \(T, error\)](<#Check>) - [func CheckStruct\(st any\) \(map\[string\]error, bool\)](<#CheckStruct>) @@ -26,6 +29,7 @@ import "github.com/cinar/checker/v2" - [func IsDiscoverCreditCard\(number string\) \(string, error\)](<#IsDiscoverCreditCard>) - [func IsEmail\(value string\) \(string, error\)](<#IsEmail>) - [func IsFQDN\(value string\) \(string, error\)](<#IsFQDN>) +- [func IsHex\(value string\) \(string, error\)](<#IsHex>) - [func IsIP\(value string\) \(string, error\)](<#IsIP>) - [func IsIPv4\(value string\) \(string, error\)](<#IsIPv4>) - [func IsIPv6\(value string\) \(string, error\)](<#IsIPv6>) @@ -34,11 +38,14 @@ import "github.com/cinar/checker/v2" - [func IsLUHN\(value string\) \(string, error\)](<#IsLUHN>) - [func IsMAC\(value string\) \(string, error\)](<#IsMAC>) - [func IsMasterCardCreditCard\(number string\) \(string, error\)](<#IsMasterCardCreditCard>) +- [func IsRegexp\(expression, value string\) \(string, error\)](<#IsRegexp>) - [func IsURL\(value string\) \(string, error\)](<#IsURL>) - [func IsUnionPayCreditCard\(number string\) \(string, error\)](<#IsUnionPayCreditCard>) - [func IsVisaCreditCard\(number string\) \(string, error\)](<#IsVisaCreditCard>) - [func Lower\(value string\) \(string, error\)](<#Lower>) - [func ReflectCheckWithConfig\(value reflect.Value, config string\) \(reflect.Value, error\)](<#ReflectCheckWithConfig>) +- [func RegisterLocale\(locale string, messages map\[string\]string\)](<#RegisterLocale>) +- [func RegisterMaker\(name string, maker MakeCheckFunc\)](<#RegisterMaker>) - [func Required\[T any\]\(value T\) \(T, error\)](<#Required>) - [func Title\(value string\) \(string, error\)](<#Title>) - [func TrimLeft\(value string\) \(string, error\)](<#TrimLeft>) @@ -49,7 +56,10 @@ import "github.com/cinar/checker/v2" - [func Upper\(value string\) \(string, error\)](<#Upper>) - [type CheckError](<#CheckError>) - [func NewCheckError\(code string\) \*CheckError](<#NewCheckError>) + - [func NewCheckErrorWithData\(code string, data map\[string\]interface\{\}\) \*CheckError](<#NewCheckErrorWithData>) - [func \(c \*CheckError\) Error\(\) string](<#CheckError.Error>) + - [func \(c \*CheckError\) ErrorWithLocale\(locale string\) string](<#CheckError.ErrorWithLocale>) + - [func \(c \*CheckError\) Is\(target error\) bool](<#CheckError.Is>) - [type CheckFunc](<#CheckFunc>) - [func MakeRegexpChecker\(expression string, invalidError error\) CheckFunc\[reflect.Value\]](<#MakeRegexpChecker>) - [func MaxLen\[T any\]\(n int\) CheckFunc\[T\]](<#MaxLen>) @@ -57,6 +67,17 @@ import "github.com/cinar/checker/v2" - [type MakeCheckFunc](<#MakeCheckFunc>) +## Constants + + + +```go +const ( + // DefaultLocale is the default locale. + DefaultLocale = locales.EnUS +) +``` + ## Variables @@ -64,7 +85,7 @@ import "github.com/cinar/checker/v2" ```go var ( // ErrMaxLen indicates that the value's length is greater than the specified maximum. - ErrMaxLen = NewCheckError("MAX_LEN") + ErrMaxLen = NewCheckError("NOT_MAX_LEN") ) ``` @@ -73,7 +94,7 @@ var ( ```go var ( // ErrMinLen indicates that the value's length is less than the specified minimum. - ErrMinLen = NewCheckError("MIN_LEN") + ErrMinLen = NewCheckError("NOT_MIN_LEN") ) ``` @@ -82,7 +103,7 @@ var ( ```go var ( // ErrNotASCII indicates that the given string contains non-ASCII characters. - ErrNotASCII = NewCheckError("ASCII") + ErrNotASCII = NewCheckError("NOT_ASCII") ) ``` @@ -91,7 +112,7 @@ var ( ```go var ( // ErrNotAlphanumeric indicates that the given string contains non-alphanumeric characters. - ErrNotAlphanumeric = NewCheckError("ALPHANUMERIC") + ErrNotAlphanumeric = NewCheckError("NOT_ALPHANUMERIC") ) ``` @@ -100,7 +121,7 @@ var ( ```go var ( // ErrNotCIDR indicates that the given value is not a valid CIDR. - ErrNotCIDR = NewCheckError("CIDR") + ErrNotCIDR = NewCheckError("NOT_CIDR") ) ``` @@ -109,7 +130,7 @@ var ( ```go var ( // ErrNotCreditCard indicates that the given value is not a valid credit card number. - ErrNotCreditCard = NewCheckError("CreditCard") + ErrNotCreditCard = NewCheckError("NOT_CREDIT_CARD") ) ``` @@ -118,7 +139,7 @@ var ( ```go var ( // ErrNotDigits indicates that the given value is not a valid digits string. - ErrNotDigits = NewCheckError("Digits") + ErrNotDigits = NewCheckError("NOT_DIGITS") ) ``` @@ -127,7 +148,7 @@ var ( ```go var ( // ErrNotEmail indicates that the given value is not a valid email address. - ErrNotEmail = NewCheckError("Email") + ErrNotEmail = NewCheckError("NOT_EMAIL") ) ``` @@ -140,12 +161,21 @@ var ( ) ``` + + +```go +var ( + // ErrNotHex indicates that the given string contains hex characters. + ErrNotHex = NewCheckError("NOT_HEX") +) +``` + ```go var ( // ErrNotIP indicates that the given value is not a valid IP address. - ErrNotIP = NewCheckError("IP") + ErrNotIP = NewCheckError("NOT_IP") ) ``` @@ -154,7 +184,7 @@ var ( ```go var ( // ErrNotIPv4 indicates that the given value is not a valid IPv4 address. - ErrNotIPv4 = NewCheckError("IPv4") + ErrNotIPv4 = NewCheckError("NOT_IPV4") ) ``` @@ -163,7 +193,7 @@ var ( ```go var ( // ErrNotIPv6 indicates that the given value is not a valid IPv6 address. - ErrNotIPv6 = NewCheckError("IPv6") + ErrNotIPv6 = NewCheckError("NOT_IPV6") ) ``` @@ -172,7 +202,7 @@ var ( ```go var ( // ErrNotISBN indicates that the given value is not a valid ISBN. - ErrNotISBN = NewCheckError("ISBN") + ErrNotISBN = NewCheckError("NOT_ISBN") ) ``` @@ -181,7 +211,7 @@ var ( ```go var ( // ErrNotLUHN indicates that the given value is not a valid LUHN number. - ErrNotLUHN = NewCheckError("LUHN") + ErrNotLUHN = NewCheckError("NOT_LUHN") ) ``` @@ -190,7 +220,7 @@ var ( ```go var ( // ErrNotMAC indicates that the given value is not a valid MAC address. - ErrNotMAC = NewCheckError("MAC") + ErrNotMAC = NewCheckError("NOT_MAC") ) ``` @@ -205,7 +235,7 @@ var ErrNotMatch = NewCheckError("REGEXP") ```go var ( // ErrNotURL indicates that the given value is not a valid URL. - ErrNotURL = NewCheckError("URL") + ErrNotURL = NewCheckError("NOT_URL") ) ``` @@ -219,7 +249,7 @@ var ( ``` -## func [Check]() +## func [Check]() ```go func Check[T any](value T, checks ...CheckFunc[T]) (T, error) @@ -264,7 +294,7 @@ Onur Cinar -## func [CheckStruct]() +## func [CheckStruct]() ```go func CheckStruct(st any) (map[string]error, bool) @@ -315,7 +345,7 @@ Onur Cinar -## func [CheckWithConfig]() +## func [CheckWithConfig]() ```go func CheckWithConfig[T any](value T, config string) (T, error) @@ -677,6 +707,40 @@ func main() {

+ +## func [IsHex]() + +```go +func IsHex(value string) (string, error) +``` + +IsHex checks if the given string consists of only hex characters. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsHex("0123456789abcdefABCDEF") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ ## func [IsIP]() @@ -947,6 +1011,40 @@ func main() {

+ +## func [IsRegexp]() + +```go +func IsRegexp(expression, value string) (string, error) +``` + +IsRegexp checks if the given string matches the given regexp expression. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + _, err := v2.IsRegexp("^[0-9a-fA-F]+$", "ABcd1234") + if err != nil { + fmt.Println(err) + } +} +``` + +

+
+ ## func [IsURL]() @@ -1057,7 +1155,7 @@ func Lower(value string) (string, error) Lower maps all Unicode letters in the given value to their lower case. -## func [ReflectCheckWithConfig]() +## func [ReflectCheckWithConfig]() ```go func ReflectCheckWithConfig(value reflect.Value, config string) (reflect.Value, error) @@ -1065,6 +1163,70 @@ func ReflectCheckWithConfig(value reflect.Value, config string) (reflect.Value, ReflectCheckWithConfig applies the check functions specified by the config string to the given reflect.Value. It returns the modified reflect.Value and the first encountered error, if any. + +## func [RegisterLocale]() + +```go +func RegisterLocale(locale string, messages map[string]string) +``` + +RegisterLocale registers the localized error messages for the given locale. + + +## func [RegisterMaker]() + +```go +func RegisterMaker(name string, maker MakeCheckFunc) +``` + +RegisterMaker registers a new maker function with the given name. + +
Example +

+ + + +```go +package main + +import ( + "fmt" + "reflect" + + v2 "github.com/cinar/checker/v2" +) + +func main() { + v2.RegisterMaker("is-fruit", func(params string) v2.CheckFunc[reflect.Value] { + return func(value reflect.Value) (reflect.Value, error) { + stringValue := value.Interface().(string) + + if stringValue == "apple" || stringValue == "banana" { + return value, nil + } + + return value, v2.NewCheckError("NOT_FRUIT") + } + }) + + type Item struct { + Name string `checkers:"is-fruit"` + } + + person := &Item{ + Name: "banana", + } + + err, ok := v2.CheckStruct(person) + if !ok { + fmt.Println(err) + } +} +``` + +

+
+ ## func [Required]() @@ -1138,27 +1300,40 @@ func Upper(value string) (string, error) Upper maps all Unicode letters in the given value to their upper case. -## type [CheckError]() +## type [CheckError]() CheckError defines the check error. ```go type CheckError struct { - // contains filtered or unexported fields + // Code is the error code. + Code string + + // data is the error data. + Data map[string]interface{} } ``` -### func [NewCheckError]() +### func [NewCheckError]() ```go func NewCheckError(code string) *CheckError ``` -NewCheckError creates a new check error with the specified error code. +NewCheckError creates a new check error with the given code. + + +### func [NewCheckErrorWithData]() + +```go +func NewCheckErrorWithData(code string, data map[string]interface{}) *CheckError +``` + +NewCheckErrorWithData creates a new check error with the given code and data. -### func \(\*CheckError\) [Error]() +### func \(\*CheckError\) [Error]() ```go func (c *CheckError) Error() string @@ -1166,6 +1341,24 @@ func (c *CheckError) Error() string Error returns the error message for the check. + +### func \(\*CheckError\) [ErrorWithLocale]() + +```go +func (c *CheckError) ErrorWithLocale(locale string) string +``` + +ErrorWithLocale returns the localized error message for the check with the given locale. + + +### func \(\*CheckError\) [Is]() + +```go +func (c *CheckError) Is(target error) bool +``` + +Is reports whether the check error is the same as the target error. + ## type [CheckFunc]() @@ -1176,7 +1369,7 @@ type CheckFunc[T any] func(value T) (T, error) ``` -### func [MakeRegexpChecker]() +### func [MakeRegexpChecker]() ```go func MakeRegexpChecker(expression string, invalidError error) CheckFunc[reflect.Value] diff --git a/v2/README.md b/v2/README.md index 683f6db..0378979 100644 --- a/v2/README.md +++ b/v2/README.md @@ -99,6 +99,8 @@ type Person struct { - [`isbn`](DOC.md#IsISBN): Ensures the string is a valid ISBN. - [`luhn`](DOC.md#IsLUHN): Ensures the string is a valid LUHN number. - [`mac`](DOC.md#IsMAC): Ensures the string is a valid MAC address. +- [`max-len`](DOC.md#func-maxlen): Ensures the length of the given value (string, slice, or map) is at most n. +- [`min-len`](DOC.md#func-minlen): Ensures the length of the given value (string, slice, or map) is at least n. - [`required`](DOC.md#func-required) Ensures the value is provided. - [`regexp`](DOC.md#func-makeregexpchecker) Ensured the string matches the pattern. - [`url`](DOC.md#IsURL): Ensures the string is a valid URL. @@ -116,9 +118,7 @@ type Person struct { - [`url-escape`](DOC.md#URLEscape): Escapes special characters in the string for URLs. - [`url-unescape`](DOC.md#URLUnescape): Unescapes special characters in the string for URLs. -# Custom Checkers - -## Custom Checkers and Normalizers +# Custom Checkers and Normalizers You can define custom checkers or normalizers and register them for use in your validation logic. Here is an example of how to create and register a custom checker: @@ -157,6 +157,16 @@ if !valid { In this example, the `is-fruit` checker is used to validate that the `Name` field of the `Item` struct is either "apple" or "banana". +# Localized Error Messages + +When validation fails, Checker returns an error. By default, the [Error()](DOC.md#CheckError.Error) function provides a human-readable error message in `en-US` locale. + +To get error messages in other languages, use the [ErrorWithLocale()](DOC.md#CheckError.ErrorWithLocale) function. By default, only `en-US` is registered. You can register additional languages by calling [RegisterLocale](DOC.md##RegisterLocale). + +You can also customize existing error messages or add new ones to `locales.EnUSMessages` and other locale maps. + +Error messages are generated using Golang template functions, allowing them to include variables. + # Contributing to the Project Anyone can contribute to Checkers library. Please make sure to read our [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md) guide first. Follow the [How to Contribute to Checker](./CONTRIBUTING.md) to contribute. diff --git a/v2/alphanumeric.go b/v2/alphanumeric.go index 35ecaec..de3618d 100644 --- a/v2/alphanumeric.go +++ b/v2/alphanumeric.go @@ -17,7 +17,7 @@ const ( var ( // ErrNotAlphanumeric indicates that the given string contains non-alphanumeric characters. - ErrNotAlphanumeric = NewCheckError("ALPHANUMERIC") + ErrNotAlphanumeric = NewCheckError("NOT_ALPHANUMERIC") ) // IsAlphanumeric checks if the given string consists of only alphanumeric characters. diff --git a/v2/ascii.go b/v2/ascii.go index 5bb7946..f05125f 100644 --- a/v2/ascii.go +++ b/v2/ascii.go @@ -17,7 +17,7 @@ const ( var ( // ErrNotASCII indicates that the given string contains non-ASCII characters. - ErrNotASCII = NewCheckError("ASCII") + ErrNotASCII = NewCheckError("NOT_ASCII") ) // IsASCII checks if the given string consists of only ASCII characters. diff --git a/v2/check_error.go b/v2/check_error.go index c523177..139e72c 100644 --- a/v2/check_error.go +++ b/v2/check_error.go @@ -5,20 +5,95 @@ package v2 +import ( + "html/template" + "strings" + + "github.com/cinar/checker/v2/locales" +) + // CheckError defines the check error. type CheckError struct { - // code is the error code. - code string + // Code is the error code. + Code string + + // data is the error data. + Data map[string]interface{} } -// NewCheckError creates a new check error with the specified error code. +const ( + // DefaultLocale is the default locale. + DefaultLocale = locales.EnUS +) + +// errorMessages is the map of localized error messages. +var errorMessages = map[string]map[string]string{ + locales.EnUS: locales.EnUsMessages, +} + +// NewCheckError creates a new check error with the given code. func NewCheckError(code string) *CheckError { + return NewCheckErrorWithData( + code, + make(map[string]interface{}), + ) +} + +// NewCheckErrorWithData creates a new check error with the given code and data. +func NewCheckErrorWithData(code string, data map[string]interface{}) *CheckError { return &CheckError{ - code: code, + Code: code, + Data: data, } } // Error returns the error message for the check. func (c *CheckError) Error() string { - return c.code + return c.ErrorWithLocale(DefaultLocale) +} + +// Is reports whether the check error is the same as the target error. +func (c *CheckError) Is(target error) bool { + if other, ok := target.(*CheckError); ok { + return c.Code == other.Code + } + + return false +} + +// ErrorWithLocale returns the localized error message for the check with the given locale. +func (c *CheckError) ErrorWithLocale(locale string) string { + tmpl, err := template.New("error").Parse(getLocalizedErrorMessage(locale, c.Code)) + if err != nil { + return c.Code + } + + var message strings.Builder + if err := tmpl.Execute(&message, c.Data); err != nil { + return c.Code + } + + return message.String() +} + +// RegisterLocale registers the localized error messages for the given locale. +func RegisterLocale(locale string, messages map[string]string) { + errorMessages[locale] = messages +} + +// getLocalizedErrorMessage returns the localized error message for the given locale and code. +func getLocalizedErrorMessage(locale, code string) string { + if messages, found := errorMessages[locale]; found { + if message, exists := messages[code]; exists { + return message + } + } + + if messages, found := errorMessages[DefaultLocale]; found { + if message, exists := messages[code]; exists { + return message + } + } + + return code } diff --git a/v2/check_error_test.go b/v2/check_error_test.go index 672a5e1..ec7dec3 100644 --- a/v2/check_error_test.go +++ b/v2/check_error_test.go @@ -6,17 +6,138 @@ package v2_test import ( + "io/fs" "testing" v2 "github.com/cinar/checker/v2" + "github.com/cinar/checker/v2/locales" ) -func TestCheckErrorError(t *testing.T) { - code := "CODE" +func TestCheckErrorWithNotLocalizedCode(t *testing.T) { + code := "TEST" err := v2.NewCheckError(code) if err.Error() != code { - t.Fatalf("actaul %s expected %s", err.Error(), code) + t.Fatalf("actual %s expected %s", err.Error(), code) + } +} + +func TestCheckErrorWithLocalizedCode(t *testing.T) { + code := "TEST" + message := "Test message" + + locales.EnUsMessages[code] = message + + err := v2.NewCheckError(code) + + if err.ErrorWithLocale("fr-FR") != message { + t.Fatalf("actual %s expected %s", err.Error(), message) + } +} + +func TestCheckErrorWithDefaultLocalizedCode(t *testing.T) { + code := "TEST" + message := "Test message" + + locales.EnUsMessages[code] = message + + err := v2.NewCheckError(code) + + if err.Error() != message { + t.Fatalf("actual %s expected %s", err.Error(), message) + } +} + +func TestCheckErrorWithDataAndLocalizedCode(t *testing.T) { + code := "TEST" + message := "Test message {{.Name}}" + + locales.EnUsMessages[code] = message + + err := v2.NewCheckErrorWithData(code, map[string]interface{}{ + "Name": "Onur", + }) + + expected := "Test message Onur" + + if err.Error() != expected { + t.Fatalf("actual %s expected %s", err.Error(), expected) + } +} + +func TestCheckErrorWithLocalizedCodeInvalidTemplate(t *testing.T) { + code := "TEST" + message := "Test message {{}" + + locales.EnUsMessages[code] = message + + err := v2.NewCheckError(code) + + if err.Error() != code { + t.Fatalf("actual %s expected %s", err.Error(), code) + } +} + +func TestCheckErrorWithLocalizedCodeInvalidExecute(t *testing.T) { + code := "TEST" + message := "{{ len .Name}}" + + locales.EnUsMessages[code] = message + + err := v2.NewCheckError(code) + + if err.Error() != code { + t.Fatalf("actual %s expected %s", err.Error(), code) + } +} + +func TestCheckErrorIsSuccess(t *testing.T) { + code := "TEST" + + err1 := v2.NewCheckError(code) + err2 := v2.NewCheckError(code) + + if !err1.Is(err2) { + t.Fatalf("actual %t expected %t", err1.Is(err2), true) + } +} + +func TestCheckErrorIsFailure(t *testing.T) { + code1 := "TEST1" + code2 := "TEST2" + + err1 := v2.NewCheckError(code1) + err2 := v2.NewCheckError(code2) + + if err1.Is(err2) { + t.Fatalf("actual %t expected %t", err1.Is(err2), false) + } +} + +func TestCheckErrorIsFailureWithDifferentType(t *testing.T) { + code := "TEST" + + err1 := v2.NewCheckError(code) + err2 := fs.ErrExist + + if err1.Is(err2) { + t.Fatalf("actual %t expected %t", err1.Is(err2), false) + } +} + +func TestRegisterLocale(t *testing.T) { + locale := "de-DE" + code := "TEST" + message := "Testmeldung" + + v2.RegisterLocale(locale, map[string]string{ + code: message, + }) + + err := v2.NewCheckError(code) + + if err.ErrorWithLocale("de-DE") != message { + t.Fatalf("actual %s expected %s", err.Error(), message) } } diff --git a/v2/cidr.go b/v2/cidr.go index c2dd44d..193aedc 100644 --- a/v2/cidr.go +++ b/v2/cidr.go @@ -17,7 +17,7 @@ const ( var ( // ErrNotCIDR indicates that the given value is not a valid CIDR. - ErrNotCIDR = NewCheckError("CIDR") + ErrNotCIDR = NewCheckError("NOT_CIDR") ) // IsCIDR checks if the value is a valid CIDR notation IP address and prefix length. diff --git a/v2/credit_card.go b/v2/credit_card.go index 32c9230..59d5c85 100644 --- a/v2/credit_card.go +++ b/v2/credit_card.go @@ -18,7 +18,7 @@ const ( var ( // ErrNotCreditCard indicates that the given value is not a valid credit card number. - ErrNotCreditCard = NewCheckError("CreditCard") + ErrNotCreditCard = NewCheckError("NOT_CREDIT_CARD") // amexExpression is the regexp for the AMEX cards. They start with 34 or 37, and has 15 digits. amexExpression = "(?:^(?:3[47])[0-9]{13}$)" diff --git a/v2/digits.go b/v2/digits.go index adcddb8..0a01b72 100644 --- a/v2/digits.go +++ b/v2/digits.go @@ -6,37 +6,37 @@ package v2 import ( - "reflect" - "unicode" + "reflect" + "unicode" ) const ( - // nameDigits is the name of the digits check. - nameDigits = "digits" + // nameDigits is the name of the digits check. + nameDigits = "digits" ) var ( - // ErrNotDigits indicates that the given value is not a valid digits string. - ErrNotDigits = NewCheckError("Digits") + // ErrNotDigits indicates that the given value is not a valid digits string. + ErrNotDigits = NewCheckError("NOT_DIGITS") ) // IsDigits checks if the value contains only digit characters. func IsDigits(value string) (string, error) { - for _, r := range value { - if !unicode.IsDigit(r) { - return value, ErrNotDigits - } - } - return value, nil + for _, r := range value { + if !unicode.IsDigit(r) { + return value, ErrNotDigits + } + } + return value, nil } // checkDigits checks if the value contains only digit characters. func checkDigits(value reflect.Value) (reflect.Value, error) { - _, err := IsDigits(value.Interface().(string)) - return value, err + _, err := IsDigits(value.Interface().(string)) + return value, err } // makeDigits makes a checker function for the digits checker. func makeDigits(_ string) CheckFunc[reflect.Value] { - return checkDigits -} \ No newline at end of file + return checkDigits +} diff --git a/v2/email.go b/v2/email.go index 7251850..8ab0aca 100644 --- a/v2/email.go +++ b/v2/email.go @@ -17,7 +17,7 @@ const ( var ( // ErrNotEmail indicates that the given value is not a valid email address. - ErrNotEmail = NewCheckError("Email") + ErrNotEmail = NewCheckError("NOT_EMAIL") ) // IsEmail checks if the value is a valid email address. diff --git a/v2/hex.go b/v2/hex.go index 0b6144d..52617a3 100644 --- a/v2/hex.go +++ b/v2/hex.go @@ -16,7 +16,7 @@ const ( var ( // ErrNotHex indicates that the given string contains hex characters. - ErrNotHex = NewCheckError("HEX") + ErrNotHex = NewCheckError("NOT_HEX") ) // IsHex checks if the given string consists of only hex characters. diff --git a/v2/ip.go b/v2/ip.go index 09e1d6b..e03ce6e 100644 --- a/v2/ip.go +++ b/v2/ip.go @@ -17,7 +17,7 @@ const ( var ( // ErrNotIP indicates that the given value is not a valid IP address. - ErrNotIP = NewCheckError("IP") + ErrNotIP = NewCheckError("NOT_IP") ) // IsIP checks if the value is a valid IP address. diff --git a/v2/ipv4.go b/v2/ipv4.go index 791bcf4..560327c 100644 --- a/v2/ipv4.go +++ b/v2/ipv4.go @@ -17,7 +17,7 @@ const ( var ( // ErrNotIPv4 indicates that the given value is not a valid IPv4 address. - ErrNotIPv4 = NewCheckError("IPv4") + ErrNotIPv4 = NewCheckError("NOT_IPV4") ) // IsIPv4 checks if the value is a valid IPv4 address. diff --git a/v2/ipv6.go b/v2/ipv6.go index 2856166..ad3bb54 100644 --- a/v2/ipv6.go +++ b/v2/ipv6.go @@ -17,7 +17,7 @@ const ( var ( // ErrNotIPv6 indicates that the given value is not a valid IPv6 address. - ErrNotIPv6 = NewCheckError("IPv6") + ErrNotIPv6 = NewCheckError("NOT_IPV6") ) // IsIPv6 checks if the value is a valid IPv6 address. diff --git a/v2/isbn.go b/v2/isbn.go index 84cf832..b2e1e15 100644 --- a/v2/isbn.go +++ b/v2/isbn.go @@ -17,7 +17,7 @@ const ( var ( // ErrNotISBN indicates that the given value is not a valid ISBN. - ErrNotISBN = NewCheckError("ISBN") + ErrNotISBN = NewCheckError("NOT_ISBN") // isbnRegex is the regular expression for validating ISBN-10 and ISBN-13. isbnRegex = regexp.MustCompile(`^(97(8|9))?\d{9}(\d|X)$`) diff --git a/v2/locales/DOC.md b/v2/locales/DOC.md new file mode 100644 index 0000000..a512cd3 --- /dev/null +++ b/v2/locales/DOC.md @@ -0,0 +1,60 @@ + + + + +# locales + +```go +import "github.com/cinar/checker/v2/locales" +``` + +Package locales provides the localized error messages for the check errors. + +## Index + +- [Constants](<#constants>) +- [Variables](<#variables>) + + +## Constants + + + +```go +const ( + // EnUS is the en_us locale. + EnUS = "en-US" +) +``` + +## Variables + +EnUsMessages is the map of en\-US messages. + +```go +var EnUsMessages = map[string]string{ + "NOT_ALPHANUMERIC": "Not an alphanumeric string.", + "NOT_ASCII": "Can only contain ASCII characters.", + "NOT_CIDR": "Not a valid CIDR notation.", + "NOT_CREDIT_CARD": "Not a valid credit card number.", + "NOT_DIGITS": "Can only contain digits.", + "NOT_EMAIL": "Not a valid email address.", + "NOT_FQDN": "Not a fully qualified domain name (FQDN).", + "NOT_HEX": "Can only contain hexadecimal characters.", + "NOT_IP": "Not a valid IP address.", + "NOT_IPV4": "Not a valid IPv4 address.", + "NOT_IPV6": "Not a valid IPv6 address.", + "NOT_ISBN": "Not a valid ISBN number.", + "NOT_LUHN": "Not a valid LUHN number.", + "NOT_MAC": "Not a valid MAC address.", + "NOT_MAX_LEN": "Value cannot be greater than {{ .max }}.", + "NOT_MIN_LEN": "Value cannot be less than {{ .min }}.", + "REQUIRED": "Required value is missing.", + "NOT_URL": "Not a valid URL.", +} +``` + +Generated by [gomarkdoc]() + + + \ No newline at end of file diff --git a/v2/locales/en_us.go b/v2/locales/en_us.go new file mode 100644 index 0000000..774be83 --- /dev/null +++ b/v2/locales/en_us.go @@ -0,0 +1,28 @@ +package locales + +const ( + // EnUS is the en_us locale. + EnUS = "en-US" +) + +// EnUsMessages is the map of en-US messages. +var EnUsMessages = map[string]string{ + "NOT_ALPHANUMERIC": "Not an alphanumeric string.", + "NOT_ASCII": "Can only contain ASCII characters.", + "NOT_CIDR": "Not a valid CIDR notation.", + "NOT_CREDIT_CARD": "Not a valid credit card number.", + "NOT_DIGITS": "Can only contain digits.", + "NOT_EMAIL": "Not a valid email address.", + "NOT_FQDN": "Not a fully qualified domain name (FQDN).", + "NOT_HEX": "Can only contain hexadecimal characters.", + "NOT_IP": "Not a valid IP address.", + "NOT_IPV4": "Not a valid IPv4 address.", + "NOT_IPV6": "Not a valid IPv6 address.", + "NOT_ISBN": "Not a valid ISBN number.", + "NOT_LUHN": "Not a valid LUHN number.", + "NOT_MAC": "Not a valid MAC address.", + "NOT_MAX_LEN": "Value cannot be greater than {{ .max }}.", + "NOT_MIN_LEN": "Value cannot be less than {{ .min }}.", + "REQUIRED": "Required value is missing.", + "NOT_URL": "Not a valid URL.", +} diff --git a/v2/locales/locales.go b/v2/locales/locales.go new file mode 100644 index 0000000..29b7cb2 --- /dev/null +++ b/v2/locales/locales.go @@ -0,0 +1,2 @@ +// Package locales provides the localized error messages for the check errors. +package locales diff --git a/v2/luhn.go b/v2/luhn.go index 9b3f5e4..5040449 100644 --- a/v2/luhn.go +++ b/v2/luhn.go @@ -17,7 +17,7 @@ const ( var ( // ErrNotLUHN indicates that the given value is not a valid LUHN number. - ErrNotLUHN = NewCheckError("LUHN") + ErrNotLUHN = NewCheckError("NOT_LUHN") ) // IsLUHN checks if the value is a valid LUHN number. diff --git a/v2/mac.go b/v2/mac.go index 33312a3..43e1370 100644 --- a/v2/mac.go +++ b/v2/mac.go @@ -17,7 +17,7 @@ const ( var ( // ErrNotMAC indicates that the given value is not a valid MAC address. - ErrNotMAC = NewCheckError("MAC") + ErrNotMAC = NewCheckError("NOT_MAC") ) // IsMAC checks if the value is a valid MAC address. diff --git a/v2/max_len.go b/v2/max_len.go index 8abad39..76157eb 100644 --- a/v2/max_len.go +++ b/v2/max_len.go @@ -17,7 +17,7 @@ const ( var ( // ErrMaxLen indicates that the value's length is greater than the specified maximum. - ErrMaxLen = NewCheckError("MAX_LEN") + ErrMaxLen = NewCheckError("NOT_MAX_LEN") ) // MaxLen checks if the length of the given value (string, slice, or map) is at most n. @@ -32,7 +32,7 @@ func MaxLen[T any](n int) CheckFunc[T] { v = reflect.Indirect(v) if v.Len() > n { - return value, ErrMaxLen + return value, newMaxLenError(n) } return value, nil @@ -49,3 +49,13 @@ func makeMaxLen(params string) CheckFunc[reflect.Value] { return MaxLen[reflect.Value](n) } + +// newMaxLenError creates a new maximum length error with the given maximum length. +func newMaxLenError(n int) error { + return NewCheckErrorWithData( + ErrMaxLen.Code, + map[string]interface{}{ + "max": n, + }, + ) +} diff --git a/v2/max_len_test.go b/v2/max_len_test.go index 97d0087..f755601 100644 --- a/v2/max_len_test.go +++ b/v2/max_len_test.go @@ -6,8 +6,6 @@ package v2_test import ( - "errors" - "log" "testing" v2 "github.com/cinar/checker/v2" @@ -38,11 +36,11 @@ func TestMaxLenError(t *testing.T) { t.Fatalf("result (%s) is not the original value (%s)", result, value) } - if !errors.Is(err, v2.ErrMaxLen) { - t.Fatalf("got unexpected error %v", err) - } + message := "Value cannot be greater than 5." - log.Println(err) + if err.Error() != message { + t.Fatalf("expected %s actual %s", message, err.Error()) + } } func TestReflectMaxLenError(t *testing.T) { diff --git a/v2/min_len.go b/v2/min_len.go index c996a0e..1a78941 100644 --- a/v2/min_len.go +++ b/v2/min_len.go @@ -17,7 +17,7 @@ const ( var ( // ErrMinLen indicates that the value's length is less than the specified minimum. - ErrMinLen = NewCheckError("MIN_LEN") + ErrMinLen = NewCheckError("NOT_MIN_LEN") ) // MinLen checks if the length of the given value (string, slice, or map) is at least n. @@ -32,7 +32,7 @@ func MinLen[T any](n int) CheckFunc[T] { v = reflect.Indirect(v) if v.Len() < n { - return value, ErrMinLen + return value, newMinLenError(n) } return value, nil @@ -49,3 +49,13 @@ func makeMinLen(params string) CheckFunc[reflect.Value] { return MinLen[reflect.Value](n) } + +// newMinLenError creates a new minimum length error with the given minimum value. +func newMinLenError(n int) error { + return NewCheckErrorWithData( + ErrMinLen.Code, + map[string]interface{}{ + "min": n, + }, + ) +} diff --git a/v2/min_len_test.go b/v2/min_len_test.go index 6a7b6a0..9622245 100644 --- a/v2/min_len_test.go +++ b/v2/min_len_test.go @@ -6,8 +6,6 @@ package v2_test import ( - "errors" - "log" "testing" v2 "github.com/cinar/checker/v2" @@ -38,11 +36,11 @@ func TestMinLenError(t *testing.T) { t.Fatalf("result (%s) is not the original value (%s)", result, value) } - if !errors.Is(err, v2.ErrMinLen) { - t.Fatalf("got unexpected error %v", err) - } + message := "Value cannot be less than 5." - log.Println(err) + if err.Error() != message { + t.Fatalf("expected %s actual %s", message, err.Error()) + } } func TestReflectMinLenError(t *testing.T) { diff --git a/v2/revive.toml b/v2/revive.toml new file mode 100644 index 0000000..f9e2405 --- /dev/null +++ b/v2/revive.toml @@ -0,0 +1,30 @@ +ignoreGeneratedHeader = false +severity = "warning" +confidence = 0.8 +errorCode = 0 +warningCode = 0 + +[rule.blank-imports] +[rule.context-as-argument] +[rule.context-keys-type] +[rule.dot-imports] +[rule.error-return] +[rule.error-strings] +[rule.error-naming] +[rule.exported] +[rule.if-return] +[rule.increment-decrement] +[rule.var-naming] +[rule.var-declaration] +[rule.package-comments] +[rule.range] +[rule.receiver-naming] +[rule.time-naming] +[rule.unexported-return] +[rule.indent-error-flow] +[rule.errorf] +[rule.empty-block] +[rule.superfluous-else] +[rule.unused-parameter] +[rule.unreachable-code] +[rule.redefines-builtin-id] diff --git a/v2/taskfile.yml b/v2/taskfile.yml new file mode 100644 index 0000000..0d8e575 --- /dev/null +++ b/v2/taskfile.yml @@ -0,0 +1,34 @@ +version: '3' +output: 'prefixed' + +tasks: + default: + cmds: + - task: fmt + - task: lint + - task: test + - task: docs + + action: + deps: [lint, test] + + fmt: + cmds: + - go fix ./... + + lint: + cmds: + - go vet ./... + - go run github.com/securego/gosec/v2/cmd/gosec@v2.20.0 ./... + - go run honnef.co/go/tools/cmd/staticcheck@v0.5.1 ./... + - go run github.com/mgechev/revive@v1.3.4 -config=revive.toml ./... + + test: + cmds: + - go test -cover -coverprofile=coverage.out ./... + - go tool cover -func=coverage.out + + docs: + cmds: + - go run github.com/princjef/gomarkdoc/cmd/gomarkdoc@v1.1.0 -e ./... + diff --git a/v2/url.go b/v2/url.go index 6c32ff8..3219ae2 100644 --- a/v2/url.go +++ b/v2/url.go @@ -17,7 +17,7 @@ const ( var ( // ErrNotURL indicates that the given value is not a valid URL. - ErrNotURL = NewCheckError("URL") + ErrNotURL = NewCheckError("NOT_URL") ) // IsURL checks if the value is a valid URL. From e119e842fbfd32688aaa87b489a060a496ae47fe Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Sat, 28 Dec 2024 22:25:50 -0800 Subject: [PATCH 33/41] RegisterLocale link is fixed. (#154) # Describe Request RegisterLocale link is fixed. # Change Type Documentation fix. --- v2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v2/README.md b/v2/README.md index 0378979..e0dd250 100644 --- a/v2/README.md +++ b/v2/README.md @@ -161,7 +161,7 @@ In this example, the `is-fruit` checker is used to validate that the `Name` fiel When validation fails, Checker returns an error. By default, the [Error()](DOC.md#CheckError.Error) function provides a human-readable error message in `en-US` locale. -To get error messages in other languages, use the [ErrorWithLocale()](DOC.md#CheckError.ErrorWithLocale) function. By default, only `en-US` is registered. You can register additional languages by calling [RegisterLocale](DOC.md##RegisterLocale). +To get error messages in other languages, use the [ErrorWithLocale()](DOC.md#CheckError.ErrorWithLocale) function. By default, only `en-US` is registered. You can register additional languages by calling [RegisterLocale](DOC.md#RegisterLocale). You can also customize existing error messages or add new ones to `locales.EnUSMessages` and other locale maps. From 2c3a57bcea3ca834061ad75d9b3b01112562593e Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Sun, 29 Dec 2024 04:20:52 -0800 Subject: [PATCH 34/41] Slice level checkers documented. (#155) # Describe Request Slice level checkers documented. # Change Type Documentation improvement. --- v2/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/v2/README.md b/v2/README.md index e0dd250..9faecb8 100644 --- a/v2/README.md +++ b/v2/README.md @@ -157,6 +157,21 @@ if !valid { In this example, the `is-fruit` checker is used to validate that the `Name` field of the `Item` struct is either "apple" or "banana". +# Slice and Item Level Checkers + +When adding checker struct tags to a slice, you can use the `@` prefix to indicate that the checker should be applied to the slice itself. Checkers without the `@` prefix will be applied to the individual items within the slice. Here is an example: + +```golang +type Person struct { + Name string `checkers:"required"` + Emails []string `checkers:"@max-len:1 max-len:4"` +} +``` + +In this example: +- `@max-len:1` ensures that the `Emails` slice itself has at most one item. +- `max-len:4` ensures that each email string within the `Emails` slice has a maximum length of 4 characters. + # Localized Error Messages When validation fails, Checker returns an error. By default, the [Error()](DOC.md#CheckError.Error) function provides a human-readable error message in `en-US` locale. From 72272ff90313211f3c05639fabd3d3ade09ae7c3 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Sun, 29 Dec 2024 04:45:26 -0800 Subject: [PATCH 35/41] Added examples for the localized error messages. (#156) # Describe Request Added examples for the localized error messages section of the documentation. # Change Type Documentation improvement. --- v2/DOC.md | 4 ++++ v2/README.md | 49 ++++++++++++++++++++++++++++++++++++++++++ v2/check_error.go | 2 +- v2/check_error_test.go | 10 ++++----- v2/locales/DOC.md | 4 ++-- v2/locales/en_us.go | 4 ++-- v2/maker_test.go | 4 ++++ 7 files changed, 67 insertions(+), 10 deletions(-) diff --git a/v2/DOC.md b/v2/DOC.md index ceea0fe..f64c329 100644 --- a/v2/DOC.md +++ b/v2/DOC.md @@ -1193,10 +1193,14 @@ import ( "fmt" "reflect" + "github.com/cinar/checker/v2/locales" + v2 "github.com/cinar/checker/v2" ) func main() { + locales.EnUSMessages["NOT_FRUIT"] = "Not a fruit name." + v2.RegisterMaker("is-fruit", func(params string) v2.CheckFunc[reflect.Value] { return func(value reflect.Value) (reflect.Value, error) { stringValue := value.Interface().(string) diff --git a/v2/README.md b/v2/README.md index 9faecb8..3b7f41d 100644 --- a/v2/README.md +++ b/v2/README.md @@ -176,12 +176,61 @@ In this example: When validation fails, Checker returns an error. By default, the [Error()](DOC.md#CheckError.Error) function provides a human-readable error message in `en-US` locale. +```golang +_, err := checker.IsEmail("abcd") +if err != nil { + fmt.Println(err) + // Output: Not a valid email address. +} +``` + To get error messages in other languages, use the [ErrorWithLocale()](DOC.md#CheckError.ErrorWithLocale) function. By default, only `en-US` is registered. You can register additional languages by calling [RegisterLocale](DOC.md#RegisterLocale). +```golang +// Register de-DE localized error messages. +checker.RegisterLocale(locales.DeDE, locales.DeDEMessages) + +_, err := checker.IsEmail("abcd") +if err != nil { + fmt.Println(err) + // Output: Keine gültige E-Mail-Adresse. +} +``` + You can also customize existing error messages or add new ones to `locales.EnUSMessages` and other locale maps. +```golang +// Register the en-US localized error message for the custom NOT_FRUIT error code. +locales.EnUSMessages["NOT_FRUIT"] = "Not a fruit name." + +errors, valid := v2.CheckStruct(item) +if !valid { + fmt.Println(errors) + // Output: map[Name:Not a fruit name.] +} +``` + Error messages are generated using Golang template functions, allowing them to include variables. +```golang +// Custrom checker error containing the item name. +err := NewCheckErrorWithData( + "NOT_FRUIT", + map[string]interface{}{ + "name": name, + }, +) + +// Register the en-US localized error message for the custom NOT_FRUIT error code. +locales.EnUSMessages["NOT_FRUIT"] = "Name {{ .name }} is not a fruit name." + +errors, valid := v2.CheckStruct(item) +if !valid { + fmt.Println(errors) + // Output: map[Name:Name abcd is not a fruit name.] +} +``` + # Contributing to the Project Anyone can contribute to Checkers library. Please make sure to read our [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md) guide first. Follow the [How to Contribute to Checker](./CONTRIBUTING.md) to contribute. diff --git a/v2/check_error.go b/v2/check_error.go index 139e72c..0f96876 100644 --- a/v2/check_error.go +++ b/v2/check_error.go @@ -28,7 +28,7 @@ const ( // errorMessages is the map of localized error messages. var errorMessages = map[string]map[string]string{ - locales.EnUS: locales.EnUsMessages, + locales.EnUS: locales.EnUSMessages, } // NewCheckError creates a new check error with the given code. diff --git a/v2/check_error_test.go b/v2/check_error_test.go index ec7dec3..c400490 100644 --- a/v2/check_error_test.go +++ b/v2/check_error_test.go @@ -27,7 +27,7 @@ func TestCheckErrorWithLocalizedCode(t *testing.T) { code := "TEST" message := "Test message" - locales.EnUsMessages[code] = message + locales.EnUSMessages[code] = message err := v2.NewCheckError(code) @@ -40,7 +40,7 @@ func TestCheckErrorWithDefaultLocalizedCode(t *testing.T) { code := "TEST" message := "Test message" - locales.EnUsMessages[code] = message + locales.EnUSMessages[code] = message err := v2.NewCheckError(code) @@ -53,7 +53,7 @@ func TestCheckErrorWithDataAndLocalizedCode(t *testing.T) { code := "TEST" message := "Test message {{.Name}}" - locales.EnUsMessages[code] = message + locales.EnUSMessages[code] = message err := v2.NewCheckErrorWithData(code, map[string]interface{}{ "Name": "Onur", @@ -70,7 +70,7 @@ func TestCheckErrorWithLocalizedCodeInvalidTemplate(t *testing.T) { code := "TEST" message := "Test message {{}" - locales.EnUsMessages[code] = message + locales.EnUSMessages[code] = message err := v2.NewCheckError(code) @@ -83,7 +83,7 @@ func TestCheckErrorWithLocalizedCodeInvalidExecute(t *testing.T) { code := "TEST" message := "{{ len .Name}}" - locales.EnUsMessages[code] = message + locales.EnUSMessages[code] = message err := v2.NewCheckError(code) diff --git a/v2/locales/DOC.md b/v2/locales/DOC.md index a512cd3..88718f0 100644 --- a/v2/locales/DOC.md +++ b/v2/locales/DOC.md @@ -29,10 +29,10 @@ const ( ## Variables -EnUsMessages is the map of en\-US messages. +EnUSMessages is the map of en\-US messages. ```go -var EnUsMessages = map[string]string{ +var EnUSMessages = map[string]string{ "NOT_ALPHANUMERIC": "Not an alphanumeric string.", "NOT_ASCII": "Can only contain ASCII characters.", "NOT_CIDR": "Not a valid CIDR notation.", diff --git a/v2/locales/en_us.go b/v2/locales/en_us.go index 774be83..c35c55b 100644 --- a/v2/locales/en_us.go +++ b/v2/locales/en_us.go @@ -5,8 +5,8 @@ const ( EnUS = "en-US" ) -// EnUsMessages is the map of en-US messages. -var EnUsMessages = map[string]string{ +// EnUSMessages is the map of en-US messages. +var EnUSMessages = map[string]string{ "NOT_ALPHANUMERIC": "Not an alphanumeric string.", "NOT_ASCII": "Can only contain ASCII characters.", "NOT_CIDR": "Not a valid CIDR notation.", diff --git a/v2/maker_test.go b/v2/maker_test.go index 428bd3b..fcc839c 100644 --- a/v2/maker_test.go +++ b/v2/maker_test.go @@ -10,6 +10,8 @@ import ( "reflect" "testing" + "github.com/cinar/checker/v2/locales" + v2 "github.com/cinar/checker/v2" ) @@ -28,6 +30,8 @@ func TestMakeCheckersUnknown(t *testing.T) { } func ExampleRegisterMaker() { + locales.EnUSMessages["NOT_FRUIT"] = "Not a fruit name." + v2.RegisterMaker("is-fruit", func(params string) v2.CheckFunc[reflect.Value] { return func(value reflect.Value) (reflect.Value, error) { stringValue := value.Interface().(string) From ceffc4932d268229061d2d1595c21728e190ed8e Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Sun, 29 Dec 2024 04:51:01 -0800 Subject: [PATCH 36/41] Small update on examples in the documentation. (#157) # Describe Request Small update on examples in the documentation. # Change Type Documentation improvements. --- v2/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/v2/README.md b/v2/README.md index 3b7f41d..8d0dd36 100644 --- a/v2/README.md +++ b/v2/README.md @@ -164,13 +164,13 @@ When adding checker struct tags to a slice, you can use the `@` prefix to indica ```golang type Person struct { Name string `checkers:"required"` - Emails []string `checkers:"@max-len:1 max-len:4"` + Emails []string `checkers:"@max-len:2 max-len:64"` } ``` In this example: -- `@max-len:1` ensures that the `Emails` slice itself has at most one item. -- `max-len:4` ensures that each email string within the `Emails` slice has a maximum length of 4 characters. +- `@max-len:2` ensures that the `Emails` slice itself has at most two items. +- `max-len:64` ensures that each email string within the `Emails` slice has a maximum length of 64 characters. # Localized Error Messages @@ -192,7 +192,7 @@ checker.RegisterLocale(locales.DeDE, locales.DeDEMessages) _, err := checker.IsEmail("abcd") if err != nil { - fmt.Println(err) + fmt.Println(err.ErrorWithLocale(locales.DeDE)) // Output: Keine gültige E-Mail-Adresse. } ``` @@ -217,7 +217,7 @@ Error messages are generated using Golang template functions, allowing them to i err := NewCheckErrorWithData( "NOT_FRUIT", map[string]interface{}{ - "name": name, + "name": "abcd", }, ) From bdb9c8ea9b5af13fc2b6535d68a8f2178092259e Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Sun, 29 Dec 2024 05:12:21 -0800 Subject: [PATCH 37/41] Version v2. (#158) # Describe Request Version v2. # Change Type New version. --- .gomarkdoc.yml | 3 +- v2/DOC.md => DOC.md | 102 +- README.md | 1345 ++--------------- alphanumeric.go | 40 +- alphanumeric_test.go | 57 +- ascii.go | 40 +- ascii_test.go | 37 +- v2/check_error.go => check_error.go | 0 v2/check_error_test.go => check_error_test.go | 0 v2/check_func.go => check_func.go | 0 checker.go | 220 ++- checker_test.go | 280 ++-- cidr.go | 40 +- cidr_test.go | 47 +- credit_card.go | 130 +- credit_card_test.go | 131 +- digits.go | 43 +- digits_test.go | 57 +- email.go | 131 +- email_test.go | 103 +- fqdn.go | 88 +- fqdn_test.go | 124 +- go.mod | 4 +- v2/helper_test.go => helper_test.go | 0 v2/hex.go => hex.go | 0 v2/hex_test.go => hex_test.go | 0 html_escape.go | 33 +- html_escape_test.go | 35 +- html_unescape.go | 31 +- html_unescape_test.go | 34 +- ip.go | 46 +- ip_test.go | 61 +- ipv4.go | 47 +- ipv4_test.go | 95 +- ipv6.go | 48 +- ipv6_test.go | 96 +- isbn.go | 132 +- isbn_test.go | 179 +-- {v2/locales => locales}/DOC.md | 0 {v2/locales => locales}/en_us.go | 0 {v2/locales => locales}/locales.go | 0 lower.go | 32 +- lower_test.go | 54 +- luhn.go | 89 +- luhn_test.go | 113 +- mac.go | 41 +- mac_test.go | 69 +- v2/maker.go => maker.go | 0 v2/maker_test.go => maker_test.go | 0 max.go | 43 - v2/max_len.go => max_len.go | 0 v2/max_len_test.go => max_len_test.go | 0 max_test.go | 71 - maxlength.go | 42 - maxlength_test.go | 71 - min.go | 43 - v2/min_len.go => min_len.go | 0 v2/min_len_test.go => min_len_test.go | 0 min_test.go | 71 - minlenght.go | 42 - minlength_test.go | 71 - regexp.go | 49 +- regexp_test.go | 70 +- required.go | 52 +- required_test.go | 150 +- same.go | 41 - same_test.go | 62 - taskfile.yml | 3 +- test_helper.go | 15 - test_helper_test.go | 22 - title.go | 38 +- title_test.go | 52 +- trim.go | 30 - trim_left.go | 32 +- trim_left_test.go | 54 +- trim_right.go | 32 +- trim_right_test.go | 54 +- v2/trim_space.go => trim_space.go | 0 v2/trim_space_test.go => trim_space_test.go | 0 trim_test.go | 55 - upper.go | 32 +- upper_test.go | 54 +- url.go | 49 +- url_escape.go | 31 +- url_escape_test.go | 34 +- url_test.go | 97 +- url_unescape.go | 35 +- url_unescape_test.go | 34 +- v2/.gomarkdoc.yml | 3 - v2/README.md | 244 --- v2/alphanumeric.go | 43 - v2/alphanumeric_test.go | 76 - v2/ascii.go | 43 - v2/ascii_test.go | 76 - v2/checker.go | 154 -- v2/checker_test.go | 198 --- v2/cidr.go | 42 - v2/cidr_test.go | 76 - v2/credit_card.go | 156 -- v2/credit_card_test.go | 312 ---- v2/digits.go | 42 - v2/digits_test.go | 76 - v2/email.go | 41 - v2/email_test.go | 76 - v2/fqdn.go | 43 - v2/fqdn_test.go | 76 - v2/go.mod | 3 - v2/html_escape.go | 32 - v2/html_escape_test.go | 47 - v2/html_unescape.go | 30 - v2/html_unescape_test.go | 47 - v2/ip.go | 40 - v2/ip_test.go | 76 - v2/ipv4.go | 41 - v2/ipv4_test.go | 76 - v2/ipv6.go | 40 - v2/ipv6_test.go | 76 - v2/isbn.go | 43 - v2/isbn_test.go | 76 - v2/lower.go | 32 - v2/lower_test.go | 47 - v2/luhn.go | 61 - v2/luhn_test.go | 83 - v2/mac.go | 41 - v2/mac_test.go | 76 - v2/regexp.go | 47 - v2/regexp_test.go | 76 - v2/required.go | 42 - v2/required_test.go | 41 - v2/revive.toml | 30 - v2/taskfile.yml | 34 - v2/title.go | 51 - v2/title_test.go | 47 - v2/trim_left.go | 32 - v2/trim_left_test.go | 47 - v2/trim_right.go | 32 - v2/trim_right_test.go | 47 - v2/upper.go | 32 - v2/upper_test.go | 47 - v2/url.go | 41 - v2/url_escape.go | 30 - v2/url_escape_test.go | 47 - v2/url_test.go | 76 - v2/url_unescape.go | 31 - v2/url_unescape_test.go | 47 - 145 files changed, 1812 insertions(+), 7697 deletions(-) rename v2/DOC.md => DOC.md (90%) rename v2/check_error.go => check_error.go (100%) rename v2/check_error_test.go => check_error_test.go (100%) rename v2/check_func.go => check_func.go (100%) rename v2/helper_test.go => helper_test.go (100%) rename v2/hex.go => hex.go (100%) rename v2/hex_test.go => hex_test.go (100%) rename {v2/locales => locales}/DOC.md (100%) rename {v2/locales => locales}/en_us.go (100%) rename {v2/locales => locales}/locales.go (100%) rename v2/maker.go => maker.go (100%) rename v2/maker_test.go => maker_test.go (100%) delete mode 100644 max.go rename v2/max_len.go => max_len.go (100%) rename v2/max_len_test.go => max_len_test.go (100%) delete mode 100644 max_test.go delete mode 100644 maxlength.go delete mode 100644 maxlength_test.go delete mode 100644 min.go rename v2/min_len.go => min_len.go (100%) rename v2/min_len_test.go => min_len_test.go (100%) delete mode 100644 min_test.go delete mode 100644 minlenght.go delete mode 100644 minlength_test.go delete mode 100644 same.go delete mode 100644 same_test.go delete mode 100644 test_helper.go delete mode 100644 test_helper_test.go delete mode 100644 trim.go rename v2/trim_space.go => trim_space.go (100%) rename v2/trim_space_test.go => trim_space_test.go (100%) delete mode 100644 trim_test.go delete mode 100644 v2/.gomarkdoc.yml delete mode 100644 v2/README.md delete mode 100644 v2/alphanumeric.go delete mode 100644 v2/alphanumeric_test.go delete mode 100644 v2/ascii.go delete mode 100644 v2/ascii_test.go delete mode 100644 v2/checker.go delete mode 100644 v2/checker_test.go delete mode 100644 v2/cidr.go delete mode 100644 v2/cidr_test.go delete mode 100644 v2/credit_card.go delete mode 100644 v2/credit_card_test.go delete mode 100644 v2/digits.go delete mode 100644 v2/digits_test.go delete mode 100644 v2/email.go delete mode 100644 v2/email_test.go delete mode 100644 v2/fqdn.go delete mode 100644 v2/fqdn_test.go delete mode 100644 v2/go.mod delete mode 100644 v2/html_escape.go delete mode 100644 v2/html_escape_test.go delete mode 100644 v2/html_unescape.go delete mode 100644 v2/html_unescape_test.go delete mode 100644 v2/ip.go delete mode 100644 v2/ip_test.go delete mode 100644 v2/ipv4.go delete mode 100644 v2/ipv4_test.go delete mode 100644 v2/ipv6.go delete mode 100644 v2/ipv6_test.go delete mode 100644 v2/isbn.go delete mode 100644 v2/isbn_test.go delete mode 100644 v2/lower.go delete mode 100644 v2/lower_test.go delete mode 100644 v2/luhn.go delete mode 100644 v2/luhn_test.go delete mode 100644 v2/mac.go delete mode 100644 v2/mac_test.go delete mode 100644 v2/regexp.go delete mode 100644 v2/regexp_test.go delete mode 100644 v2/required.go delete mode 100644 v2/required_test.go delete mode 100644 v2/revive.toml delete mode 100644 v2/taskfile.yml delete mode 100644 v2/title.go delete mode 100644 v2/title_test.go delete mode 100644 v2/trim_left.go delete mode 100644 v2/trim_left_test.go delete mode 100644 v2/trim_right.go delete mode 100644 v2/trim_right_test.go delete mode 100644 v2/upper.go delete mode 100644 v2/upper_test.go delete mode 100644 v2/url.go delete mode 100644 v2/url_escape.go delete mode 100644 v2/url_escape_test.go delete mode 100644 v2/url_test.go delete mode 100644 v2/url_unescape.go delete mode 100644 v2/url_unescape_test.go diff --git a/.gomarkdoc.yml b/.gomarkdoc.yml index dee4a39..729da91 100644 --- a/.gomarkdoc.yml +++ b/.gomarkdoc.yml @@ -1,4 +1,3 @@ -output: "{{.Dir}}/README.md" +output: "{{.Dir}}/DOC.md" repository: url: https://github.com/cinar/checker -exclude-dirs: "./v2/..." \ No newline at end of file diff --git a/v2/DOC.md b/DOC.md similarity index 90% rename from v2/DOC.md rename to DOC.md index f64c329..c56b2b6 100644 --- a/v2/DOC.md +++ b/DOC.md @@ -249,7 +249,7 @@ var ( ``` -## func [Check]() +## func [Check]() ```go func Check[T any](value T, checks ...CheckFunc[T]) (T, error) @@ -294,7 +294,7 @@ Onur Cinar -## func [CheckStruct]() +## func [CheckStruct]() ```go func CheckStruct(st any) (map[string]error, bool) @@ -345,7 +345,7 @@ Onur Cinar -## func [CheckWithConfig]() +## func [CheckWithConfig]() ```go func CheckWithConfig[T any](value T, config string) (T, error) @@ -354,7 +354,7 @@ func CheckWithConfig[T any](value T, config string) (T, error) CheckWithConfig applies the check functions specified by the config string to the given value. It returns the modified value and the first encountered error, if any. -## func [HTMLEscape]() +## func [HTMLEscape]() ```go func HTMLEscape(value string) (string, error) @@ -363,7 +363,7 @@ func HTMLEscape(value string) (string, error) HTMLEscape applies HTML escaping to special characters. -## func [HTMLUnescape]() +## func [HTMLUnescape]() ```go func HTMLUnescape(value string) (string, error) @@ -372,7 +372,7 @@ func HTMLUnescape(value string) (string, error) HTMLUnescape applies HTML unescaping to special characters. -## func [IsASCII]() +## func [IsASCII]() ```go func IsASCII(value string) (string, error) @@ -406,7 +406,7 @@ func main() { -## func [IsAlphanumeric]() +## func [IsAlphanumeric]() ```go func IsAlphanumeric(value string) (string, error) @@ -440,7 +440,7 @@ func main() { -## func [IsAmexCreditCard]() +## func [IsAmexCreditCard]() ```go func IsAmexCreditCard(number string) (string, error) @@ -473,7 +473,7 @@ func main() { -## func [IsAnyCreditCard]() +## func [IsAnyCreditCard]() ```go func IsAnyCreditCard(number string) (string, error) @@ -506,7 +506,7 @@ func main() { -## func [IsCIDR]() +## func [IsCIDR]() ```go func IsCIDR(value string) (string, error) @@ -540,7 +540,7 @@ func main() { -## func [IsDigits]() +## func [IsDigits]() ```go func IsDigits(value string) (string, error) @@ -574,7 +574,7 @@ func main() { -## func [IsDinersCreditCard]() +## func [IsDinersCreditCard]() ```go func IsDinersCreditCard(number string) (string, error) @@ -607,7 +607,7 @@ func main() { -## func [IsDiscoverCreditCard]() +## func [IsDiscoverCreditCard]() ```go func IsDiscoverCreditCard(number string) (string, error) @@ -640,7 +640,7 @@ func main() { -## func [IsEmail]() +## func [IsEmail]() ```go func IsEmail(value string) (string, error) @@ -674,7 +674,7 @@ func main() { -## func [IsFQDN]() +## func [IsFQDN]() ```go func IsFQDN(value string) (string, error) @@ -708,7 +708,7 @@ func main() { -## func [IsHex]() +## func [IsHex]() ```go func IsHex(value string) (string, error) @@ -742,7 +742,7 @@ func main() { -## func [IsIP]() +## func [IsIP]() ```go func IsIP(value string) (string, error) @@ -776,7 +776,7 @@ func main() { -## func [IsIPv4]() +## func [IsIPv4]() ```go func IsIPv4(value string) (string, error) @@ -810,7 +810,7 @@ func main() { -## func [IsIPv6]() +## func [IsIPv6]() ```go func IsIPv6(value string) (string, error) @@ -844,7 +844,7 @@ func main() { -## func [IsISBN]() +## func [IsISBN]() ```go func IsISBN(value string) (string, error) @@ -878,7 +878,7 @@ func main() { -## func [IsJcbCreditCard]() +## func [IsJcbCreditCard]() ```go func IsJcbCreditCard(number string) (string, error) @@ -911,7 +911,7 @@ func main() { -## func [IsLUHN]() +## func [IsLUHN]() ```go func IsLUHN(value string) (string, error) @@ -945,7 +945,7 @@ func main() { -## func [IsMAC]() +## func [IsMAC]() ```go func IsMAC(value string) (string, error) @@ -979,7 +979,7 @@ func main() { -## func [IsMasterCardCreditCard]() +## func [IsMasterCardCreditCard]() ```go func IsMasterCardCreditCard(number string) (string, error) @@ -1012,7 +1012,7 @@ func main() { -## func [IsRegexp]() +## func [IsRegexp]() ```go func IsRegexp(expression, value string) (string, error) @@ -1046,7 +1046,7 @@ func main() { -## func [IsURL]() +## func [IsURL]() ```go func IsURL(value string) (string, error) @@ -1080,7 +1080,7 @@ func main() { -## func [IsUnionPayCreditCard]() +## func [IsUnionPayCreditCard]() ```go func IsUnionPayCreditCard(number string) (string, error) @@ -1113,7 +1113,7 @@ func main() { -## func [IsVisaCreditCard]() +## func [IsVisaCreditCard]() ```go func IsVisaCreditCard(number string) (string, error) @@ -1146,7 +1146,7 @@ func main() { -## func [Lower]() +## func [Lower]() ```go func Lower(value string) (string, error) @@ -1155,7 +1155,7 @@ func Lower(value string) (string, error) Lower maps all Unicode letters in the given value to their lower case. -## func [ReflectCheckWithConfig]() +## func [ReflectCheckWithConfig]() ```go func ReflectCheckWithConfig(value reflect.Value, config string) (reflect.Value, error) @@ -1164,7 +1164,7 @@ func ReflectCheckWithConfig(value reflect.Value, config string) (reflect.Value, ReflectCheckWithConfig applies the check functions specified by the config string to the given reflect.Value. It returns the modified reflect.Value and the first encountered error, if any. -## func [RegisterLocale]() +## func [RegisterLocale]() ```go func RegisterLocale(locale string, messages map[string]string) @@ -1173,7 +1173,7 @@ func RegisterLocale(locale string, messages map[string]string) RegisterLocale registers the localized error messages for the given locale. -## func [RegisterMaker]() +## func [RegisterMaker]() ```go func RegisterMaker(name string, maker MakeCheckFunc) @@ -1232,7 +1232,7 @@ func main() { -## func [Required]() +## func [Required]() ```go func Required[T any](value T) (T, error) @@ -1241,7 +1241,7 @@ func Required[T any](value T) (T, error) Required checks if the given value of type T is its zero value. It returns an error if the value is zero. -## func [Title]() +## func [Title]() ```go func Title(value string) (string, error) @@ -1250,7 +1250,7 @@ func Title(value string) (string, error) Title returns the value of the string with the first letter of each word in upper case. -## func [TrimLeft]() +## func [TrimLeft]() ```go func TrimLeft(value string) (string, error) @@ -1259,7 +1259,7 @@ func TrimLeft(value string) (string, error) TrimLeft returns the value of the string with whitespace removed from the beginning. -## func [TrimRight]() +## func [TrimRight]() ```go func TrimRight(value string) (string, error) @@ -1268,7 +1268,7 @@ func TrimRight(value string) (string, error) TrimRight returns the value of the string with whitespace removed from the end. -## func [TrimSpace]() +## func [TrimSpace]() ```go func TrimSpace(value string) (string, error) @@ -1277,7 +1277,7 @@ func TrimSpace(value string) (string, error) TrimSpace returns the value of the string with whitespace removed from both ends. -## func [URLEscape]() +## func [URLEscape]() ```go func URLEscape(value string) (string, error) @@ -1286,7 +1286,7 @@ func URLEscape(value string) (string, error) URLEscape applies URL escaping to special characters. -## func [URLUnescape]() +## func [URLUnescape]() ```go func URLUnescape(value string) (string, error) @@ -1295,7 +1295,7 @@ func URLUnescape(value string) (string, error) URLUnescape applies URL unescaping to special characters. -## func [Upper]() +## func [Upper]() ```go func Upper(value string) (string, error) @@ -1304,7 +1304,7 @@ func Upper(value string) (string, error) Upper maps all Unicode letters in the given value to their upper case. -## type [CheckError]() +## type [CheckError]() CheckError defines the check error. @@ -1319,7 +1319,7 @@ type CheckError struct { ``` -### func [NewCheckError]() +### func [NewCheckError]() ```go func NewCheckError(code string) *CheckError @@ -1328,7 +1328,7 @@ func NewCheckError(code string) *CheckError NewCheckError creates a new check error with the given code. -### func [NewCheckErrorWithData]() +### func [NewCheckErrorWithData]() ```go func NewCheckErrorWithData(code string, data map[string]interface{}) *CheckError @@ -1337,7 +1337,7 @@ func NewCheckErrorWithData(code string, data map[string]interface{}) *CheckError NewCheckErrorWithData creates a new check error with the given code and data. -### func \(\*CheckError\) [Error]() +### func \(\*CheckError\) [Error]() ```go func (c *CheckError) Error() string @@ -1346,7 +1346,7 @@ func (c *CheckError) Error() string Error returns the error message for the check. -### func \(\*CheckError\) [ErrorWithLocale]() +### func \(\*CheckError\) [ErrorWithLocale]() ```go func (c *CheckError) ErrorWithLocale(locale string) string @@ -1355,7 +1355,7 @@ func (c *CheckError) ErrorWithLocale(locale string) string ErrorWithLocale returns the localized error message for the check with the given locale. -### func \(\*CheckError\) [Is]() +### func \(\*CheckError\) [Is]() ```go func (c *CheckError) Is(target error) bool @@ -1364,7 +1364,7 @@ func (c *CheckError) Is(target error) bool Is reports whether the check error is the same as the target error. -## type [CheckFunc]() +## type [CheckFunc]() CheckFunc is a function that takes a value of type T and performs a check on it. It returns the resulting value and any error that occurred during the check. @@ -1373,7 +1373,7 @@ type CheckFunc[T any] func(value T) (T, error) ``` -### func [MakeRegexpChecker]() +### func [MakeRegexpChecker]() ```go func MakeRegexpChecker(expression string, invalidError error) CheckFunc[reflect.Value] @@ -1382,7 +1382,7 @@ func MakeRegexpChecker(expression string, invalidError error) CheckFunc[reflect. MakeRegexpChecker makes a regexp checker for the given regexp expression with the given invalid result. -### func [MaxLen]() +### func [MaxLen]() ```go func MaxLen[T any](n int) CheckFunc[T] @@ -1391,7 +1391,7 @@ func MaxLen[T any](n int) CheckFunc[T] MaxLen checks if the length of the given value \(string, slice, or map\) is at most n. Returns an error if the length is greater than n. -### func [MinLen]() +### func [MinLen]() ```go func MinLen[T any](n int) CheckFunc[T] @@ -1400,7 +1400,7 @@ func MinLen[T any](n int) CheckFunc[T] MinLen checks if the length of the given value \(string, slice, or map\) is at least n. Returns an error if the length is less than n. -## type [MakeCheckFunc]() +## type [MakeCheckFunc]() MakeCheckFunc is a function that returns a check function using the given params. diff --git a/README.md b/README.md index 8fe9c09..8d0dd36 100644 --- a/README.md +++ b/README.md @@ -6,1292 +6,231 @@ # Checker -Checker is a Go library that helps you validate user input. It can be used to validate user input stored in a struct, or to validate individual pieces of input. +Checker is a lightweight Go library designed to validate user input efficiently. It supports validation of both struct fields and individual input values. -There are many validation libraries available, but I prefer to build my own tools and avoid pulling in unnecessary dependencies. That's why I created Checker, a simple validation library with no dependencies. It's easy to use and gets the job done. +While there are numerous validation libraries available, Checker stands out due to its simplicity and lack of external dependencies. This makes it an ideal choice for developers who prefer to minimize dependencies and maintain control over their tools. Checker is straightforward to use and effectively meets your validation needs. ## Usage -To get started, install the Checker library with the following command: +To begin using the Checker library, install it with the following command: ```bash -go get github.com/cinar/checker +go get github.com/cinar/checker/v2 ``` -Next, you will need to import the library into your source file. You can do this by following the example below: +Then, import the library into your source file as shown below: ```golang import ( - "github.com/cinar/checker" + checker "github.com/cinar/checker/v2" ) ``` ### Validating User Input Stored in a Struct -Checker can be used in two ways. The first way is to validate user input stored in a struct. To do this, you can list the checkers through the struct tag for each field. Here is an example: +Checker can validate user input stored in a struct by listing the checkers in the struct tags for each field. Here is an example: ```golang type Person struct { - Name string `checkers:"required"` + Name string `checkers:"trim required"` } -person := &Person{} +person := &Person{ + Name: " Onur Cinar ", +} -errors, valid := checker.Check(person) +errors, valid := checker.CheckStruct(person) if !valid { - // Send the errors back to the user + // Handle validation errors +} +``` + +### Validating Individual User Input with Multiple Checkers + +You can also validate individual user input by calling checker functions directly. Here is an example: + +```golang +name := " Onur Cinar " + +name, err := checker.Check(name, checker.Trim, checker.Required) +if err != nil { + // Handle validation error } ``` ### Validating Individual User Input -If you do not want to validate user input stored in a struct, you can individually call the checker functions to validate the user input. Here is an example: +For simpler validation, you can call individual checker functions. Here is an example: ```golang -var name +name := "Onur Cinar" err := checker.IsRequired(name) if err != nil { - // Send the result back to the user + // Handle validation error } ``` ## Normalizers and Checkers -Checkers are used to check for problems in user input, while normalizers are used to transform user input into a preferred format. For example, a normalizer could be used to trim spaces from the beginning and end of a string, or to convert a string to title case. +Checkers validate user input, while normalizers transform it into a preferred format. For example, a normalizer can trim spaces from a string or convert it to title case. -I am not entirely happy with the decision to combine checkers and normalizers into a single library, but using them together can be useful. Normalizers and checkers can be mixed in any order when defining the validation steps for user data. For example, the trim normalizer can be used in conjunction with the required checker to first trim the user input and then check if the user provided the required information. Here is an example: +Although combining checkers and normalizers into a single library might seem unconventional, using them together can be beneficial. They can be mixed in any order when defining validation steps. For instance, you can use the `trim` normalizer with the `required` checker to first trim the input and then ensure it is provided. Here is an example: ```golang type Person struct { - Name string `checkers:"trim required"` + Name string `checkers:"trim required"` } ``` # Checkers Provided -This package currently provides the following checkers: - -- `alphanumeric` checks if the given string consists of only alphanumeric characters. -- `ascii` checks if the given string consists of only ASCII characters. -- `cidr` checker checks if the value is a valid CIDR notation IP address and prefix length. -- `credit-card` checks if the given value is a valid credit card number. -- `digits` checks if the given string consists of only digit characters. -- `email` checks if the given string is an email address. -- `fqdn` checks if the given string is a fully qualified domain name. -- `ip` checks if the given value is an IP address. -- `ipv4` checks if the given value is an IPv4 address. -- `ipv6` checks if the given value is an IPv6 address. -- `isbn` checks if the given value is a valid ISBN number. -- `luhn` checks if the given number is valid based on the Luhn algorithm. -- `mac` checks if the given value is a valid an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet IP over InfiniBand link-layer address. -- `max` checks if the given value is less than the given maximum. -- `max-length` checks if the length of the given value is less than the given maximum length. -- `min` checks if the given value is greather than the given minimum. -- `min-length` checks if the length of the given value is greather than the given minimum length. -- `regexp` checks if the given string matches the regexp pattern. -- `required` checks if the required value is provided. -- `same` checks if the given value is equal to the value of the field with the given name. -- `url` checks if the given value is a valid URL. +- [`ascii`](DOC.md#IsASCII): Ensures the string contains only ASCII characters. +- [`alphanumeric`](DOC.md#IsAlphanumeric): Ensures the string contains only letters and numbers. +- [`credit-card`](DOC.md#IsAnyCreditCard): Ensures the string is a valid credit card number. +- [`cidr`](DOC.md#IsCIDR): Ensures the string is a valid CIDR notation. +- [`digits`](DOC.md#IsDigits): Ensures the string contains only digits. +- [`email`](DOC.md#IsEmail): Ensures the string is a valid email address. +- [`fqdn`](DOC.md#IsFQDN): Ensures the string is a valid fully qualified domain name. +- [`hex`](DOC.md#IsHex): Ensures the string contains only hex digits. +- [`ip`](DOC.md#IsIP): Ensures the string is a valid IP address. +- [`ipv4`](DOC.md#IsIPv4): Ensures the string is a valid IPv4 address. +- [`ipv6`](DOC.md#IsIPv6): Ensures the string is a valid IPv6 address. +- [`isbn`](DOC.md#IsISBN): Ensures the string is a valid ISBN. +- [`luhn`](DOC.md#IsLUHN): Ensures the string is a valid LUHN number. +- [`mac`](DOC.md#IsMAC): Ensures the string is a valid MAC address. +- [`max-len`](DOC.md#func-maxlen): Ensures the length of the given value (string, slice, or map) is at most n. +- [`min-len`](DOC.md#func-minlen): Ensures the length of the given value (string, slice, or map) is at least n. +- [`required`](DOC.md#func-required) Ensures the value is provided. +- [`regexp`](DOC.md#func-makeregexpchecker) Ensured the string matches the pattern. +- [`url`](DOC.md#IsURL): Ensures the string is a valid URL. # Normalizers Provided -This package currently provides the following normalizers. They can be mixed with the checkers when defining the validation steps for user data. +- [`lower`](DOC.md#Lower): Converts the string to lowercase. +- [`title`](DOC.md#Title): Converts the string to title case. +- [`trim-left`](DOC.md#TrimLeft): Trims whitespace from the left side of the string. +- [`trim-right`](DOC.md#TrimRight): Trims whitespace from the right side of the string. +- [`trim`](DOC.md#TrimSpace): Trims whitespace from both sides of the string. +- [`upper`](DOC.md#Upper): Converts the string to uppercase. +- [`html-escape`](DOC.md#HTMLEscape): Escapes special characters in the string for HTML. +- [`html-unescape`](DOC.md#HTMLUnescape): Unescapes special characters in the string for HTML. +- [`url-escape`](DOC.md#URLEscape): Escapes special characters in the string for URLs. +- [`url-unescape`](DOC.md#URLUnescape): Unescapes special characters in the string for URLs. -- `html-escape` applies HTML escaping to special characters. -- `html-unescape` applies HTML unescaping to special characters. -- `lower` maps all Unicode letters in the given value to their lower case. -- `upper` maps all Unicode letters in the given value to their upper case. -- `title` maps the first letter of each word to their upper case. -- `trim` removes the whitespaces at the beginning and at the end of the given value. -- `trim-left` removes the whitespaces at the beginning of the given value. -- `trim-right` removes the whitespaces at the end of the given value. -- `url-escape` applies URL escaping to special characters. -- `url-unescape` applies URL unescaping to special characters. +# Custom Checkers and Normalizers -# Custom Checkers - -To define a custom checker, you need to create a new function with the following parameters: +You can define custom checkers or normalizers and register them for use in your validation logic. Here is an example of how to create and register a custom checker: ```golang -func CustomChecker(value, parent reflect.Value) error { - return nil -} +checker.RegisterMaker("is-fruit", func(params string) v2.CheckFunc[reflect.Value] { + return func(value reflect.Value) (reflect.Value, error) { + stringValue := value.Interface().(string) + + if stringValue == "apple" || stringValue == "banana" { + return value, nil + } + + return value, v2.NewCheckError("NOT_FRUIT") + } +}) ``` -type MakeFunc -You also need to create a make function that takes the checker configuration and returns a reference to the checker function. + +In this example, the custom checker `is-fruit` checks if the input value is either "apple" or "banana". If the value is not one of these, it returns an error. + +Once registered, you can use your custom checker in struct tags just like the built-in checkers: ```golang -func CustomMaker(params string) CheckFunc { - return CustomChecker +type Item struct { + Name string `checkers:"is-fruit"` +} + +item := &Item{ + Name: "banana", +} + +errors, valid := v2.CheckStruct(item) +if !valid { + fmt.Println(errors) } ``` -Finally, you need to call the ```Register``` function to register your custom checker. +In this example, the `is-fruit` checker is used to validate that the `Name` field of the `Item` struct is either "apple" or "banana". + +# Slice and Item Level Checkers + +When adding checker struct tags to a slice, you can use the `@` prefix to indicate that the checker should be applied to the slice itself. Checkers without the `@` prefix will be applied to the individual items within the slice. Here is an example: ```golang -checker.Register("custom-checker", CustomMaker) +type Person struct { + Name string `checkers:"required"` + Emails []string `checkers:"@max-len:2 max-len:64"` +} ``` -Once you have registered your custom checker, you can use it by simply specifying its name. +In this example: +- `@max-len:2` ensures that the `Emails` slice itself has at most two items. +- `max-len:64` ensures that each email string within the `Emails` slice has a maximum length of 64 characters. + +# Localized Error Messages + +When validation fails, Checker returns an error. By default, the [Error()](DOC.md#CheckError.Error) function provides a human-readable error message in `en-US` locale. ```golang -type User struct { - Username string `checkers:"custom-checker"` +_, err := checker.IsEmail("abcd") +if err != nil { + fmt.Println(err) + // Output: Not a valid email address. } ``` - +To get error messages in other languages, use the [ErrorWithLocale()](DOC.md#CheckError.ErrorWithLocale) function. By default, only `en-US` is registered. You can register additional languages by calling [RegisterLocale](DOC.md#RegisterLocale). - +```golang +// Register de-DE localized error messages. +checker.RegisterLocale(locales.DeDE, locales.DeDEMessages) -# checker - -```go -import "github.com/cinar/checker" +_, err := checker.IsEmail("abcd") +if err != nil { + fmt.Println(err.ErrorWithLocale(locales.DeDE)) + // Output: Keine gültige E-Mail-Adresse. +} ``` -Package checker is a Go library for validating user input through struct tags. +You can also customize existing error messages or add new ones to `locales.EnUSMessages` and other locale maps. -Copyright \(c\) 2023\-2024 Onur Cinar. Use of this source code is governed by a MIT\-style license that can be found in the LICENSE file. https://github.com/cinar/checker +```golang +// Register the en-US localized error message for the custom NOT_FRUIT error code. +locales.EnUSMessages["NOT_FRUIT"] = "Not a fruit name." -## Index - -- [Variables](<#variables>) -- [func FailIfNoPanic\(t \*testing.T\)](<#FailIfNoPanic>) -- [func IsASCII\(value string\) error](<#IsASCII>) -- [func IsAlphanumeric\(value string\) error](<#IsAlphanumeric>) -- [func IsAmexCreditCard\(number string\) error](<#IsAmexCreditCard>) -- [func IsAnyCreditCard\(number string\) error](<#IsAnyCreditCard>) -- [func IsCidr\(value string\) error](<#IsCidr>) -- [func IsDigits\(value string\) error](<#IsDigits>) -- [func IsDinersCreditCard\(number string\) error](<#IsDinersCreditCard>) -- [func IsDiscoverCreditCard\(number string\) error](<#IsDiscoverCreditCard>) -- [func IsEmail\(email string\) error](<#IsEmail>) -- [func IsFqdn\(domain string\) error](<#IsFqdn>) -- [func IsIP\(value string\) error](<#IsIP>) -- [func IsIPV4\(value string\) error](<#IsIPV4>) -- [func IsIPV6\(value string\) error](<#IsIPV6>) -- [func IsISBN\(value string\) error](<#IsISBN>) -- [func IsISBN10\(value string\) error](<#IsISBN10>) -- [func IsISBN13\(value string\) error](<#IsISBN13>) -- [func IsJcbCreditCard\(number string\) error](<#IsJcbCreditCard>) -- [func IsLuhn\(number string\) error](<#IsLuhn>) -- [func IsMac\(value string\) error](<#IsMac>) -- [func IsMasterCardCreditCard\(number string\) error](<#IsMasterCardCreditCard>) -- [func IsMax\(value interface\{\}, max float64\) error](<#IsMax>) -- [func IsMaxLength\(value interface\{\}, maxLength int\) error](<#IsMaxLength>) -- [func IsMin\(value interface\{\}, min float64\) error](<#IsMin>) -- [func IsMinLength\(value interface\{\}, minLength int\) error](<#IsMinLength>) -- [func IsRequired\(v interface\{\}\) error](<#IsRequired>) -- [func IsURL\(value string\) error](<#IsURL>) -- [func IsUnionPayCreditCard\(number string\) error](<#IsUnionPayCreditCard>) -- [func IsVisaCreditCard\(number string\) error](<#IsVisaCreditCard>) -- [func Register\(name string, maker MakeFunc\)](<#Register>) -- [type CheckFunc](<#CheckFunc>) - - [func MakeRegexpChecker\(expression string, invalidError error\) CheckFunc](<#MakeRegexpChecker>) -- [type Errors](<#Errors>) - - [func Check\(s interface\{\}\) \(Errors, bool\)](<#Check>) -- [type MakeFunc](<#MakeFunc>) - - [func MakeRegexpMaker\(expression string, invalidError error\) MakeFunc](<#MakeRegexpMaker>) - - -## Variables - -ErrNotASCII indicates that the given string contains non\-ASCII characters. - -```go -var ErrNotASCII = errors.New("please use standard English characters only") +errors, valid := v2.CheckStruct(item) +if !valid { + fmt.Println(errors) + // Output: map[Name:Not a fruit name.] +} ``` -ErrNotAlphanumeric indicates that the given string contains non\-alphanumeric characters. +Error messages are generated using Golang template functions, allowing them to include variables. -```go -var ErrNotAlphanumeric = errors.New("please use only letters and numbers") -``` - -ErrNotCidr indicates that the given value is not a valid CIDR. - -```go -var ErrNotCidr = errors.New("please enter a valid CIDR") -``` - -ErrNotCreditCard indicates that the given value is not a valid credit card number. - -```go -var ErrNotCreditCard = errors.New("please enter a valid credit card number") -``` - -ErrNotDigits indicates that the given string contains non\-digit characters. - -```go -var ErrNotDigits = errors.New("please enter a valid number") -``` - -ErrNotEmail indicates that the given string is not a valid email. - -```go -var ErrNotEmail = errors.New("please enter a valid email address") -``` - -ErrNotFqdn indicates that the given string is not a valid FQDN. - -```go -var ErrNotFqdn = errors.New("please enter a valid domain name") -``` - -ErrNotIP indicates that the given value is not an IP address. - -```go -var ErrNotIP = errors.New("please enter a valid IP address") -``` - -ErrNotIPV4 indicates that the given value is not an IPv4 address. - -```go -var ErrNotIPV4 = errors.New("please enter a valid IPv4 address") -``` - -ErrNotIPV6 indicates that the given value is not an IPv6 address. - -```go -var ErrNotIPV6 = errors.New("please enter a valid IPv6 address") -``` - -ErrNotISBN indicates that the given value is not a valid ISBN. - -```go -var ErrNotISBN = errors.New("please enter a valid ISBN number") -``` - -ErrNotLuhn indicates that the given number is not valid based on the Luhn algorithm. - -```go -var ErrNotLuhn = errors.New("please enter a valid LUHN") -``` - -ErrNotMac indicates that the given value is not an MAC address. - -```go -var ErrNotMac = errors.New("please enter a valid MAC address") -``` - -ErrNotMatch indicates that the given string does not match the regexp pattern. - -```go -var ErrNotMatch = errors.New("please enter a valid input") -``` - -ErrNotSame indicates that the given two values are not equal to each other. - -```go -var ErrNotSame = errors.New("does not match the other") -``` - -ErrNotURL indicates that the given value is not a valid URL. - -```go -var ErrNotURL = errors.New("please enter a valid URL") -``` - -ErrRequired indicates that the required value is missing. - -```go -var ErrRequired = errors.New("is required") -``` - - -## func [FailIfNoPanic]() - -```go -func FailIfNoPanic(t *testing.T) -``` - -FailIfNoPanic fails if test didn't panic. Use this function with the defer. - - -## func [IsASCII]() - -```go -func IsASCII(value string) error -``` - -IsASCII checks if the given string consists of only ASCII characters. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" +```golang +// Custrom checker error containing the item name. +err := NewCheckErrorWithData( + "NOT_FRUIT", + map[string]interface{}{ + "name": "abcd", + }, ) -func main() { - err := checker.IsASCII("Checker") - if err != nil { - // Send the errors back to the user - } +// Register the en-US localized error message for the custom NOT_FRUIT error code. +locales.EnUSMessages["NOT_FRUIT"] = "Name {{ .name }} is not a fruit name." + +errors, valid := v2.CheckStruct(item) +if !valid { + fmt.Println(errors) + // Output: map[Name:Name abcd is not a fruit name.] } ``` -

-
- - -## func [IsAlphanumeric]() - -```go -func IsAlphanumeric(value string) error -``` - -IsAlphanumeric checks if the given string consists of only alphanumeric characters. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsAlphanumeric("ABcd1234") - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsAmexCreditCard]() - -```go -func IsAmexCreditCard(number string) error -``` - -IsAmexCreditCard checks if the given valie is a valid AMEX credit card. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsAmexCreditCard("378282246310005") - - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsAnyCreditCard]() - -```go -func IsAnyCreditCard(number string) error -``` - -IsAnyCreditCard checks if the given value is a valid credit card number. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsAnyCreditCard("6011111111111117") - - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsCidr]() - -```go -func IsCidr(value string) error -``` - -IsCidr checker checks if the value is a valid CIDR notation IP address and prefix length. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsCidr("2001:db8::/32") - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsDigits]() - -```go -func IsDigits(value string) error -``` - -IsDigits checks if the given string consists of only digit characters. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsDigits("1234") - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsDinersCreditCard]() - -```go -func IsDinersCreditCard(number string) error -``` - -IsDinersCreditCard checks if the given valie is a valid Diners credit card. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsDinersCreditCard("36227206271667") - - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsDiscoverCreditCard]() - -```go -func IsDiscoverCreditCard(number string) error -``` - -IsDiscoverCreditCard checks if the given valie is a valid Discover credit card. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsDiscoverCreditCard("6011111111111117") - - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsEmail]() - -```go -func IsEmail(email string) error -``` - -IsEmail checks if the given string is an email address. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsEmail("user@zdo.com") - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsFqdn]() - -```go -func IsFqdn(domain string) error -``` - -IsFqdn checks if the given string is a fully qualified domain name. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsFqdn("zdo.com") - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsIP]() - -```go -func IsIP(value string) error -``` - -IsIP checks if the given value is an IP address. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsIP("2001:db8::68") - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsIPV4]() - -```go -func IsIPV4(value string) error -``` - -IsIPV4 checks if the given value is an IPv4 address. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsIPV4("192.168.1.1") - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsIPV6]() - -```go -func IsIPV6(value string) error -``` - -IsIPV6 checks if the given value is an IPv6 address. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsIPV6("2001:db8::68") - - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsISBN]() - -```go -func IsISBN(value string) error -``` - -IsISBN checks if the given value is a valid ISBN number. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsISBN("1430248270") - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsISBN10]() - -```go -func IsISBN10(value string) error -``` - -IsISBN10 checks if the given value is a valid ISBN\-10 number. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsISBN10("1430248270") - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsISBN13]() - -```go -func IsISBN13(value string) error -``` - -IsISBN13 checks if the given value is a valid ISBN\-13 number. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsISBN13("9781430248279") - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsJcbCreditCard]() - -```go -func IsJcbCreditCard(number string) error -``` - -IsJcbCreditCard checks if the given valie is a valid JCB 15 credit card. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsJcbCreditCard("3530111333300000") - - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsLuhn]() - -```go -func IsLuhn(number string) error -``` - -IsLuhn checks if the given number is valid based on the Luhn algorithm. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsLuhn("4012888888881881") - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsMac]() - -```go -func IsMac(value string) error -``` - -IsMac checks if the given value is a valid an IEEE 802 MAC\-48, EUI\-48, EUI\-64, or a 20\-octet IP over InfiniBand link\-layer address. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsMac("00:00:5e:00:53:01") - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsMasterCardCreditCard]() - -```go -func IsMasterCardCreditCard(number string) error -``` - -IsMasterCardCreditCard checks if the given valie is a valid MasterCard credit card. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsMasterCardCreditCard("5555555555554444") - - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsMax]() - -```go -func IsMax(value interface{}, max float64) error -``` - -IsMax checks if the given value is below than the given maximum. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - quantity := 5 - - err := checker.IsMax(quantity, 10) - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsMaxLength]() - -```go -func IsMaxLength(value interface{}, maxLength int) error -``` - -IsMaxLength checks if the length of the given value is less than the given maximum length. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - s := "1234" - - err := checker.IsMaxLength(s, 4) - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsMin]() - -```go -func IsMin(value interface{}, min float64) error -``` - -IsMin checks if the given value is above than the given minimum. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - age := 45 - - err := checker.IsMin(age, 21) - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsMinLength]() - -```go -func IsMinLength(value interface{}, minLength int) error -``` - -IsMinLength checks if the length of the given value is greather than the given minimum length. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - s := "1234" - - err := checker.IsMinLength(s, 4) - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsRequired]() - -```go -func IsRequired(v interface{}) error -``` - -IsRequired checks if the given required value is present. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - var name string - - err := checker.IsRequired(name) - if err != nil { - // Send the err back to the user - } -} -``` - -

-
- - -## func [IsURL]() - -```go -func IsURL(value string) error -``` - -IsURL checks if the given value is a valid URL. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsURL("https://zdo.com") - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsUnionPayCreditCard]() - -```go -func IsUnionPayCreditCard(number string) error -``` - -IsUnionPayCreditCard checks if the given valie is a valid UnionPay credit card. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsUnionPayCreditCard("6200000000000005") - - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [IsVisaCreditCard]() - -```go -func IsVisaCreditCard(number string) error -``` - -IsVisaCreditCard checks if the given valie is a valid Visa credit card. - -
Example -

- - - -```go -package main - -import ( - "github.com/cinar/checker" -) - -func main() { - err := checker.IsVisaCreditCard("4111111111111111") - - if err != nil { - // Send the errors back to the user - } -} -``` - -

-
- - -## func [Register]() - -```go -func Register(name string, maker MakeFunc) -``` - -Register registers the given checker name and the maker function. - - -## type [CheckFunc]() - -CheckFunc defines the signature for the checker functions. - -```go -type CheckFunc func(value, parent reflect.Value) error -``` - - -### func [MakeRegexpChecker]() - -```go -func MakeRegexpChecker(expression string, invalidError error) CheckFunc -``` - -MakeRegexpChecker makes a regexp checker for the given regexp expression with the given invalid result. - - -## type [Errors]() - -Errors provides a mapping of the checker errors keyed by the field names. - -```go -type Errors map[string]error -``` - - -### func [Check]() - -```go -func Check(s interface{}) (Errors, bool) -``` - -Check function checks the given struct based on the checkers listed in field tag names. - - -## type [MakeFunc]() - -MakeFunc defines the signature for the checker maker functions. - -```go -type MakeFunc func(params string) CheckFunc -``` - - -### func [MakeRegexpMaker]() - -```go -func MakeRegexpMaker(expression string, invalidError error) MakeFunc -``` - -MakeRegexpMaker makes a regexp checker maker for the given regexp expression with the given invalid result. - -Generated by [gomarkdoc]() - - - - # Contributing to the Project Anyone can contribute to Checkers library. Please make sure to read our [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md) guide first. Follow the [How to Contribute to Checker](./CONTRIBUTING.md) to contribute. diff --git a/alphanumeric.go b/alphanumeric.go index 0eb4f52..de3618d 100644 --- a/alphanumeric.go +++ b/alphanumeric.go @@ -3,41 +3,41 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" "reflect" "unicode" ) -// tagAlphanumeric is the tag of the checker. -const tagAlphanumeric = "alphanumeric" +const ( + // nameAlphanumeric is the name of the alphanumeric check. + nameAlphanumeric = "alphanumeric" +) -// ErrNotAlphanumeric indicates that the given string contains non-alphanumeric characters. -var ErrNotAlphanumeric = errors.New("please use only letters and numbers") +var ( + // ErrNotAlphanumeric indicates that the given string contains non-alphanumeric characters. + ErrNotAlphanumeric = NewCheckError("NOT_ALPHANUMERIC") +) // IsAlphanumeric checks if the given string consists of only alphanumeric characters. -func IsAlphanumeric(value string) error { +func IsAlphanumeric(value string) (string, error) { for _, c := range value { if !unicode.IsDigit(c) && !unicode.IsLetter(c) { - return ErrNotAlphanumeric + return value, ErrNotAlphanumeric } } - return nil -} - -// makeAlphanumeric makes a checker function for the alphanumeric checker. -func makeAlphanumeric(_ string) CheckFunc { - return checkAlphanumeric + return value, nil } // checkAlphanumeric checks if the given string consists of only alphanumeric characters. -func checkAlphanumeric(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - return IsAlphanumeric(value.String()) +func isAlphanumeric(value reflect.Value) (reflect.Value, error) { + _, err := IsAlphanumeric(value.Interface().(string)) + return value, err +} + +// makeAlphanumeric makes a checker function for the alphanumeric checker. +func makeAlphanumeric(_ string) CheckFunc[reflect.Value] { + return isAlphanumeric } diff --git a/alphanumeric_test.go b/alphanumeric_test.go index e94768c..5d339b3 100644 --- a/alphanumeric_test.go +++ b/alphanumeric_test.go @@ -3,71 +3,74 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) func ExampleIsAlphanumeric() { - err := checker.IsAlphanumeric("ABcd1234") + _, err := v2.IsAlphanumeric("ABcd1234") if err != nil { - // Send the errors back to the user + fmt.Println(err) } } func TestIsAlphanumericInvalid(t *testing.T) { - if checker.IsAlphanumeric("-/") == nil { - t.Fail() + _, err := v2.IsAlphanumeric("-/") + if err == nil { + t.Fatal("expected error") } } func TestIsAlphanumericValid(t *testing.T) { - if checker.IsAlphanumeric("ABcd1234") != nil { - t.Fail() + _, err := v2.IsAlphanumeric("ABcd1234") + if err != nil { + t.Fatal(err) } } func TestCheckAlphanumericNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) + defer FailIfNoPanic(t, "expected panic") - type User struct { - Username int `checkers:"alphanumeric"` + type Person struct { + Name int `checkers:"alphanumeric"` } - user := &User{} + person := &Person{} - checker.Check(user) + v2.CheckStruct(person) } func TestCheckAlphanumericInvalid(t *testing.T) { - type User struct { - Username string `checkers:"alphanumeric"` + type Person struct { + Name string `checkers:"alphanumeric"` } - user := &User{ - Username: "user-/", + person := &Person{ + Name: "name-/", } - _, valid := checker.Check(user) - if valid { - t.Fail() + _, ok := v2.CheckStruct(person) + if ok { + t.Fatal("expected error") } } func TestCheckAlphanumericValid(t *testing.T) { - type User struct { - Username string `checkers:"alphanumeric"` + type Person struct { + Name string `checkers:"alphanumeric"` } - user := &User{ - Username: "ABcd1234", + person := &Person{ + Name: "ABcd1234", } - _, valid := checker.Check(user) - if !valid { - t.Fail() + errs, ok := v2.CheckStruct(person) + if !ok { + t.Fatal(errs) } } diff --git a/ascii.go b/ascii.go index b9b4318..f05125f 100644 --- a/ascii.go +++ b/ascii.go @@ -3,41 +3,41 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" "reflect" "unicode" ) -// tagASCII is the tag of the checker. -const tagASCII = "ascii" +const ( + // nameASCII is the name of the ASCII check. + nameASCII = "ascii" +) -// ErrNotASCII indicates that the given string contains non-ASCII characters. -var ErrNotASCII = errors.New("please use standard English characters only") +var ( + // ErrNotASCII indicates that the given string contains non-ASCII characters. + ErrNotASCII = NewCheckError("NOT_ASCII") +) // IsASCII checks if the given string consists of only ASCII characters. -func IsASCII(value string) error { +func IsASCII(value string) (string, error) { for _, c := range value { if c > unicode.MaxASCII { - return ErrNotASCII + return value, ErrNotASCII } } - return nil -} - -// makeASCII makes a checker function for the ASCII checker. -func makeASCII(_ string) CheckFunc { - return checkASCII + return value, nil } // checkASCII checks if the given string consists of only ASCII characters. -func checkASCII(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - return IsASCII(value.String()) +func isASCII(value reflect.Value) (reflect.Value, error) { + _, err := IsASCII(value.Interface().(string)) + return value, err +} + +// makeASCII makes a checker function for the ASCII checker. +func makeASCII(_ string) CheckFunc[reflect.Value] { + return isASCII } diff --git a/ascii_test.go b/ascii_test.go index 238f60b..cffca70 100644 --- a/ascii_test.go +++ b/ascii_test.go @@ -3,43 +3,46 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) func ExampleIsASCII() { - err := checker.IsASCII("Checker") + _, err := v2.IsASCII("Checker") if err != nil { - // Send the errors back to the user + fmt.Println(err) } } func TestIsASCIIInvalid(t *testing.T) { - if checker.IsASCII("𝄞 Music!") == nil { - t.Fail() + _, err := v2.IsASCII("𝄞 Music!") + if err == nil { + t.Fatal("expected error") } } func TestIsASCIIValid(t *testing.T) { - if checker.IsASCII("Checker") != nil { - t.Fail() + _, err := v2.IsASCII("Checker") + if err != nil { + t.Fatal(err) } } func TestCheckASCIINonString(t *testing.T) { - defer checker.FailIfNoPanic(t) + defer FailIfNoPanic(t, "expected panic") type User struct { - Age int `checkers:"ascii"` + Username int `checkers:"ascii"` } user := &User{} - checker.Check(user) + v2.CheckStruct(user) } func TestCheckASCIIInvalid(t *testing.T) { @@ -51,9 +54,9 @@ func TestCheckASCIIInvalid(t *testing.T) { Username: "𝄞 Music!", } - _, valid := checker.Check(user) - if valid { - t.Fail() + _, ok := v2.CheckStruct(user) + if ok { + t.Fatal("expected error") } } @@ -66,8 +69,8 @@ func TestCheckASCIIValid(t *testing.T) { Username: "checker", } - _, valid := checker.Check(user) - if !valid { - t.Fail() + _, ok := v2.CheckStruct(user) + if !ok { + t.Fatal("expected valid") } } diff --git a/v2/check_error.go b/check_error.go similarity index 100% rename from v2/check_error.go rename to check_error.go diff --git a/v2/check_error_test.go b/check_error_test.go similarity index 100% rename from v2/check_error_test.go rename to check_error_test.go diff --git a/v2/check_func.go b/check_func.go similarity index 100% rename from v2/check_func.go rename to check_func.go diff --git a/checker.go b/checker.go index b5ef498..3616767 100644 --- a/checker.go +++ b/checker.go @@ -1,10 +1,10 @@ -// Package checker is a Go library for validating user input through struct tags. -// // Copyright (c) 2023-2024 Onur Cinar. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker + +// Package v2 Checker is a Go library for validating user input through checker rules provided in struct tags. +package v2 import ( "fmt" @@ -12,77 +12,60 @@ import ( "strings" ) -// CheckFunc defines the signature for the checker functions. -type CheckFunc func(value, parent reflect.Value) error +const ( + // checkerTag is the name of the field tag used for checker. + checkerTag = "checkers" -// MakeFunc defines the signature for the checker maker functions. -type MakeFunc func(params string) CheckFunc + // sliceConfigPrefix is the prefix used to distinguish slice-level checks from item-level checks. + sliceConfigPrefix = "@" +) -// Errors provides a mapping of the checker errors keyed by the field names. -type Errors map[string]error - -type checkerJob struct { - Parent reflect.Value +// checkStructJob defines a check strcut job. +type checkStructJob struct { Name string Value reflect.Value Config string } -// makers provides a mapping of the maker functions keyed by the respective checker names. -var makers = map[string]MakeFunc{ - tagAlphanumeric: makeAlphanumeric, - tagASCII: makeASCII, - tagCreditCard: makeCreditCard, - tagCidr: makeCidr, - tagDigits: makeDigits, - tagEmail: makeEmail, - tagFqdn: makeFqdn, - tagIP: makeIP, - tagIPV4: makeIPV4, - tagIPV6: makeIPV6, - tagISBN: makeISBN, - tagLuhn: makeLuhn, - tagMac: makeMac, - tagMax: makeMax, - tagMaxLength: makeMaxLength, - tagMin: makeMin, - tagMinLength: makeMinLength, - tagRegexp: makeRegexp, - tagRequired: makeRequired, - tagSame: makeSame, - tagURL: makeURL, - tagHTMLEscape: makeHTMLEscape, - tagHTMLUnescape: makeHTMLUnescape, - tagLower: makeLower, - tagUpper: makeUpper, - tagTitle: makeTitle, - tagTrim: makeTrim, - tagTrimLeft: makeTrimLeft, - tagTrimRight: makeTrimRight, - tagURLEscape: makeURLEscape, - tagURLUnescape: makeURLUnescape, -} +// Check applies the given check functions to a value sequentially. +// It returns the final value and the first encountered error, if any. +func Check[T any](value T, checks ...CheckFunc[T]) (T, error) { + var err error -// Register registers the given checker name and the maker function. -func Register(name string, maker MakeFunc) { - makers[name] = maker -} - -// Check function checks the given struct based on the checkers listed in field tag names. -func Check(s interface{}) (Errors, bool) { - root := reflect.Indirect(reflect.ValueOf(s)) - if root.Kind() != reflect.Struct { - panic("expecting struct") + for _, check := range checks { + value, err = check(value) + if err != nil { + break + } } - errs := Errors{} + return value, err +} - jobs := []checkerJob{ +// CheckWithConfig applies the check functions specified by the config string to the given value. +// It returns the modified value and the first encountered error, if any. +func CheckWithConfig[T any](value T, config string) (T, error) { + newValue, err := ReflectCheckWithConfig(reflect.Indirect(reflect.ValueOf(value)), config) + return newValue.Interface().(T), err +} + +// ReflectCheckWithConfig applies the check functions specified by the config string +// to the given reflect.Value. It returns the modified reflect.Value and the first +// encountered error, if any. +func ReflectCheckWithConfig(value reflect.Value, config string) (reflect.Value, error) { + return Check(value, makeChecks(config)...) +} + +// CheckStruct checks the given struct based on the validation rules specified in the +// "checker" tag of each struct field. It returns a map of field names to their +// corresponding errors, and a boolean indicating if all checks passed. +func CheckStruct(st any) (map[string]error, bool) { + errs := make(map[string]error) + + jobs := []*checkStructJob{ { - Parent: reflect.ValueOf(nil), - Name: "", - Value: root, - Config: "", + Name: "", + Value: reflect.Indirect(reflect.ValueOf(st)), }, } @@ -90,73 +73,82 @@ func Check(s interface{}) (Errors, bool) { job := jobs[0] jobs = jobs[1:] - if job.Value.Kind() == reflect.Struct { + switch job.Value.Kind() { + case reflect.Struct: for i := 0; i < job.Value.NumField(); i++ { field := job.Value.Type().Field(i) - addJob := field.Type.Kind() == reflect.Struct - config := "" - if !addJob { - config = field.Tag.Get("checkers") - addJob = config != "" - } + name := fieldName(job.Name, field) + value := reflect.Indirect(job.Value.FieldByIndex(field.Index)) - if addJob { - name := field.Name - if job.Name != "" { - name = job.Name + "." + name - } - - jobs = append(jobs, checkerJob{ - Parent: job.Value, - Name: name, - Value: reflect.Indirect(job.Value.FieldByIndex(field.Index)), - Config: config, - }) - } + jobs = append(jobs, &checkStructJob{ + Name: name, + Value: value, + Config: field.Tag.Get(checkerTag), + }) } - } else { - for _, checker := range initCheckers(job.Config) { - if err := checker(job.Value, job.Parent); err != nil { - errs[job.Name] = err - break - } + + case reflect.Slice: + sliceConfig, itemConfig := splitSliceConfig(job.Config) + job.Config = sliceConfig + + for i := 0; i < job.Value.Len(); i++ { + name := fmt.Sprintf("%s[%d]", job.Name, i) + value := reflect.Indirect(job.Value.Index(i)) + + jobs = append(jobs, &checkStructJob{ + Name: name, + Value: value, + Config: itemConfig, + }) } } + + if job.Config != "" { + newValue, err := ReflectCheckWithConfig(job.Value, job.Config) + if err != nil { + errs[job.Name] = err + } + + job.Value.Set(newValue) + } } return errs, len(errs) == 0 } -// initCheckers initializes the checkers provided in the config. -func initCheckers(config string) []CheckFunc { - fields := strings.Fields(config) - checkers := make([]CheckFunc, len(fields)) +// fieldName returns the field name. If a "json" tag is present, it uses the +// tag value instead. It also prepends the parent struct's name (if any) to +// create a fully qualified field name. +func fieldName(prefix string, field reflect.StructField) string { + // Default to field name + name := field.Name - for i, field := range fields { - name, params, _ := strings.Cut(field, ":") + // Use json tag if present + if jsonTag, ok := field.Tag.Lookup("json"); ok { + name = jsonTag + } - maker, ok := makers[name] - if !ok { - panic(fmt.Sprintf("checker %s is unknown", name)) + // Prepend parent name + if prefix != "" { + name = prefix + "." + name + } + + return name +} + +// splitSliceConfig splits config string into slice and item-level configurations. +func splitSliceConfig(config string) (string, string) { + sliceFileds := make([]string, 0) + itemFields := make([]string, 0) + + for _, configField := range strings.Fields(config) { + if strings.HasPrefix(configField, sliceConfigPrefix) { + sliceFileds = append(sliceFileds, strings.TrimPrefix(configField, sliceConfigPrefix)) + } else { + itemFields = append(itemFields, configField) } - - checkers[i] = maker(params) } - return checkers -} - -// numberOf gives value's numerical value given that it is either an int or a float. -func numberOf(value reflect.Value) float64 { - switch value.Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return float64(value.Int()) - - case reflect.Float32, reflect.Float64: - return value.Float() - - default: - panic("expecting int or float") - } + return strings.Join(sliceFileds, " "), strings.Join(itemFields, " ") } diff --git a/checker_test.go b/checker_test.go index 540bca5..32c8b96 100644 --- a/checker_test.go +++ b/checker_test.go @@ -3,178 +3,196 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2_test import ( - "reflect" - "strings" + "errors" + "fmt" "testing" + + v2 "github.com/cinar/checker/v2" ) -func TestInitCheckersUnknown(t *testing.T) { - defer FailIfNoPanic(t) +func ExampleCheck() { + name := " Onur Cinar " - initCheckers("unknown") + name, err := v2.Check(name, v2.TrimSpace, v2.Required) + if err != nil { + fmt.Println(err) + return + } + + fmt.Println(name) + // Output: Onur Cinar } -func TestInitCheckersKnwon(t *testing.T) { - checkers := initCheckers("required") - - if len(checkers) != 1 { - t.Fail() - } - - if reflect.ValueOf(checkers[0]).Pointer() != reflect.ValueOf(checkRequired).Pointer() { - t.Fail() - } -} - -func TestRegister(t *testing.T) { - var checker CheckFunc = func(_, _ reflect.Value) error { - return nil - } - - var maker MakeFunc = func(_ string) CheckFunc { - return checker - } - - name := "test" - - Register(name, maker) - - checkers := initCheckers(name) - - if len(checkers) != 1 { - t.Fail() - } - - if reflect.ValueOf(checkers[0]).Pointer() != reflect.ValueOf(checker).Pointer() { - t.Fail() - } -} - -func TestCheckInvalid(t *testing.T) { +func ExampleCheckStruct() { type Person struct { - Name string `checkers:"required"` - } - - person := &Person{} - - errors, valid := Check(person) - if valid { - t.Fail() - } - - if len(errors) != 1 { - t.Fail() - } - - if errors["Name"] != ErrRequired { - t.Fail() - } -} - -func TestCheckValid(t *testing.T) { - type Person struct { - Name string `checkers:"required"` + Name string `checkers:"trim required"` } person := &Person{ - Name: "Onur", + Name: " Onur Cinar ", } - errors, valid := Check(person) - if !valid { - t.Fail() + errs, ok := v2.CheckStruct(person) + if !ok { + fmt.Println(errs) + return } - if len(errors) != 0 { - t.Fail() + fmt.Println(person.Name) + // Output: Onur Cinar +} + +func TestCheckTrimSpaceRequiredSuccess(t *testing.T) { + input := " test " + expected := "test" + + actual, err := v2.Check(input, v2.TrimSpace, v2.Required) + if err != nil { + t.Fatal(err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) } } -func TestCheckNoStruct(t *testing.T) { - defer FailIfNoPanic(t) +func TestCheckTrimSpaceRequiredMissing(t *testing.T) { + input := " " + expected := "" - s := "unknown" - Check(s) + actual, err := v2.Check(input, v2.TrimSpace, v2.Required) + if !errors.Is(err, v2.ErrRequired) { + t.Fatalf("got unexpected error %v", err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } } -func TestCheckNestedStruct(t *testing.T) { +func TestCheckWithConfigSuccess(t *testing.T) { + input := " test " + expected := "test" + + actual, err := v2.CheckWithConfig(input, "trim required") + if err != nil { + t.Fatal(err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestCheckWithConfigRequiredMissing(t *testing.T) { + input := " " + expected := "" + + actual, err := v2.CheckWithConfig(input, "trim required") + if !errors.Is(err, v2.ErrRequired) { + t.Fatalf("got unexpected error %v", err) + } + + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } +} + +func TestCheckStructSuccess(t *testing.T) { type Address struct { Street string `checkers:"required"` } type Person struct { - Name string `checkers:"required"` - Home Address + Name string `checkers:"required"` + Address *Address } - person := &Person{} - - errors, valid := Check(person) - if valid { - t.Fail() + person := &Person{ + Name: "Onur Cinar", + Address: &Address{ + Street: "1234 Main", + }, } - if len(errors) != 2 { - t.Fail() - } - - if errors["Name"] != ErrRequired { - t.Fail() - } - - if errors["Home.Street"] != ErrRequired { - t.Fail() + errors, ok := v2.CheckStruct(person) + if !ok { + t.Fatalf("got unexpected errors %v", errors) } } -func TestNumberOfInvalid(t *testing.T) { - defer FailIfNoPanic(t) - - s := "invalid" - - numberOf(reflect.ValueOf(s)) -} - -func TestNumberOfInt(t *testing.T) { - n := 10 - - if numberOf(reflect.ValueOf(n)) != float64(n) { - t.Fail() - } -} - -func TestNumberOfFloat(t *testing.T) { - n := float64(10.10) - - if numberOf(reflect.ValueOf(n)) != n { - t.Fail() - } -} - -func TestCheckerNamesAreLowerCase(t *testing.T) { - for checker := range makers { - if strings.ToLower(checker) != checker { - t.Fail() - } - } -} - -func BenchmarkCheck(b *testing.B) { +func TestCheckStructRequiredMissing(t *testing.T) { type Address struct { Street string `checkers:"required"` } type Person struct { - Name string `checkers:"required"` - Home Address + Name string `checkers:"required"` + Address *Address } - person := &Person{} + person := &Person{ + Name: "", + Address: &Address{ + Street: "", + }, + } - for n := 0; n < b.N; n++ { - Check(person) + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatal("expected errors") + } + + if !errors.Is(errs["Name"], v2.ErrRequired) { + t.Fatalf("expected name required %v", errs) + } + + if !errors.Is(errs["Address.Street"], v2.ErrRequired) { + t.Fatalf("expected streed required %v", errs) + } +} + +func TestCheckStructCustomName(t *testing.T) { + type Person struct { + Name string `json:"name" checkers:"required"` + } + + person := &Person{ + Name: "", + } + + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatal("expected errors") + } + + if !errors.Is(errs["name"], v2.ErrRequired) { + t.Fatalf("expected name required %v", errs) + } +} + +func TestCheckStructSlice(t *testing.T) { + type Person struct { + Name string `checkers:"required"` + Emails []string `checkers:"@max-len:1 max-len:4"` + } + + person := &Person{ + Name: "Onur Cinar", + Emails: []string{ + "onur.cinar", + }, + } + + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatal("expected errors") + } + + if !errors.Is(errs["Emails[0]"], v2.ErrMaxLen) { + t.Fatalf("expected email max len") } } diff --git a/cidr.go b/cidr.go index 0bad38d..193aedc 100644 --- a/cidr.go +++ b/cidr.go @@ -3,40 +3,40 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" "net" "reflect" ) -// tagCidr is the tag of the checker. -const tagCidr = "cidr" +const ( + // nameCIDR is the name of the CIDR check. + nameCIDR = "cidr" +) -// ErrNotCidr indicates that the given value is not a valid CIDR. -var ErrNotCidr = errors.New("please enter a valid CIDR") +var ( + // ErrNotCIDR indicates that the given value is not a valid CIDR. + ErrNotCIDR = NewCheckError("NOT_CIDR") +) -// IsCidr checker checks if the value is a valid CIDR notation IP address and prefix length. -func IsCidr(value string) error { +// IsCIDR checks if the value is a valid CIDR notation IP address and prefix length. +func IsCIDR(value string) (string, error) { _, _, err := net.ParseCIDR(value) if err != nil { - return ErrNotCidr + return value, ErrNotCIDR } - return nil + return value, nil } -// makeCidr makes a checker function for the ip checker. -func makeCidr(_ string) CheckFunc { - return checkCidr +// checkCIDR checks if the value is a valid CIDR notation IP address and prefix length. +func checkCIDR(value reflect.Value) (reflect.Value, error) { + _, err := IsCIDR(value.Interface().(string)) + return value, err } -// checkCidr checker checks if the value is a valid CIDR notation IP address and prefix length. -func checkCidr(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - return IsCidr(value.String()) +// makeCIDR makes a checker function for the CIDR checker. +func makeCIDR(_ string) CheckFunc[reflect.Value] { + return checkCIDR } diff --git a/cidr_test.go b/cidr_test.go index 2d7852d..d402c23 100644 --- a/cidr_test.go +++ b/cidr_test.go @@ -3,35 +3,38 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func ExampleIsCidr() { - err := checker.IsCidr("2001:db8::/32") +func ExampleIsCIDR() { + _, err := v2.IsCIDR("2001:db8::/32") if err != nil { - // Send the errors back to the user + fmt.Println(err) } } -func TestIsCidrInvalid(t *testing.T) { - if checker.IsCidr("900.800.200.100//24") == nil { - t.Fail() +func TestIsCIDRInvalid(t *testing.T) { + _, err := v2.IsCIDR("900.800.200.100//24") + if err == nil { + t.Fatal("expected error") } } -func TestIsCidrValid(t *testing.T) { - if checker.IsCidr("2001:db8::/32") != nil { - t.Fail() +func TestIsCIDRValid(t *testing.T) { + _, err := v2.IsCIDR("2001:db8::/32") + if err != nil { + t.Fatal(err) } } -func TestCheckCidrNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) +func TestCheckCIDRNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") type Network struct { Subnet int `checkers:"cidr"` @@ -39,10 +42,10 @@ func TestCheckCidrNonString(t *testing.T) { network := &Network{} - checker.Check(network) + v2.CheckStruct(network) } -func TestCheckCidrInvalid(t *testing.T) { +func TestCheckCIDRInvalid(t *testing.T) { type Network struct { Subnet string `checkers:"cidr"` } @@ -51,13 +54,13 @@ func TestCheckCidrInvalid(t *testing.T) { Subnet: "900.800.200.100//24", } - _, valid := checker.Check(network) - if valid { - t.Fail() + _, ok := v2.CheckStruct(network) + if ok { + t.Fatal("expected error") } } -func TestCheckCidrValid(t *testing.T) { +func TestCheckCIDRValid(t *testing.T) { type Network struct { Subnet string `checkers:"cidr"` } @@ -66,8 +69,8 @@ func TestCheckCidrValid(t *testing.T) { Subnet: "192.0.2.0/24", } - _, valid := checker.Check(network) - if !valid { - t.Fail() + _, ok := v2.CheckStruct(network) + if !ok { + t.Fatal("expected valid") } } diff --git a/credit_card.go b/credit_card.go index 5016153..59d5c85 100644 --- a/credit_card.go +++ b/credit_card.go @@ -3,113 +3,116 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" "reflect" "regexp" "strings" ) -// tagCreditCard is the tag of the checker. -const tagCreditCard = "credit-card" +const ( + // nameCreditCard is the name of the credit card check. + nameCreditCard = "credit-card" +) -// ErrNotCreditCard indicates that the given value is not a valid credit card number. -var ErrNotCreditCard = errors.New("please enter a valid credit card number") +var ( + // ErrNotCreditCard indicates that the given value is not a valid credit card number. + ErrNotCreditCard = NewCheckError("NOT_CREDIT_CARD") -// amexExpression is the regexp for the AMEX cards. They start with 34 or 37, and has 15 digits. -var amexExpression = "(?:^(?:3[47])[0-9]{13}$)" -var amexPattern = regexp.MustCompile(amexExpression) + // amexExpression is the regexp for the AMEX cards. They start with 34 or 37, and has 15 digits. + amexExpression = "(?:^(?:3[47])[0-9]{13}$)" + amexPattern = regexp.MustCompile(amexExpression) -// dinersExpression is the regexp for the Diners cards. They start with 305, 36, 38, and has 14 digits. -var dinersExpression = "(?:^3(?:(?:05[0-9]{11})|(?:[68][0-9]{12}))$)" -var dinersPattern = regexp.MustCompile(dinersExpression) + // dinersExpression is the regexp for the Diners cards. They start with 305, 36, 38, and has 14 digits. + dinersExpression = "(?:^3(?:(?:05[0-9]{11})|(?:[68][0-9]{12}))$)" + dinersPattern = regexp.MustCompile(dinersExpression) -// discoverExpression is the regexp for the Discover cards. They start with 6011 and has 16 digits. -var discoverExpression = "(?:^6011[0-9]{12}$)" -var discoverPattern = regexp.MustCompile(discoverExpression) + // discoverExpression is the regexp for the Discover cards. They start with 6011 and has 16 digits. + discoverExpression = "(?:^6011[0-9]{12}$)" + discoverPattern = regexp.MustCompile(discoverExpression) -// jcbExpression is the regexp for the JCB 15 cards. They start with 2131, 1800, and has 15 digits, or start with 35 and has 16 digits. -var jcbExpression = "(?:^(?:(?:2131)|(?:1800)|(?:35[0-9]{3}))[0-9]{11}$)" -var jcbPattern = regexp.MustCompile(jcbExpression) + // jcbExpression is the regexp for the JCB 15 cards. They start with 2131, 1800, and has 15 digits, or start with 35 and has 16 digits. + jcbExpression = "(?:^(?:(?:2131)|(?:1800)|(?:35[0-9]{3}))[0-9]{11}$)" + jcbPattern = regexp.MustCompile(jcbExpression) -// masterCardExpression is the regexp for the MasterCard cards. They start with 51, 52, 53, 54, or 55, and has 15 digits. -var masterCardExpression = "(?:^5[12345][0-9]{14}$)" -var masterCardPattern = regexp.MustCompile(masterCardExpression) + // masterCardExpression is the regexp for the MasterCard cards. They start with 51, 52, 53, 54, or 55, and has 15 digits. + masterCardExpression = "(?:^5[12345][0-9]{14}$)" + masterCardPattern = regexp.MustCompile(masterCardExpression) -// unionPayExpression is the regexp for the UnionPay cards. They start either with 62 or 67, and has 16 digits, or they start with 81 and has 16 to 19 digits. -var unionPayExpression = "(?:(?:6[27][0-9]{14})|(?:81[0-9]{14,17})^$)" -var unionPayPattern = regexp.MustCompile(unionPayExpression) + // unionPayExpression is the regexp for the UnionPay cards. They start either with 62 or 67, and has 16 digits, or they start with 81 and has 16 to 19 digits. + unionPayExpression = "(?:(?:6[27][0-9]{14})|(?:81[0-9]{14,17})^$)" + unionPayPattern = regexp.MustCompile(unionPayExpression) -// visaExpression is the regexp for the Visa cards. They start with 4 and has 13 or 16 digits. -var visaExpression = "(?:^4[0-9]{12}(?:[0-9]{3})?$)" -var visaPattern = regexp.MustCompile(visaExpression) + // visaExpression is the regexp for the Visa cards. They start with 4 and has 13 or 16 digits. + visaExpression = "(?:^4[0-9]{12}(?:[0-9]{3})?$)" + visaPattern = regexp.MustCompile(visaExpression) -// anyCreditCardPattern is the regexp for any credit card. -var anyCreditCardPattern = regexp.MustCompile(strings.Join([]string{ - amexExpression, - dinersExpression, - discoverExpression, - jcbExpression, - masterCardExpression, - unionPayExpression, - visaExpression, -}, "|")) + // anyCreditCardPattern is the regexp for any credit card. + anyCreditCardPattern = regexp.MustCompile(strings.Join([]string{ + amexExpression, + dinersExpression, + discoverExpression, + jcbExpression, + masterCardExpression, + unionPayExpression, + visaExpression, + }, "|")) -// creditCardPatterns is the mapping for credit card names to patterns. -var creditCardPatterns = map[string]*regexp.Regexp{ - "amex": amexPattern, - "diners": dinersPattern, - "discover": discoverPattern, - "jcb": jcbPattern, - "mastercard": masterCardPattern, - "unionpay": unionPayPattern, - "visa": visaPattern, -} + // creditCardPatterns is the mapping for credit card names to patterns. + creditCardPatterns = map[string]*regexp.Regexp{ + "amex": amexPattern, + "diners": dinersPattern, + "discover": discoverPattern, + "jcb": jcbPattern, + "mastercard": masterCardPattern, + "unionpay": unionPayPattern, + "visa": visaPattern, + } +) // IsAnyCreditCard checks if the given value is a valid credit card number. -func IsAnyCreditCard(number string) error { +func IsAnyCreditCard(number string) (string, error) { return isCreditCard(number, anyCreditCardPattern) } // IsAmexCreditCard checks if the given valie is a valid AMEX credit card. -func IsAmexCreditCard(number string) error { +func IsAmexCreditCard(number string) (string, error) { return isCreditCard(number, amexPattern) } // IsDinersCreditCard checks if the given valie is a valid Diners credit card. -func IsDinersCreditCard(number string) error { +func IsDinersCreditCard(number string) (string, error) { return isCreditCard(number, dinersPattern) } // IsDiscoverCreditCard checks if the given valie is a valid Discover credit card. -func IsDiscoverCreditCard(number string) error { +func IsDiscoverCreditCard(number string) (string, error) { return isCreditCard(number, discoverPattern) } // IsJcbCreditCard checks if the given valie is a valid JCB 15 credit card. -func IsJcbCreditCard(number string) error { +func IsJcbCreditCard(number string) (string, error) { return isCreditCard(number, jcbPattern) } // IsMasterCardCreditCard checks if the given valie is a valid MasterCard credit card. -func IsMasterCardCreditCard(number string) error { +func IsMasterCardCreditCard(number string) (string, error) { return isCreditCard(number, masterCardPattern) } // IsUnionPayCreditCard checks if the given valie is a valid UnionPay credit card. -func IsUnionPayCreditCard(number string) error { +func IsUnionPayCreditCard(number string) (string, error) { return isCreditCard(number, unionPayPattern) } // IsVisaCreditCard checks if the given valie is a valid Visa credit card. -func IsVisaCreditCard(number string) error { +func IsVisaCreditCard(number string) (string, error) { return isCreditCard(number, visaPattern) } // makeCreditCard makes a checker function for the credit card checker. -func makeCreditCard(config string) CheckFunc { +func makeCreditCard(config string) CheckFunc[reflect.Value] { patterns := []*regexp.Regexp{} if config != "" { @@ -125,7 +128,7 @@ func makeCreditCard(config string) CheckFunc { patterns = append(patterns, anyCreditCardPattern) } - return func(value, _ reflect.Value) error { + return func(value reflect.Value) (reflect.Value, error) { if value.Kind() != reflect.String { panic("string expected") } @@ -133,20 +136,21 @@ func makeCreditCard(config string) CheckFunc { number := value.String() for _, pattern := range patterns { - if isCreditCard(number, pattern) == nil { - return nil + _, err := isCreditCard(number, pattern) + if err == nil { + return value, nil } } - return ErrNotCreditCard + return value, ErrNotCreditCard } } // isCreditCard checks if the given number based on the given credit card pattern and the Luhn algorithm check digit. -func isCreditCard(number string, pattern *regexp.Regexp) error { +func isCreditCard(number string, pattern *regexp.Regexp) (string, error) { if !pattern.MatchString(number) { - return ErrNotCreditCard + return number, ErrNotCreditCard } - return IsLuhn(number) + return IsLUHN(number) } diff --git a/credit_card_test.go b/credit_card_test.go index fc81997..efedc17 100644 --- a/credit_card_test.go +++ b/credit_card_test.go @@ -3,13 +3,13 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( "strconv" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) // Test numbers from https://stripe.com/docs/testing @@ -35,7 +35,7 @@ func changeToInvalidLuhn(number string) string { } func ExampleIsAnyCreditCard() { - err := checker.IsAnyCreditCard("6011111111111117") + _, err := v2.IsAnyCreditCard("6011111111111117") if err != nil { // Send the errors back to the user @@ -43,25 +43,26 @@ func ExampleIsAnyCreditCard() { } func TestIsAnyCreditCardValid(t *testing.T) { - if checker.IsAnyCreditCard(amexCard) != nil { - t.Fail() + _, err := v2.IsAnyCreditCard(amexCard) + if err != nil { + t.Error(err) } } func TestIsAnyCreditCardInvalidPattern(t *testing.T) { - if checker.IsAnyCreditCard(invalidCard) == nil { - t.Fail() + if _, err := v2.IsAnyCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") } } func TestIsAnyCreditCardInvalidLuhn(t *testing.T) { - if checker.IsAnyCreditCard(changeToInvalidLuhn(amexCard)) == nil { - t.Fail() + if _, err := v2.IsAnyCreditCard(changeToInvalidLuhn(amexCard)); err == nil { + t.Error("expected error for invalid Luhn") } } func ExampleIsAmexCreditCard() { - err := checker.IsAmexCreditCard("378282246310005") + _, err := v2.IsAmexCreditCard("378282246310005") if err != nil { // Send the errors back to the user @@ -69,75 +70,75 @@ func ExampleIsAmexCreditCard() { } func TestIsAmexCreditCardValid(t *testing.T) { - if checker.IsAmexCreditCard(amexCard) != nil { - t.Fail() + if _, err := v2.IsAmexCreditCard(amexCard); err != nil { + t.Error(err) } } func TestIsAmexCreditCardInvalidPattern(t *testing.T) { - if checker.IsAmexCreditCard(invalidCard) == nil { - t.Fail() + if _, err := v2.IsAmexCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") } } func TestIsAmexCreditCardInvalidLuhn(t *testing.T) { - if checker.IsAmexCreditCard(changeToInvalidLuhn(amexCard)) == nil { - t.Fail() + if _, err := v2.IsAmexCreditCard(changeToInvalidLuhn(amexCard)); err == nil { + t.Error("expected error for invalid Luhn") } } func ExampleIsDinersCreditCard() { - err := checker.IsDinersCreditCard("36227206271667") + _, err := v2.IsDinersCreditCard("36227206271667") if err != nil { // Send the errors back to the user } } func TestIsDinersCreditCardValid(t *testing.T) { - if checker.IsDinersCreditCard(dinersCard) != nil { - t.Fail() + if _, err := v2.IsDinersCreditCard(dinersCard); err != nil { + t.Error(err) } } func TestIsDinersCreditCardInvalidPattern(t *testing.T) { - if checker.IsDinersCreditCard(invalidCard) == nil { - t.Fail() + if _, err := v2.IsDinersCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") } } func TestIsDinersCreditCardInvalidLuhn(t *testing.T) { - if checker.IsDinersCreditCard(changeToInvalidLuhn(dinersCard)) == nil { - t.Fail() + if _, err := v2.IsDinersCreditCard(changeToInvalidLuhn(dinersCard)); err == nil { + t.Error("expected error for invalid Luhn") } } func ExampleIsDiscoverCreditCard() { - err := checker.IsDiscoverCreditCard("6011111111111117") + _, err := v2.IsDiscoverCreditCard("6011111111111117") if err != nil { // Send the errors back to the user } } func TestIsDiscoverCreditCardValid(t *testing.T) { - if checker.IsDiscoverCreditCard(discoverCard) != nil { - t.Fail() + if _, err := v2.IsDiscoverCreditCard(discoverCard); err != nil { + t.Error(err) } } func TestIsDiscoverCreditCardInvalidPattern(t *testing.T) { - if checker.IsDiscoverCreditCard(invalidCard) == nil { - t.Fail() + if _, err := v2.IsDiscoverCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") } } func TestIsDiscoverCreditCardInvalidLuhn(t *testing.T) { - if checker.IsDiscoverCreditCard(changeToInvalidLuhn(discoverCard)) == nil { - t.Fail() + if _, err := v2.IsDiscoverCreditCard(changeToInvalidLuhn(discoverCard)); err == nil { + t.Error("expected error for invalid Luhn") } } func ExampleIsJcbCreditCard() { - err := checker.IsJcbCreditCard("3530111333300000") + _, err := v2.IsJcbCreditCard("3530111333300000") if err != nil { // Send the errors back to the user @@ -145,25 +146,25 @@ func ExampleIsJcbCreditCard() { } func TestIsJcbCreditCardValid(t *testing.T) { - if checker.IsJcbCreditCard(jcbCard) != nil { - t.Fail() + if _, err := v2.IsJcbCreditCard(jcbCard); err != nil { + t.Error(err) } } func TestIsJcbCreditCardInvalidPattern(t *testing.T) { - if checker.IsJcbCreditCard(invalidCard) == nil { - t.Fail() + if _, err := v2.IsJcbCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") } } func TestIsJcbCreditCardInvalidLuhn(t *testing.T) { - if checker.IsJcbCreditCard(changeToInvalidLuhn(jcbCard)) == nil { - t.Fail() + if _, err := v2.IsJcbCreditCard(changeToInvalidLuhn(jcbCard)); err == nil { + t.Error("expected error for invalid Luhn") } } func ExampleIsMasterCardCreditCard() { - err := checker.IsMasterCardCreditCard("5555555555554444") + _, err := v2.IsMasterCardCreditCard("5555555555554444") if err != nil { // Send the errors back to the user @@ -171,25 +172,25 @@ func ExampleIsMasterCardCreditCard() { } func TestIsMasterCardCreditCardValid(t *testing.T) { - if checker.IsMasterCardCreditCard(masterCard) != nil { - t.Fail() + if _, err := v2.IsMasterCardCreditCard(masterCard); err != nil { + t.Error(err) } } func TestIsMasterCardCreditCardInvalidPattern(t *testing.T) { - if checker.IsMasterCardCreditCard(invalidCard) == nil { - t.Fail() + if _, err := v2.IsMasterCardCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") } } func TestIsMasterCardCreditCardInvalidLuhn(t *testing.T) { - if checker.IsMasterCardCreditCard(changeToInvalidLuhn(masterCard)) == nil { - t.Fail() + if _, err := v2.IsMasterCardCreditCard(changeToInvalidLuhn(masterCard)); err == nil { + t.Error("expected error for invalid Luhn") } } func ExampleIsUnionPayCreditCard() { - err := checker.IsUnionPayCreditCard("6200000000000005") + _, err := v2.IsUnionPayCreditCard("6200000000000005") if err != nil { // Send the errors back to the user @@ -197,50 +198,50 @@ func ExampleIsUnionPayCreditCard() { } func TestIsUnionPayCreditCardValid(t *testing.T) { - if checker.IsUnionPayCreditCard(unionPayCard) != nil { - t.Fail() + if _, err := v2.IsUnionPayCreditCard(unionPayCard); err != nil { + t.Error(err) } } func TestIsUnionPayCreditCardInvalidPattern(t *testing.T) { - if checker.IsUnionPayCreditCard(invalidCard) == nil { - t.Fail() + if _, err := v2.IsUnionPayCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") } } func TestIsUnionPayCreditCardInvalidLuhn(t *testing.T) { - if checker.IsUnionPayCreditCard(changeToInvalidLuhn(unionPayCard)) == nil { - t.Fail() + if _, err := v2.IsUnionPayCreditCard(changeToInvalidLuhn(unionPayCard)); err == nil { + t.Error("expected error for invalid Luhn") } } func ExampleIsVisaCreditCard() { - err := checker.IsVisaCreditCard("4111111111111111") + _, err := v2.IsVisaCreditCard("4111111111111111") if err != nil { // Send the errors back to the user } } func TestIsVisaCreditCardValid(t *testing.T) { - if checker.IsVisaCreditCard(visaCard) != nil { - t.Fail() + if _, err := v2.IsVisaCreditCard(visaCard); err != nil { + t.Error(err) } } func TestIsVisaCreditCardInvalidPattern(t *testing.T) { - if checker.IsVisaCreditCard(invalidCard) == nil { - t.Fail() + if _, err := v2.IsVisaCreditCard(invalidCard); err == nil { + t.Error("expected error for invalid card pattern") } } func TestIsVisaCreditCardInvalidLuhn(t *testing.T) { - if checker.IsVisaCreditCard(changeToInvalidLuhn(visaCard)) == nil { - t.Fail() + if _, err := v2.IsVisaCreditCard(changeToInvalidLuhn(visaCard)); err == nil { + t.Error("expected error for invalid Luhn") } } func TestCheckCreditCardNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) + defer FailIfNoPanic(t, "expected panic for non-string credit card") type Order struct { CreditCard int `checkers:"credit-card"` @@ -248,7 +249,7 @@ func TestCheckCreditCardNonString(t *testing.T) { order := &Order{} - checker.Check(order) + v2.CheckStruct(order) } func TestCheckCreditCardValid(t *testing.T) { @@ -260,7 +261,7 @@ func TestCheckCreditCardValid(t *testing.T) { CreditCard: amexCard, } - _, valid := checker.Check(order) + _, valid := v2.CheckStruct(order) if !valid { t.Fail() } @@ -275,14 +276,14 @@ func TestCheckCreditCardInvalid(t *testing.T) { CreditCard: invalidCard, } - _, valid := checker.Check(order) + _, valid := v2.CheckStruct(order) if valid { t.Fail() } } func TestCheckCreditCardMultipleUnknown(t *testing.T) { - defer checker.FailIfNoPanic(t) + defer FailIfNoPanic(t, "expected panic for unknown credit card") type Order struct { CreditCard string `checkers:"credit-card:amex,unknown"` @@ -292,7 +293,7 @@ func TestCheckCreditCardMultipleUnknown(t *testing.T) { CreditCard: amexCard, } - checker.Check(order) + v2.CheckStruct(order) } func TestCheckCreditCardMultipleInvalid(t *testing.T) { @@ -304,7 +305,7 @@ func TestCheckCreditCardMultipleInvalid(t *testing.T) { CreditCard: discoverCard, } - _, valid := checker.Check(order) + _, valid := v2.CheckStruct(order) if valid { t.Fail() } diff --git a/digits.go b/digits.go index 83e41b7..0a01b72 100644 --- a/digits.go +++ b/digits.go @@ -3,41 +3,40 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" "reflect" "unicode" ) -// tagDigits is the tag of the checker. -const tagDigits = "digits" +const ( + // nameDigits is the name of the digits check. + nameDigits = "digits" +) -// ErrNotDigits indicates that the given string contains non-digit characters. -var ErrNotDigits = errors.New("please enter a valid number") +var ( + // ErrNotDigits indicates that the given value is not a valid digits string. + ErrNotDigits = NewCheckError("NOT_DIGITS") +) -// IsDigits checks if the given string consists of only digit characters. -func IsDigits(value string) error { - for _, c := range value { - if !unicode.IsDigit(c) { - return ErrNotDigits +// IsDigits checks if the value contains only digit characters. +func IsDigits(value string) (string, error) { + for _, r := range value { + if !unicode.IsDigit(r) { + return value, ErrNotDigits } } + return value, nil +} - return nil +// checkDigits checks if the value contains only digit characters. +func checkDigits(value reflect.Value) (reflect.Value, error) { + _, err := IsDigits(value.Interface().(string)) + return value, err } // makeDigits makes a checker function for the digits checker. -func makeDigits(_ string) CheckFunc { +func makeDigits(_ string) CheckFunc[reflect.Value] { return checkDigits } - -// checkDigits checks if the given string consists of only digit characters. -func checkDigits(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - return IsDigits(value.String()) -} diff --git a/digits_test.go b/digits_test.go index 4246713..6858ca6 100644 --- a/digits_test.go +++ b/digits_test.go @@ -3,71 +3,74 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) func ExampleIsDigits() { - err := checker.IsDigits("1234") + _, err := v2.IsDigits("123456") if err != nil { - // Send the errors back to the user + fmt.Println(err) } } func TestIsDigitsInvalid(t *testing.T) { - if checker.IsDigits("checker") == nil { - t.Fail() + _, err := v2.IsDigits("123a456") + if err == nil { + t.Fatal("expected error") } } func TestIsDigitsValid(t *testing.T) { - if checker.IsDigits("1234") != nil { - t.Fail() + _, err := v2.IsDigits("123456") + if err != nil { + t.Fatal(err) } } func TestCheckDigitsNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) + defer FailIfNoPanic(t, "expected panic") - type User struct { - ID int `checkers:"digits"` + type Code struct { + Value int `checkers:"digits"` } - user := &User{} + code := &Code{} - checker.Check(user) + v2.CheckStruct(code) } func TestCheckDigitsInvalid(t *testing.T) { - type User struct { - ID string `checkers:"digits"` + type Code struct { + Value string `checkers:"digits"` } - user := &User{ - ID: "checker", + code := &Code{ + Value: "123a456", } - _, valid := checker.Check(user) - if valid { - t.Fail() + _, ok := v2.CheckStruct(code) + if ok { + t.Fatal("expected error") } } func TestCheckDigitsValid(t *testing.T) { - type User struct { - ID string `checkers:"digits"` + type Code struct { + Value string `checkers:"digits"` } - user := &User{ - ID: "1234", + code := &Code{ + Value: "123456", } - _, valid := checker.Check(user) - if !valid { - t.Fail() + _, ok := v2.CheckStruct(code) + if !ok { + t.Fatal("expected valid") } } diff --git a/email.go b/email.go index 2c456c4..8ab0aca 100644 --- a/email.go +++ b/email.go @@ -3,126 +3,39 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" + "net/mail" "reflect" - "regexp" - "strings" ) -// tagEmail is the tag of the checker. -const tagEmail = "email" +const ( + // nameEmail is the name of the email check. + nameEmail = "email" +) -// ErrNotEmail indicates that the given string is not a valid email. -var ErrNotEmail = errors.New("please enter a valid email address") +var ( + // ErrNotEmail indicates that the given value is not a valid email address. + ErrNotEmail = NewCheckError("NOT_EMAIL") +) -// ipV6Prefix is the IPv6 prefix for the domain. -const ipV6Prefix = "[IPv6:" - -// notQuotedChars is the valid not quoted characters. -var notQuotedChars = regexp.MustCompile("[a-zA-Z0-9!#$%&'*\\+\\-/=?^_`{|}~]") - -// IsEmail checks if the given string is an email address. -func IsEmail(email string) error { - atIndex := strings.LastIndex(email, "@") - if atIndex == -1 || atIndex == len(email)-1 { - return ErrNotEmail +// IsEmail checks if the value is a valid email address. +func IsEmail(value string) (string, error) { + _, err := mail.ParseAddress(value) + if err != nil { + return value, ErrNotEmail } + return value, nil +} - domain := email[atIndex+1:] - if isValidEmailDomain(domain) != nil { - return ErrNotEmail - } - - return isValidEmailUser(email[:atIndex]) +// checkEmail checks if the value is a valid email address. +func checkEmail(value reflect.Value) (reflect.Value, error) { + _, err := IsEmail(value.Interface().(string)) + return value, err } // makeEmail makes a checker function for the email checker. -func makeEmail(_ string) CheckFunc { +func makeEmail(_ string) CheckFunc[reflect.Value] { return checkEmail } - -// checkEmail checks if the given string is an email address. -func checkEmail(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - return IsEmail(value.String()) -} - -// isValidEmailDomain checks if the email domain is a IPv4 or IPv6 address, or a FQDN. -func isValidEmailDomain(domain string) error { - if len(domain) > 255 { - return ErrNotEmail - } - - if domain[0] == '[' { - if strings.HasPrefix(domain, ipV6Prefix) { - // postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334] - return IsIPV6(domain[len(ipV6Prefix) : len(domain)-1]) - } - - // postmaster@[123.123.123.123] - return IsIPV4(domain[1 : len(domain)-1]) - } - - return IsFqdn(domain) -} - -// isValidEmailUser checks if the email user is valid. -func isValidEmailUser(user string) error { - // Cannot be empty user - if user == "" || len(user) > 64 { - return ErrNotEmail - } - - // Cannot start or end with dot - if user[0] == '.' || user[len(user)-1] == '.' { - return ErrNotEmail - } - - return isValidEmailUserCharacters(user) -} - -// isValidEmailUserCharacters if the email user characters are valid. -func isValidEmailUserCharacters(user string) error { - quoted := false - start := true - prev := ' ' - - for _, c := range user { - // Cannot have a double dot unless quoted - if !quoted && c == '.' && prev == '.' { - return ErrNotEmail - } - - if start { - start = false - - if c == '"' { - quoted = true - prev = c - continue - } - } - - if !quoted { - if c == '.' { - start = true - } else if !notQuotedChars.MatchString(string(c)) { - return ErrNotEmail - } - } else { - if c == '"' && prev != '\\' { - quoted = false - } - } - - prev = c - } - - return nil -} diff --git a/email_test.go b/email_test.go index c200ed0..20e3665 100644 --- a/email_test.go +++ b/email_test.go @@ -3,23 +3,38 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) func ExampleIsEmail() { - err := checker.IsEmail("user@zdo.com") + _, err := v2.IsEmail("test@example.com") if err != nil { - // Send the errors back to the user + fmt.Println(err) + } +} + +func TestIsEmailInvalid(t *testing.T) { + _, err := v2.IsEmail("invalid-email") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsEmailValid(t *testing.T) { + _, err := v2.IsEmail("test@example.com") + if err != nil { + t.Fatal(err) } } func TestCheckEmailNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) + defer FailIfNoPanic(t, "expected panic") type User struct { Email int `checkers:"email"` @@ -27,7 +42,22 @@ func TestCheckEmailNonString(t *testing.T) { user := &User{} - checker.Check(user) + v2.CheckStruct(user) +} + +func TestCheckEmailInvalid(t *testing.T) { + type User struct { + Email string `checkers:"email"` + } + + user := &User{ + Email: "invalid-email", + } + + _, ok := v2.CheckStruct(user) + if ok { + t.Fatal("expected error") + } } func TestCheckEmailValid(t *testing.T) { @@ -36,64 +66,11 @@ func TestCheckEmailValid(t *testing.T) { } user := &User{ - Email: "user@zdo.com", + Email: "test@example.com", } - _, valid := checker.Check(user) - if !valid { - t.Fail() - } -} - -func TestIsEmailValid(t *testing.T) { - validEmails := []string{ - "simple@example.com", - "very.common@example.com", - "disposable.style.email.with+symbol@example.com", - "other.email-with-hyphen@and.subdomains.example.com", - "fully-qualified-domain@example.com", - "user.name+tag+sorting@example.com", - "x@example.com", - "example-indeed@strange-example.com", - "test/test@test.com", - "example@s.example", - "\" \"@example.org", - "\"john..doe\"@example.org", - "mailhost!username@example.org", - "\"very.(),:;<>[]\\\".VERY.\\\"very@\\\\ \\\"very\\\".unusual\"@strange.example.com", - "user%example.com@example.org", - "user-@example.org", - "postmaster@[123.123.123.123]", - "postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]", - } - - for _, email := range validEmails { - if checker.IsEmail(email) != nil { - t.Fatal(email) - } - } -} - -func TestIsEmailInvalid(t *testing.T) { - validEmails := []string{ - "Abc.example.com", - "A@b@c@example.com", - "a\"b(c)d,e:f;gi[j\\k]l@example.com", - "just\"not\"right@example.com", - "this is\"not\\allowed@example.com", - "this\\ still\\\"not\\\\allowed@example.com", - "1234567890123456789012345678901234567890123456789012345678901234+x@example.com", - "i_like_underscore@but_its_not_allowed_in_this_part.example.com", - "QA[icon]CHOCOLATE[icon]@test.com", - ".cannot.start.with.dot@example.com", - "cannot.end.with.dot.@example.com", - "cannot.have..double.dots@example.com", - "user@domaincannotbemorethan255charactersdomaincannotbemorethan255charactersdomaincannotbemorethan255charactersdomaincannotbemorethan255charactersdomaincannotbemorethan255charactersdomaincannotbemorethan255charactersdomaincannotbemorethan255charactersdomaincannotbemorethan255characters.com", - } - - for _, email := range validEmails { - if checker.IsEmail(email) == nil { - t.Fatal(email) - } + _, ok := v2.CheckStruct(user) + if !ok { + t.Fatal("expected valid") } } diff --git a/fqdn.go b/fqdn.go index c6c816d..899edec 100644 --- a/fqdn.go +++ b/fqdn.go @@ -3,75 +3,41 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" - "reflect" - "regexp" - "strings" + "reflect" + "regexp" ) -// tagFqdn is the tag of the checker. -const tagFqdn = "fqdn" +const ( + // nameFQDN is the name of the FQDN check. + nameFQDN = "fqdn" +) -// ErrNotFqdn indicates that the given string is not a valid FQDN. -var ErrNotFqdn = errors.New("please enter a valid domain name") +var ( + // ErrNotFQDN indicates that the given value is not a valid FQDN. + ErrNotFQDN = NewCheckError("FQDN") -// Valid characters excluding full-width characters. -var fqdnValidChars = regexp.MustCompile("^[a-z0-9\u00a1-\uff00\uff06-\uffff\\-]+$") + // fqdnRegex is the regular expression for validating FQDN. + fqdnRegex = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`) +) -// IsFqdn checks if the given string is a fully qualified domain name. -func IsFqdn(domain string) error { - parts := strings.Split(domain, ".") - - // Require TLD - if len(parts) < 2 { - return ErrNotFqdn - } - - tld := parts[len(parts)-1] - - // Should be all numeric TLD - if IsDigits(tld) == nil { - return ErrNotFqdn - } - - // Short TLD - if len(tld) < 2 { - return ErrNotFqdn - } - - for _, part := range parts { - // Cannot be more than 63 characters - if len(part) > 63 { - return ErrNotFqdn - } - - // Check for valid characters - if !fqdnValidChars.MatchString(part) { - return ErrNotFqdn - } - - // Should not start or end with a hyphen (-) character. - if part[0] == '-' || part[len(part)-1] == '-' { - return ErrNotFqdn - } - } - - return nil +// IsFQDN checks if the value is a valid fully qualified domain name (FQDN). +func IsFQDN(value string) (string, error) { + if !fqdnRegex.MatchString(value) { + return value, ErrNotFQDN + } + return value, nil } -// makeFqdn makes a checker function for the fqdn checker. -func makeFqdn(_ string) CheckFunc { - return checkFqdn +// checkFQDN checks if the value is a valid fully qualified domain name (FQDN). +func checkFQDN(value reflect.Value) (reflect.Value, error) { + _, err := IsFQDN(value.Interface().(string)) + return value, err } -// checkFqdn checks if the given string is a fully qualified domain name. -func checkFqdn(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - return IsFqdn(value.String()) -} +// makeFQDN makes a checker function for the FQDN checker. +func makeFQDN(_ string) CheckFunc[reflect.Value] { + return checkFQDN +} \ No newline at end of file diff --git a/fqdn_test.go b/fqdn_test.go index 0a8d76e..8ffc8ed 100644 --- a/fqdn_test.go +++ b/fqdn_test.go @@ -3,98 +3,74 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func ExampleIsFqdn() { - err := checker.IsFqdn("zdo.com") +func ExampleIsFQDN() { + _, err := v2.IsFQDN("example.com") if err != nil { - // Send the errors back to the user + fmt.Println(err) } } -func TestCheckFdqnWithoutTld(t *testing.T) { - if checker.IsFqdn("abcd") == nil { - t.Fail() +func TestIsFQDNInvalid(t *testing.T) { + _, err := v2.IsFQDN("invalid_fqdn") + if err == nil { + t.Fatal("expected error") } } -func TestCheckFdqnShortTld(t *testing.T) { - if checker.IsFqdn("abcd.c") == nil { - t.Fail() +func TestIsFQDNValid(t *testing.T) { + _, err := v2.IsFQDN("example.com") + if err != nil { + t.Fatal(err) } } -func TestCheckFdqnNumericTld(t *testing.T) { - if checker.IsFqdn("abcd.1234") == nil { - t.Fail() +func TestCheckFQDNNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Domain struct { + Name int `checkers:"fqdn"` + } + + domain := &Domain{} + + v2.CheckStruct(domain) +} + +func TestCheckFQDNInvalid(t *testing.T) { + type Domain struct { + Name string `checkers:"fqdn"` + } + + domain := &Domain{ + Name: "invalid_fqdn", + } + + _, ok := v2.CheckStruct(domain) + if ok { + t.Fatal("expected error") } } -func TestCheckFdqnLong(t *testing.T) { - if checker.IsFqdn("abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd.com") == nil { - t.Fail() - } -} - -func TestCheckFdqnInvalidCharacters(t *testing.T) { - if checker.IsFqdn("ab_cd.com") == nil { - t.Fail() - } -} - -func TestCheckFdqnStaringWithHyphen(t *testing.T) { - if checker.IsFqdn("-abcd.com") == nil { - t.Fail() - } -} - -func TestCheckFdqnStaringEndingWithHyphen(t *testing.T) { - if checker.IsFqdn("abcd-.com") == nil { - t.Fail() - } -} - -func TestCheckFdqnStartingWithDot(t *testing.T) { - if checker.IsFqdn(".abcd.com") == nil { - t.Fail() - } -} - -func TestCheckFdqnEndingWithDot(t *testing.T) { - if checker.IsFqdn("abcd.com.") == nil { - t.Fail() - } -} - -func TestCheckFqdnNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) - - type Request struct { - Domain int `checkers:"fqdn"` - } - - request := &Request{} - - checker.Check(request) -} - -func TestCheckFqdnValid(t *testing.T) { - type Request struct { - Domain string `checkers:"fqdn"` - } - - request := &Request{ - Domain: "zdo.com", - } - - _, valid := checker.Check(request) - if !valid { - t.Fail() +func TestCheckFQDNValid(t *testing.T) { + type Domain struct { + Name string `checkers:"fqdn"` + } + + domain := &Domain{ + Name: "example.com", + } + + _, ok := v2.CheckStruct(domain) + if !ok { + t.Fatal("expected valid") } } diff --git a/go.mod b/go.mod index 37bb71b..4a6df3a 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/cinar/checker +module github.com/cinar/checker/v2 -go 1.22 +go 1.23.2 diff --git a/v2/helper_test.go b/helper_test.go similarity index 100% rename from v2/helper_test.go rename to helper_test.go diff --git a/v2/hex.go b/hex.go similarity index 100% rename from v2/hex.go rename to hex.go diff --git a/v2/hex_test.go b/hex_test.go similarity index 100% rename from v2/hex_test.go rename to hex_test.go diff --git a/html_escape.go b/html_escape.go index 0a08372..e209d5b 100644 --- a/html_escape.go +++ b/html_escape.go @@ -3,29 +3,30 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( "html" "reflect" ) -// tagHTMLEscape is the tag of the normalizer. -const tagHTMLEscape = "html-escape" +const ( + // nameHTMLEscape is the name of the HTML escape normalizer. + nameHTMLEscape = "html-escape" +) -// makeHTMLEscape makes a normalizer function for the HTML escape normalizer. -func makeHTMLEscape(_ string) CheckFunc { - return normalizeHTMLEscape +// HTMLEscape applies HTML escaping to special characters. +func HTMLEscape(value string) (string, error) { + return html.EscapeString(value), nil } -// normalizeHTMLEscape applies HTML escaping to special characters. -// Uses html.EscapeString for the actual escape operation. -func normalizeHTMLEscape(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - value.SetString(html.EscapeString(value.String())) - - return nil +// reflectHTMLEscape applies HTML escaping to special characters. +func reflectHTMLEscape(value reflect.Value) (reflect.Value, error) { + newValue, err := HTMLEscape(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeHTMLEscape returns the HTML escape normalizer function. +func makeHTMLEscape(_ string) CheckFunc[reflect.Value] { + return reflectHTMLEscape } diff --git a/html_escape_test.go b/html_escape_test.go index f823afc..8a60010 100644 --- a/html_escape_test.go +++ b/html_escape_test.go @@ -3,26 +3,29 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func TestNormalizeHTMLEscapeNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) - type Comment struct { - Body int `checkers:"html-escape"` +func TestHTMLEscape(t *testing.T) { + input := " \"Checker\" & 'Library' " + expected := "<tag> "Checker" & 'Library' </tag>" + + actual, err := v2.HTMLEscape(input) + if err != nil { + t.Fatal(err) } - comment := &Comment{} - - checker.Check(comment) + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } } -func TestNormalizeHTMLEscape(t *testing.T) { +func TestReflectHTMLEscape(t *testing.T) { type Comment struct { Body string `checkers:"html-escape"` } @@ -31,12 +34,14 @@ func TestNormalizeHTMLEscape(t *testing.T) { Body: " \"Checker\" & 'Library' ", } - _, valid := checker.Check(comment) - if !valid { - t.Fail() + expected := "<tag> "Checker" & 'Library' </tag>" + + errs, ok := v2.CheckStruct(comment) + if !ok { + t.Fatalf("got unexpected errors %v", errs) } - if comment.Body != "<tag> "Checker" & 'Library' </tag>" { - t.Fail() + if comment.Body != expected { + t.Fatalf("actual %s expected %s", comment.Body, expected) } } diff --git a/html_unescape.go b/html_unescape.go index df78575..27ba376 100644 --- a/html_unescape.go +++ b/html_unescape.go @@ -3,29 +3,28 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( "html" "reflect" ) -// tagHTMLUnescape is the tag of the normalizer. -const tagHTMLUnescape = "html-unescape" +// nameHTMLUnescape is the name of the HTML unescape normalizer. +const nameHTMLUnescape = "html-unescape" -// makeHTMLUnescape makes a normalizer function for the HTML unscape normalizer. -func makeHTMLUnescape(_ string) CheckFunc { - return normalizeHTMLUnescape +// HTMLUnescape applies HTML unescaping to special characters. +func HTMLUnescape(value string) (string, error) { + return html.UnescapeString(value), nil } -// normalizeHTMLUnescape applies HTML unescaping to special characters. -// Uses html.UnescapeString for the actual unescape operation. -func normalizeHTMLUnescape(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - value.SetString(html.UnescapeString(value.String())) - - return nil +// reflectHTMLUnescape applies HTML unescaping to special characters. +func reflectHTMLUnescape(value reflect.Value) (reflect.Value, error) { + newValue, err := HTMLUnescape(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeHTMLUnescape returns the HTML unescape normalizer function. +func makeHTMLUnescape(_ string) CheckFunc[reflect.Value] { + return reflectHTMLUnescape } diff --git a/html_unescape_test.go b/html_unescape_test.go index f6f333c..1bf735b 100644 --- a/html_unescape_test.go +++ b/html_unescape_test.go @@ -3,27 +3,29 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func TestNormalizeHTMLUnescapeNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) +func TestHTMLUnescape(t *testing.T) { + input := "<tag> "Checker" & 'Library' </tag>" + expected := " \"Checker\" & 'Library' " - type Comment struct { - Body int `checkers:"html-unescape"` + actual, err := v2.HTMLUnescape(input) + if err != nil { + t.Fatal(err) } - comment := &Comment{} - - checker.Check(comment) + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } } -func TestNormalizeHTMLUnescape(t *testing.T) { +func TestReflectHTMLUnescape(t *testing.T) { type Comment struct { Body string `checkers:"html-unescape"` } @@ -32,12 +34,14 @@ func TestNormalizeHTMLUnescape(t *testing.T) { Body: "<tag> "Checker" & 'Library' </tag>", } - _, valid := checker.Check(comment) - if !valid { - t.Fail() + expected := " \"Checker\" & 'Library' " + + errs, ok := v2.CheckStruct(comment) + if !ok { + t.Fatalf("got unexpected errors %v", errs) } - if comment.Body != " \"Checker\" & 'Library' " { - t.Fail() + if comment.Body != expected { + t.Fatalf("actual %s expected %s", comment.Body, expected) } } diff --git a/ip.go b/ip.go index 023dc6c..e03ce6e 100644 --- a/ip.go +++ b/ip.go @@ -3,40 +3,38 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" "net" "reflect" ) -// tagIP is the tag of the checker. -const tagIP = "ip" +const ( + // nameIP is the name of the IP check. + nameIP = "ip" +) -// ErrNotIP indicates that the given value is not an IP address. -var ErrNotIP = errors.New("please enter a valid IP address") +var ( + // ErrNotIP indicates that the given value is not a valid IP address. + ErrNotIP = NewCheckError("NOT_IP") +) -// IsIP checks if the given value is an IP address. -func IsIP(value string) error { - ip := net.ParseIP(value) - if ip == nil { - return ErrNotIP +// IsIP checks if the value is a valid IP address. +func IsIP(value string) (string, error) { + if net.ParseIP(value) == nil { + return value, ErrNotIP } - - return nil + return value, nil } -// makeIP makes a checker function for the ip checker. -func makeIP(_ string) CheckFunc { +// checkIP checks if the value is a valid IP address. +func checkIP(value reflect.Value) (reflect.Value, error) { + _, err := IsIP(value.Interface().(string)) + return value, err +} + +// makeIP makes a checker function for the IP checker. +func makeIP(_ string) CheckFunc[reflect.Value] { return checkIP } - -// checkIP checks if the given value is an IP address. -func checkIP(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - return IsIP(value.String()) -} diff --git a/ip_test.go b/ip_test.go index a9dd74d..5bd23c5 100644 --- a/ip_test.go +++ b/ip_test.go @@ -3,71 +3,74 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) func ExampleIsIP() { - err := checker.IsIP("2001:db8::68") + _, err := v2.IsIP("192.168.1.1") if err != nil { - // Send the errors back to the user + fmt.Println(err) } } func TestIsIPInvalid(t *testing.T) { - if checker.IsIP("900.800.200.100") == nil { - t.Fail() + _, err := v2.IsIP("invalid-ip") + if err == nil { + t.Fatal("expected error") } } func TestIsIPValid(t *testing.T) { - if checker.IsIP("2001:db8::68") != nil { - t.Fail() + _, err := v2.IsIP("192.168.1.1") + if err != nil { + t.Fatal(err) } } -func TestCheckIpNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) +func TestCheckIPNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") - type Request struct { - RemoteIP int `checkers:"ip"` + type Network struct { + Address int `checkers:"ip"` } - request := &Request{} + network := &Network{} - checker.Check(request) + v2.CheckStruct(network) } -func TestCheckIpInvalid(t *testing.T) { - type Request struct { - RemoteIP string `checkers:"ip"` +func TestCheckIPInvalid(t *testing.T) { + type Network struct { + Address string `checkers:"ip"` } - request := &Request{ - RemoteIP: "900.800.200.100", + network := &Network{ + Address: "invalid-ip", } - _, valid := checker.Check(request) - if valid { - t.Fail() + _, ok := v2.CheckStruct(network) + if ok { + t.Fatal("expected error") } } func TestCheckIPValid(t *testing.T) { - type Request struct { - RemoteIP string `checkers:"ip"` + type Network struct { + Address string `checkers:"ip"` } - request := &Request{ - RemoteIP: "192.168.1.1", + network := &Network{ + Address: "192.168.1.1", } - _, valid := checker.Check(request) - if !valid { - t.Fail() + _, ok := v2.CheckStruct(network) + if !ok { + t.Fatal("expected valid") } } diff --git a/ipv4.go b/ipv4.go index 4168626..560327c 100644 --- a/ipv4.go +++ b/ipv4.go @@ -3,44 +3,39 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" "net" "reflect" ) -// tagIPV4 is the tag of the checker. -const tagIPV4 = "ipv4" +const ( + // nameIPv4 is the name of the IPv4 check. + nameIPv4 = "ipv4" +) -// ErrNotIPV4 indicates that the given value is not an IPv4 address. -var ErrNotIPV4 = errors.New("please enter a valid IPv4 address") +var ( + // ErrNotIPv4 indicates that the given value is not a valid IPv4 address. + ErrNotIPv4 = NewCheckError("NOT_IPV4") +) -// IsIPV4 checks if the given value is an IPv4 address. -func IsIPV4(value string) error { +// IsIPv4 checks if the value is a valid IPv4 address. +func IsIPv4(value string) (string, error) { ip := net.ParseIP(value) - if ip == nil { - return ErrNotIPV4 + if ip == nil || ip.To4() == nil { + return value, ErrNotIPv4 } - - if ip.To4() == nil { - return ErrNotIPV4 - } - - return nil + return value, nil } -// makeIPV4 makes a checker function for the ipV4 checker. -func makeIPV4(_ string) CheckFunc { - return checkIPV4 +// checkIPv4 checks if the value is a valid IPv4 address. +func checkIPv4(value reflect.Value) (reflect.Value, error) { + _, err := IsIPv4(value.Interface().(string)) + return value, err } -// checkIPV4 checks if the given value is an IPv4 address. -func checkIPV4(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - return IsIPV4(value.String()) +// makeIPv4 makes a checker function for the IPv4 checker. +func makeIPv4(_ string) CheckFunc[reflect.Value] { + return checkIPv4 } diff --git a/ipv4_test.go b/ipv4_test.go index 1e2509c..ba34feb 100644 --- a/ipv4_test.go +++ b/ipv4_test.go @@ -3,77 +3,74 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func ExampleIsIPV4() { - err := checker.IsIPV4("192.168.1.1") +func ExampleIsIPv4() { + _, err := v2.IsIPv4("192.168.1.1") if err != nil { - // Send the errors back to the user + fmt.Println(err) } } -func TestIsIPV4Invalid(t *testing.T) { - if checker.IsIPV4("900.800.200.100") == nil { - t.Fail() +func TestIsIPv4Invalid(t *testing.T) { + _, err := v2.IsIPv4("2001:db8::1") + if err == nil { + t.Fatal("expected error") } } -func TestIsIPV4InvalidV6(t *testing.T) { - if checker.IsIPV4("2001:db8::68") == nil { - t.Fail() +func TestIsIPv4Valid(t *testing.T) { + _, err := v2.IsIPv4("192.168.1.1") + if err != nil { + t.Fatal(err) } } -func TestIsIPV4Valid(t *testing.T) { - if checker.IsIPV4("192.168.1.1") != nil { - t.Fail() +func TestCheckIPv4NonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Network struct { + Address int `checkers:"ipv4"` + } + + network := &Network{} + + v2.CheckStruct(network) +} + +func TestCheckIPv4Invalid(t *testing.T) { + type Network struct { + Address string `checkers:"ipv4"` + } + + network := &Network{ + Address: "2001:db8::1", + } + + _, ok := v2.CheckStruct(network) + if ok { + t.Fatal("expected error") } } -func TestCheckIPV4NonString(t *testing.T) { - defer checker.FailIfNoPanic(t) - - type Request struct { - RemoteIP int `checkers:"ipv4"` +func TestCheckIPv4Valid(t *testing.T) { + type Network struct { + Address string `checkers:"ipv4"` } - request := &Request{} - - checker.Check(request) -} - -func TestCheckIPV4Invalid(t *testing.T) { - type Request struct { - RemoteIP string `checkers:"ipv4"` + network := &Network{ + Address: "192.168.1.1", } - request := &Request{ - RemoteIP: "900.800.200.100", - } - - _, valid := checker.Check(request) - if valid { - t.Fail() - } -} - -func TestCheckIPV4Valid(t *testing.T) { - type Request struct { - RemoteIP string `checkers:"ipv4"` - } - - request := &Request{ - RemoteIP: "192.168.1.1", - } - - _, valid := checker.Check(request) - if !valid { - t.Fail() + _, ok := v2.CheckStruct(network) + if !ok { + t.Fatal("expected valid") } } diff --git a/ipv6.go b/ipv6.go index 49c7533..ad3bb54 100644 --- a/ipv6.go +++ b/ipv6.go @@ -3,44 +3,38 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" "net" "reflect" ) -// tagIPV6 is the tag of the checker. -const tagIPV6 = "ipv6" +const ( + // nameIPv6 is the name of the IPv6 check. + nameIPv6 = "ipv6" +) -// ErrNotIPV6 indicates that the given value is not an IPv6 address. -var ErrNotIPV6 = errors.New("please enter a valid IPv6 address") +var ( + // ErrNotIPv6 indicates that the given value is not a valid IPv6 address. + ErrNotIPv6 = NewCheckError("NOT_IPV6") +) -// IsIPV6 checks if the given value is an IPv6 address. -func IsIPV6(value string) error { - ip := net.ParseIP(value) - if ip == nil { - return ErrNotIPV6 +// IsIPv6 checks if the value is a valid IPv6 address. +func IsIPv6(value string) (string, error) { + if net.ParseIP(value) == nil || net.ParseIP(value).To4() != nil { + return value, ErrNotIPv6 } - - if ip.To4() != nil { - return ErrNotIPV6 - } - - return nil + return value, nil } -// makeIPV6 makes a checker function for the ipV6 checker. -func makeIPV6(_ string) CheckFunc { - return checkIPV6 +// checkIPv6 checks if the value is a valid IPv6 address. +func checkIPv6(value reflect.Value) (reflect.Value, error) { + _, err := IsIPv6(value.Interface().(string)) + return value, err } -// checkIPV6 checks if the given value is an IPv6 address. -func checkIPV6(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - return IsIPV6(value.String()) +// makeIPv6 makes a checker function for the IPv6 checker. +func makeIPv6(_ string) CheckFunc[reflect.Value] { + return checkIPv6 } diff --git a/ipv6_test.go b/ipv6_test.go index 97be750..2a7ea58 100644 --- a/ipv6_test.go +++ b/ipv6_test.go @@ -3,78 +3,74 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func ExampleIsIPV6() { - err := checker.IsIPV6("2001:db8::68") - +func ExampleIsIPv6() { + _, err := v2.IsIPv6("2001:db8::1") if err != nil { - // Send the errors back to the user + fmt.Println(err) } } -func TestIsIPV6Invalid(t *testing.T) { - if checker.IsIPV6("900.800.200.100") == nil { - t.Fail() +func TestIsIPv6Invalid(t *testing.T) { + _, err := v2.IsIPv6("192.168.1.1") + if err == nil { + t.Fatal("expected error") } } -func TestIsIPV6InvalidV4(t *testing.T) { - if checker.IsIPV6("192.168.1.1") == nil { - t.Fail() +func TestIsIPv6Valid(t *testing.T) { + _, err := v2.IsIPv6("2001:db8::1") + if err != nil { + t.Fatal(err) } } -func TestIsIPV6Valid(t *testing.T) { - if checker.IsIPV6("2001:db8::68") != nil { - t.Fail() +func TestCheckIPv6NonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Network struct { + Address int `checkers:"ipv6"` + } + + network := &Network{} + + v2.CheckStruct(network) +} + +func TestCheckIPv6Invalid(t *testing.T) { + type Network struct { + Address string `checkers:"ipv6"` + } + + network := &Network{ + Address: "192.168.1.1", + } + + _, ok := v2.CheckStruct(network) + if ok { + t.Fatal("expected error") } } -func TestCheckIPV6NonString(t *testing.T) { - defer checker.FailIfNoPanic(t) - - type Request struct { - RemoteIP int `checkers:"ipv6"` +func TestCheckIPv6Valid(t *testing.T) { + type Network struct { + Address string `checkers:"ipv6"` } - request := &Request{} - - checker.Check(request) -} - -func TestCheckIPV6Invalid(t *testing.T) { - type Request struct { - RemoteIP string `checkers:"ipv6"` + network := &Network{ + Address: "2001:db8::1", } - request := &Request{ - RemoteIP: "900.800.200.100", - } - - _, valid := checker.Check(request) - if valid { - t.Fail() - } -} - -func TestCheckIPV6Valid(t *testing.T) { - type Request struct { - RemoteIP string `checkers:"ipv6"` - } - - request := &Request{ - RemoteIP: "2001:db8::68", - } - - _, valid := checker.Check(request) - if !valid { - t.Fail() + _, ok := v2.CheckStruct(network) + if !ok { + t.Fatal("expected valid") } } diff --git a/isbn.go b/isbn.go index 11ebf28..b2e1e15 100644 --- a/isbn.go +++ b/isbn.go @@ -3,125 +3,41 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" "reflect" - "strings" + "regexp" ) -// Program to check for ISBN -// https://www.geeksforgeeks.org/program-check-isbn/ +const ( + // nameISBN is the name of the ISBN check. + nameISBN = "isbn" +) -// How to Verify an ISBN -// https://www.instructables.com/How-to-verify-a-ISBN/ +var ( + // ErrNotISBN indicates that the given value is not a valid ISBN. + ErrNotISBN = NewCheckError("NOT_ISBN") -// tagISBN is the tag of the checker. -const tagISBN = "isbn" + // isbnRegex is the regular expression for validating ISBN-10 and ISBN-13. + isbnRegex = regexp.MustCompile(`^(97(8|9))?\d{9}(\d|X)$`) +) -// ErrNotISBN indicates that the given value is not a valid ISBN. -var ErrNotISBN = errors.New("please enter a valid ISBN number") - -// IsISBN10 checks if the given value is a valid ISBN-10 number. -func IsISBN10(value string) error { - value = strings.ReplaceAll(value, "-", "") - - if len(value) != 10 { - return ErrNotISBN +// IsISBN checks if the value is a valid ISBN-10 or ISBN-13. +func IsISBN(value string) (string, error) { + if !isbnRegex.MatchString(value) { + return value, ErrNotISBN } - - digits := []rune(value) - sum := 0 - - for i, e := 0, len(digits); i < e; i++ { - n := isbnDigitToInt(digits[i]) - sum += n * (e - i) - } - - if sum%11 != 0 { - return ErrNotISBN - } - - return nil + return value, nil } -// IsISBN13 checks if the given value is a valid ISBN-13 number. -func IsISBN13(value string) error { - value = strings.ReplaceAll(value, "-", "") - - if len(value) != 13 { - return ErrNotISBN - } - - digits := []rune(value) - sum := 0 - - for i, d := range digits { - n := isbnDigitToInt(d) - if i%2 != 0 { - n *= 3 - } - - sum += n - } - - if sum%10 != 0 { - return ErrNotISBN - } - - return nil +// checkISBN checks if the value is a valid ISBN-10 or ISBN-13. +func checkISBN(value reflect.Value) (reflect.Value, error) { + _, err := IsISBN(value.Interface().(string)) + return value, err } -// IsISBN checks if the given value is a valid ISBN number. -func IsISBN(value string) error { - value = strings.ReplaceAll(value, "-", "") - - if len(value) == 10 { - return IsISBN10(value) - } else if len(value) == 13 { - return IsISBN13(value) - } - - return ErrNotISBN -} - -// isbnDigitToInt returns the integer value of given ISBN digit. -func isbnDigitToInt(r rune) int { - if r == 'X' { - return 10 - } - - return int(r - '0') -} - -// makeISBN makes a checker function for the URL checker. -func makeISBN(config string) CheckFunc { - if config != "" && config != "10" && config != "13" { - panic("invalid format") - } - - return func(value, parent reflect.Value) error { - return checkISBN(value, parent, config) - } -} - -// checkISBN checks if the given value is a valid ISBN number. -func checkISBN(value, _ reflect.Value, mode string) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - number := value.String() - - switch mode { - case "10": - return IsISBN10(number) - - case "13": - return IsISBN13(number) - - default: - return IsISBN(number) - } +// makeISBN makes a checker function for the ISBN checker. +func makeISBN(_ string) CheckFunc[reflect.Value] { + return checkISBN } diff --git a/isbn_test.go b/isbn_test.go index 8d12e60..cb8c48c 100644 --- a/isbn_test.go +++ b/isbn_test.go @@ -3,122 +3,38 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( - "errors" + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func ExampleIsISBN10() { - err := checker.IsISBN10("1430248270") - if err != nil { - // Send the errors back to the user - } -} - -func TestIsISBN10Valid(t *testing.T) { - err := checker.IsISBN10("1430248270") - if err != nil { - t.Fail() - } -} - -func TestIsISBN10ValidX(t *testing.T) { - err := checker.IsISBN10("007462542X") - if err != nil { - t.Fail() - } -} - -func TestIsISBN10ValidWithDashes(t *testing.T) { - err := checker.IsISBN10("1-4302-4827-0") - if err != nil { - t.Fail() - } -} - -func TestIsISBN10InvalidLength(t *testing.T) { - err := checker.IsISBN10("143024827") - if !errors.Is(err, checker.ErrNotISBN) { - t.Fail() - } -} - -func TestIsISBN10InvalidCheck(t *testing.T) { - err := checker.IsISBN10("1430248272") - if !errors.Is(err, checker.ErrNotISBN) { - t.Fail() - } -} - -func ExampleIsISBN13() { - err := checker.IsISBN13("9781430248279") - if err != nil { - // Send the errors back to the user - } -} - -func TestIsISBN13Valid(t *testing.T) { - err := checker.IsISBN13("9781430248279") - if err != nil { - t.Fail() - } -} - -func TestIsISBN13ValidWithDashes(t *testing.T) { - err := checker.IsISBN13("978-1-4302-4827-9") - if err != nil { - t.Fail() - } -} - -func TestIsISBN13InvalidLength(t *testing.T) { - err := checker.IsISBN13("978143024827") - if !errors.Is(err, checker.ErrNotISBN) { - t.Fail() - } -} - -func TestIsISBN13InvalidCheck(t *testing.T) { - err := checker.IsISBN13("9781430248272") - if !errors.Is(err, checker.ErrNotISBN) { - t.Fail() - } -} - func ExampleIsISBN() { - err := checker.IsISBN("1430248270") + _, err := v2.IsISBN("1430248270") if err != nil { - // Send the errors back to the user + fmt.Println(err) } } -func TestIsISBNValid10(t *testing.T) { - err := checker.IsISBN("1430248270") - if err != nil { - t.Fail() +func TestIsISBNInvalid(t *testing.T) { + _, err := v2.IsISBN("invalid-isbn") + if err == nil { + t.Fatal("expected error") } } -func TestIsISBNValid13(t *testing.T) { - err := checker.IsISBN("9781430248279") +func TestIsISBNValid(t *testing.T) { + _, err := v2.IsISBN("1430248270") if err != nil { - t.Fail() - } -} - -func TestIsISBNInvalidLenght(t *testing.T) { - err := checker.IsISBN("978143024827") - if err != checker.ErrNotISBN { - t.Fail() + t.Fatal(err) } } func TestCheckISBNNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) + defer FailIfNoPanic(t, "expected panic") type Book struct { ISBN int `checkers:"isbn"` @@ -126,7 +42,22 @@ func TestCheckISBNNonString(t *testing.T) { book := &Book{} - checker.Check(book) + v2.CheckStruct(book) +} + +func TestCheckISBNInvalid(t *testing.T) { + type Book struct { + ISBN string `checkers:"isbn"` + } + + book := &Book{ + ISBN: "invalid-isbn", + } + + _, ok := v2.CheckStruct(book) + if ok { + t.Fatal("expected error") + } } func TestCheckISBNValid(t *testing.T) { @@ -135,55 +66,11 @@ func TestCheckISBNValid(t *testing.T) { } book := &Book{ - ISBN: "1430248270", + ISBN: "9783161484100", } - _, valid := checker.Check(book) - if !valid { - t.Fail() - } -} - -func TestCheckISBNInvalid(t *testing.T) { - defer checker.FailIfNoPanic(t) - - type Book struct { - ISBN string `checkers:"isbn:20"` - } - - book := &Book{ - ISBN: "1430248270", - } - - checker.Check(book) -} - -func TestCheckISBNValid10(t *testing.T) { - type Book struct { - ISBN string `checkers:"isbn:10"` - } - - book := &Book{ - ISBN: "1430248270", - } - - _, valid := checker.Check(book) - if !valid { - t.Fail() - } -} - -func TestCheckISBNValid13(t *testing.T) { - type Book struct { - ISBN string `checkers:"isbn:13"` - } - - book := &Book{ - ISBN: "9781430248279", - } - - _, valid := checker.Check(book) - if !valid { - t.Fail() + _, ok := v2.CheckStruct(book) + if !ok { + t.Fatal("expected valid") } } diff --git a/v2/locales/DOC.md b/locales/DOC.md similarity index 100% rename from v2/locales/DOC.md rename to locales/DOC.md diff --git a/v2/locales/en_us.go b/locales/en_us.go similarity index 100% rename from v2/locales/en_us.go rename to locales/en_us.go diff --git a/v2/locales/locales.go b/locales/locales.go similarity index 100% rename from v2/locales/locales.go rename to locales/locales.go diff --git a/lower.go b/lower.go index 585792d..803ca99 100644 --- a/lower.go +++ b/lower.go @@ -3,28 +3,30 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( "reflect" "strings" ) -// tagLower is the tag of the normalizer. -const tagLower = "lower" +const ( + // nameLower is the name of the lower normalizer. + nameLower = "lower" +) -// makeLower makes a normalizer function for the lower normalizer. -func makeLower(_ string) CheckFunc { - return normalizeLower +// Lower maps all Unicode letters in the given value to their lower case. +func Lower(value string) (string, error) { + return strings.ToLower(value), nil } -// normalizeLower maps all Unicode letters in the given value to their lower case. -func normalizeLower(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - value.SetString(strings.ToLower(value.String())) - - return nil +// reflectLower maps all Unicode letters in the given value to their lower case. +func reflectLower(value reflect.Value) (reflect.Value, error) { + newValue, err := Lower(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeLower returns the lower normalizer function. +func makeLower(_ string) CheckFunc[reflect.Value] { + return reflectLower } diff --git a/lower_test.go b/lower_test.go index 112fbd8..491e671 100644 --- a/lower_test.go +++ b/lower_test.go @@ -3,53 +3,45 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func TestNormalizeLowerNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) +func TestLower(t *testing.T) { + input := "CHECKER" + expected := "checker" - type User struct { - Username int `checkers:"lower"` + actual, err := v2.Lower(input) + if err != nil { + t.Fatal(err) } - user := &User{} - - checker.Check(user) -} - -func TestNormalizeLowerErrValid(t *testing.T) { - type User struct { - Username string `checkers:"lower"` - } - - user := &User{ - Username: "chECker", - } - - _, valid := checker.Check(user) - if !valid { - t.Fail() + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) } } -func TestNormalizeLower(t *testing.T) { - type User struct { - Username string `checkers:"lower"` +func TestReflectLower(t *testing.T) { + type Person struct { + Name string `checkers:"lower"` } - user := &User{ - Username: "chECker", + person := &Person{ + Name: "CHECKER", } - checker.Check(user) + expected := "checker" - if user.Username != "checker" { - t.Fail() + errs, ok := v2.CheckStruct(person) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if person.Name != expected { + t.Fatalf("actual %s expected %s", person.Name, expected) } } diff --git a/luhn.go b/luhn.go index d1543ef..5040449 100644 --- a/luhn.go +++ b/luhn.go @@ -3,64 +3,59 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" "reflect" + "unicode" ) -// tagLuhn is the tag of the checker. -const tagLuhn = "luhn" +const ( + // nameLUHN is the name of the LUHN check. + nameLUHN = "luhn" +) -// ErrNotLuhn indicates that the given number is not valid based on the Luhn algorithm. -var ErrNotLuhn = errors.New("please enter a valid LUHN") +var ( + // ErrNotLUHN indicates that the given value is not a valid LUHN number. + ErrNotLUHN = NewCheckError("NOT_LUHN") +) -// doubleTable is the values for the last digits of doubled digits added. -var doubleTable = [10]int{0, 2, 4, 6, 8, 1, 3, 5, 7, 9} +// IsLUHN checks if the value is a valid LUHN number. +func IsLUHN(value string) (string, error) { + var sum int + var alt bool -// IsLuhn checks if the given number is valid based on the Luhn algorithm. -func IsLuhn(number string) error { - digits := number[:len(number)-1] - check := rune(number[len(number)-1]) - - if calculateLuhnCheckDigit(digits) != check { - return ErrNotLuhn - } - - return nil -} - -// makeLuhn makes a checker function for the Luhn algorithm. -func makeLuhn(_ string) CheckFunc { - return checkLuhn -} - -// checkLuhn checks if the given number is valid based on the Luhn algorithm. -func checkLuhn(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - return IsLuhn(value.String()) -} - -// Calculates the Luhn algorighm check digit for the given number. -func calculateLuhnCheckDigit(number string) rune { - digits := []rune(number) - check := 0 - - for i, j := 0, len(digits)-1; i <= j; i++ { - d := int(digits[j-i] - '0') - if i%2 == 0 { - d = doubleTable[d] + for i := len(value) - 1; i >= 0; i-- { + r := rune(value[i]) + if !unicode.IsDigit(r) { + return value, ErrNotLUHN } - check += d + n := int(r - '0') + if alt { + n *= 2 + if n > 9 { + n -= 9 + } + } + sum += n + alt = !alt } - check *= 9 - check %= 10 + if sum%10 != 0 { + return value, ErrNotLUHN + } - return rune('0' + check) + return value, nil +} + +// checkLUHN checks if the value is a valid LUHN number. +func checkLUHN(value reflect.Value) (reflect.Value, error) { + _, err := IsLUHN(value.Interface().(string)) + return value, err +} + +// makeLUHN makes a checker function for the LUHN checker. +func makeLUHN(_ string) CheckFunc[reflect.Value] { + return checkLUHN } diff --git a/luhn_test.go b/luhn_test.go index 3ddb5e1..877d5c5 100644 --- a/luhn_test.go +++ b/luhn_test.go @@ -3,74 +3,81 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func ExampleIsLuhn() { - err := checker.IsLuhn("4012888888881881") +func ExampleIsLUHN() { + _, err := v2.IsLUHN("4012888888881881") if err != nil { - // Send the errors back to the user + fmt.Println(err) } } -func TestIsLuhnValid(t *testing.T) { - numbers := []string{ - "4012888888881881", - "4222222222222", - "5555555555554444", - "5105105105105100", - } - - for _, number := range numbers { - if checker.IsLuhn(number) != nil { - t.Fail() - } +func TestIsLUHNInvalid(t *testing.T) { + _, err := v2.IsLUHN("123456789") + if err == nil { + t.Fatal("expected error") } } -func TestCheckLuhnNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) - - type Order struct { - CreditCard int `checkers:"luhn"` - } - - order := &Order{} - - checker.Check(order) -} - -func TestCheckLuhnInvalid(t *testing.T) { - type Order struct { - CreditCard string `checkers:"luhn"` - } - - order := &Order{ - CreditCard: "4012888888881884", - } - - _, valid := checker.Check(order) - if valid { - t.Fail() +func TestIsLUHNInvalidDigits(t *testing.T) { + _, err := v2.IsLUHN("ABCD") + if err == nil { + t.Fatal("expected error") } } -func TestCheckLuhnValid(t *testing.T) { - type Order struct { - CreditCard string `checkers:"luhn"` - } - - order := &Order{ - CreditCard: "4012888888881881", - } - - _, valid := checker.Check(order) - if !valid { - t.Fail() +func TestIsLUHNValid(t *testing.T) { + _, err := v2.IsLUHN("4012888888881881") + if err != nil { + t.Fatal(err) + } +} + +func TestCheckLUHNNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Card struct { + Number int `checkers:"luhn"` + } + + card := &Card{} + + v2.CheckStruct(card) +} + +func TestCheckLUHNInvalid(t *testing.T) { + type Card struct { + Number string `checkers:"luhn"` + } + + card := &Card{ + Number: "123456789", + } + + _, ok := v2.CheckStruct(card) + if ok { + t.Fatal("expected error") + } +} + +func TestCheckLUHNValid(t *testing.T) { + type Card struct { + Number string `checkers:"luhn"` + } + + card := &Card{ + Number: "79927398713", + } + + _, ok := v2.CheckStruct(card) + if !ok { + t.Fatal("expected valid") } } diff --git a/mac.go b/mac.go index 58f6284..43e1370 100644 --- a/mac.go +++ b/mac.go @@ -3,40 +3,39 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" "net" "reflect" ) -// tagMac is the tag of the checker. -const tagMac = "mac" +const ( + // nameMAC is the name of the MAC check. + nameMAC = "mac" +) -// ErrNotMac indicates that the given value is not an MAC address. -var ErrNotMac = errors.New("please enter a valid MAC address") +var ( + // ErrNotMAC indicates that the given value is not a valid MAC address. + ErrNotMAC = NewCheckError("NOT_MAC") +) -// IsMac checks if the given value is a valid an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet IP over InfiniBand link-layer address. -func IsMac(value string) error { +// IsMAC checks if the value is a valid MAC address. +func IsMAC(value string) (string, error) { _, err := net.ParseMAC(value) if err != nil { - return ErrNotMac + return value, ErrNotMAC } - - return nil + return value, nil } -// makeMac makes a checker function for the ip checker. -func makeMac(_ string) CheckFunc { - return checkMac +// checkMAC checks if the value is a valid MAC address. +func checkMAC(value reflect.Value) (reflect.Value, error) { + _, err := IsMAC(value.Interface().(string)) + return value, err } -// checkMac checks if the given value is a valid an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet IP over InfiniBand link-layer address. -func checkMac(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - return IsMac(value.String()) +// makeMAC makes a checker function for the MAC checker. +func makeMAC(_ string) CheckFunc[reflect.Value] { + return checkMAC } diff --git a/mac_test.go b/mac_test.go index 7e8ba63..23ec403 100644 --- a/mac_test.go +++ b/mac_test.go @@ -3,71 +3,74 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func ExampleIsMac() { - err := checker.IsMac("00:00:5e:00:53:01") +func ExampleIsMAC() { + _, err := v2.IsMAC("00:1A:2B:3C:4D:5E") if err != nil { - // Send the errors back to the user + fmt.Println(err) } } -func TestIsMacInvalid(t *testing.T) { - if checker.IsMac("1234") == nil { - t.Fail() +func TestIsMACInvalid(t *testing.T) { + _, err := v2.IsMAC("invalid-mac") + if err == nil { + t.Fatal("expected error") } } -func TestIsMacValid(t *testing.T) { - if checker.IsMac("00:00:5e:00:53:01") != nil { - t.Fail() +func TestIsMACValid(t *testing.T) { + _, err := v2.IsMAC("00:1A:2B:3C:4D:5E") + if err != nil { + t.Fatal(err) } } -func TestCheckMacNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) +func TestCheckMACNonString(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") - type Network struct { - HardwareAddress int `checkers:"mac"` + type Device struct { + MAC int `checkers:"mac"` } - network := &Network{} + device := &Device{} - checker.Check(network) + v2.CheckStruct(device) } -func TestCheckMacInvalid(t *testing.T) { - type Network struct { - HardwareAddress string `checkers:"mac"` +func TestCheckMACInvalid(t *testing.T) { + type Device struct { + MAC string `checkers:"mac"` } - network := &Network{ - HardwareAddress: "1234", + device := &Device{ + MAC: "invalid-mac", } - _, valid := checker.Check(network) - if valid { - t.Fail() + _, ok := v2.CheckStruct(device) + if ok { + t.Fatal("expected error") } } -func TestCheckMacValid(t *testing.T) { - type Network struct { - HardwareAddress string `checkers:"mac"` +func TestCheckMACValid(t *testing.T) { + type Device struct { + MAC string `checkers:"mac"` } - network := &Network{ - HardwareAddress: "00:00:5e:00:53:01", + device := &Device{ + MAC: "00:1A:2B:3C:4D:5E", } - _, valid := checker.Check(network) - if !valid { - t.Fail() + _, ok := v2.CheckStruct(device) + if !ok { + t.Fatal("expected valid") } } diff --git a/v2/maker.go b/maker.go similarity index 100% rename from v2/maker.go rename to maker.go diff --git a/v2/maker_test.go b/maker_test.go similarity index 100% rename from v2/maker_test.go rename to maker_test.go diff --git a/max.go b/max.go deleted file mode 100644 index f26246d..0000000 --- a/max.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker - -import ( - "fmt" - "reflect" - "strconv" -) - -// tagMax is the tag of the checker. -const tagMax = "max" - -// IsMax checks if the given value is below than the given maximum. -func IsMax(value interface{}, max float64) error { - return checkMax(reflect.Indirect(reflect.ValueOf(value)), reflect.ValueOf(nil), max) -} - -// makeMax makes a checker function for the max checker. -func makeMax(config string) CheckFunc { - max, err := strconv.ParseFloat(config, 64) - if err != nil { - panic("unable to parse max") - } - - return func(value, parent reflect.Value) error { - return checkMax(value, parent, max) - } -} - -// checkMax checks if the given value is less than the given maximum. -func checkMax(value, _ reflect.Value, max float64) error { - n := numberOf(value) - - if n > max { - return fmt.Errorf("please enter a number less than %g", max) - } - - return nil -} diff --git a/v2/max_len.go b/max_len.go similarity index 100% rename from v2/max_len.go rename to max_len.go diff --git a/v2/max_len_test.go b/max_len_test.go similarity index 100% rename from v2/max_len_test.go rename to max_len_test.go diff --git a/max_test.go b/max_test.go deleted file mode 100644 index 6f5ce65..0000000 --- a/max_test.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker_test - -import ( - "testing" - - "github.com/cinar/checker" -) - -func ExampleIsMax() { - quantity := 5 - - err := checker.IsMax(quantity, 10) - if err != nil { - // Send the errors back to the user - } -} - -func TestIsMaxValid(t *testing.T) { - n := 5 - - if checker.IsMax(n, 10) != nil { - t.Fail() - } -} - -func TestCheckMaxInvalidConfig(t *testing.T) { - defer checker.FailIfNoPanic(t) - - type Order struct { - Quantity int `checkers:"max:AB"` - } - - order := &Order{} - - checker.Check(order) -} - -func TestCheckMaxValid(t *testing.T) { - type Order struct { - Quantity int `checkers:"max:10"` - } - - order := &Order{ - Quantity: 5, - } - - _, valid := checker.Check(order) - if !valid { - t.Fail() - } -} - -func TestCheckMaxInvalid(t *testing.T) { - type Order struct { - Quantity int `checkers:"max:10"` - } - - order := &Order{ - Quantity: 20, - } - - _, valid := checker.Check(order) - if valid { - t.Fail() - } -} diff --git a/maxlength.go b/maxlength.go deleted file mode 100644 index 3b12f35..0000000 --- a/maxlength.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker - -import ( - "fmt" - "reflect" - "strconv" -) - -// tagMaxLength is the tag of the checker. -const tagMaxLength = "max-length" - -// IsMaxLength checks if the length of the given value is less than the given maximum length. -func IsMaxLength(value interface{}, maxLength int) error { - return checkMaxLength(reflect.Indirect(reflect.ValueOf(value)), reflect.ValueOf(nil), maxLength) -} - -// makeMaxLength makes a checker function for the max length checker. -func makeMaxLength(config string) CheckFunc { - maxLength, err := strconv.Atoi(config) - if err != nil { - panic("unable to parse max length value") - } - - return func(value, parent reflect.Value) error { - return checkMaxLength(value, parent, maxLength) - } -} - -// checkMaxLength checks if the length of the given value is less than the given maximum length. -// The function uses the reflect.Value.Len() function to determaxe the length of the value. -func checkMaxLength(value, _ reflect.Value, maxLength int) error { - if value.Len() > maxLength { - return fmt.Errorf("please enter %d characters or less", maxLength-1) - } - - return nil -} diff --git a/maxlength_test.go b/maxlength_test.go deleted file mode 100644 index cb9b395..0000000 --- a/maxlength_test.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker_test - -import ( - "testing" - - "github.com/cinar/checker" -) - -func ExampleIsMaxLength() { - s := "1234" - - err := checker.IsMaxLength(s, 4) - if err != nil { - // Send the errors back to the user - } -} - -func TestIsMaxLengthValid(t *testing.T) { - s := "1234" - - if checker.IsMaxLength(s, 4) != nil { - t.Fail() - } -} - -func TestCheckMaxLengthInvalidConfig(t *testing.T) { - defer checker.FailIfNoPanic(t) - - type User struct { - Password string `checkers:"max-length:AB"` - } - - user := &User{} - - checker.Check(user) -} - -func TestCheckMaxLengthValid(t *testing.T) { - type User struct { - Password string `checkers:"max-length:4"` - } - - user := &User{ - Password: "1234", - } - - _, valid := checker.Check(user) - if !valid { - t.Fail() - } -} - -func TestCheckMaxLengthInvalid(t *testing.T) { - type User struct { - Password string `checkers:"max-length:4"` - } - - user := &User{ - Password: "123456", - } - - _, valid := checker.Check(user) - if valid { - t.Fail() - } -} diff --git a/min.go b/min.go deleted file mode 100644 index 9531db1..0000000 --- a/min.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker - -import ( - "fmt" - "reflect" - "strconv" -) - -// tagMin is the tag of the checker. -const tagMin = "min" - -// IsMin checks if the given value is above than the given minimum. -func IsMin(value interface{}, min float64) error { - return checkMin(reflect.Indirect(reflect.ValueOf(value)), reflect.ValueOf(nil), min) -} - -// makeMin makes a checker function for the min checker. -func makeMin(config string) CheckFunc { - min, err := strconv.ParseFloat(config, 64) - if err != nil { - panic("unable to parse min") - } - - return func(value, parent reflect.Value) error { - return checkMin(value, parent, min) - } -} - -// checkMin checks if the given value is greather than the given minimum. -func checkMin(value, _ reflect.Value, min float64) error { - n := numberOf(value) - - if n < min { - return fmt.Errorf("please enter a number less than %g", min) - } - - return nil -} diff --git a/v2/min_len.go b/min_len.go similarity index 100% rename from v2/min_len.go rename to min_len.go diff --git a/v2/min_len_test.go b/min_len_test.go similarity index 100% rename from v2/min_len_test.go rename to min_len_test.go diff --git a/min_test.go b/min_test.go deleted file mode 100644 index dc12888..0000000 --- a/min_test.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker_test - -import ( - "testing" - - "github.com/cinar/checker" -) - -func ExampleIsMin() { - age := 45 - - err := checker.IsMin(age, 21) - if err != nil { - // Send the errors back to the user - } -} - -func TestIsMinValid(t *testing.T) { - n := 45 - - if checker.IsMin(n, 21) != nil { - t.Fail() - } -} - -func TestCheckMinInvalidConfig(t *testing.T) { - defer checker.FailIfNoPanic(t) - - type User struct { - Age int `checkers:"min:AB"` - } - - user := &User{} - - checker.Check(user) -} - -func TestCheckMinValid(t *testing.T) { - type User struct { - Age int `checkers:"min:21"` - } - - user := &User{ - Age: 45, - } - - _, valid := checker.Check(user) - if !valid { - t.Fail() - } -} - -func TestCheckMinInvalid(t *testing.T) { - type User struct { - Age int `checkers:"min:21"` - } - - user := &User{ - Age: 18, - } - - _, valid := checker.Check(user) - if valid { - t.Fail() - } -} diff --git a/minlenght.go b/minlenght.go deleted file mode 100644 index f1cefac..0000000 --- a/minlenght.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker - -import ( - "fmt" - "reflect" - "strconv" -) - -// tagMinLength is the tag of the checker. -const tagMinLength = "min-length" - -// IsMinLength checks if the length of the given value is greather than the given minimum length. -func IsMinLength(value interface{}, minLength int) error { - return checkMinLength(reflect.Indirect(reflect.ValueOf(value)), reflect.ValueOf(nil), minLength) -} - -// makeMinLength makes a checker function for the min length checker. -func makeMinLength(config string) CheckFunc { - minLength, err := strconv.Atoi(config) - if err != nil { - panic("unable to parse min length value") - } - - return func(value, parent reflect.Value) error { - return checkMinLength(value, parent, minLength) - } -} - -// checkMinLength checks if the length of the given value is greather than the given minimum length. -// The function uses the reflect.Value.Len() function to determine the length of the value. -func checkMinLength(value, _ reflect.Value, minLength int) error { - if value.Len() < minLength { - return fmt.Errorf("please enter at least %d characters", minLength) - } - - return nil -} diff --git a/minlength_test.go b/minlength_test.go deleted file mode 100644 index bac0898..0000000 --- a/minlength_test.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker_test - -import ( - "testing" - - "github.com/cinar/checker" -) - -func ExampleIsMinLength() { - s := "1234" - - err := checker.IsMinLength(s, 4) - if err != nil { - // Send the errors back to the user - } -} - -func TestIsMinLengthValid(t *testing.T) { - s := "1234" - - if checker.IsMinLength(s, 4) != nil { - t.Fail() - } -} - -func TestCheckMinLengthInvalidConfig(t *testing.T) { - defer checker.FailIfNoPanic(t) - - type User struct { - Password string `checkers:"min-length:AB"` - } - - user := &User{} - - checker.Check(user) -} - -func TestCheckMinLengthValid(t *testing.T) { - type User struct { - Password string `checkers:"min-length:4"` - } - - user := &User{ - Password: "1234", - } - - _, valid := checker.Check(user) - if !valid { - t.Fail() - } -} - -func TestCheckMinLengthInvalid(t *testing.T) { - type User struct { - Password string `checkers:"min-length:4"` - } - - user := &User{ - Password: "12", - } - - _, valid := checker.Check(user) - if valid { - t.Fail() - } -} diff --git a/regexp.go b/regexp.go index 1e95a9b..8ae5663 100644 --- a/regexp.go +++ b/regexp.go @@ -3,50 +3,45 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" "reflect" "regexp" ) -// tagRegexp is the tag of the checker. -const tagRegexp = "regexp" +// nameRegexp is the name of the regexp check. +const nameRegexp = "regexp" // ErrNotMatch indicates that the given string does not match the regexp pattern. -var ErrNotMatch = errors.New("please enter a valid input") +var ErrNotMatch = NewCheckError("REGEXP") -// MakeRegexpMaker makes a regexp checker maker for the given regexp expression with the given invalid result. -func MakeRegexpMaker(expression string, invalidError error) MakeFunc { - return func(_ string) CheckFunc { - return MakeRegexpChecker(expression, invalidError) +// IsRegexp checks if the given string matches the given regexp expression. +func IsRegexp(expression, value string) (string, error) { + if !regexp.MustCompile(expression).MatchString(value) { + return value, ErrNotMatch } + + return value, nil } // MakeRegexpChecker makes a regexp checker for the given regexp expression with the given invalid result. -func MakeRegexpChecker(expression string, invalidError error) CheckFunc { - pattern := regexp.MustCompile(expression) +func MakeRegexpChecker(expression string, invalidError error) CheckFunc[reflect.Value] { + return func(value reflect.Value) (reflect.Value, error) { + if value.Kind() != reflect.String { + panic("string expected") + } - return func(value, parent reflect.Value) error { - return checkRegexp(value, pattern, invalidError) + _, err := IsRegexp(expression, value.String()) + if err != nil { + return value, invalidError + } + + return value, nil } } // makeRegexp makes a checker function for the regexp. -func makeRegexp(config string) CheckFunc { +func makeRegexp(config string) CheckFunc[reflect.Value] { return MakeRegexpChecker(config, ErrNotMatch) } - -// checkRegexp checks if the given string matches the regexp pattern. -func checkRegexp(value reflect.Value, pattern *regexp.Regexp, invalidError error) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - if !pattern.MatchString(value.String()) { - return invalidError - } - - return nil -} diff --git a/regexp_test.go b/regexp_test.go index 58fa217..cdb78dd 100644 --- a/regexp_test.go +++ b/regexp_test.go @@ -3,18 +3,38 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( - "errors" - "reflect" + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) +func ExampleIsRegexp() { + _, err := v2.IsRegexp("^[0-9a-fA-F]+$", "ABcd1234") + if err != nil { + fmt.Println(err) + } +} + +func TestIsRegexpInvalid(t *testing.T) { + _, err := v2.IsRegexp("^[0-9a-fA-F]+$", "Onur") + if err == nil { + t.Fatal("expected error") + } +} + +func TestIsRegexpValid(t *testing.T) { + _, err := v2.IsRegexp("^[0-9a-fA-F]+$", "ABcd1234") + if err != nil { + t.Fatal(err) + } +} + func TestCheckRegexpNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) + defer FailIfNoPanic(t, "expected panic") type User struct { Username int `checkers:"regexp:^[A-Za-z]$"` @@ -22,7 +42,7 @@ func TestCheckRegexpNonString(t *testing.T) { user := &User{} - checker.Check(user) + v2.CheckStruct(user) } func TestCheckRegexpInvalid(t *testing.T) { @@ -34,9 +54,9 @@ func TestCheckRegexpInvalid(t *testing.T) { Username: "abcd1234", } - _, valid := checker.Check(user) - if valid { - t.Fail() + _, ok := v2.CheckStruct(user) + if ok { + t.Fatal("expected error") } } @@ -49,34 +69,8 @@ func TestCheckRegexpValid(t *testing.T) { Username: "abcd", } - _, valid := checker.Check(user) - if !valid { - t.Fail() - } -} - -func TestMakeRegexpChecker(t *testing.T) { - checkHex := checker.MakeRegexpChecker("^[A-Fa-f0-9]+$", errors.New("Not Hex")) - - err := checkHex(reflect.ValueOf("f0f0f0"), reflect.ValueOf(nil)) - if err != nil { - t.Fail() - } -} - -func TestMakeRegexpMaker(t *testing.T) { - checker.Register("hex", checker.MakeRegexpMaker("^[A-Fa-f0-9]+$", errors.New("Not Hex"))) - - type Theme struct { - Color string `checkers:"hex"` - } - - theme := &Theme{ - Color: "f0f0f0", - } - - _, valid := checker.Check(theme) - if !valid { - t.Fail() + _, ok := v2.CheckStruct(user) + if !ok { + t.Fatal("expected valid") } } diff --git a/required.go b/required.go index 01a35f8..70c5d79 100644 --- a/required.go +++ b/required.go @@ -3,40 +3,40 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 -import ( - "errors" - "reflect" +import "reflect" + +const ( + // nameRequired is the name of the required check. + nameRequired = "required" ) -// tagRequired is the tag of the checker. -const tagRequired = "required" +var ( + // ErrRequired indicates that a required value was missing. + ErrRequired = NewCheckError("REQUIRED") +) -// ErrRequired indicates that the required value is missing. -var ErrRequired = errors.New("is required") - -// IsRequired checks if the given required value is present. -func IsRequired(v interface{}) error { - return checkRequired(reflect.ValueOf(v), reflect.ValueOf(nil)) +// Required checks if the given value of type T is its zero value. It +// returns an error if the value is zero. +func Required[T any](value T) (T, error) { + _, err := reflectRequired(reflect.ValueOf(value)) + return value, err } -// makeRequired makes a checker function for required. -func makeRequired(_ string) CheckFunc { - return checkRequired -} +// reflectRequired checks if the given value is its zero value. It +// returns an error if the value is zero. +func reflectRequired(value reflect.Value) (reflect.Value, error) { + var err error -// checkRequired checks if the required value is present. -func checkRequired(value, _ reflect.Value) error { if value.IsZero() { - return ErrRequired + err = ErrRequired } - k := value.Kind() - - if (k == reflect.Array || k == reflect.Map || k == reflect.Slice) && value.Len() == 0 { - return ErrRequired - } - - return nil + return value, err +} + +// makeRequired returns the required check function. +func makeRequired(_ string) CheckFunc[reflect.Value] { + return reflectRequired } diff --git a/required_test.go b/required_test.go index 958aff9..3231aff 100644 --- a/required_test.go +++ b/required_test.go @@ -3,145 +3,39 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( + "errors" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func ExampleIsRequired() { - var name string +func TestRequiredSuccess(t *testing.T) { + value := "test" + + result, err := v2.Required(value) + + if result != value { + t.Fatalf("result (%s) is not the original value (%s)", result, value) + } - err := checker.IsRequired(name) if err != nil { - // Send the err back to the user + t.Fatal(err) } } -func TestIsRequired(t *testing.T) { - s := "valid" +func TestRequiredMissing(t *testing.T) { + var value string - if checker.IsRequired(s) != nil { - t.Fail() - } -} - -func TestIsRequiredUninitializedString(t *testing.T) { - var s string - - if checker.IsRequired(s) != checker.ErrRequired { - t.Fail() - } -} - -func TestIsRequiredEmptyString(t *testing.T) { - s := "" - - if checker.IsRequired(s) == nil { - t.Fail() - } -} - -func TestIsRequiredUninitializedNumber(t *testing.T) { - var n int - - if checker.IsRequired(n) != checker.ErrRequired { - t.Fail() - } -} - -func TestIsRequiredValidSlice(t *testing.T) { - s := []int{1} - - if checker.IsRequired(s) != nil { - t.Fail() - } -} - -func TestIsRequiredUninitializedSlice(t *testing.T) { - var s []int - - if checker.IsRequired(s) != checker.ErrRequired { - t.Fail() - } -} - -func TestIsRequiredEmptySlice(t *testing.T) { - s := make([]int, 0) - - if checker.IsRequired(s) != checker.ErrRequired { - t.Fail() - } -} - -func TestIsRequiredValidArray(t *testing.T) { - s := [1]int{1} - - if checker.IsRequired(s) != nil { - t.Fail() - } -} - -func TestIsRequiredEmptyArray(t *testing.T) { - s := [1]int{} - - if checker.IsRequired(s) != checker.ErrRequired { - t.Fail() - } -} - -func TestIsRequiredValidMap(t *testing.T) { - m := map[string]string{ - "a": "b", - } - - if checker.IsRequired(m) != nil { - t.Fail() - } -} - -func TestIsRequiredUninitializedMap(t *testing.T) { - var m map[string]string - - if checker.IsRequired(m) != checker.ErrRequired { - t.Fail() - } -} - -func TestCheckRequiredEmptyMap(t *testing.T) { - m := map[string]string{} - - if checker.IsRequired(m) != checker.ErrRequired { - t.Fail() - } -} - -func TestCheckRequiredValid(t *testing.T) { - type User struct { - Username string `checkers:"required"` - } - - user := &User{ - Username: "checker", - } - - _, valid := checker.Check(user) - if !valid { - t.Fail() - } -} - -func TestCheckRequiredInvalid(t *testing.T) { - type User struct { - Username string `checkers:"required"` - } - - user := &User{} - - _, valid := checker.Check(user) - if valid { - t.Fail() + result, err := v2.Required(value) + + if result != value { + t.Fatalf("result (%s) is not the original value (%s)", result, value) + } + + if !errors.Is(err, v2.ErrRequired) { + t.Fatalf("got unexpected error %v", err) } } diff --git a/same.go b/same.go deleted file mode 100644 index f4a3e06..0000000 --- a/same.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker - -import ( - "errors" - "reflect" -) - -// tagSame is the tag of the checker. -const tagSame = "same" - -// ErrNotSame indicates that the given two values are not equal to each other. -var ErrNotSame = errors.New("does not match the other") - -// makeSame makes a checker function for the same checker. -func makeSame(config string) CheckFunc { - return func(value, parent reflect.Value) error { - return checkSame(value, parent, config) - } -} - -// checkSame checks if the given value is equal to the value of the field with the given name. -func checkSame(value, parent reflect.Value, name string) error { - other := parent.FieldByName(name) - - if !other.IsValid() { - panic("other field not found") - } - - other = reflect.Indirect(other) - - if !value.Equal(other) { - return ErrNotSame - } - - return nil -} diff --git a/same_test.go b/same_test.go deleted file mode 100644 index 31e8181..0000000 --- a/same_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker_test - -import ( - "testing" - - "github.com/cinar/checker" -) - -func TestSameValid(t *testing.T) { - type User struct { - Password string - Confirm string `checkers:"same:Password"` - } - - user := &User{ - Password: "1234", - Confirm: "1234", - } - - _, valid := checker.Check(user) - if !valid { - t.Fail() - } -} - -func TestSameInvalid(t *testing.T) { - type User struct { - Password string - Confirm string `checkers:"same:Password"` - } - - user := &User{ - Password: "1234", - Confirm: "12", - } - - _, valid := checker.Check(user) - if valid { - t.Fail() - } -} - -func TestSameInvalidName(t *testing.T) { - defer checker.FailIfNoPanic(t) - - type User struct { - Password string - Confirm string `checkers:"same:Unknown"` - } - - user := &User{ - Password: "1234", - Confirm: "1234", - } - - checker.Check(user) -} diff --git a/taskfile.yml b/taskfile.yml index 158205c..0d8e575 100644 --- a/taskfile.yml +++ b/taskfile.yml @@ -19,13 +19,14 @@ tasks: lint: cmds: - go vet ./... - - go run github.com/securego/gosec/v2/cmd/gosec@v2.20.0 -exclude-dir=v2 ./... + - go run github.com/securego/gosec/v2/cmd/gosec@v2.20.0 ./... - go run honnef.co/go/tools/cmd/staticcheck@v0.5.1 ./... - go run github.com/mgechev/revive@v1.3.4 -config=revive.toml ./... test: cmds: - go test -cover -coverprofile=coverage.out ./... + - go tool cover -func=coverage.out docs: cmds: diff --git a/test_helper.go b/test_helper.go deleted file mode 100644 index ce78ede..0000000 --- a/test_helper.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker - -import "testing" - -// FailIfNoPanic fails if test didn't panic. Use this function with the defer. -func FailIfNoPanic(t *testing.T) { - if r := recover(); r == nil { - t.Fail() - } -} diff --git a/test_helper_test.go b/test_helper_test.go deleted file mode 100644 index 94ca47f..0000000 --- a/test_helper_test.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker_test - -import ( - "testing" - - "github.com/cinar/checker" -) - -func TestFailIfNoPanicValid(t *testing.T) { - defer checker.FailIfNoPanic(t) - panic("") -} - -func TestFailIfNoPanicInvalid(t *testing.T) { - defer checker.FailIfNoPanic(t) - defer checker.FailIfNoPanic(nil) -} diff --git a/title.go b/title.go index 604ca2c..5d4fd54 100644 --- a/title.go +++ b/title.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( "reflect" @@ -11,24 +11,17 @@ import ( "unicode" ) -// tagTitle is the tag of the normalizer. -const tagTitle = "title" - -// makeTitle makes a normalizer function for the title normalizer. -func makeTitle(_ string) CheckFunc { - return normalizeTitle -} - -// normalizeTitle maps the first letter of each word to their upper case. -func normalizeTitle(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } +const ( + // nameTitle is the name of the title normalizer. + nameTitle = "title" +) +// Title returns the value of the string with the first letter of each word in upper case. +func Title(value string) (string, error) { var sb strings.Builder begin := true - for _, c := range value.String() { + for _, c := range value { if unicode.IsLetter(c) { if begin { c = unicode.ToUpper(c) @@ -43,7 +36,16 @@ func normalizeTitle(value, _ reflect.Value) error { sb.WriteRune(c) } - value.SetString(sb.String()) - - return nil + return sb.String(), nil +} + +// reflectTitle returns the value of the string with the first letter of each word in upper case. +func reflectTitle(value reflect.Value) (reflect.Value, error) { + newValue, err := Title(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeTitle returns the title normalizer function. +func makeTitle(_ string) CheckFunc[reflect.Value] { + return reflectTitle } diff --git a/title_test.go b/title_test.go index bf6900c..ad278d1 100644 --- a/title_test.go +++ b/title_test.go @@ -3,53 +3,45 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func TestNormalizeTitleNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) +func TestTitle(t *testing.T) { + input := "the checker" + expected := "The Checker" - type Book struct { - Chapter int `checkers:"title"` + actual, err := v2.Title(input) + if err != nil { + t.Fatal(err) } - book := &Book{} - - checker.Check(book) + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } } -func TestNormalizeTitleErrValid(t *testing.T) { +func TestReflectTitle(t *testing.T) { type Book struct { Chapter string `checkers:"title"` } book := &Book{ - Chapter: "THE checker", + Chapter: "the checker", } - _, valid := checker.Check(book) - if !valid { - t.Fail() - } -} - -func TestNormalizeTitle(t *testing.T) { - type Book struct { - Chapter string `checkers:"title"` - } - - book := &Book{ - Chapter: "THE checker", - } - - checker.Check(book) - - if book.Chapter != "The Checker" { - t.Fail() + expected := "The Checker" + + errs, ok := v2.CheckStruct(book) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if book.Chapter != expected { + t.Fatalf("actual %s expected %s", book.Chapter, expected) } } diff --git a/trim.go b/trim.go deleted file mode 100644 index 749cb22..0000000 --- a/trim.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker - -import ( - "reflect" - "strings" -) - -// tagTrim is the tag of the normalizer. -const tagTrim = "trim" - -// makeTrim makes a normalizer function for the trim normalizer. -func makeTrim(_ string) CheckFunc { - return normalizeTrim -} - -// normalizeTrim removes the whitespaces at the beginning and at the end of the given value. -func normalizeTrim(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - value.SetString(strings.Trim(value.String(), " \t")) - - return nil -} diff --git a/trim_left.go b/trim_left.go index f17c699..20e41d2 100644 --- a/trim_left.go +++ b/trim_left.go @@ -3,28 +3,30 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( "reflect" "strings" ) -// tagTrimLeft is the tag of the normalizer. -const tagTrimLeft = "trim-left" +const ( + // nameTrimLeft is the name of the trim left normalizer. + nameTrimLeft = "trim-left" +) -// makeTrimLeft makes a normalizer function for the trim left normalizer. -func makeTrimLeft(_ string) CheckFunc { - return normalizeTrimLeft +// TrimLeft returns the value of the string with whitespace removed from the beginning. +func TrimLeft(value string) (string, error) { + return strings.TrimLeft(value, " \t"), nil } -// normalizeTrim removes the whitespaces at the beginning of the given value. -func normalizeTrimLeft(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - value.SetString(strings.TrimLeft(value.String(), " \t")) - - return nil +// reflectTrimLeft returns the value of the string with whitespace removed from the beginning. +func reflectTrimLeft(value reflect.Value) (reflect.Value, error) { + newValue, err := TrimLeft(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeTrimLeft returns the trim left normalizer function. +func makeTrimLeft(_ string) CheckFunc[reflect.Value] { + return reflectTrimLeft } diff --git a/trim_left_test.go b/trim_left_test.go index 2f93471..3989789 100644 --- a/trim_left_test.go +++ b/trim_left_test.go @@ -3,53 +3,45 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func TestNormalizeTrimLeftNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) +func TestTrimLeft(t *testing.T) { + input := " test " + expected := "test " - type User struct { - Username int `checkers:"trim-left"` + actual, err := v2.TrimLeft(input) + if err != nil { + t.Fatal(err) } - user := &User{} - - checker.Check(user) -} - -func TestNormalizeTrimLeftErrValid(t *testing.T) { - type User struct { - Username string `checkers:"trim-left"` - } - - user := &User{ - Username: " normalizer ", - } - - _, valid := checker.Check(user) - if !valid { - t.Fail() + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) } } -func TestNormalizeTrimLeft(t *testing.T) { - type User struct { - Username string `checkers:"trim-left"` +func TestReflectTrimLeft(t *testing.T) { + type Person struct { + Name string `checkers:"trim-left"` } - user := &User{ - Username: " normalizer ", + person := &Person{ + Name: " test ", } - checker.Check(user) + expected := "test " - if user.Username != "normalizer " { - t.Fail() + errs, ok := v2.CheckStruct(person) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if person.Name != expected { + t.Fatalf("actual %s expected %s", person.Name, expected) } } diff --git a/trim_right.go b/trim_right.go index e2edb9e..aeae386 100644 --- a/trim_right.go +++ b/trim_right.go @@ -3,28 +3,30 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( "reflect" "strings" ) -// tagTrimRight is the tag of the normalizer. -const tagTrimRight = "trim-right" +const ( + // nameTrimRight is the name of the trim right normalizer. + nameTrimRight = "trim-right" +) -// makeTrimRight makes a normalizer function for the trim right normalizer. -func makeTrimRight(_ string) CheckFunc { - return normalizeTrimRight +// TrimRight returns the value of the string with whitespace removed from the end. +func TrimRight(value string) (string, error) { + return strings.TrimRight(value, " \t"), nil } -// normalizeTrimRight removes the whitespaces at the end of the given value. -func normalizeTrimRight(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - value.SetString(strings.TrimRight(value.String(), " \t")) - - return nil +// reflectTrimRight returns the value of the string with whitespace removed from the end. +func reflectTrimRight(value reflect.Value) (reflect.Value, error) { + newValue, err := TrimRight(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeTrimRight returns the trim right normalizer function. +func makeTrimRight(_ string) CheckFunc[reflect.Value] { + return reflectTrimRight } diff --git a/trim_right_test.go b/trim_right_test.go index 88fda32..e75eb45 100644 --- a/trim_right_test.go +++ b/trim_right_test.go @@ -3,53 +3,45 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func TestNormalizeTrimRightNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) +func TestTrimRight(t *testing.T) { + input := " test " + expected := " test" - type User struct { - Username int `checkers:"trim-right"` + actual, err := v2.TrimRight(input) + if err != nil { + t.Fatal(err) } - user := &User{} - - checker.Check(user) -} - -func TestNormalizeTrimRightErrValid(t *testing.T) { - type User struct { - Username string `checkers:"trim-right"` - } - - user := &User{ - Username: " normalizer ", - } - - _, valid := checker.Check(user) - if !valid { - t.Fail() + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) } } -func TestNormalizeTrimRight(t *testing.T) { - type User struct { - Username string `checkers:"trim-right"` +func TestReflectTrimRight(t *testing.T) { + type Person struct { + Name string `checkers:"trim-right"` } - user := &User{ - Username: " normalizer ", + person := &Person{ + Name: " test ", } - checker.Check(user) + expected := " test" - if user.Username != " normalizer" { - t.Fail() + errs, ok := v2.CheckStruct(person) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if person.Name != expected { + t.Fatalf("actual %s expected %s", person.Name, expected) } } diff --git a/v2/trim_space.go b/trim_space.go similarity index 100% rename from v2/trim_space.go rename to trim_space.go diff --git a/v2/trim_space_test.go b/trim_space_test.go similarity index 100% rename from v2/trim_space_test.go rename to trim_space_test.go diff --git a/trim_test.go b/trim_test.go deleted file mode 100644 index 7648840..0000000 --- a/trim_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package checker_test - -import ( - "testing" - - "github.com/cinar/checker" -) - -func TestNormalizeTrimNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) - - type User struct { - Username int `checkers:"trim"` - } - - user := &User{} - - checker.Check(user) -} - -func TestNormalizeTrimErrValid(t *testing.T) { - type User struct { - Username string `checkers:"trim"` - } - - user := &User{ - Username: " normalizer ", - } - - _, valid := checker.Check(user) - if !valid { - t.Fail() - } -} - -func TestNormalizeTrim(t *testing.T) { - type User struct { - Username string `checkers:"trim"` - } - - user := &User{ - Username: " normalizer ", - } - - checker.Check(user) - - if user.Username != "normalizer" { - t.Fail() - } -} diff --git a/upper.go b/upper.go index 7bef24b..cd8ea86 100644 --- a/upper.go +++ b/upper.go @@ -3,28 +3,30 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( "reflect" "strings" ) -// tagUpper is the tag of the normalizer. -const tagUpper = "upper" +const ( + // nameUpper is the name of the upper normalizer. + nameUpper = "upper" +) -// makeUpper makes a normalizer function for the upper normalizer. -func makeUpper(_ string) CheckFunc { - return normalizeUpper +// Upper maps all Unicode letters in the given value to their upper case. +func Upper(value string) (string, error) { + return strings.ToUpper(value), nil } -// normalizeUpper maps all Unicode letters in the given value to their upper case. -func normalizeUpper(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - value.SetString(strings.ToUpper(value.String())) - - return nil +// reflectUpper maps all Unicode letters in the given value to their upper case. +func reflectUpper(value reflect.Value) (reflect.Value, error) { + newValue, err := Upper(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeUpper returns the upper normalizer function. +func makeUpper(_ string) CheckFunc[reflect.Value] { + return reflectUpper } diff --git a/upper_test.go b/upper_test.go index 6e0fb8f..d7fa127 100644 --- a/upper_test.go +++ b/upper_test.go @@ -3,53 +3,45 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func TestNormalizeUpperNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) +func TestUpper(t *testing.T) { + input := "checker" + expected := "CHECKER" - type User struct { - Username int `checkers:"upper"` + actual, err := v2.Upper(input) + if err != nil { + t.Fatal(err) } - user := &User{} - - checker.Check(user) -} - -func TestNormalizeUpperErrValid(t *testing.T) { - type User struct { - Username string `checkers:"upper"` - } - - user := &User{ - Username: "chECker", - } - - _, valid := checker.Check(user) - if !valid { - t.Fail() + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) } } -func TestNormalizeUpper(t *testing.T) { - type User struct { - Username string `checkers:"upper"` +func TestReflectUpper(t *testing.T) { + type Person struct { + Name string `checkers:"upper"` } - user := &User{ - Username: "chECker", + person := &Person{ + Name: "checker", } - checker.Check(user) + expected := "CHECKER" - if user.Username != "CHECKER" { - t.Fail() + errs, ok := v2.CheckStruct(person) + if !ok { + t.Fatalf("got unexpected errors %v", errs) + } + + if person.Name != expected { + t.Fatalf("actual %s expected %s", person.Name, expected) } } diff --git a/url.go b/url.go index a27b85a..3219ae2 100644 --- a/url.go +++ b/url.go @@ -3,48 +3,39 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( - "errors" "net/url" "reflect" ) -// tagURL is the tag of the checker. -const tagURL = "url" +const ( + // nameURL is the name of the URL check. + nameURL = "url" +) -// ErrNotURL indicates that the given value is not a valid URL. -var ErrNotURL = errors.New("please enter a valid URL") +var ( + // ErrNotURL indicates that the given value is not a valid URL. + ErrNotURL = NewCheckError("NOT_URL") +) -// IsURL checks if the given value is a valid URL. -func IsURL(value string) error { - url, err := url.ParseRequestURI(value) +// IsURL checks if the value is a valid URL. +func IsURL(value string) (string, error) { + _, err := url.ParseRequestURI(value) if err != nil { - return ErrNotURL + return value, ErrNotURL } + return value, nil +} - if url.Scheme == "" { - return ErrNotURL - } - - if url.Host == "" { - return ErrNotURL - } - - return nil +// checkURL checks if the value is a valid URL. +func checkURL(value reflect.Value) (reflect.Value, error) { + _, err := IsURL(value.Interface().(string)) + return value, err } // makeURL makes a checker function for the URL checker. -func makeURL(_ string) CheckFunc { +func makeURL(_ string) CheckFunc[reflect.Value] { return checkURL } - -// checkURL checks if the given value is a valid URL. -func checkURL(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - return IsURL(value.String()) -} diff --git a/url_escape.go b/url_escape.go index d7ab2d7..439d91b 100644 --- a/url_escape.go +++ b/url_escape.go @@ -3,29 +3,28 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( "net/url" "reflect" ) -// tagURLEscape is the tag of the normalizer. -const tagURLEscape = "url-escape" +// nameURLEscape is the name of the URL escape normalizer. +const nameURLEscape = "url-escape" -// makeURLEscape makes a normalizer function for the URL escape normalizer. -func makeURLEscape(_ string) CheckFunc { - return normalizeURLEscape +// URLEscape applies URL escaping to special characters. +func URLEscape(value string) (string, error) { + return url.QueryEscape(value), nil } -// normalizeURLEscape applies URL escaping to special characters. -// Uses net.url.QueryEscape for the actual escape operation. -func normalizeURLEscape(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - value.SetString(url.QueryEscape(value.String())) - - return nil +// reflectURLEscape applies URL escaping to special characters. +func reflectURLEscape(value reflect.Value) (reflect.Value, error) { + newValue, err := URLEscape(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeURLEscape returns the URL escape normalizer function. +func makeURLEscape(_ string) CheckFunc[reflect.Value] { + return reflectURLEscape } diff --git a/url_escape_test.go b/url_escape_test.go index a28ec11..badc16c 100644 --- a/url_escape_test.go +++ b/url_escape_test.go @@ -3,27 +3,29 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func TestNormalizeURLEscapeNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) +func TestURLEscape(t *testing.T) { + input := "param1/param2 = 1 + 2 & 3 + 4" + expected := "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4" - type Request struct { - Query int `checkers:"url-escape"` + actual, err := v2.URLEscape(input) + if err != nil { + t.Fatal(err) } - request := &Request{} - - checker.Check(request) + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } } -func TestNormalizeURLEscape(t *testing.T) { +func TestReflectURLEscape(t *testing.T) { type Request struct { Query string `checkers:"url-escape"` } @@ -32,12 +34,14 @@ func TestNormalizeURLEscape(t *testing.T) { Query: "param1/param2 = 1 + 2 & 3 + 4", } - _, valid := checker.Check(request) - if !valid { - t.Fail() + expected := "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4" + + errs, ok := v2.CheckStruct(request) + if !ok { + t.Fatalf("got unexpected errors %v", errs) } - if request.Query != "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4" { - t.Fail() + if request.Query != expected { + t.Fatalf("actual %s expected %s", request.Query, expected) } } diff --git a/url_test.go b/url_test.go index ed5334e..d3777c1 100644 --- a/url_test.go +++ b/url_test.go @@ -3,103 +3,74 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( + "fmt" "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) func ExampleIsURL() { - err := checker.IsURL("https://zdo.com") + _, err := v2.IsURL("https://example.com") if err != nil { - // Send the errors back to the user - } -} - -func TestIsURLValid(t *testing.T) { - err := checker.IsURL("https://zdo.com") - if err != nil { - t.Fail() + fmt.Println(err) } } func TestIsURLInvalid(t *testing.T) { - err := checker.IsURL("https:://index.html") + _, err := v2.IsURL("invalid-url") if err == nil { - t.Fail() + t.Fatal("expected error") + } +} + +func TestIsURLValid(t *testing.T) { + _, err := v2.IsURL("https://example.com") + if err != nil { + t.Fatal(err) } } func TestCheckURLNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) + defer FailIfNoPanic(t, "expected panic") - type Bookmark struct { - URL int `checkers:"url"` + type Website struct { + Link int `checkers:"url"` } - bookmark := &Bookmark{} + website := &Website{} - checker.Check(bookmark) -} - -func TestCheckURLValid(t *testing.T) { - type Bookmark struct { - URL string `checkers:"url"` - } - - bookmark := &Bookmark{ - URL: "https://zdo.com", - } - - _, valid := checker.Check(bookmark) - if !valid { - t.Fail() - } + v2.CheckStruct(website) } func TestCheckURLInvalid(t *testing.T) { - type Bookmark struct { - URL string `checkers:"url"` + type Website struct { + Link string `checkers:"url"` } - bookmark := &Bookmark{ - URL: "zdo.com/index.html", + website := &Website{ + Link: "invalid-url", } - _, valid := checker.Check(bookmark) - if valid { - t.Fail() + _, ok := v2.CheckStruct(website) + if ok { + t.Fatal("expected error") } } -func TestCheckURLWithoutSchema(t *testing.T) { - type Bookmark struct { - URL string `checkers:"url"` +func TestCheckURLValid(t *testing.T) { + type Website struct { + Link string `checkers:"url"` } - bookmark := &Bookmark{ - URL: "//zdo.com/index.html", + website := &Website{ + Link: "https://example.com", } - _, valid := checker.Check(bookmark) - if valid { - t.Fail() - } -} - -func TestCheckURLWithoutHost(t *testing.T) { - type Bookmark struct { - URL string `checkers:"url"` - } - - bookmark := &Bookmark{ - URL: "https:://index.html", - } - - _, valid := checker.Check(bookmark) - if valid { - t.Fail() + _, ok := v2.CheckStruct(website) + if !ok { + t.Fatal("expected valid") } } diff --git a/url_unescape.go b/url_unescape.go index dc6a807..c48045c 100644 --- a/url_unescape.go +++ b/url_unescape.go @@ -3,32 +3,29 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker +package v2 import ( "net/url" "reflect" ) -// tagURLUnescape is the tag of the normalizer. -const tagURLUnescape = "url-unescape" +// nameURLUnescape is the name of the URL unescape normalizer. +const nameURLUnescape = "url-unescape" -// makeURLUnescape makes a normalizer function for the URL unscape normalizer. -func makeURLUnescape(_ string) CheckFunc { - return normalizeURLUnescape +// URLUnescape applies URL unescaping to special characters. +func URLUnescape(value string) (string, error) { + unescaped, err := url.QueryUnescape(value) + return unescaped, err } -// normalizeURLUnescape applies URL unescaping to special characters. -// Uses url.QueryUnescape for the actual unescape operation. -func normalizeURLUnescape(value, _ reflect.Value) error { - if value.Kind() != reflect.String { - panic("string expected") - } - - unescaped, err := url.QueryUnescape(value.String()) - if err == nil { - value.SetString(unescaped) - } - - return nil +// reflectURLUnescape applies URL unescaping to special characters. +func reflectURLUnescape(value reflect.Value) (reflect.Value, error) { + newValue, err := URLUnescape(value.Interface().(string)) + return reflect.ValueOf(newValue), err +} + +// makeURLUnescape returns the URL unescape normalizer function. +func makeURLUnescape(_ string) CheckFunc[reflect.Value] { + return reflectURLUnescape } diff --git a/url_unescape_test.go b/url_unescape_test.go index 6c4995b..21b2c43 100644 --- a/url_unescape_test.go +++ b/url_unescape_test.go @@ -3,27 +3,29 @@ // license that can be found in the LICENSE file. // https://github.com/cinar/checker -package checker_test +package v2_test import ( "testing" - "github.com/cinar/checker" + v2 "github.com/cinar/checker/v2" ) -func TestNormalizeURLUnescapeNonString(t *testing.T) { - defer checker.FailIfNoPanic(t) +func TestURLUnescape(t *testing.T) { + input := "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4" + expected := "param1/param2 = 1 + 2 & 3 + 4" - type Request struct { - Query int `checkers:"url-unescape"` + actual, err := v2.URLUnescape(input) + if err != nil { + t.Fatal(err) } - request := &Request{} - - checker.Check(request) + if actual != expected { + t.Fatalf("actual %s expected %s", actual, expected) + } } -func TestNormalizeURLUnescape(t *testing.T) { +func TestReflectURLUnescape(t *testing.T) { type Request struct { Query string `checkers:"url-unescape"` } @@ -32,12 +34,14 @@ func TestNormalizeURLUnescape(t *testing.T) { Query: "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4", } - _, valid := checker.Check(request) - if !valid { - t.Fail() + expected := "param1/param2 = 1 + 2 & 3 + 4" + + errs, ok := v2.CheckStruct(request) + if !ok { + t.Fatalf("got unexpected errors %v", errs) } - if request.Query != "param1/param2 = 1 + 2 & 3 + 4" { - t.Fail() + if request.Query != expected { + t.Fatalf("actual %s expected %s", request.Query, expected) } } diff --git a/v2/.gomarkdoc.yml b/v2/.gomarkdoc.yml deleted file mode 100644 index 729da91..0000000 --- a/v2/.gomarkdoc.yml +++ /dev/null @@ -1,3 +0,0 @@ -output: "{{.Dir}}/DOC.md" -repository: - url: https://github.com/cinar/checker diff --git a/v2/README.md b/v2/README.md deleted file mode 100644 index 8d0dd36..0000000 --- a/v2/README.md +++ /dev/null @@ -1,244 +0,0 @@ -[![GoDoc](https://godoc.org/github.com/cinar/checker?status.svg)](https://godoc.org/github.com/cinar/checker) -[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) -[![Go Report Card](https://goreportcard.com/badge/github.com/cinar/checker)](https://goreportcard.com/report/github.com/cinar/checker) -![Go CI](https://github.com/cinar/checker/actions/workflows/ci.yml/badge.svg) -[![codecov](https://codecov.io/gh/cinar/checker/branch/main/graph/badge.svg?token=VO9BYBHJHE)](https://codecov.io/gh/cinar/checker) - -# Checker - -Checker is a lightweight Go library designed to validate user input efficiently. It supports validation of both struct fields and individual input values. - -While there are numerous validation libraries available, Checker stands out due to its simplicity and lack of external dependencies. This makes it an ideal choice for developers who prefer to minimize dependencies and maintain control over their tools. Checker is straightforward to use and effectively meets your validation needs. - -## Usage - -To begin using the Checker library, install it with the following command: - -```bash -go get github.com/cinar/checker/v2 -``` - -Then, import the library into your source file as shown below: - -```golang -import ( - checker "github.com/cinar/checker/v2" -) -``` - -### Validating User Input Stored in a Struct - -Checker can validate user input stored in a struct by listing the checkers in the struct tags for each field. Here is an example: - -```golang -type Person struct { - Name string `checkers:"trim required"` -} - -person := &Person{ - Name: " Onur Cinar ", -} - -errors, valid := checker.CheckStruct(person) -if !valid { - // Handle validation errors -} -``` - -### Validating Individual User Input with Multiple Checkers - -You can also validate individual user input by calling checker functions directly. Here is an example: - -```golang -name := " Onur Cinar " - -name, err := checker.Check(name, checker.Trim, checker.Required) -if err != nil { - // Handle validation error -} -``` - -### Validating Individual User Input - -For simpler validation, you can call individual checker functions. Here is an example: - -```golang -name := "Onur Cinar" - -err := checker.IsRequired(name) -if err != nil { - // Handle validation error -} -``` - -## Normalizers and Checkers - -Checkers validate user input, while normalizers transform it into a preferred format. For example, a normalizer can trim spaces from a string or convert it to title case. - -Although combining checkers and normalizers into a single library might seem unconventional, using them together can be beneficial. They can be mixed in any order when defining validation steps. For instance, you can use the `trim` normalizer with the `required` checker to first trim the input and then ensure it is provided. Here is an example: - -```golang -type Person struct { - Name string `checkers:"trim required"` -} -``` - -# Checkers Provided - -- [`ascii`](DOC.md#IsASCII): Ensures the string contains only ASCII characters. -- [`alphanumeric`](DOC.md#IsAlphanumeric): Ensures the string contains only letters and numbers. -- [`credit-card`](DOC.md#IsAnyCreditCard): Ensures the string is a valid credit card number. -- [`cidr`](DOC.md#IsCIDR): Ensures the string is a valid CIDR notation. -- [`digits`](DOC.md#IsDigits): Ensures the string contains only digits. -- [`email`](DOC.md#IsEmail): Ensures the string is a valid email address. -- [`fqdn`](DOC.md#IsFQDN): Ensures the string is a valid fully qualified domain name. -- [`hex`](DOC.md#IsHex): Ensures the string contains only hex digits. -- [`ip`](DOC.md#IsIP): Ensures the string is a valid IP address. -- [`ipv4`](DOC.md#IsIPv4): Ensures the string is a valid IPv4 address. -- [`ipv6`](DOC.md#IsIPv6): Ensures the string is a valid IPv6 address. -- [`isbn`](DOC.md#IsISBN): Ensures the string is a valid ISBN. -- [`luhn`](DOC.md#IsLUHN): Ensures the string is a valid LUHN number. -- [`mac`](DOC.md#IsMAC): Ensures the string is a valid MAC address. -- [`max-len`](DOC.md#func-maxlen): Ensures the length of the given value (string, slice, or map) is at most n. -- [`min-len`](DOC.md#func-minlen): Ensures the length of the given value (string, slice, or map) is at least n. -- [`required`](DOC.md#func-required) Ensures the value is provided. -- [`regexp`](DOC.md#func-makeregexpchecker) Ensured the string matches the pattern. -- [`url`](DOC.md#IsURL): Ensures the string is a valid URL. - -# Normalizers Provided - -- [`lower`](DOC.md#Lower): Converts the string to lowercase. -- [`title`](DOC.md#Title): Converts the string to title case. -- [`trim-left`](DOC.md#TrimLeft): Trims whitespace from the left side of the string. -- [`trim-right`](DOC.md#TrimRight): Trims whitespace from the right side of the string. -- [`trim`](DOC.md#TrimSpace): Trims whitespace from both sides of the string. -- [`upper`](DOC.md#Upper): Converts the string to uppercase. -- [`html-escape`](DOC.md#HTMLEscape): Escapes special characters in the string for HTML. -- [`html-unescape`](DOC.md#HTMLUnescape): Unescapes special characters in the string for HTML. -- [`url-escape`](DOC.md#URLEscape): Escapes special characters in the string for URLs. -- [`url-unescape`](DOC.md#URLUnescape): Unescapes special characters in the string for URLs. - -# Custom Checkers and Normalizers - -You can define custom checkers or normalizers and register them for use in your validation logic. Here is an example of how to create and register a custom checker: - -```golang -checker.RegisterMaker("is-fruit", func(params string) v2.CheckFunc[reflect.Value] { - return func(value reflect.Value) (reflect.Value, error) { - stringValue := value.Interface().(string) - - if stringValue == "apple" || stringValue == "banana" { - return value, nil - } - - return value, v2.NewCheckError("NOT_FRUIT") - } -}) -``` - -In this example, the custom checker `is-fruit` checks if the input value is either "apple" or "banana". If the value is not one of these, it returns an error. - -Once registered, you can use your custom checker in struct tags just like the built-in checkers: - -```golang -type Item struct { - Name string `checkers:"is-fruit"` -} - -item := &Item{ - Name: "banana", -} - -errors, valid := v2.CheckStruct(item) -if !valid { - fmt.Println(errors) -} -``` - -In this example, the `is-fruit` checker is used to validate that the `Name` field of the `Item` struct is either "apple" or "banana". - -# Slice and Item Level Checkers - -When adding checker struct tags to a slice, you can use the `@` prefix to indicate that the checker should be applied to the slice itself. Checkers without the `@` prefix will be applied to the individual items within the slice. Here is an example: - -```golang -type Person struct { - Name string `checkers:"required"` - Emails []string `checkers:"@max-len:2 max-len:64"` -} -``` - -In this example: -- `@max-len:2` ensures that the `Emails` slice itself has at most two items. -- `max-len:64` ensures that each email string within the `Emails` slice has a maximum length of 64 characters. - -# Localized Error Messages - -When validation fails, Checker returns an error. By default, the [Error()](DOC.md#CheckError.Error) function provides a human-readable error message in `en-US` locale. - -```golang -_, err := checker.IsEmail("abcd") -if err != nil { - fmt.Println(err) - // Output: Not a valid email address. -} -``` - -To get error messages in other languages, use the [ErrorWithLocale()](DOC.md#CheckError.ErrorWithLocale) function. By default, only `en-US` is registered. You can register additional languages by calling [RegisterLocale](DOC.md#RegisterLocale). - -```golang -// Register de-DE localized error messages. -checker.RegisterLocale(locales.DeDE, locales.DeDEMessages) - -_, err := checker.IsEmail("abcd") -if err != nil { - fmt.Println(err.ErrorWithLocale(locales.DeDE)) - // Output: Keine gültige E-Mail-Adresse. -} -``` - -You can also customize existing error messages or add new ones to `locales.EnUSMessages` and other locale maps. - -```golang -// Register the en-US localized error message for the custom NOT_FRUIT error code. -locales.EnUSMessages["NOT_FRUIT"] = "Not a fruit name." - -errors, valid := v2.CheckStruct(item) -if !valid { - fmt.Println(errors) - // Output: map[Name:Not a fruit name.] -} -``` - -Error messages are generated using Golang template functions, allowing them to include variables. - -```golang -// Custrom checker error containing the item name. -err := NewCheckErrorWithData( - "NOT_FRUIT", - map[string]interface{}{ - "name": "abcd", - }, -) - -// Register the en-US localized error message for the custom NOT_FRUIT error code. -locales.EnUSMessages["NOT_FRUIT"] = "Name {{ .name }} is not a fruit name." - -errors, valid := v2.CheckStruct(item) -if !valid { - fmt.Println(errors) - // Output: map[Name:Name abcd is not a fruit name.] -} -``` - -# Contributing to the Project - -Anyone can contribute to Checkers library. Please make sure to read our [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md) guide first. Follow the [How to Contribute to Checker](./CONTRIBUTING.md) to contribute. - -# License - -This library is free to use, modify, and distribute under the terms of the MIT license. The full license text can be found in the [LICENSE](./LICENSE) file. - -The MIT license is a permissive license that allows you to do almost anything with the library, as long as you retain the copyright notice and the license text. This means that you can use the library in commercial products, modify it, and redistribute it without having to ask for permission from the authors. - -The [LICENSE](./LICENSE) file is located in the root directory of the library. You can open it in a text editor to read the full license text. diff --git a/v2/alphanumeric.go b/v2/alphanumeric.go deleted file mode 100644 index de3618d..0000000 --- a/v2/alphanumeric.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "reflect" - "unicode" -) - -const ( - // nameAlphanumeric is the name of the alphanumeric check. - nameAlphanumeric = "alphanumeric" -) - -var ( - // ErrNotAlphanumeric indicates that the given string contains non-alphanumeric characters. - ErrNotAlphanumeric = NewCheckError("NOT_ALPHANUMERIC") -) - -// IsAlphanumeric checks if the given string consists of only alphanumeric characters. -func IsAlphanumeric(value string) (string, error) { - for _, c := range value { - if !unicode.IsDigit(c) && !unicode.IsLetter(c) { - return value, ErrNotAlphanumeric - } - } - - return value, nil -} - -// checkAlphanumeric checks if the given string consists of only alphanumeric characters. -func isAlphanumeric(value reflect.Value) (reflect.Value, error) { - _, err := IsAlphanumeric(value.Interface().(string)) - return value, err -} - -// makeAlphanumeric makes a checker function for the alphanumeric checker. -func makeAlphanumeric(_ string) CheckFunc[reflect.Value] { - return isAlphanumeric -} diff --git a/v2/alphanumeric_test.go b/v2/alphanumeric_test.go deleted file mode 100644 index 5d339b3..0000000 --- a/v2/alphanumeric_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsAlphanumeric() { - _, err := v2.IsAlphanumeric("ABcd1234") - if err != nil { - fmt.Println(err) - } -} - -func TestIsAlphanumericInvalid(t *testing.T) { - _, err := v2.IsAlphanumeric("-/") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsAlphanumericValid(t *testing.T) { - _, err := v2.IsAlphanumeric("ABcd1234") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckAlphanumericNonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type Person struct { - Name int `checkers:"alphanumeric"` - } - - person := &Person{} - - v2.CheckStruct(person) -} - -func TestCheckAlphanumericInvalid(t *testing.T) { - type Person struct { - Name string `checkers:"alphanumeric"` - } - - person := &Person{ - Name: "name-/", - } - - _, ok := v2.CheckStruct(person) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckAlphanumericValid(t *testing.T) { - type Person struct { - Name string `checkers:"alphanumeric"` - } - - person := &Person{ - Name: "ABcd1234", - } - - errs, ok := v2.CheckStruct(person) - if !ok { - t.Fatal(errs) - } -} diff --git a/v2/ascii.go b/v2/ascii.go deleted file mode 100644 index f05125f..0000000 --- a/v2/ascii.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "reflect" - "unicode" -) - -const ( - // nameASCII is the name of the ASCII check. - nameASCII = "ascii" -) - -var ( - // ErrNotASCII indicates that the given string contains non-ASCII characters. - ErrNotASCII = NewCheckError("NOT_ASCII") -) - -// IsASCII checks if the given string consists of only ASCII characters. -func IsASCII(value string) (string, error) { - for _, c := range value { - if c > unicode.MaxASCII { - return value, ErrNotASCII - } - } - - return value, nil -} - -// checkASCII checks if the given string consists of only ASCII characters. -func isASCII(value reflect.Value) (reflect.Value, error) { - _, err := IsASCII(value.Interface().(string)) - return value, err -} - -// makeASCII makes a checker function for the ASCII checker. -func makeASCII(_ string) CheckFunc[reflect.Value] { - return isASCII -} diff --git a/v2/ascii_test.go b/v2/ascii_test.go deleted file mode 100644 index cffca70..0000000 --- a/v2/ascii_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsASCII() { - _, err := v2.IsASCII("Checker") - if err != nil { - fmt.Println(err) - } -} - -func TestIsASCIIInvalid(t *testing.T) { - _, err := v2.IsASCII("𝄞 Music!") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsASCIIValid(t *testing.T) { - _, err := v2.IsASCII("Checker") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckASCIINonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type User struct { - Username int `checkers:"ascii"` - } - - user := &User{} - - v2.CheckStruct(user) -} - -func TestCheckASCIIInvalid(t *testing.T) { - type User struct { - Username string `checkers:"ascii"` - } - - user := &User{ - Username: "𝄞 Music!", - } - - _, ok := v2.CheckStruct(user) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckASCIIValid(t *testing.T) { - type User struct { - Username string `checkers:"ascii"` - } - - user := &User{ - Username: "checker", - } - - _, ok := v2.CheckStruct(user) - if !ok { - t.Fatal("expected valid") - } -} diff --git a/v2/checker.go b/v2/checker.go deleted file mode 100644 index 3616767..0000000 --- a/v2/checker.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -// Package v2 Checker is a Go library for validating user input through checker rules provided in struct tags. -package v2 - -import ( - "fmt" - "reflect" - "strings" -) - -const ( - // checkerTag is the name of the field tag used for checker. - checkerTag = "checkers" - - // sliceConfigPrefix is the prefix used to distinguish slice-level checks from item-level checks. - sliceConfigPrefix = "@" -) - -// checkStructJob defines a check strcut job. -type checkStructJob struct { - Name string - Value reflect.Value - Config string -} - -// Check applies the given check functions to a value sequentially. -// It returns the final value and the first encountered error, if any. -func Check[T any](value T, checks ...CheckFunc[T]) (T, error) { - var err error - - for _, check := range checks { - value, err = check(value) - if err != nil { - break - } - } - - return value, err -} - -// CheckWithConfig applies the check functions specified by the config string to the given value. -// It returns the modified value and the first encountered error, if any. -func CheckWithConfig[T any](value T, config string) (T, error) { - newValue, err := ReflectCheckWithConfig(reflect.Indirect(reflect.ValueOf(value)), config) - return newValue.Interface().(T), err -} - -// ReflectCheckWithConfig applies the check functions specified by the config string -// to the given reflect.Value. It returns the modified reflect.Value and the first -// encountered error, if any. -func ReflectCheckWithConfig(value reflect.Value, config string) (reflect.Value, error) { - return Check(value, makeChecks(config)...) -} - -// CheckStruct checks the given struct based on the validation rules specified in the -// "checker" tag of each struct field. It returns a map of field names to their -// corresponding errors, and a boolean indicating if all checks passed. -func CheckStruct(st any) (map[string]error, bool) { - errs := make(map[string]error) - - jobs := []*checkStructJob{ - { - Name: "", - Value: reflect.Indirect(reflect.ValueOf(st)), - }, - } - - for len(jobs) > 0 { - job := jobs[0] - jobs = jobs[1:] - - switch job.Value.Kind() { - case reflect.Struct: - for i := 0; i < job.Value.NumField(); i++ { - field := job.Value.Type().Field(i) - - name := fieldName(job.Name, field) - value := reflect.Indirect(job.Value.FieldByIndex(field.Index)) - - jobs = append(jobs, &checkStructJob{ - Name: name, - Value: value, - Config: field.Tag.Get(checkerTag), - }) - } - - case reflect.Slice: - sliceConfig, itemConfig := splitSliceConfig(job.Config) - job.Config = sliceConfig - - for i := 0; i < job.Value.Len(); i++ { - name := fmt.Sprintf("%s[%d]", job.Name, i) - value := reflect.Indirect(job.Value.Index(i)) - - jobs = append(jobs, &checkStructJob{ - Name: name, - Value: value, - Config: itemConfig, - }) - } - } - - if job.Config != "" { - newValue, err := ReflectCheckWithConfig(job.Value, job.Config) - if err != nil { - errs[job.Name] = err - } - - job.Value.Set(newValue) - } - } - - return errs, len(errs) == 0 -} - -// fieldName returns the field name. If a "json" tag is present, it uses the -// tag value instead. It also prepends the parent struct's name (if any) to -// create a fully qualified field name. -func fieldName(prefix string, field reflect.StructField) string { - // Default to field name - name := field.Name - - // Use json tag if present - if jsonTag, ok := field.Tag.Lookup("json"); ok { - name = jsonTag - } - - // Prepend parent name - if prefix != "" { - name = prefix + "." + name - } - - return name -} - -// splitSliceConfig splits config string into slice and item-level configurations. -func splitSliceConfig(config string) (string, string) { - sliceFileds := make([]string, 0) - itemFields := make([]string, 0) - - for _, configField := range strings.Fields(config) { - if strings.HasPrefix(configField, sliceConfigPrefix) { - sliceFileds = append(sliceFileds, strings.TrimPrefix(configField, sliceConfigPrefix)) - } else { - itemFields = append(itemFields, configField) - } - } - - return strings.Join(sliceFileds, " "), strings.Join(itemFields, " ") -} diff --git a/v2/checker_test.go b/v2/checker_test.go deleted file mode 100644 index 32c8b96..0000000 --- a/v2/checker_test.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "errors" - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleCheck() { - name := " Onur Cinar " - - name, err := v2.Check(name, v2.TrimSpace, v2.Required) - if err != nil { - fmt.Println(err) - return - } - - fmt.Println(name) - // Output: Onur Cinar -} - -func ExampleCheckStruct() { - type Person struct { - Name string `checkers:"trim required"` - } - - person := &Person{ - Name: " Onur Cinar ", - } - - errs, ok := v2.CheckStruct(person) - if !ok { - fmt.Println(errs) - return - } - - fmt.Println(person.Name) - // Output: Onur Cinar -} - -func TestCheckTrimSpaceRequiredSuccess(t *testing.T) { - input := " test " - expected := "test" - - actual, err := v2.Check(input, v2.TrimSpace, v2.Required) - if err != nil { - t.Fatal(err) - } - - if actual != expected { - t.Fatalf("actual %s expected %s", actual, expected) - } -} - -func TestCheckTrimSpaceRequiredMissing(t *testing.T) { - input := " " - expected := "" - - actual, err := v2.Check(input, v2.TrimSpace, v2.Required) - if !errors.Is(err, v2.ErrRequired) { - t.Fatalf("got unexpected error %v", err) - } - - if actual != expected { - t.Fatalf("actual %s expected %s", actual, expected) - } -} - -func TestCheckWithConfigSuccess(t *testing.T) { - input := " test " - expected := "test" - - actual, err := v2.CheckWithConfig(input, "trim required") - if err != nil { - t.Fatal(err) - } - - if actual != expected { - t.Fatalf("actual %s expected %s", actual, expected) - } -} - -func TestCheckWithConfigRequiredMissing(t *testing.T) { - input := " " - expected := "" - - actual, err := v2.CheckWithConfig(input, "trim required") - if !errors.Is(err, v2.ErrRequired) { - t.Fatalf("got unexpected error %v", err) - } - - if actual != expected { - t.Fatalf("actual %s expected %s", actual, expected) - } -} - -func TestCheckStructSuccess(t *testing.T) { - type Address struct { - Street string `checkers:"required"` - } - - type Person struct { - Name string `checkers:"required"` - Address *Address - } - - person := &Person{ - Name: "Onur Cinar", - Address: &Address{ - Street: "1234 Main", - }, - } - - errors, ok := v2.CheckStruct(person) - if !ok { - t.Fatalf("got unexpected errors %v", errors) - } -} - -func TestCheckStructRequiredMissing(t *testing.T) { - type Address struct { - Street string `checkers:"required"` - } - - type Person struct { - Name string `checkers:"required"` - Address *Address - } - - person := &Person{ - Name: "", - Address: &Address{ - Street: "", - }, - } - - errs, ok := v2.CheckStruct(person) - if ok { - t.Fatal("expected errors") - } - - if !errors.Is(errs["Name"], v2.ErrRequired) { - t.Fatalf("expected name required %v", errs) - } - - if !errors.Is(errs["Address.Street"], v2.ErrRequired) { - t.Fatalf("expected streed required %v", errs) - } -} - -func TestCheckStructCustomName(t *testing.T) { - type Person struct { - Name string `json:"name" checkers:"required"` - } - - person := &Person{ - Name: "", - } - - errs, ok := v2.CheckStruct(person) - if ok { - t.Fatal("expected errors") - } - - if !errors.Is(errs["name"], v2.ErrRequired) { - t.Fatalf("expected name required %v", errs) - } -} - -func TestCheckStructSlice(t *testing.T) { - type Person struct { - Name string `checkers:"required"` - Emails []string `checkers:"@max-len:1 max-len:4"` - } - - person := &Person{ - Name: "Onur Cinar", - Emails: []string{ - "onur.cinar", - }, - } - - errs, ok := v2.CheckStruct(person) - if ok { - t.Fatal("expected errors") - } - - if !errors.Is(errs["Emails[0]"], v2.ErrMaxLen) { - t.Fatalf("expected email max len") - } -} diff --git a/v2/cidr.go b/v2/cidr.go deleted file mode 100644 index 193aedc..0000000 --- a/v2/cidr.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "net" - "reflect" -) - -const ( - // nameCIDR is the name of the CIDR check. - nameCIDR = "cidr" -) - -var ( - // ErrNotCIDR indicates that the given value is not a valid CIDR. - ErrNotCIDR = NewCheckError("NOT_CIDR") -) - -// IsCIDR checks if the value is a valid CIDR notation IP address and prefix length. -func IsCIDR(value string) (string, error) { - _, _, err := net.ParseCIDR(value) - if err != nil { - return value, ErrNotCIDR - } - - return value, nil -} - -// checkCIDR checks if the value is a valid CIDR notation IP address and prefix length. -func checkCIDR(value reflect.Value) (reflect.Value, error) { - _, err := IsCIDR(value.Interface().(string)) - return value, err -} - -// makeCIDR makes a checker function for the CIDR checker. -func makeCIDR(_ string) CheckFunc[reflect.Value] { - return checkCIDR -} diff --git a/v2/cidr_test.go b/v2/cidr_test.go deleted file mode 100644 index d402c23..0000000 --- a/v2/cidr_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsCIDR() { - _, err := v2.IsCIDR("2001:db8::/32") - if err != nil { - fmt.Println(err) - } -} - -func TestIsCIDRInvalid(t *testing.T) { - _, err := v2.IsCIDR("900.800.200.100//24") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsCIDRValid(t *testing.T) { - _, err := v2.IsCIDR("2001:db8::/32") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckCIDRNonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type Network struct { - Subnet int `checkers:"cidr"` - } - - network := &Network{} - - v2.CheckStruct(network) -} - -func TestCheckCIDRInvalid(t *testing.T) { - type Network struct { - Subnet string `checkers:"cidr"` - } - - network := &Network{ - Subnet: "900.800.200.100//24", - } - - _, ok := v2.CheckStruct(network) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckCIDRValid(t *testing.T) { - type Network struct { - Subnet string `checkers:"cidr"` - } - - network := &Network{ - Subnet: "192.0.2.0/24", - } - - _, ok := v2.CheckStruct(network) - if !ok { - t.Fatal("expected valid") - } -} diff --git a/v2/credit_card.go b/v2/credit_card.go deleted file mode 100644 index 59d5c85..0000000 --- a/v2/credit_card.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "reflect" - "regexp" - "strings" -) - -const ( - // nameCreditCard is the name of the credit card check. - nameCreditCard = "credit-card" -) - -var ( - // ErrNotCreditCard indicates that the given value is not a valid credit card number. - ErrNotCreditCard = NewCheckError("NOT_CREDIT_CARD") - - // amexExpression is the regexp for the AMEX cards. They start with 34 or 37, and has 15 digits. - amexExpression = "(?:^(?:3[47])[0-9]{13}$)" - amexPattern = regexp.MustCompile(amexExpression) - - // dinersExpression is the regexp for the Diners cards. They start with 305, 36, 38, and has 14 digits. - dinersExpression = "(?:^3(?:(?:05[0-9]{11})|(?:[68][0-9]{12}))$)" - dinersPattern = regexp.MustCompile(dinersExpression) - - // discoverExpression is the regexp for the Discover cards. They start with 6011 and has 16 digits. - discoverExpression = "(?:^6011[0-9]{12}$)" - discoverPattern = regexp.MustCompile(discoverExpression) - - // jcbExpression is the regexp for the JCB 15 cards. They start with 2131, 1800, and has 15 digits, or start with 35 and has 16 digits. - jcbExpression = "(?:^(?:(?:2131)|(?:1800)|(?:35[0-9]{3}))[0-9]{11}$)" - jcbPattern = regexp.MustCompile(jcbExpression) - - // masterCardExpression is the regexp for the MasterCard cards. They start with 51, 52, 53, 54, or 55, and has 15 digits. - masterCardExpression = "(?:^5[12345][0-9]{14}$)" - masterCardPattern = regexp.MustCompile(masterCardExpression) - - // unionPayExpression is the regexp for the UnionPay cards. They start either with 62 or 67, and has 16 digits, or they start with 81 and has 16 to 19 digits. - unionPayExpression = "(?:(?:6[27][0-9]{14})|(?:81[0-9]{14,17})^$)" - unionPayPattern = regexp.MustCompile(unionPayExpression) - - // visaExpression is the regexp for the Visa cards. They start with 4 and has 13 or 16 digits. - visaExpression = "(?:^4[0-9]{12}(?:[0-9]{3})?$)" - visaPattern = regexp.MustCompile(visaExpression) - - // anyCreditCardPattern is the regexp for any credit card. - anyCreditCardPattern = regexp.MustCompile(strings.Join([]string{ - amexExpression, - dinersExpression, - discoverExpression, - jcbExpression, - masterCardExpression, - unionPayExpression, - visaExpression, - }, "|")) - - // creditCardPatterns is the mapping for credit card names to patterns. - creditCardPatterns = map[string]*regexp.Regexp{ - "amex": amexPattern, - "diners": dinersPattern, - "discover": discoverPattern, - "jcb": jcbPattern, - "mastercard": masterCardPattern, - "unionpay": unionPayPattern, - "visa": visaPattern, - } -) - -// IsAnyCreditCard checks if the given value is a valid credit card number. -func IsAnyCreditCard(number string) (string, error) { - return isCreditCard(number, anyCreditCardPattern) -} - -// IsAmexCreditCard checks if the given valie is a valid AMEX credit card. -func IsAmexCreditCard(number string) (string, error) { - return isCreditCard(number, amexPattern) -} - -// IsDinersCreditCard checks if the given valie is a valid Diners credit card. -func IsDinersCreditCard(number string) (string, error) { - return isCreditCard(number, dinersPattern) -} - -// IsDiscoverCreditCard checks if the given valie is a valid Discover credit card. -func IsDiscoverCreditCard(number string) (string, error) { - return isCreditCard(number, discoverPattern) -} - -// IsJcbCreditCard checks if the given valie is a valid JCB 15 credit card. -func IsJcbCreditCard(number string) (string, error) { - return isCreditCard(number, jcbPattern) -} - -// IsMasterCardCreditCard checks if the given valie is a valid MasterCard credit card. -func IsMasterCardCreditCard(number string) (string, error) { - return isCreditCard(number, masterCardPattern) -} - -// IsUnionPayCreditCard checks if the given valie is a valid UnionPay credit card. -func IsUnionPayCreditCard(number string) (string, error) { - return isCreditCard(number, unionPayPattern) -} - -// IsVisaCreditCard checks if the given valie is a valid Visa credit card. -func IsVisaCreditCard(number string) (string, error) { - return isCreditCard(number, visaPattern) -} - -// makeCreditCard makes a checker function for the credit card checker. -func makeCreditCard(config string) CheckFunc[reflect.Value] { - patterns := []*regexp.Regexp{} - - if config != "" { - for _, card := range strings.Split(config, ",") { - pattern, ok := creditCardPatterns[card] - if !ok { - panic("unknown credit card name") - } - - patterns = append(patterns, pattern) - } - } else { - patterns = append(patterns, anyCreditCardPattern) - } - - return func(value reflect.Value) (reflect.Value, error) { - if value.Kind() != reflect.String { - panic("string expected") - } - - number := value.String() - - for _, pattern := range patterns { - _, err := isCreditCard(number, pattern) - if err == nil { - return value, nil - } - } - - return value, ErrNotCreditCard - } -} - -// isCreditCard checks if the given number based on the given credit card pattern and the Luhn algorithm check digit. -func isCreditCard(number string, pattern *regexp.Regexp) (string, error) { - if !pattern.MatchString(number) { - return number, ErrNotCreditCard - } - - return IsLUHN(number) -} diff --git a/v2/credit_card_test.go b/v2/credit_card_test.go deleted file mode 100644 index efedc17..0000000 --- a/v2/credit_card_test.go +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "strconv" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -// Test numbers from https://stripe.com/docs/testing -var invalidCard = "1234123412341234" -var amexCard = "378282246310005" -var dinersCard = "36227206271667" -var discoverCard = "6011111111111117" -var jcbCard = "3530111333300000" -var masterCard = "5555555555554444" -var unionPayCard = "6200000000000005" -var visaCard = "4111111111111111" - -// changeToInvalidLuhn increments the luhn digit to make the number invalid. It assumes that the given number is valid. -func changeToInvalidLuhn(number string) string { - luhn, err := strconv.Atoi(number[len(number)-1:]) - if err != nil { - panic(err) - } - - luhn = (luhn + 1) % 10 - - return number[:len(number)-1] + strconv.Itoa(luhn) -} - -func ExampleIsAnyCreditCard() { - _, err := v2.IsAnyCreditCard("6011111111111117") - - if err != nil { - // Send the errors back to the user - } -} - -func TestIsAnyCreditCardValid(t *testing.T) { - _, err := v2.IsAnyCreditCard(amexCard) - if err != nil { - t.Error(err) - } -} - -func TestIsAnyCreditCardInvalidPattern(t *testing.T) { - if _, err := v2.IsAnyCreditCard(invalidCard); err == nil { - t.Error("expected error for invalid card pattern") - } -} - -func TestIsAnyCreditCardInvalidLuhn(t *testing.T) { - if _, err := v2.IsAnyCreditCard(changeToInvalidLuhn(amexCard)); err == nil { - t.Error("expected error for invalid Luhn") - } -} - -func ExampleIsAmexCreditCard() { - _, err := v2.IsAmexCreditCard("378282246310005") - - if err != nil { - // Send the errors back to the user - } -} - -func TestIsAmexCreditCardValid(t *testing.T) { - if _, err := v2.IsAmexCreditCard(amexCard); err != nil { - t.Error(err) - } -} - -func TestIsAmexCreditCardInvalidPattern(t *testing.T) { - if _, err := v2.IsAmexCreditCard(invalidCard); err == nil { - t.Error("expected error for invalid card pattern") - } -} - -func TestIsAmexCreditCardInvalidLuhn(t *testing.T) { - if _, err := v2.IsAmexCreditCard(changeToInvalidLuhn(amexCard)); err == nil { - t.Error("expected error for invalid Luhn") - } -} - -func ExampleIsDinersCreditCard() { - _, err := v2.IsDinersCreditCard("36227206271667") - - if err != nil { - // Send the errors back to the user - } -} -func TestIsDinersCreditCardValid(t *testing.T) { - if _, err := v2.IsDinersCreditCard(dinersCard); err != nil { - t.Error(err) - } -} - -func TestIsDinersCreditCardInvalidPattern(t *testing.T) { - if _, err := v2.IsDinersCreditCard(invalidCard); err == nil { - t.Error("expected error for invalid card pattern") - } -} - -func TestIsDinersCreditCardInvalidLuhn(t *testing.T) { - if _, err := v2.IsDinersCreditCard(changeToInvalidLuhn(dinersCard)); err == nil { - t.Error("expected error for invalid Luhn") - } -} - -func ExampleIsDiscoverCreditCard() { - _, err := v2.IsDiscoverCreditCard("6011111111111117") - - if err != nil { - // Send the errors back to the user - } -} -func TestIsDiscoverCreditCardValid(t *testing.T) { - if _, err := v2.IsDiscoverCreditCard(discoverCard); err != nil { - t.Error(err) - } -} - -func TestIsDiscoverCreditCardInvalidPattern(t *testing.T) { - if _, err := v2.IsDiscoverCreditCard(invalidCard); err == nil { - t.Error("expected error for invalid card pattern") - } -} - -func TestIsDiscoverCreditCardInvalidLuhn(t *testing.T) { - if _, err := v2.IsDiscoverCreditCard(changeToInvalidLuhn(discoverCard)); err == nil { - t.Error("expected error for invalid Luhn") - } -} - -func ExampleIsJcbCreditCard() { - _, err := v2.IsJcbCreditCard("3530111333300000") - - if err != nil { - // Send the errors back to the user - } -} - -func TestIsJcbCreditCardValid(t *testing.T) { - if _, err := v2.IsJcbCreditCard(jcbCard); err != nil { - t.Error(err) - } -} - -func TestIsJcbCreditCardInvalidPattern(t *testing.T) { - if _, err := v2.IsJcbCreditCard(invalidCard); err == nil { - t.Error("expected error for invalid card pattern") - } -} - -func TestIsJcbCreditCardInvalidLuhn(t *testing.T) { - if _, err := v2.IsJcbCreditCard(changeToInvalidLuhn(jcbCard)); err == nil { - t.Error("expected error for invalid Luhn") - } -} - -func ExampleIsMasterCardCreditCard() { - _, err := v2.IsMasterCardCreditCard("5555555555554444") - - if err != nil { - // Send the errors back to the user - } -} - -func TestIsMasterCardCreditCardValid(t *testing.T) { - if _, err := v2.IsMasterCardCreditCard(masterCard); err != nil { - t.Error(err) - } -} - -func TestIsMasterCardCreditCardInvalidPattern(t *testing.T) { - if _, err := v2.IsMasterCardCreditCard(invalidCard); err == nil { - t.Error("expected error for invalid card pattern") - } -} - -func TestIsMasterCardCreditCardInvalidLuhn(t *testing.T) { - if _, err := v2.IsMasterCardCreditCard(changeToInvalidLuhn(masterCard)); err == nil { - t.Error("expected error for invalid Luhn") - } -} - -func ExampleIsUnionPayCreditCard() { - _, err := v2.IsUnionPayCreditCard("6200000000000005") - - if err != nil { - // Send the errors back to the user - } -} - -func TestIsUnionPayCreditCardValid(t *testing.T) { - if _, err := v2.IsUnionPayCreditCard(unionPayCard); err != nil { - t.Error(err) - } -} - -func TestIsUnionPayCreditCardInvalidPattern(t *testing.T) { - if _, err := v2.IsUnionPayCreditCard(invalidCard); err == nil { - t.Error("expected error for invalid card pattern") - } -} - -func TestIsUnionPayCreditCardInvalidLuhn(t *testing.T) { - if _, err := v2.IsUnionPayCreditCard(changeToInvalidLuhn(unionPayCard)); err == nil { - t.Error("expected error for invalid Luhn") - } -} - -func ExampleIsVisaCreditCard() { - _, err := v2.IsVisaCreditCard("4111111111111111") - - if err != nil { - // Send the errors back to the user - } -} -func TestIsVisaCreditCardValid(t *testing.T) { - if _, err := v2.IsVisaCreditCard(visaCard); err != nil { - t.Error(err) - } -} - -func TestIsVisaCreditCardInvalidPattern(t *testing.T) { - if _, err := v2.IsVisaCreditCard(invalidCard); err == nil { - t.Error("expected error for invalid card pattern") - } -} - -func TestIsVisaCreditCardInvalidLuhn(t *testing.T) { - if _, err := v2.IsVisaCreditCard(changeToInvalidLuhn(visaCard)); err == nil { - t.Error("expected error for invalid Luhn") - } -} - -func TestCheckCreditCardNonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic for non-string credit card") - - type Order struct { - CreditCard int `checkers:"credit-card"` - } - - order := &Order{} - - v2.CheckStruct(order) -} - -func TestCheckCreditCardValid(t *testing.T) { - type Order struct { - CreditCard string `checkers:"credit-card"` - } - - order := &Order{ - CreditCard: amexCard, - } - - _, valid := v2.CheckStruct(order) - if !valid { - t.Fail() - } -} - -func TestCheckCreditCardInvalid(t *testing.T) { - type Order struct { - CreditCard string `checkers:"credit-card"` - } - - order := &Order{ - CreditCard: invalidCard, - } - - _, valid := v2.CheckStruct(order) - if valid { - t.Fail() - } -} - -func TestCheckCreditCardMultipleUnknown(t *testing.T) { - defer FailIfNoPanic(t, "expected panic for unknown credit card") - - type Order struct { - CreditCard string `checkers:"credit-card:amex,unknown"` - } - - order := &Order{ - CreditCard: amexCard, - } - - v2.CheckStruct(order) -} - -func TestCheckCreditCardMultipleInvalid(t *testing.T) { - type Order struct { - CreditCard string `checkers:"credit-card:amex,visa"` - } - - order := &Order{ - CreditCard: discoverCard, - } - - _, valid := v2.CheckStruct(order) - if valid { - t.Fail() - } -} diff --git a/v2/digits.go b/v2/digits.go deleted file mode 100644 index 0a01b72..0000000 --- a/v2/digits.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "reflect" - "unicode" -) - -const ( - // nameDigits is the name of the digits check. - nameDigits = "digits" -) - -var ( - // ErrNotDigits indicates that the given value is not a valid digits string. - ErrNotDigits = NewCheckError("NOT_DIGITS") -) - -// IsDigits checks if the value contains only digit characters. -func IsDigits(value string) (string, error) { - for _, r := range value { - if !unicode.IsDigit(r) { - return value, ErrNotDigits - } - } - return value, nil -} - -// checkDigits checks if the value contains only digit characters. -func checkDigits(value reflect.Value) (reflect.Value, error) { - _, err := IsDigits(value.Interface().(string)) - return value, err -} - -// makeDigits makes a checker function for the digits checker. -func makeDigits(_ string) CheckFunc[reflect.Value] { - return checkDigits -} diff --git a/v2/digits_test.go b/v2/digits_test.go deleted file mode 100644 index 6858ca6..0000000 --- a/v2/digits_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsDigits() { - _, err := v2.IsDigits("123456") - if err != nil { - fmt.Println(err) - } -} - -func TestIsDigitsInvalid(t *testing.T) { - _, err := v2.IsDigits("123a456") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsDigitsValid(t *testing.T) { - _, err := v2.IsDigits("123456") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckDigitsNonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type Code struct { - Value int `checkers:"digits"` - } - - code := &Code{} - - v2.CheckStruct(code) -} - -func TestCheckDigitsInvalid(t *testing.T) { - type Code struct { - Value string `checkers:"digits"` - } - - code := &Code{ - Value: "123a456", - } - - _, ok := v2.CheckStruct(code) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckDigitsValid(t *testing.T) { - type Code struct { - Value string `checkers:"digits"` - } - - code := &Code{ - Value: "123456", - } - - _, ok := v2.CheckStruct(code) - if !ok { - t.Fatal("expected valid") - } -} diff --git a/v2/email.go b/v2/email.go deleted file mode 100644 index 8ab0aca..0000000 --- a/v2/email.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "net/mail" - "reflect" -) - -const ( - // nameEmail is the name of the email check. - nameEmail = "email" -) - -var ( - // ErrNotEmail indicates that the given value is not a valid email address. - ErrNotEmail = NewCheckError("NOT_EMAIL") -) - -// IsEmail checks if the value is a valid email address. -func IsEmail(value string) (string, error) { - _, err := mail.ParseAddress(value) - if err != nil { - return value, ErrNotEmail - } - return value, nil -} - -// checkEmail checks if the value is a valid email address. -func checkEmail(value reflect.Value) (reflect.Value, error) { - _, err := IsEmail(value.Interface().(string)) - return value, err -} - -// makeEmail makes a checker function for the email checker. -func makeEmail(_ string) CheckFunc[reflect.Value] { - return checkEmail -} diff --git a/v2/email_test.go b/v2/email_test.go deleted file mode 100644 index 20e3665..0000000 --- a/v2/email_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsEmail() { - _, err := v2.IsEmail("test@example.com") - if err != nil { - fmt.Println(err) - } -} - -func TestIsEmailInvalid(t *testing.T) { - _, err := v2.IsEmail("invalid-email") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsEmailValid(t *testing.T) { - _, err := v2.IsEmail("test@example.com") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckEmailNonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type User struct { - Email int `checkers:"email"` - } - - user := &User{} - - v2.CheckStruct(user) -} - -func TestCheckEmailInvalid(t *testing.T) { - type User struct { - Email string `checkers:"email"` - } - - user := &User{ - Email: "invalid-email", - } - - _, ok := v2.CheckStruct(user) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckEmailValid(t *testing.T) { - type User struct { - Email string `checkers:"email"` - } - - user := &User{ - Email: "test@example.com", - } - - _, ok := v2.CheckStruct(user) - if !ok { - t.Fatal("expected valid") - } -} diff --git a/v2/fqdn.go b/v2/fqdn.go deleted file mode 100644 index 899edec..0000000 --- a/v2/fqdn.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "reflect" - "regexp" -) - -const ( - // nameFQDN is the name of the FQDN check. - nameFQDN = "fqdn" -) - -var ( - // ErrNotFQDN indicates that the given value is not a valid FQDN. - ErrNotFQDN = NewCheckError("FQDN") - - // fqdnRegex is the regular expression for validating FQDN. - fqdnRegex = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`) -) - -// IsFQDN checks if the value is a valid fully qualified domain name (FQDN). -func IsFQDN(value string) (string, error) { - if !fqdnRegex.MatchString(value) { - return value, ErrNotFQDN - } - return value, nil -} - -// checkFQDN checks if the value is a valid fully qualified domain name (FQDN). -func checkFQDN(value reflect.Value) (reflect.Value, error) { - _, err := IsFQDN(value.Interface().(string)) - return value, err -} - -// makeFQDN makes a checker function for the FQDN checker. -func makeFQDN(_ string) CheckFunc[reflect.Value] { - return checkFQDN -} \ No newline at end of file diff --git a/v2/fqdn_test.go b/v2/fqdn_test.go deleted file mode 100644 index 8ffc8ed..0000000 --- a/v2/fqdn_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsFQDN() { - _, err := v2.IsFQDN("example.com") - if err != nil { - fmt.Println(err) - } -} - -func TestIsFQDNInvalid(t *testing.T) { - _, err := v2.IsFQDN("invalid_fqdn") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsFQDNValid(t *testing.T) { - _, err := v2.IsFQDN("example.com") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckFQDNNonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type Domain struct { - Name int `checkers:"fqdn"` - } - - domain := &Domain{} - - v2.CheckStruct(domain) -} - -func TestCheckFQDNInvalid(t *testing.T) { - type Domain struct { - Name string `checkers:"fqdn"` - } - - domain := &Domain{ - Name: "invalid_fqdn", - } - - _, ok := v2.CheckStruct(domain) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckFQDNValid(t *testing.T) { - type Domain struct { - Name string `checkers:"fqdn"` - } - - domain := &Domain{ - Name: "example.com", - } - - _, ok := v2.CheckStruct(domain) - if !ok { - t.Fatal("expected valid") - } -} diff --git a/v2/go.mod b/v2/go.mod deleted file mode 100644 index 4a6df3a..0000000 --- a/v2/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/cinar/checker/v2 - -go 1.23.2 diff --git a/v2/html_escape.go b/v2/html_escape.go deleted file mode 100644 index e209d5b..0000000 --- a/v2/html_escape.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "html" - "reflect" -) - -const ( - // nameHTMLEscape is the name of the HTML escape normalizer. - nameHTMLEscape = "html-escape" -) - -// HTMLEscape applies HTML escaping to special characters. -func HTMLEscape(value string) (string, error) { - return html.EscapeString(value), nil -} - -// reflectHTMLEscape applies HTML escaping to special characters. -func reflectHTMLEscape(value reflect.Value) (reflect.Value, error) { - newValue, err := HTMLEscape(value.Interface().(string)) - return reflect.ValueOf(newValue), err -} - -// makeHTMLEscape returns the HTML escape normalizer function. -func makeHTMLEscape(_ string) CheckFunc[reflect.Value] { - return reflectHTMLEscape -} diff --git a/v2/html_escape_test.go b/v2/html_escape_test.go deleted file mode 100644 index 8a60010..0000000 --- a/v2/html_escape_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func TestHTMLEscape(t *testing.T) { - input := " \"Checker\" & 'Library' " - expected := "<tag> "Checker" & 'Library' </tag>" - - actual, err := v2.HTMLEscape(input) - if err != nil { - t.Fatal(err) - } - - if actual != expected { - t.Fatalf("actual %s expected %s", actual, expected) - } -} - -func TestReflectHTMLEscape(t *testing.T) { - type Comment struct { - Body string `checkers:"html-escape"` - } - - comment := &Comment{ - Body: " \"Checker\" & 'Library' ", - } - - expected := "<tag> "Checker" & 'Library' </tag>" - - errs, ok := v2.CheckStruct(comment) - if !ok { - t.Fatalf("got unexpected errors %v", errs) - } - - if comment.Body != expected { - t.Fatalf("actual %s expected %s", comment.Body, expected) - } -} diff --git a/v2/html_unescape.go b/v2/html_unescape.go deleted file mode 100644 index 27ba376..0000000 --- a/v2/html_unescape.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "html" - "reflect" -) - -// nameHTMLUnescape is the name of the HTML unescape normalizer. -const nameHTMLUnescape = "html-unescape" - -// HTMLUnescape applies HTML unescaping to special characters. -func HTMLUnescape(value string) (string, error) { - return html.UnescapeString(value), nil -} - -// reflectHTMLUnescape applies HTML unescaping to special characters. -func reflectHTMLUnescape(value reflect.Value) (reflect.Value, error) { - newValue, err := HTMLUnescape(value.Interface().(string)) - return reflect.ValueOf(newValue), err -} - -// makeHTMLUnescape returns the HTML unescape normalizer function. -func makeHTMLUnescape(_ string) CheckFunc[reflect.Value] { - return reflectHTMLUnescape -} diff --git a/v2/html_unescape_test.go b/v2/html_unescape_test.go deleted file mode 100644 index 1bf735b..0000000 --- a/v2/html_unescape_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func TestHTMLUnescape(t *testing.T) { - input := "<tag> "Checker" & 'Library' </tag>" - expected := " \"Checker\" & 'Library' " - - actual, err := v2.HTMLUnescape(input) - if err != nil { - t.Fatal(err) - } - - if actual != expected { - t.Fatalf("actual %s expected %s", actual, expected) - } -} - -func TestReflectHTMLUnescape(t *testing.T) { - type Comment struct { - Body string `checkers:"html-unescape"` - } - - comment := &Comment{ - Body: "<tag> "Checker" & 'Library' </tag>", - } - - expected := " \"Checker\" & 'Library' " - - errs, ok := v2.CheckStruct(comment) - if !ok { - t.Fatalf("got unexpected errors %v", errs) - } - - if comment.Body != expected { - t.Fatalf("actual %s expected %s", comment.Body, expected) - } -} diff --git a/v2/ip.go b/v2/ip.go deleted file mode 100644 index e03ce6e..0000000 --- a/v2/ip.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "net" - "reflect" -) - -const ( - // nameIP is the name of the IP check. - nameIP = "ip" -) - -var ( - // ErrNotIP indicates that the given value is not a valid IP address. - ErrNotIP = NewCheckError("NOT_IP") -) - -// IsIP checks if the value is a valid IP address. -func IsIP(value string) (string, error) { - if net.ParseIP(value) == nil { - return value, ErrNotIP - } - return value, nil -} - -// checkIP checks if the value is a valid IP address. -func checkIP(value reflect.Value) (reflect.Value, error) { - _, err := IsIP(value.Interface().(string)) - return value, err -} - -// makeIP makes a checker function for the IP checker. -func makeIP(_ string) CheckFunc[reflect.Value] { - return checkIP -} diff --git a/v2/ip_test.go b/v2/ip_test.go deleted file mode 100644 index 5bd23c5..0000000 --- a/v2/ip_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsIP() { - _, err := v2.IsIP("192.168.1.1") - if err != nil { - fmt.Println(err) - } -} - -func TestIsIPInvalid(t *testing.T) { - _, err := v2.IsIP("invalid-ip") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsIPValid(t *testing.T) { - _, err := v2.IsIP("192.168.1.1") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckIPNonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type Network struct { - Address int `checkers:"ip"` - } - - network := &Network{} - - v2.CheckStruct(network) -} - -func TestCheckIPInvalid(t *testing.T) { - type Network struct { - Address string `checkers:"ip"` - } - - network := &Network{ - Address: "invalid-ip", - } - - _, ok := v2.CheckStruct(network) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckIPValid(t *testing.T) { - type Network struct { - Address string `checkers:"ip"` - } - - network := &Network{ - Address: "192.168.1.1", - } - - _, ok := v2.CheckStruct(network) - if !ok { - t.Fatal("expected valid") - } -} diff --git a/v2/ipv4.go b/v2/ipv4.go deleted file mode 100644 index 560327c..0000000 --- a/v2/ipv4.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "net" - "reflect" -) - -const ( - // nameIPv4 is the name of the IPv4 check. - nameIPv4 = "ipv4" -) - -var ( - // ErrNotIPv4 indicates that the given value is not a valid IPv4 address. - ErrNotIPv4 = NewCheckError("NOT_IPV4") -) - -// IsIPv4 checks if the value is a valid IPv4 address. -func IsIPv4(value string) (string, error) { - ip := net.ParseIP(value) - if ip == nil || ip.To4() == nil { - return value, ErrNotIPv4 - } - return value, nil -} - -// checkIPv4 checks if the value is a valid IPv4 address. -func checkIPv4(value reflect.Value) (reflect.Value, error) { - _, err := IsIPv4(value.Interface().(string)) - return value, err -} - -// makeIPv4 makes a checker function for the IPv4 checker. -func makeIPv4(_ string) CheckFunc[reflect.Value] { - return checkIPv4 -} diff --git a/v2/ipv4_test.go b/v2/ipv4_test.go deleted file mode 100644 index ba34feb..0000000 --- a/v2/ipv4_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsIPv4() { - _, err := v2.IsIPv4("192.168.1.1") - if err != nil { - fmt.Println(err) - } -} - -func TestIsIPv4Invalid(t *testing.T) { - _, err := v2.IsIPv4("2001:db8::1") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsIPv4Valid(t *testing.T) { - _, err := v2.IsIPv4("192.168.1.1") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckIPv4NonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type Network struct { - Address int `checkers:"ipv4"` - } - - network := &Network{} - - v2.CheckStruct(network) -} - -func TestCheckIPv4Invalid(t *testing.T) { - type Network struct { - Address string `checkers:"ipv4"` - } - - network := &Network{ - Address: "2001:db8::1", - } - - _, ok := v2.CheckStruct(network) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckIPv4Valid(t *testing.T) { - type Network struct { - Address string `checkers:"ipv4"` - } - - network := &Network{ - Address: "192.168.1.1", - } - - _, ok := v2.CheckStruct(network) - if !ok { - t.Fatal("expected valid") - } -} diff --git a/v2/ipv6.go b/v2/ipv6.go deleted file mode 100644 index ad3bb54..0000000 --- a/v2/ipv6.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "net" - "reflect" -) - -const ( - // nameIPv6 is the name of the IPv6 check. - nameIPv6 = "ipv6" -) - -var ( - // ErrNotIPv6 indicates that the given value is not a valid IPv6 address. - ErrNotIPv6 = NewCheckError("NOT_IPV6") -) - -// IsIPv6 checks if the value is a valid IPv6 address. -func IsIPv6(value string) (string, error) { - if net.ParseIP(value) == nil || net.ParseIP(value).To4() != nil { - return value, ErrNotIPv6 - } - return value, nil -} - -// checkIPv6 checks if the value is a valid IPv6 address. -func checkIPv6(value reflect.Value) (reflect.Value, error) { - _, err := IsIPv6(value.Interface().(string)) - return value, err -} - -// makeIPv6 makes a checker function for the IPv6 checker. -func makeIPv6(_ string) CheckFunc[reflect.Value] { - return checkIPv6 -} diff --git a/v2/ipv6_test.go b/v2/ipv6_test.go deleted file mode 100644 index 2a7ea58..0000000 --- a/v2/ipv6_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsIPv6() { - _, err := v2.IsIPv6("2001:db8::1") - if err != nil { - fmt.Println(err) - } -} - -func TestIsIPv6Invalid(t *testing.T) { - _, err := v2.IsIPv6("192.168.1.1") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsIPv6Valid(t *testing.T) { - _, err := v2.IsIPv6("2001:db8::1") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckIPv6NonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type Network struct { - Address int `checkers:"ipv6"` - } - - network := &Network{} - - v2.CheckStruct(network) -} - -func TestCheckIPv6Invalid(t *testing.T) { - type Network struct { - Address string `checkers:"ipv6"` - } - - network := &Network{ - Address: "192.168.1.1", - } - - _, ok := v2.CheckStruct(network) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckIPv6Valid(t *testing.T) { - type Network struct { - Address string `checkers:"ipv6"` - } - - network := &Network{ - Address: "2001:db8::1", - } - - _, ok := v2.CheckStruct(network) - if !ok { - t.Fatal("expected valid") - } -} diff --git a/v2/isbn.go b/v2/isbn.go deleted file mode 100644 index b2e1e15..0000000 --- a/v2/isbn.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "reflect" - "regexp" -) - -const ( - // nameISBN is the name of the ISBN check. - nameISBN = "isbn" -) - -var ( - // ErrNotISBN indicates that the given value is not a valid ISBN. - ErrNotISBN = NewCheckError("NOT_ISBN") - - // isbnRegex is the regular expression for validating ISBN-10 and ISBN-13. - isbnRegex = regexp.MustCompile(`^(97(8|9))?\d{9}(\d|X)$`) -) - -// IsISBN checks if the value is a valid ISBN-10 or ISBN-13. -func IsISBN(value string) (string, error) { - if !isbnRegex.MatchString(value) { - return value, ErrNotISBN - } - return value, nil -} - -// checkISBN checks if the value is a valid ISBN-10 or ISBN-13. -func checkISBN(value reflect.Value) (reflect.Value, error) { - _, err := IsISBN(value.Interface().(string)) - return value, err -} - -// makeISBN makes a checker function for the ISBN checker. -func makeISBN(_ string) CheckFunc[reflect.Value] { - return checkISBN -} diff --git a/v2/isbn_test.go b/v2/isbn_test.go deleted file mode 100644 index cb8c48c..0000000 --- a/v2/isbn_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsISBN() { - _, err := v2.IsISBN("1430248270") - if err != nil { - fmt.Println(err) - } -} - -func TestIsISBNInvalid(t *testing.T) { - _, err := v2.IsISBN("invalid-isbn") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsISBNValid(t *testing.T) { - _, err := v2.IsISBN("1430248270") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckISBNNonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type Book struct { - ISBN int `checkers:"isbn"` - } - - book := &Book{} - - v2.CheckStruct(book) -} - -func TestCheckISBNInvalid(t *testing.T) { - type Book struct { - ISBN string `checkers:"isbn"` - } - - book := &Book{ - ISBN: "invalid-isbn", - } - - _, ok := v2.CheckStruct(book) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckISBNValid(t *testing.T) { - type Book struct { - ISBN string `checkers:"isbn"` - } - - book := &Book{ - ISBN: "9783161484100", - } - - _, ok := v2.CheckStruct(book) - if !ok { - t.Fatal("expected valid") - } -} diff --git a/v2/lower.go b/v2/lower.go deleted file mode 100644 index 803ca99..0000000 --- a/v2/lower.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "reflect" - "strings" -) - -const ( - // nameLower is the name of the lower normalizer. - nameLower = "lower" -) - -// Lower maps all Unicode letters in the given value to their lower case. -func Lower(value string) (string, error) { - return strings.ToLower(value), nil -} - -// reflectLower maps all Unicode letters in the given value to their lower case. -func reflectLower(value reflect.Value) (reflect.Value, error) { - newValue, err := Lower(value.Interface().(string)) - return reflect.ValueOf(newValue), err -} - -// makeLower returns the lower normalizer function. -func makeLower(_ string) CheckFunc[reflect.Value] { - return reflectLower -} diff --git a/v2/lower_test.go b/v2/lower_test.go deleted file mode 100644 index 491e671..0000000 --- a/v2/lower_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func TestLower(t *testing.T) { - input := "CHECKER" - expected := "checker" - - actual, err := v2.Lower(input) - if err != nil { - t.Fatal(err) - } - - if actual != expected { - t.Fatalf("actual %s expected %s", actual, expected) - } -} - -func TestReflectLower(t *testing.T) { - type Person struct { - Name string `checkers:"lower"` - } - - person := &Person{ - Name: "CHECKER", - } - - expected := "checker" - - errs, ok := v2.CheckStruct(person) - if !ok { - t.Fatalf("got unexpected errors %v", errs) - } - - if person.Name != expected { - t.Fatalf("actual %s expected %s", person.Name, expected) - } -} diff --git a/v2/luhn.go b/v2/luhn.go deleted file mode 100644 index 5040449..0000000 --- a/v2/luhn.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "reflect" - "unicode" -) - -const ( - // nameLUHN is the name of the LUHN check. - nameLUHN = "luhn" -) - -var ( - // ErrNotLUHN indicates that the given value is not a valid LUHN number. - ErrNotLUHN = NewCheckError("NOT_LUHN") -) - -// IsLUHN checks if the value is a valid LUHN number. -func IsLUHN(value string) (string, error) { - var sum int - var alt bool - - for i := len(value) - 1; i >= 0; i-- { - r := rune(value[i]) - if !unicode.IsDigit(r) { - return value, ErrNotLUHN - } - - n := int(r - '0') - if alt { - n *= 2 - if n > 9 { - n -= 9 - } - } - sum += n - alt = !alt - } - - if sum%10 != 0 { - return value, ErrNotLUHN - } - - return value, nil -} - -// checkLUHN checks if the value is a valid LUHN number. -func checkLUHN(value reflect.Value) (reflect.Value, error) { - _, err := IsLUHN(value.Interface().(string)) - return value, err -} - -// makeLUHN makes a checker function for the LUHN checker. -func makeLUHN(_ string) CheckFunc[reflect.Value] { - return checkLUHN -} diff --git a/v2/luhn_test.go b/v2/luhn_test.go deleted file mode 100644 index 877d5c5..0000000 --- a/v2/luhn_test.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsLUHN() { - _, err := v2.IsLUHN("4012888888881881") - if err != nil { - fmt.Println(err) - } -} - -func TestIsLUHNInvalid(t *testing.T) { - _, err := v2.IsLUHN("123456789") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsLUHNInvalidDigits(t *testing.T) { - _, err := v2.IsLUHN("ABCD") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsLUHNValid(t *testing.T) { - _, err := v2.IsLUHN("4012888888881881") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckLUHNNonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type Card struct { - Number int `checkers:"luhn"` - } - - card := &Card{} - - v2.CheckStruct(card) -} - -func TestCheckLUHNInvalid(t *testing.T) { - type Card struct { - Number string `checkers:"luhn"` - } - - card := &Card{ - Number: "123456789", - } - - _, ok := v2.CheckStruct(card) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckLUHNValid(t *testing.T) { - type Card struct { - Number string `checkers:"luhn"` - } - - card := &Card{ - Number: "79927398713", - } - - _, ok := v2.CheckStruct(card) - if !ok { - t.Fatal("expected valid") - } -} diff --git a/v2/mac.go b/v2/mac.go deleted file mode 100644 index 43e1370..0000000 --- a/v2/mac.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "net" - "reflect" -) - -const ( - // nameMAC is the name of the MAC check. - nameMAC = "mac" -) - -var ( - // ErrNotMAC indicates that the given value is not a valid MAC address. - ErrNotMAC = NewCheckError("NOT_MAC") -) - -// IsMAC checks if the value is a valid MAC address. -func IsMAC(value string) (string, error) { - _, err := net.ParseMAC(value) - if err != nil { - return value, ErrNotMAC - } - return value, nil -} - -// checkMAC checks if the value is a valid MAC address. -func checkMAC(value reflect.Value) (reflect.Value, error) { - _, err := IsMAC(value.Interface().(string)) - return value, err -} - -// makeMAC makes a checker function for the MAC checker. -func makeMAC(_ string) CheckFunc[reflect.Value] { - return checkMAC -} diff --git a/v2/mac_test.go b/v2/mac_test.go deleted file mode 100644 index 23ec403..0000000 --- a/v2/mac_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsMAC() { - _, err := v2.IsMAC("00:1A:2B:3C:4D:5E") - if err != nil { - fmt.Println(err) - } -} - -func TestIsMACInvalid(t *testing.T) { - _, err := v2.IsMAC("invalid-mac") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsMACValid(t *testing.T) { - _, err := v2.IsMAC("00:1A:2B:3C:4D:5E") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckMACNonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type Device struct { - MAC int `checkers:"mac"` - } - - device := &Device{} - - v2.CheckStruct(device) -} - -func TestCheckMACInvalid(t *testing.T) { - type Device struct { - MAC string `checkers:"mac"` - } - - device := &Device{ - MAC: "invalid-mac", - } - - _, ok := v2.CheckStruct(device) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckMACValid(t *testing.T) { - type Device struct { - MAC string `checkers:"mac"` - } - - device := &Device{ - MAC: "00:1A:2B:3C:4D:5E", - } - - _, ok := v2.CheckStruct(device) - if !ok { - t.Fatal("expected valid") - } -} diff --git a/v2/regexp.go b/v2/regexp.go deleted file mode 100644 index 8ae5663..0000000 --- a/v2/regexp.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "reflect" - "regexp" -) - -// nameRegexp is the name of the regexp check. -const nameRegexp = "regexp" - -// ErrNotMatch indicates that the given string does not match the regexp pattern. -var ErrNotMatch = NewCheckError("REGEXP") - -// IsRegexp checks if the given string matches the given regexp expression. -func IsRegexp(expression, value string) (string, error) { - if !regexp.MustCompile(expression).MatchString(value) { - return value, ErrNotMatch - } - - return value, nil -} - -// MakeRegexpChecker makes a regexp checker for the given regexp expression with the given invalid result. -func MakeRegexpChecker(expression string, invalidError error) CheckFunc[reflect.Value] { - return func(value reflect.Value) (reflect.Value, error) { - if value.Kind() != reflect.String { - panic("string expected") - } - - _, err := IsRegexp(expression, value.String()) - if err != nil { - return value, invalidError - } - - return value, nil - } -} - -// makeRegexp makes a checker function for the regexp. -func makeRegexp(config string) CheckFunc[reflect.Value] { - return MakeRegexpChecker(config, ErrNotMatch) -} diff --git a/v2/regexp_test.go b/v2/regexp_test.go deleted file mode 100644 index cdb78dd..0000000 --- a/v2/regexp_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsRegexp() { - _, err := v2.IsRegexp("^[0-9a-fA-F]+$", "ABcd1234") - if err != nil { - fmt.Println(err) - } -} - -func TestIsRegexpInvalid(t *testing.T) { - _, err := v2.IsRegexp("^[0-9a-fA-F]+$", "Onur") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsRegexpValid(t *testing.T) { - _, err := v2.IsRegexp("^[0-9a-fA-F]+$", "ABcd1234") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckRegexpNonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type User struct { - Username int `checkers:"regexp:^[A-Za-z]$"` - } - - user := &User{} - - v2.CheckStruct(user) -} - -func TestCheckRegexpInvalid(t *testing.T) { - type User struct { - Username string `checkers:"regexp:^[A-Za-z]+$"` - } - - user := &User{ - Username: "abcd1234", - } - - _, ok := v2.CheckStruct(user) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckRegexpValid(t *testing.T) { - type User struct { - Username string `checkers:"regexp:^[A-Za-z]+$"` - } - - user := &User{ - Username: "abcd", - } - - _, ok := v2.CheckStruct(user) - if !ok { - t.Fatal("expected valid") - } -} diff --git a/v2/required.go b/v2/required.go deleted file mode 100644 index 70c5d79..0000000 --- a/v2/required.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import "reflect" - -const ( - // nameRequired is the name of the required check. - nameRequired = "required" -) - -var ( - // ErrRequired indicates that a required value was missing. - ErrRequired = NewCheckError("REQUIRED") -) - -// Required checks if the given value of type T is its zero value. It -// returns an error if the value is zero. -func Required[T any](value T) (T, error) { - _, err := reflectRequired(reflect.ValueOf(value)) - return value, err -} - -// reflectRequired checks if the given value is its zero value. It -// returns an error if the value is zero. -func reflectRequired(value reflect.Value) (reflect.Value, error) { - var err error - - if value.IsZero() { - err = ErrRequired - } - - return value, err -} - -// makeRequired returns the required check function. -func makeRequired(_ string) CheckFunc[reflect.Value] { - return reflectRequired -} diff --git a/v2/required_test.go b/v2/required_test.go deleted file mode 100644 index 3231aff..0000000 --- a/v2/required_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "errors" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func TestRequiredSuccess(t *testing.T) { - value := "test" - - result, err := v2.Required(value) - - if result != value { - t.Fatalf("result (%s) is not the original value (%s)", result, value) - } - - if err != nil { - t.Fatal(err) - } -} - -func TestRequiredMissing(t *testing.T) { - var value string - - result, err := v2.Required(value) - - if result != value { - t.Fatalf("result (%s) is not the original value (%s)", result, value) - } - - if !errors.Is(err, v2.ErrRequired) { - t.Fatalf("got unexpected error %v", err) - } -} diff --git a/v2/revive.toml b/v2/revive.toml deleted file mode 100644 index f9e2405..0000000 --- a/v2/revive.toml +++ /dev/null @@ -1,30 +0,0 @@ -ignoreGeneratedHeader = false -severity = "warning" -confidence = 0.8 -errorCode = 0 -warningCode = 0 - -[rule.blank-imports] -[rule.context-as-argument] -[rule.context-keys-type] -[rule.dot-imports] -[rule.error-return] -[rule.error-strings] -[rule.error-naming] -[rule.exported] -[rule.if-return] -[rule.increment-decrement] -[rule.var-naming] -[rule.var-declaration] -[rule.package-comments] -[rule.range] -[rule.receiver-naming] -[rule.time-naming] -[rule.unexported-return] -[rule.indent-error-flow] -[rule.errorf] -[rule.empty-block] -[rule.superfluous-else] -[rule.unused-parameter] -[rule.unreachable-code] -[rule.redefines-builtin-id] diff --git a/v2/taskfile.yml b/v2/taskfile.yml deleted file mode 100644 index 0d8e575..0000000 --- a/v2/taskfile.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: '3' -output: 'prefixed' - -tasks: - default: - cmds: - - task: fmt - - task: lint - - task: test - - task: docs - - action: - deps: [lint, test] - - fmt: - cmds: - - go fix ./... - - lint: - cmds: - - go vet ./... - - go run github.com/securego/gosec/v2/cmd/gosec@v2.20.0 ./... - - go run honnef.co/go/tools/cmd/staticcheck@v0.5.1 ./... - - go run github.com/mgechev/revive@v1.3.4 -config=revive.toml ./... - - test: - cmds: - - go test -cover -coverprofile=coverage.out ./... - - go tool cover -func=coverage.out - - docs: - cmds: - - go run github.com/princjef/gomarkdoc/cmd/gomarkdoc@v1.1.0 -e ./... - diff --git a/v2/title.go b/v2/title.go deleted file mode 100644 index 5d4fd54..0000000 --- a/v2/title.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "reflect" - "strings" - "unicode" -) - -const ( - // nameTitle is the name of the title normalizer. - nameTitle = "title" -) - -// Title returns the value of the string with the first letter of each word in upper case. -func Title(value string) (string, error) { - var sb strings.Builder - begin := true - - for _, c := range value { - if unicode.IsLetter(c) { - if begin { - c = unicode.ToUpper(c) - begin = false - } else { - c = unicode.ToLower(c) - } - } else { - begin = true - } - - sb.WriteRune(c) - } - - return sb.String(), nil -} - -// reflectTitle returns the value of the string with the first letter of each word in upper case. -func reflectTitle(value reflect.Value) (reflect.Value, error) { - newValue, err := Title(value.Interface().(string)) - return reflect.ValueOf(newValue), err -} - -// makeTitle returns the title normalizer function. -func makeTitle(_ string) CheckFunc[reflect.Value] { - return reflectTitle -} diff --git a/v2/title_test.go b/v2/title_test.go deleted file mode 100644 index ad278d1..0000000 --- a/v2/title_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func TestTitle(t *testing.T) { - input := "the checker" - expected := "The Checker" - - actual, err := v2.Title(input) - if err != nil { - t.Fatal(err) - } - - if actual != expected { - t.Fatalf("actual %s expected %s", actual, expected) - } -} - -func TestReflectTitle(t *testing.T) { - type Book struct { - Chapter string `checkers:"title"` - } - - book := &Book{ - Chapter: "the checker", - } - - expected := "The Checker" - - errs, ok := v2.CheckStruct(book) - if !ok { - t.Fatalf("got unexpected errors %v", errs) - } - - if book.Chapter != expected { - t.Fatalf("actual %s expected %s", book.Chapter, expected) - } -} diff --git a/v2/trim_left.go b/v2/trim_left.go deleted file mode 100644 index 20e41d2..0000000 --- a/v2/trim_left.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "reflect" - "strings" -) - -const ( - // nameTrimLeft is the name of the trim left normalizer. - nameTrimLeft = "trim-left" -) - -// TrimLeft returns the value of the string with whitespace removed from the beginning. -func TrimLeft(value string) (string, error) { - return strings.TrimLeft(value, " \t"), nil -} - -// reflectTrimLeft returns the value of the string with whitespace removed from the beginning. -func reflectTrimLeft(value reflect.Value) (reflect.Value, error) { - newValue, err := TrimLeft(value.Interface().(string)) - return reflect.ValueOf(newValue), err -} - -// makeTrimLeft returns the trim left normalizer function. -func makeTrimLeft(_ string) CheckFunc[reflect.Value] { - return reflectTrimLeft -} diff --git a/v2/trim_left_test.go b/v2/trim_left_test.go deleted file mode 100644 index 3989789..0000000 --- a/v2/trim_left_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func TestTrimLeft(t *testing.T) { - input := " test " - expected := "test " - - actual, err := v2.TrimLeft(input) - if err != nil { - t.Fatal(err) - } - - if actual != expected { - t.Fatalf("actual %s expected %s", actual, expected) - } -} - -func TestReflectTrimLeft(t *testing.T) { - type Person struct { - Name string `checkers:"trim-left"` - } - - person := &Person{ - Name: " test ", - } - - expected := "test " - - errs, ok := v2.CheckStruct(person) - if !ok { - t.Fatalf("got unexpected errors %v", errs) - } - - if person.Name != expected { - t.Fatalf("actual %s expected %s", person.Name, expected) - } -} diff --git a/v2/trim_right.go b/v2/trim_right.go deleted file mode 100644 index aeae386..0000000 --- a/v2/trim_right.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "reflect" - "strings" -) - -const ( - // nameTrimRight is the name of the trim right normalizer. - nameTrimRight = "trim-right" -) - -// TrimRight returns the value of the string with whitespace removed from the end. -func TrimRight(value string) (string, error) { - return strings.TrimRight(value, " \t"), nil -} - -// reflectTrimRight returns the value of the string with whitespace removed from the end. -func reflectTrimRight(value reflect.Value) (reflect.Value, error) { - newValue, err := TrimRight(value.Interface().(string)) - return reflect.ValueOf(newValue), err -} - -// makeTrimRight returns the trim right normalizer function. -func makeTrimRight(_ string) CheckFunc[reflect.Value] { - return reflectTrimRight -} diff --git a/v2/trim_right_test.go b/v2/trim_right_test.go deleted file mode 100644 index e75eb45..0000000 --- a/v2/trim_right_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func TestTrimRight(t *testing.T) { - input := " test " - expected := " test" - - actual, err := v2.TrimRight(input) - if err != nil { - t.Fatal(err) - } - - if actual != expected { - t.Fatalf("actual %s expected %s", actual, expected) - } -} - -func TestReflectTrimRight(t *testing.T) { - type Person struct { - Name string `checkers:"trim-right"` - } - - person := &Person{ - Name: " test ", - } - - expected := " test" - - errs, ok := v2.CheckStruct(person) - if !ok { - t.Fatalf("got unexpected errors %v", errs) - } - - if person.Name != expected { - t.Fatalf("actual %s expected %s", person.Name, expected) - } -} diff --git a/v2/upper.go b/v2/upper.go deleted file mode 100644 index cd8ea86..0000000 --- a/v2/upper.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "reflect" - "strings" -) - -const ( - // nameUpper is the name of the upper normalizer. - nameUpper = "upper" -) - -// Upper maps all Unicode letters in the given value to their upper case. -func Upper(value string) (string, error) { - return strings.ToUpper(value), nil -} - -// reflectUpper maps all Unicode letters in the given value to their upper case. -func reflectUpper(value reflect.Value) (reflect.Value, error) { - newValue, err := Upper(value.Interface().(string)) - return reflect.ValueOf(newValue), err -} - -// makeUpper returns the upper normalizer function. -func makeUpper(_ string) CheckFunc[reflect.Value] { - return reflectUpper -} diff --git a/v2/upper_test.go b/v2/upper_test.go deleted file mode 100644 index d7fa127..0000000 --- a/v2/upper_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func TestUpper(t *testing.T) { - input := "checker" - expected := "CHECKER" - - actual, err := v2.Upper(input) - if err != nil { - t.Fatal(err) - } - - if actual != expected { - t.Fatalf("actual %s expected %s", actual, expected) - } -} - -func TestReflectUpper(t *testing.T) { - type Person struct { - Name string `checkers:"upper"` - } - - person := &Person{ - Name: "checker", - } - - expected := "CHECKER" - - errs, ok := v2.CheckStruct(person) - if !ok { - t.Fatalf("got unexpected errors %v", errs) - } - - if person.Name != expected { - t.Fatalf("actual %s expected %s", person.Name, expected) - } -} diff --git a/v2/url.go b/v2/url.go deleted file mode 100644 index 3219ae2..0000000 --- a/v2/url.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "net/url" - "reflect" -) - -const ( - // nameURL is the name of the URL check. - nameURL = "url" -) - -var ( - // ErrNotURL indicates that the given value is not a valid URL. - ErrNotURL = NewCheckError("NOT_URL") -) - -// IsURL checks if the value is a valid URL. -func IsURL(value string) (string, error) { - _, err := url.ParseRequestURI(value) - if err != nil { - return value, ErrNotURL - } - return value, nil -} - -// checkURL checks if the value is a valid URL. -func checkURL(value reflect.Value) (reflect.Value, error) { - _, err := IsURL(value.Interface().(string)) - return value, err -} - -// makeURL makes a checker function for the URL checker. -func makeURL(_ string) CheckFunc[reflect.Value] { - return checkURL -} diff --git a/v2/url_escape.go b/v2/url_escape.go deleted file mode 100644 index 439d91b..0000000 --- a/v2/url_escape.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "net/url" - "reflect" -) - -// nameURLEscape is the name of the URL escape normalizer. -const nameURLEscape = "url-escape" - -// URLEscape applies URL escaping to special characters. -func URLEscape(value string) (string, error) { - return url.QueryEscape(value), nil -} - -// reflectURLEscape applies URL escaping to special characters. -func reflectURLEscape(value reflect.Value) (reflect.Value, error) { - newValue, err := URLEscape(value.Interface().(string)) - return reflect.ValueOf(newValue), err -} - -// makeURLEscape returns the URL escape normalizer function. -func makeURLEscape(_ string) CheckFunc[reflect.Value] { - return reflectURLEscape -} diff --git a/v2/url_escape_test.go b/v2/url_escape_test.go deleted file mode 100644 index badc16c..0000000 --- a/v2/url_escape_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func TestURLEscape(t *testing.T) { - input := "param1/param2 = 1 + 2 & 3 + 4" - expected := "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4" - - actual, err := v2.URLEscape(input) - if err != nil { - t.Fatal(err) - } - - if actual != expected { - t.Fatalf("actual %s expected %s", actual, expected) - } -} - -func TestReflectURLEscape(t *testing.T) { - type Request struct { - Query string `checkers:"url-escape"` - } - - request := &Request{ - Query: "param1/param2 = 1 + 2 & 3 + 4", - } - - expected := "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4" - - errs, ok := v2.CheckStruct(request) - if !ok { - t.Fatalf("got unexpected errors %v", errs) - } - - if request.Query != expected { - t.Fatalf("actual %s expected %s", request.Query, expected) - } -} diff --git a/v2/url_test.go b/v2/url_test.go deleted file mode 100644 index d3777c1..0000000 --- a/v2/url_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "fmt" - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func ExampleIsURL() { - _, err := v2.IsURL("https://example.com") - if err != nil { - fmt.Println(err) - } -} - -func TestIsURLInvalid(t *testing.T) { - _, err := v2.IsURL("invalid-url") - if err == nil { - t.Fatal("expected error") - } -} - -func TestIsURLValid(t *testing.T) { - _, err := v2.IsURL("https://example.com") - if err != nil { - t.Fatal(err) - } -} - -func TestCheckURLNonString(t *testing.T) { - defer FailIfNoPanic(t, "expected panic") - - type Website struct { - Link int `checkers:"url"` - } - - website := &Website{} - - v2.CheckStruct(website) -} - -func TestCheckURLInvalid(t *testing.T) { - type Website struct { - Link string `checkers:"url"` - } - - website := &Website{ - Link: "invalid-url", - } - - _, ok := v2.CheckStruct(website) - if ok { - t.Fatal("expected error") - } -} - -func TestCheckURLValid(t *testing.T) { - type Website struct { - Link string `checkers:"url"` - } - - website := &Website{ - Link: "https://example.com", - } - - _, ok := v2.CheckStruct(website) - if !ok { - t.Fatal("expected valid") - } -} diff --git a/v2/url_unescape.go b/v2/url_unescape.go deleted file mode 100644 index c48045c..0000000 --- a/v2/url_unescape.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2 - -import ( - "net/url" - "reflect" -) - -// nameURLUnescape is the name of the URL unescape normalizer. -const nameURLUnescape = "url-unescape" - -// URLUnescape applies URL unescaping to special characters. -func URLUnescape(value string) (string, error) { - unescaped, err := url.QueryUnescape(value) - return unescaped, err -} - -// reflectURLUnescape applies URL unescaping to special characters. -func reflectURLUnescape(value reflect.Value) (reflect.Value, error) { - newValue, err := URLUnescape(value.Interface().(string)) - return reflect.ValueOf(newValue), err -} - -// makeURLUnescape returns the URL unescape normalizer function. -func makeURLUnescape(_ string) CheckFunc[reflect.Value] { - return reflectURLUnescape -} diff --git a/v2/url_unescape_test.go b/v2/url_unescape_test.go deleted file mode 100644 index 21b2c43..0000000 --- a/v2/url_unescape_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2023-2024 Onur Cinar. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. -// https://github.com/cinar/checker - -package v2_test - -import ( - "testing" - - v2 "github.com/cinar/checker/v2" -) - -func TestURLUnescape(t *testing.T) { - input := "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4" - expected := "param1/param2 = 1 + 2 & 3 + 4" - - actual, err := v2.URLUnescape(input) - if err != nil { - t.Fatal(err) - } - - if actual != expected { - t.Fatalf("actual %s expected %s", actual, expected) - } -} - -func TestReflectURLUnescape(t *testing.T) { - type Request struct { - Query string `checkers:"url-unescape"` - } - - request := &Request{ - Query: "param1%2Fparam2+%3D+1+%2B+2+%26+3+%2B+4", - } - - expected := "param1/param2 = 1 + 2 & 3 + 4" - - errs, ok := v2.CheckStruct(request) - if !ok { - t.Fatalf("got unexpected errors %v", errs) - } - - if request.Query != expected { - t.Fatalf("actual %s expected %s", request.Query, expected) - } -} From b14b02d03c23c34262b8ca7b650b2316bbc0e981 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Mon, 30 Dec 2024 12:58:02 -0800 Subject: [PATCH 38/41] Update example to include the checker module name. (#159) # Describe Request Update example to include the checker module name. # Change Type Documentation improvement. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d0dd36..f01025a 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ Error messages are generated using Golang template functions, allowing them to i ```golang // Custrom checker error containing the item name. -err := NewCheckErrorWithData( +err := checker.NewCheckErrorWithData( "NOT_FRUIT", map[string]interface{}{ "name": "abcd", From 21065d3d76c66b93635157746d37fd415fbb6e82 Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Mon, 30 Dec 2024 12:58:36 -0800 Subject: [PATCH 39/41] Updated the documentation for the CheckWithConfig function. (#160) # Describe Request Updated the documentation for the CheckWithConfig function. # Change Type Documentation improvement. --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index f01025a..395b858 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,18 @@ if err != nil { } ``` +The checkers and normalizers can also be provided through a config string. Here is an example: + +```golang +name := " Onur Cinar " + +name, err := checker.CheckWithConfig(name, "trim requied") +if err != nil { + // Handle validation error +} + +``` + ### Validating Individual User Input For simpler validation, you can call individual checker functions. Here is an example: From 1188cff9b49be09cfb00f1b752d0c33bef47763a Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Mon, 30 Dec 2024 22:10:35 -0800 Subject: [PATCH 40/41] Gte and Lte checks are added. (#161) # Describe Request Greater than or equal tp (Gte) and less than or equal to (Lte) checks are added. # Change Type New checks. --- DOC.md | 40 +++++++++++++- README.md | 4 +- gte.go | 67 +++++++++++++++++++++++ gte_test.go | 139 +++++++++++++++++++++++++++++++++++++++++++++++ locales/DOC.md | 2 + locales/en_us.go | 2 + lte.go | 67 +++++++++++++++++++++++ lte_test.go | 139 +++++++++++++++++++++++++++++++++++++++++++++++ maker.go | 2 + 9 files changed, 460 insertions(+), 2 deletions(-) create mode 100644 gte.go create mode 100644 gte_test.go create mode 100644 lte.go create mode 100644 lte_test.go diff --git a/DOC.md b/DOC.md index c56b2b6..129dd97 100644 --- a/DOC.md +++ b/DOC.md @@ -29,6 +29,7 @@ Package v2 Checker is a Go library for validating user input through checker rul - [func IsDiscoverCreditCard\(number string\) \(string, error\)](<#IsDiscoverCreditCard>) - [func IsEmail\(value string\) \(string, error\)](<#IsEmail>) - [func IsFQDN\(value string\) \(string, error\)](<#IsFQDN>) +- [func IsGte\[T cmp.Ordered\]\(value, n T\) \(T, error\)](<#IsGte>) - [func IsHex\(value string\) \(string, error\)](<#IsHex>) - [func IsIP\(value string\) \(string, error\)](<#IsIP>) - [func IsIPv4\(value string\) \(string, error\)](<#IsIPv4>) @@ -36,6 +37,7 @@ Package v2 Checker is a Go library for validating user input through checker rul - [func IsISBN\(value string\) \(string, error\)](<#IsISBN>) - [func IsJcbCreditCard\(number string\) \(string, error\)](<#IsJcbCreditCard>) - [func IsLUHN\(value string\) \(string, error\)](<#IsLUHN>) +- [func IsLte\[T cmp.Ordered\]\(value, n T\) \(T, error\)](<#IsLte>) - [func IsMAC\(value string\) \(string, error\)](<#IsMAC>) - [func IsMasterCardCreditCard\(number string\) \(string, error\)](<#IsMasterCardCreditCard>) - [func IsRegexp\(expression, value string\) \(string, error\)](<#IsRegexp>) @@ -80,6 +82,24 @@ const ( ## Variables + + +```go +var ( + // ErrGte indicates that the value is not greater than or equal to the given value. + ErrGte = NewCheckError("NOT_GTE") +) +``` + + + +```go +var ( + // ErrLte indicates that the value is not less than or equal to the given value. + ErrLte = NewCheckError("NOT_LTE") +) +``` + ```go @@ -707,6 +727,15 @@ func main() {

+ +## func [IsGte]() + +```go +func IsGte[T cmp.Ordered](value, n T) (T, error) +``` + +IsGte checks if the value is greater than or equal to the given value. + ## func [IsHex]() @@ -944,6 +973,15 @@ func main() {

+ +## func [IsLte]() + +```go +func IsLte[T cmp.Ordered](value, n T) (T, error) +``` + +IsLte checks if the value is less than or equal to the given value. + ## func [IsMAC]() @@ -1173,7 +1211,7 @@ func RegisterLocale(locale string, messages map[string]string) RegisterLocale registers the localized error messages for the given locale. -## func [RegisterMaker]() +## func [RegisterMaker]() ```go func RegisterMaker(name string, maker MakeCheckFunc) diff --git a/README.md b/README.md index 395b858..1389044 100644 --- a/README.md +++ b/README.md @@ -104,11 +104,13 @@ type Person struct { - [`digits`](DOC.md#IsDigits): Ensures the string contains only digits. - [`email`](DOC.md#IsEmail): Ensures the string is a valid email address. - [`fqdn`](DOC.md#IsFQDN): Ensures the string is a valid fully qualified domain name. -- [`hex`](DOC.md#IsHex): Ensures the string contains only hex digits. +- [`gte`](DOC.md#IsGte): Ensures the value is greater than or equal to the specified number. +- [`hex`](DOC.md#IsHex): Ensures the string contains only hexadecimal digits. - [`ip`](DOC.md#IsIP): Ensures the string is a valid IP address. - [`ipv4`](DOC.md#IsIPv4): Ensures the string is a valid IPv4 address. - [`ipv6`](DOC.md#IsIPv6): Ensures the string is a valid IPv6 address. - [`isbn`](DOC.md#IsISBN): Ensures the string is a valid ISBN. +- [`lte`](DOC.md#ISLte): Ensures the value is less than or equal to the specified number. - [`luhn`](DOC.md#IsLUHN): Ensures the string is a valid LUHN number. - [`mac`](DOC.md#IsMAC): Ensures the string is a valid MAC address. - [`max-len`](DOC.md#func-maxlen): Ensures the length of the given value (string, slice, or map) is at most n. diff --git a/gte.go b/gte.go new file mode 100644 index 0000000..1ae5fa5 --- /dev/null +++ b/gte.go @@ -0,0 +1,67 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "cmp" + "reflect" + "strconv" +) + +const ( + // nameGte is the name of the greater than or equal to check. + nameGte = "gte" +) + +var ( + // ErrGte indicates that the value is not greater than or equal to the given value. + ErrGte = NewCheckError("NOT_GTE") +) + +// IsGte checks if the value is greater than or equal to the given value. +func IsGte[T cmp.Ordered](value, n T) (T, error) { + if cmp.Compare(value, n) < 0 { + return value, newGteError(n) + } + + return value, nil +} + +// makeGte creates a greater than or equal to check function from a string parameter. +// Panics if the parameter cannot be parsed as a number. +func makeGte(params string) CheckFunc[reflect.Value] { + n, err := strconv.ParseFloat(params, 64) + if err != nil { + panic("unable to parse params as float") + } + + return func(value reflect.Value) (reflect.Value, error) { + v := reflect.Indirect(value) + + switch { + case v.CanInt(): + _, err := IsGte(float64(v.Int()), n) + return v, err + + case v.CanFloat(): + _, err := IsGte(v.Float(), n) + return v, err + + default: + panic("value is not numeric") + } + } +} + +// newGteError creates a new greater than or equal to error with the given value. +func newGteError[T cmp.Ordered](n T) error { + return NewCheckErrorWithData( + ErrGte.Code, + map[string]interface{}{ + "n": n, + }, + ) +} diff --git a/gte_test.go b/gte_test.go new file mode 100644 index 0000000..48df21f --- /dev/null +++ b/gte_test.go @@ -0,0 +1,139 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "errors" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestGteIntSuccess(t *testing.T) { + value := 4 + + result, err := v2.IsGte(value, 4) + if result != value { + t.Fatalf("result (%d) is not the original value (%d)", result, value) + } + + if err != nil { + t.Fatal(err) + } +} + +func TestGteIntError(t *testing.T) { + value := 4 + + result, err := v2.IsGte(value, 5) + if result != value { + t.Fatalf("result (%d) is not the original value (%d)", result, value) + } + + if err == nil { + t.Fatal("expected error") + } + + message := "Value cannot be less than 5." + + if err.Error() != message { + t.Fatalf("expected %s actual %s", message, err.Error()) + } +} + +func TestReflectGteIntError(t *testing.T) { + type Person struct { + Age int `checkers:"gte:18"` + } + + person := &Person{ + Age: 16, + } + + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatalf("expected errors") + } + + if !errors.Is(errs["Age"], v2.ErrGte) { + t.Fatalf("expected ErrGte") + } +} + +func TestReflectGteIntInvalidGte(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Age int `checkers:"gte:abcd"` + } + + person := &Person{ + Age: 16, + } + + v2.CheckStruct(person) +} + +func TestReflectGteIntInvalidType(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Age string `checkers:"gte:18"` + } + + person := &Person{ + Age: "18", + } + + v2.CheckStruct(person) +} + +func TestReflectGteFloatError(t *testing.T) { + type Person struct { + Weight float64 `checkers:"gte:165.0"` + } + + person := &Person{ + Weight: 150, + } + + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatalf("expected errors") + } + + if !errors.Is(errs["Weight"], v2.ErrGte) { + t.Fatalf("expected ErrGte") + } +} + +func TestReflectGteFloatInvalidGte(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Weight float64 `checkers:"gte:abcd"` + } + + person := &Person{ + Weight: 170, + } + + v2.CheckStruct(person) +} + +func TestReflectGteFloatInvalidType(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Weight string `checkers:"gte:165.0"` + } + + person := &Person{ + Weight: "170", + } + + v2.CheckStruct(person) +} diff --git a/locales/DOC.md b/locales/DOC.md index 88718f0..f34002c 100644 --- a/locales/DOC.md +++ b/locales/DOC.md @@ -40,11 +40,13 @@ var EnUSMessages = map[string]string{ "NOT_DIGITS": "Can only contain digits.", "NOT_EMAIL": "Not a valid email address.", "NOT_FQDN": "Not a fully qualified domain name (FQDN).", + "NOT_GTE": "Value cannot be less than {{ .n }}.", "NOT_HEX": "Can only contain hexadecimal characters.", "NOT_IP": "Not a valid IP address.", "NOT_IPV4": "Not a valid IPv4 address.", "NOT_IPV6": "Not a valid IPv6 address.", "NOT_ISBN": "Not a valid ISBN number.", + "NOT_LTE": "Value cannot be less than {{ .n }}.", "NOT_LUHN": "Not a valid LUHN number.", "NOT_MAC": "Not a valid MAC address.", "NOT_MAX_LEN": "Value cannot be greater than {{ .max }}.", diff --git a/locales/en_us.go b/locales/en_us.go index c35c55b..01cd1eb 100644 --- a/locales/en_us.go +++ b/locales/en_us.go @@ -14,11 +14,13 @@ var EnUSMessages = map[string]string{ "NOT_DIGITS": "Can only contain digits.", "NOT_EMAIL": "Not a valid email address.", "NOT_FQDN": "Not a fully qualified domain name (FQDN).", + "NOT_GTE": "Value cannot be less than {{ .n }}.", "NOT_HEX": "Can only contain hexadecimal characters.", "NOT_IP": "Not a valid IP address.", "NOT_IPV4": "Not a valid IPv4 address.", "NOT_IPV6": "Not a valid IPv6 address.", "NOT_ISBN": "Not a valid ISBN number.", + "NOT_LTE": "Value cannot be less than {{ .n }}.", "NOT_LUHN": "Not a valid LUHN number.", "NOT_MAC": "Not a valid MAC address.", "NOT_MAX_LEN": "Value cannot be greater than {{ .max }}.", diff --git a/lte.go b/lte.go new file mode 100644 index 0000000..884bcd8 --- /dev/null +++ b/lte.go @@ -0,0 +1,67 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "cmp" + "reflect" + "strconv" +) + +const ( + // nameLte is the name of the less than or equal to check. + nameLte = "lte" +) + +var ( + // ErrLte indicates that the value is not less than or equal to the given value. + ErrLte = NewCheckError("NOT_LTE") +) + +// IsLte checks if the value is less than or equal to the given value. +func IsLte[T cmp.Ordered](value, n T) (T, error) { + if cmp.Compare(value, n) > 0 { + return value, newLteError(n) + } + + return value, nil +} + +// makeLte creates a less than or equal to check function from a string parameter. +// Panics if the parameter cannot be parsed as a number. +func makeLte(params string) CheckFunc[reflect.Value] { + n, err := strconv.ParseFloat(params, 64) + if err != nil { + panic("unable to parse params as float") + } + + return func(value reflect.Value) (reflect.Value, error) { + v := reflect.Indirect(value) + + switch { + case v.CanInt(): + _, err := IsLte(float64(v.Int()), n) + return v, err + + case v.CanFloat(): + _, err := IsLte(v.Float(), n) + return v, err + + default: + panic("value is not numeric") + } + } +} + +// newLteError creates a new less than or equal to error with the given value. +func newLteError[T cmp.Ordered](n T) error { + return NewCheckErrorWithData( + ErrLte.Code, + map[string]interface{}{ + "n": n, + }, + ) +} diff --git a/lte_test.go b/lte_test.go new file mode 100644 index 0000000..d039f53 --- /dev/null +++ b/lte_test.go @@ -0,0 +1,139 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "errors" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func TestLteIntSuccess(t *testing.T) { + value := 4 + + result, err := v2.IsLte(value, 4) + if result != value { + t.Fatalf("result (%d) is not the original value (%d)", result, value) + } + + if err != nil { + t.Fatal(err) + } +} + +func TestLteIntError(t *testing.T) { + value := 6 + + result, err := v2.IsLte(value, 5) + if result != value { + t.Fatalf("result (%d) is not the original value (%d)", result, value) + } + + if err == nil { + t.Fatal("expected error") + } + + message := "Value cannot be less than 5." + + if err.Error() != message { + t.Fatalf("expected %s actual %s", message, err.Error()) + } +} + +func TestReflectLteIntError(t *testing.T) { + type Person struct { + Age int `checkers:"lte:18"` + } + + person := &Person{ + Age: 21, + } + + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatalf("expected errors") + } + + if !errors.Is(errs["Age"], v2.ErrLte) { + t.Fatalf("expected ErrLte") + } +} + +func TestReflectLteIntInvalidLte(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Age int `checkers:"lte:abcd"` + } + + person := &Person{ + Age: 16, + } + + v2.CheckStruct(person) +} + +func TestReflectLteIntInvalidType(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Age string `checkers:"lte:18"` + } + + person := &Person{ + Age: "18", + } + + v2.CheckStruct(person) +} + +func TestReflectLteFloatError(t *testing.T) { + type Person struct { + Weight float64 `checkers:"lte:165.0"` + } + + person := &Person{ + Weight: 170, + } + + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatalf("expected errors") + } + + if !errors.Is(errs["Weight"], v2.ErrLte) { + t.Fatalf("expected ErrLte") + } +} + +func TestReflectLteFloatInvalidLte(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Weight float64 `checkers:"lte:abcd"` + } + + person := &Person{ + Weight: 170, + } + + v2.CheckStruct(person) +} + +func TestReflectLteFloatInvalidType(t *testing.T) { + defer FailIfNoPanic(t, "expected panic") + + type Person struct { + Weight string `checkers:"lte:165.0"` + } + + person := &Person{ + Weight: "170", + } + + v2.CheckStruct(person) +} diff --git a/maker.go b/maker.go index 935d04b..ed1b529 100644 --- a/maker.go +++ b/maker.go @@ -23,6 +23,7 @@ var makers = map[string]MakeCheckFunc{ nameDigits: makeDigits, nameEmail: makeEmail, nameFQDN: makeFQDN, + nameGte: makeGte, nameHex: makeHex, nameHTMLEscape: makeHTMLEscape, nameHTMLUnescape: makeHTMLUnescape, @@ -31,6 +32,7 @@ var makers = map[string]MakeCheckFunc{ nameIPv6: makeIPv6, nameISBN: makeISBN, nameLower: makeLower, + nameLte: makeLte, nameLUHN: makeLUHN, nameMAC: makeMAC, nameMaxLen: makeMaxLen, From ea60113fa1f75da6d2aa8a00010bbbcc15448fee Mon Sep 17 00:00:00 2001 From: Onur Cinar Date: Fri, 3 Jan 2025 10:35:34 -0800 Subject: [PATCH 41/41] Time check is added. (#162) # Describe Request Time check is added. # Change Type New checker. --- DOC.md | 73 ++++++++++++++++++++++++++++++++++++++++- README.md | 1 + locales/DOC.md | 1 + locales/en_us.go | 1 + maker.go | 1 + time.go | 67 ++++++++++++++++++++++++++++++++++++++ time_test.go | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 time.go create mode 100644 time_test.go diff --git a/DOC.md b/DOC.md index 129dd97..24db858 100644 --- a/DOC.md +++ b/DOC.md @@ -41,6 +41,7 @@ Package v2 Checker is a Go library for validating user input through checker rul - [func IsMAC\(value string\) \(string, error\)](<#IsMAC>) - [func IsMasterCardCreditCard\(number string\) \(string, error\)](<#IsMasterCardCreditCard>) - [func IsRegexp\(expression, value string\) \(string, error\)](<#IsRegexp>) +- [func IsTime\(params, value string\) \(string, error\)](<#IsTime>) - [func IsURL\(value string\) \(string, error\)](<#IsURL>) - [func IsUnionPayCreditCard\(number string\) \(string, error\)](<#IsUnionPayCreditCard>) - [func IsVisaCreditCard\(number string\) \(string, error\)](<#IsVisaCreditCard>) @@ -268,6 +269,15 @@ var ( ) ``` + + +```go +var ( + // ErrTime indicates that the value is not a valid based on the given time format. + ErrTime = NewCheckError("NOT_TIME") +) +``` + ## func [Check]() @@ -1083,6 +1093,67 @@ func main() {

+ +## func [IsTime]() + +```go +func IsTime(params, value string) (string, error) +``` + +IsTime checks if the given value is a valid time based on the given layout. + +
Example +

+ + + +```go +package main + +import ( + v2 "github.com/cinar/checker/v2" +) + +func main() { + value := "2024-12-31" + + _, err := v2.IsTime("DateOnly", value) + if err != nil { + panic(err) + } +} +``` + +

+
+ +
Example (Custom) +

+ + + +```go +package main + +import ( + v2 "github.com/cinar/checker/v2" +) + +func main() { + rfc3339Layout := "2006-01-02T15:04:05Z07:00" + + value := "2024-12-31T10:20:00Z07:00" + + _, err := v2.IsTime(rfc3339Layout, value) + if err != nil { + panic(err) + } +} +``` + +

+
+ ## func [IsURL]() @@ -1211,7 +1282,7 @@ func RegisterLocale(locale string, messages map[string]string) RegisterLocale registers the localized error messages for the given locale. -## func [RegisterMaker]() +## func [RegisterMaker]() ```go func RegisterMaker(name string, maker MakeCheckFunc) diff --git a/README.md b/README.md index 1389044..fd03e2d 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,7 @@ type Person struct { - [`min-len`](DOC.md#func-minlen): Ensures the length of the given value (string, slice, or map) is at least n. - [`required`](DOC.md#func-required) Ensures the value is provided. - [`regexp`](DOC.md#func-makeregexpchecker) Ensured the string matches the pattern. +- [`time`](DOC.md#func-istime) Ensured the string matches the provided time layout. - [`url`](DOC.md#IsURL): Ensures the string is a valid URL. # Normalizers Provided diff --git a/locales/DOC.md b/locales/DOC.md index f34002c..0d7131f 100644 --- a/locales/DOC.md +++ b/locales/DOC.md @@ -51,6 +51,7 @@ var EnUSMessages = map[string]string{ "NOT_MAC": "Not a valid MAC address.", "NOT_MAX_LEN": "Value cannot be greater than {{ .max }}.", "NOT_MIN_LEN": "Value cannot be less than {{ .min }}.", + "NOT_TIME": "Not a valid time.", "REQUIRED": "Required value is missing.", "NOT_URL": "Not a valid URL.", } diff --git a/locales/en_us.go b/locales/en_us.go index 01cd1eb..c519a2c 100644 --- a/locales/en_us.go +++ b/locales/en_us.go @@ -25,6 +25,7 @@ var EnUSMessages = map[string]string{ "NOT_MAC": "Not a valid MAC address.", "NOT_MAX_LEN": "Value cannot be greater than {{ .max }}.", "NOT_MIN_LEN": "Value cannot be less than {{ .min }}.", + "NOT_TIME": "Not a valid time.", "REQUIRED": "Required value is missing.", "NOT_URL": "Not a valid URL.", } diff --git a/maker.go b/maker.go index ed1b529..5d0e1bc 100644 --- a/maker.go +++ b/maker.go @@ -39,6 +39,7 @@ var makers = map[string]MakeCheckFunc{ nameMinLen: makeMinLen, nameRegexp: makeRegexp, nameRequired: makeRequired, + nameTime: makeTime, nameTitle: makeTitle, nameTrimLeft: makeTrimLeft, nameTrimRight: makeTrimRight, diff --git a/time.go b/time.go new file mode 100644 index 0000000..747e611 --- /dev/null +++ b/time.go @@ -0,0 +1,67 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2 + +import ( + "reflect" + "time" +) + +const ( + // time is the name of the time format check. + nameTime = "time" +) + +var ( + // ErrTime indicates that the value is not a valid based on the given time format. + ErrTime = NewCheckError("NOT_TIME") +) + +// timeLayouts is a map of time layouts that can be used in the time check. +var timeLayouts = map[string]string{ + "Layout": time.Layout, + "ANSIC": time.ANSIC, + "UnixDate": time.UnixDate, + "RubyDate": time.RubyDate, + "RFC822": time.RFC822, + "RFC822Z": time.RFC822Z, + "RFC850": time.RFC850, + "RFC1123": time.RFC1123, + "RFC1123Z": time.RFC1123Z, + "RFC3339": time.RFC3339, + "RFC3339Nano": time.RFC3339Nano, + "Kitchen": time.Kitchen, + "Stamp": time.Stamp, + "StampMilli": time.StampMilli, + "StampMicro": time.StampMicro, + "StampNano": time.StampNano, + "DateTime": time.DateTime, + "DateOnly": time.DateOnly, + "TimeOnly": time.TimeOnly, +} + +// IsTime checks if the given value is a valid time based on the given layout. +func IsTime(params, value string) (string, error) { + layout, ok := timeLayouts[params] + if !ok { + layout = params + } + + _, err := time.Parse(layout, value) + if err != nil { + return value, ErrTime + } + + return value, nil +} + +// makeTime makes a time format check based on the given time layout. +func makeTime(params string) CheckFunc[reflect.Value] { + return func(value reflect.Value) (reflect.Value, error) { + _, err := IsTime(params, value.String()) + return value, err + } +} diff --git a/time_test.go b/time_test.go new file mode 100644 index 0000000..f6f462a --- /dev/null +++ b/time_test.go @@ -0,0 +1,84 @@ +// Copyright (c) 2023-2024 Onur Cinar. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. +// https://github.com/cinar/checker + +package v2_test + +import ( + "errors" + "testing" + + v2 "github.com/cinar/checker/v2" +) + +func ExampleIsTime() { + value := "2024-12-31" + + _, err := v2.IsTime("DateOnly", value) + if err != nil { + panic(err) + } +} + +func ExampleIsTime_custom() { + rfc3339Layout := "2006-01-02T15:04:05Z07:00" + + value := "2024-12-31T10:20:00Z07:00" + + _, err := v2.IsTime(rfc3339Layout, value) + if err != nil { + panic(err) + } +} + +func TestIsTimeSuccess(t *testing.T) { + value := "2024-12-31" + + result, err := v2.IsTime("DateOnly", value) + if result != value { + t.Fatalf("result (%s) is not the original value (%s)", result, value) + } + + if err != nil { + t.Fatal(err) + } +} + +func TestIsTimeError(t *testing.T) { + value := "2024-12-31" + + result, err := v2.IsTime("2006-02-01", value) + if result != value { + t.Fatalf("result (%s) is not the original value (%s)", result, value) + } + + if !errors.Is(err, v2.ErrTime) { + t.Fatalf("expected error %s actual %s", v2.ErrTime, err) + } + + message := "Not a valid time." + + if err.Error() != message { + t.Fatalf("expected %s actual %s", message, err.Error()) + } +} + +func TestStructTimeError(t *testing.T) { + type Person struct { + Birthday string `checkers:"time:DateOnly"` + } + + person := &Person{ + Birthday: "2024-31-12", + } + + errs, ok := v2.CheckStruct(person) + if ok { + t.Fatalf("expected errors") + } + + if !errors.Is(errs["Birthday"], v2.ErrTime) { + t.Fatalf("expected time error") + } +}