diff --git a/helper/validation/strings.go b/helper/validation/strings.go new file mode 100644 index 0000000000..f9fdec5dca --- /dev/null +++ b/helper/validation/strings.go @@ -0,0 +1,79 @@ +package validation + +import ( + "encoding/base64" + "fmt" + "strings" +) + +// StringIsNotEmpty is a ValidateFunc that ensures a string is not empty or consisting entirely of whitespace characters +func StringIsNotEmpty(i interface{}, k string) ([]string, []error) { + v, ok := i.(string) + if !ok { + return nil, []error{fmt.Errorf("expected type of %q to be string", k)} + } + + if v == "" { + return nil, []error{fmt.Errorf("expected %q to not be an empty string", k)} + } + + return nil, nil +} + +// StringIsNotEmpty is a ValidateFunc that ensures a string is not empty or consisting entirely of whitespace characters +func StringIsNotWhiteSpace(i interface{}, k string) ([]string, []error) { + v, ok := i.(string) + if !ok { + return nil, []error{fmt.Errorf("expected type of %q to be string", k)} + } + + if strings.TrimSpace(v) == "" { + return nil, []error{fmt.Errorf("expected %q to not be an empty string or whitespace", k)} + } + + return nil, nil +} + +// StringIsEmpty is a ValidateFunc that ensures a string has no characters +func StringIsEmpty(i interface{}, k string) ([]string, []error) { + v, ok := i.(string) + if !ok { + return nil, []error{fmt.Errorf("expected type of %q to be string", k)} + } + + if v != "" { + return nil, []error{fmt.Errorf("expected %q to be an empty string: got %v", k, v)} + } + + return nil, nil +} + +// StringIsEmpty is a ValidateFunc that ensures a string is composed of entirely whitespace +func StringIsWhiteSpace(i interface{}, k string) ([]string, []error) { + v, ok := i.(string) + if !ok { + return nil, []error{fmt.Errorf("expected type of %q to be string", k)} + } + + if strings.TrimSpace(v) != "" { + return nil, []error{fmt.Errorf("expected %q to be an empty string or whitespace: got %v", k, v)} + } + + return nil, nil +} + +// StringIsBase64 is a ValidateFunc that ensures a string can be parsed as Base64 +func StringIsBase64(i interface{}, k string) (warnings []string, errors []error) { + // Empty string is not allowed + if warnings, errors = StringIsNotEmpty(i, k); len(errors) > 0 { + return + } + + // NoEmptyStrings checks it is a string + v, _ := i.(string) + if _, err := base64.StdEncoding.DecodeString(v); err != nil { + errors = append(errors, fmt.Errorf("expected %q to be a base64 string, got %v", k, v)) + } + + return +} diff --git a/helper/validation/strings_test.go b/helper/validation/strings_test.go new file mode 100644 index 0000000000..05ce90a9f1 --- /dev/null +++ b/helper/validation/strings_test.go @@ -0,0 +1,276 @@ +package validation + +import ( + "testing" +) + +func TestValidationStringIsNotEmpty(t *testing.T) { + cases := map[string]struct { + Value interface{} + Error bool + }{ + "NotString": { + Value: 7, + Error: true, + }, + "Empty": { + Value: "", + Error: true, + }, + "SingleSpace": { + Value: " ", + Error: false, + }, + "MultipleSpaces": { + Value: " ", + Error: false, + }, + "NewLine": { + Value: "\n", + Error: false, + }, + "MultipleSymbols": { + Value: "-_-", + Error: false, + }, + "Sentence": { + Value: "Hello kt's sentence.", + Error: false, + }, + "StartsWithWhitespace": { + Value: " 7", + Error: false, + }, + "EndsWithWhitespace": { + Value: "7 ", + Error: false, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + _, errors := StringIsNotEmpty(tc.Value, tn) + + if len(errors) > 0 && !tc.Error { + t.Errorf("StringIsNotEmpty(%s) produced an unexpected error", tc.Value) + } else if len(errors) == 0 && tc.Error { + t.Errorf("StringIsNotEmpty(%s) did not error", tc.Value) + } + }) + } +} + +func TestValidationStringIsNotWhitespace(t *testing.T) { + cases := map[string]struct { + Value interface{} + Error bool + }{ + "NotString": { + Value: 7, + Error: true, + }, + "Empty": { + Value: "", + Error: true, + }, + "SingleSpace": { + Value: " ", + Error: true, + }, + "MultipleSpaces": { + Value: " ", + Error: true, + }, + "CarriageReturn": { + Value: "\r", + Error: true, + }, + "NewLine": { + Value: "\n", + Error: true, + }, + "Tab": { + Value: "\t", + Error: true, + }, + "FormFeed": { + Value: "\f", + Error: true, + }, + "VerticalTab": { + Value: "\v", + Error: true, + }, + "SingleChar": { + Value: "\v", + Error: true, + }, + "MultipleChars": { + Value: "-_-", + Error: false, + }, + "Sentence": { + Value: "Hello kt's sentence.", + Error: false, + }, + + "StartsWithWhitespace": { + Value: " 7", + Error: false, + }, + "EndsWithWhitespace": { + Value: "7 ", + Error: false, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + _, errors := StringIsNotWhiteSpace(tc.Value, tn) + + if len(errors) > 0 && !tc.Error { + t.Errorf("StringIsNotWhiteSpace(%s) produced an unexpected error", tc.Value) + } else if len(errors) == 0 && tc.Error { + t.Errorf("StringIsNotWhiteSpace(%s) did not error", tc.Value) + } + }) + } +} + +func TestValidationStringIsEmpty(t *testing.T) { + cases := map[string]struct { + Value interface{} + Error bool + }{ + "NotString": { + Value: 7, + Error: true, + }, + "Empty": { + Value: "", + Error: false, + }, + "SingleSpace": { + Value: " ", + Error: true, + }, + "MultipleSpaces": { + Value: " ", + Error: true, + }, + "Sentence": { + Value: "Hello kt's sentence.", + Error: true, + }, + + "StartsWithWhitespace": { + Value: " 7", + Error: true, + }, + "EndsWithWhitespace": { + Value: "7 ", + Error: true, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + _, errors := StringIsEmpty(tc.Value, tn) + + if len(errors) > 0 && !tc.Error { + t.Errorf("StringIsEmpty(%s) produced an unexpected error", tc.Value) + } else if len(errors) == 0 && tc.Error { + t.Errorf("StringIsEmpty(%s) did not error", tc.Value) + } + }) + } +} + +func TestValidationStringIsWhiteSpace(t *testing.T) { + cases := map[string]struct { + Value interface{} + Error bool + }{ + "NotString": { + Value: 7, + Error: true, + }, + "Empty": { + Value: "", + Error: false, + }, + "SingleSpace": { + Value: " ", + Error: false, + }, + "MultipleSpaces": { + Value: " ", + Error: false, + }, + "MultipleWhitespace": { + Value: " \t\n\f ", + Error: false, + }, + "Sentence": { + Value: "Hello kt's sentence.", + Error: true, + }, + + "StartsWithWhitespace": { + Value: " 7", + Error: true, + }, + "EndsWithWhitespace": { + Value: "7 ", + Error: true, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + _, errors := StringIsWhiteSpace(tc.Value, tn) + + if len(errors) > 0 && !tc.Error { + t.Errorf("StringIsWhiteSpace(%s) produced an unexpected error", tc.Value) + } else if len(errors) == 0 && tc.Error { + t.Errorf("StringIsWhiteSpace(%s) did not error", tc.Value) + } + }) + } +} + +func TestValidationStringIsBase64(t *testing.T) { + cases := map[string]struct { + Value interface{} + Error bool + }{ + "NotString": { + Value: 7, + Error: true, + }, + "Empty": { + Value: "", + Error: true, + }, + "NotBase64": { + Value: "Do'h!", + Error: true, + }, + "Base64": { + Value: "RG8naCE=", + Error: false, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + _, errors := StringIsBase64(tc.Value, tn) + + if len(errors) > 0 && !tc.Error { + t.Errorf("StringIsBase64(%s) produced an unexpected error", tc.Value) + } else if len(errors) == 0 && tc.Error { + t.Errorf("StringIsBase64(%s) did not error", tc.Value) + } + }) + } +} diff --git a/helper/validation/uuid.go b/helper/validation/uuid.go new file mode 100644 index 0000000000..1e4939b98e --- /dev/null +++ b/helper/validation/uuid.go @@ -0,0 +1,26 @@ +package validation + +import ( + "fmt" + "regexp" + + "github.com/hashicorp/go-uuid" +) + +// UUIDRegExp is a Regular Expression that can be used to validate UUIDs +var UUIDRegExp = regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$") + +// UUID is a ValidateFunc that ensures a string can be parsed as UUID +func UUID(i interface{}, k string) (warnings []string, errors []error) { + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected type of %q to be string", k)) + return + } + + if _, err := uuid.ParseUUID(v); err != nil { + errors = append(errors, fmt.Errorf("expected %q to be a valid UUID, got %v", k, v)) + } + + return warnings, errors +} diff --git a/helper/validation/uuid_test.go b/helper/validation/uuid_test.go new file mode 100644 index 0000000000..b3960779fc --- /dev/null +++ b/helper/validation/uuid_test.go @@ -0,0 +1,45 @@ +package validation + +import ( + "testing" +) + +func TestValidationUUID(t *testing.T) { + cases := map[string]struct { + Value interface{} + Error bool + }{ + "NotString": { + Value: 7, + Error: true, + }, + "Empty": { + Value: "", + Error: true, + }, + "InvalidUuid": { + Value: "00000000-0000-123-0000-000000000000", + Error: true, + }, + "ValidUuidWithOutDashs": { + Value: "12345678123412341234123456789012", + Error: true, + }, + "ValidUuid": { + Value: "00000000-0000-0000-0000-000000000000", + Error: false, + }, + } + + for tn, tc := range cases { + t.Run(tn, func(t *testing.T) { + _, errors := UUID(tc.Value, tn) + + if len(errors) > 0 && !tc.Error { + t.Errorf("UUID(%s) produced an unexpected error", tc.Value) + } else if len(errors) == 0 && tc.Error { + t.Errorf("UUID(%s) did not error", tc.Value) + } + }) + } +}