Add wsl linter and fix

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-05-19 14:51:08 +04:00
parent eedfdcecc8
commit 9156d6a16a
882 changed files with 6068 additions and 574 deletions

View File

@@ -44,6 +44,7 @@ func TestCheckEach_NonEmptyTypedSlice(t *testing.T) {
slice := []CustomType{"abc", "def"}
callCount := 0
v.CheckEach(slice, "items", func(index int, item any) {
callCount++
// Verify the item is the correct type
@@ -51,9 +52,11 @@ func TestCheckEach_NonEmptyTypedSlice(t *testing.T) {
if !ok {
t.Errorf("expected CustomType, got %T", item)
}
if index == 0 && str != "abc" {
t.Errorf("expected 'abc', got %s", str)
}
if index == 1 && str != "def" {
t.Errorf("expected 'def', got %s", str)
}
@@ -92,12 +95,15 @@ func TestCheckEach_PointerToNonEmptySlice(t *testing.T) {
ptrToSlice := &slice
callCount := 0
v.CheckEach(ptrToSlice, "items", func(index int, item any) {
callCount++
str, ok := item.(CustomType)
if !ok {
t.Errorf("expected CustomType, got %T", item)
}
expectedValues := []CustomType{"abc", "def", "ghi"}
if str != expectedValues[index] {
t.Errorf("at index %d: expected %s, got %s", index, expectedValues[index], str)
@@ -153,12 +159,15 @@ func TestCheckEach_DoublePointerToSlice(t *testing.T) {
doublePtrToSlice := &ptrToSlice
callCount := 0
v.CheckEach(doublePtrToSlice, "items", func(index int, item any) {
callCount++
str, ok := item.(CustomType)
if !ok {
t.Errorf("expected CustomType, got %T", item)
}
expectedValues := []CustomType{"x", "y"}
if str != expectedValues[index] {
t.Errorf("at index %d: expected %s, got %s", index, expectedValues[index], str)
@@ -192,9 +201,11 @@ func TestCheckEach_NonSliceValue(t *testing.T) {
if len(errors) != 1 {
t.Errorf("expected 1 error, got %d", len(errors))
}
if errors[0].Code != ErrorCodeInvalidFormat {
t.Errorf("expected error code %s, got %s", ErrorCodeInvalidFormat, errors[0].Code)
}
if errors[0].Message != "expected a slice" {
t.Errorf("expected message 'expected a slice', got '%s'", errors[0].Message)
}

View File

@@ -62,6 +62,7 @@ func TestDoublePointerValidation(t *testing.T) {
t.Run("optional double pointer - nil outer pointer", func(t *testing.T) {
v := validator.New()
var doublePtr **string = nil
v.Check(doublePtr, "name", validator.NotEmpty(), validator.MaxLen(1000))
@@ -73,7 +74,9 @@ func TestDoublePointerValidation(t *testing.T) {
t.Run("optional double pointer - nil inner pointer", func(t *testing.T) {
v := validator.New()
var ptr *string = nil
doublePtr := &ptr
v.Check(doublePtr, "name", validator.NotEmpty(), validator.MaxLen(1000))

View File

@@ -57,6 +57,7 @@ func (ve ValidationErrors) Error() string {
for _, err := range ve {
messages = append(messages, err.Error())
}
return strings.Join(messages, "; ")
}
@@ -69,26 +70,31 @@ func (ve ValidationErrors) Fields() []string {
for _, err := range ve {
fields = append(fields, err.Field)
}
return fields
}
func (ve ValidationErrors) ByField(field string) ValidationErrors {
var errors ValidationErrors
for _, err := range ve {
if err.Field == field {
errors = append(errors, err)
}
}
return errors
}
func (ve ValidationErrors) ByCode(code ErrorCode) ValidationErrors {
var errors ValidationErrors
for _, err := range ve {
if err.Code == code {
errors = append(errors, err)
}
}
return errors
}
@@ -96,6 +102,7 @@ func (ve ValidationErrors) First() *ValidationError {
if len(ve) == 0 {
return nil
}
return ve[0]
}

View File

@@ -42,6 +42,7 @@ func (v *Validator) Check(value any, field string, validators ...ValidatorFunc)
val = val.Elem()
actualValue = val.Interface()
}
// If we ended up with a nil pointer at any level, set actualValue to nil
if val.Kind() == reflect.Pointer && val.IsNil() {
actualValue = nil
@@ -69,6 +70,7 @@ func (v *Validator) CheckEach(items any, field string, fn func(index int, item a
for i, item := range slice {
fn(i, item)
}
return
}
@@ -78,6 +80,7 @@ func (v *Validator) CheckEach(items any, field string, fn func(index int, item a
if val.IsNil() {
return
}
val = val.Elem()
}
@@ -88,6 +91,7 @@ func (v *Validator) CheckEach(items any, field string, fn func(index int, item a
Message: "expected a slice",
Value: items,
})
return
}
@@ -100,6 +104,7 @@ func (v *Validator) Error() error {
if len(v.errors) == 0 {
return nil
}
return v.errors
}
@@ -118,6 +123,7 @@ func dereferenceValue(value any) (any, bool) {
if val.IsNil() {
return nil, true
}
val = val.Elem()
}

View File

@@ -23,6 +23,7 @@ func BenchmarkValidate_SingleField(b *testing.B) {
email := "test@example.com"
b.ResetTimer()
for i := 0; i < b.N; i++ {
v := New()
v.Check(&email, "email", Required(), NotEmpty())
@@ -35,6 +36,7 @@ func BenchmarkValidate_MultipleFields(b *testing.B) {
age := 25
b.ResetTimer()
for i := 0; i < b.N; i++ {
v := New()
v.Check(&email, "email", Required(), NotEmpty())
@@ -47,6 +49,7 @@ func BenchmarkValidate_OptionalField(b *testing.B) {
var website *string
b.ResetTimer()
for i := 0; i < b.N; i++ {
v := New()
v.Check(website, "website", URL())
@@ -58,6 +61,7 @@ func BenchmarkURL(b *testing.B) {
validator := URL()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = validator(&urlStr)
}
@@ -68,6 +72,7 @@ func BenchmarkMinLen(b *testing.B) {
validator := MinLen(5)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = validator(&str)
}
@@ -78,6 +83,7 @@ func BenchmarkMin(b *testing.B) {
validator := Min(18)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = validator(&num)
}
@@ -88,6 +94,7 @@ func BenchmarkNotEmpty(b *testing.B) {
validator := NotEmpty()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = validator(&str)
}
@@ -97,9 +104,11 @@ func BenchmarkValidate_WithErrors(b *testing.B) {
email := ""
b.ResetTimer()
for i := 0; i < b.N; i++ {
v := New()
v.Check(&email, "email", Required(), NotEmpty())
if v.Error() == nil {
b.Fatal("expected validation error")
}
@@ -139,6 +148,7 @@ func BenchmarkValidate_ComplexForm(b *testing.B) {
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
v := New()
v.Check(&user.Email, "email", Required(), NotEmpty())
@@ -155,6 +165,7 @@ func BenchmarkAfter(b *testing.B) {
validator := After(now)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = validator(&future)
}
@@ -166,6 +177,7 @@ func BenchmarkBefore(b *testing.B) {
validator := Before(now)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = validator(&past)
}
@@ -176,6 +188,7 @@ func BenchmarkDomain(b *testing.B) {
validator := Domain()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = validator(&str)
}
@@ -186,6 +199,7 @@ func BenchmarkHTTPSUrl(b *testing.B) {
validator := HTTPSUrl()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = validator(&str)
}

View File

@@ -198,6 +198,7 @@ func TestDuplicateValidators(t *testing.T) {
if errors[0].Message != "must be at least 5 characters" {
t.Errorf("unexpected first error: %s", errors[0].Message)
}
if errors[1].Message != "must be at least 5 characters" {
t.Errorf("unexpected second error: %s", errors[1].Message)
}
@@ -238,6 +239,7 @@ func TestDuplicateValidators(t *testing.T) {
if errors[0].Message != "must be at least 5 characters" {
t.Errorf("unexpected first error: %s", errors[0].Message)
}
if errors[1].Message != "must be at least 10 characters" {
t.Errorf("unexpected second error: %s", errors[1].Message)
}
@@ -250,6 +252,7 @@ func TestStandardErrorPattern(t *testing.T) {
v := New()
v.Check(&email, "email", Required(), NotEmpty())
v.Check(&password, "password", Required(), MinLen(8))
return v.Error()
}

View File

@@ -70,6 +70,7 @@ func NoDuplicates() ValidatorFunc {
if _, ok := seen[elem]; ok {
return newValidationError(ErrorCodeInvalidFormat, "must not contain duplicates")
}
seen[elem] = struct{}{}
}

View File

@@ -30,6 +30,7 @@ func TestOptionalByDefault(t *testing.T) {
t.Run("nil pointer skips validation by default", func(t *testing.T) {
v := New()
var str *string
v.Check(str, "field", MinLen(5))
@@ -80,6 +81,7 @@ func TestOptionalByDefault(t *testing.T) {
t.Run("Required() validates nil values", func(t *testing.T) {
v := New()
var str *string
v.Check(str, "field", Required())
@@ -92,6 +94,7 @@ func TestOptionalByDefault(t *testing.T) {
func TestRequired(t *testing.T) {
t.Run("valid string", func(t *testing.T) {
str := "hello"
err := Required()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -100,6 +103,7 @@ func TestRequired(t *testing.T) {
t.Run("empty string", func(t *testing.T) {
str := ""
err := Required()(&str)
if err == nil {
t.Fatal("expected validation error")
@@ -110,6 +114,7 @@ func TestRequired(t *testing.T) {
t.Run("whitespace string", func(t *testing.T) {
str := " "
err := Required()(&str)
if err == nil {
t.Error("expected validation error for whitespace")
@@ -118,6 +123,7 @@ func TestRequired(t *testing.T) {
t.Run("nil string pointer", func(t *testing.T) {
var str *string
err := Required()(str)
if err == nil {
t.Error("expected validation error for nil pointer")
@@ -126,6 +132,7 @@ func TestRequired(t *testing.T) {
t.Run("valid string pointer", func(t *testing.T) {
str := "hello"
err := Required()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -141,6 +148,7 @@ func TestRequired(t *testing.T) {
t.Run("zero int", func(t *testing.T) {
num := 0
err := Required()(&num)
if err != nil {
t.Errorf("expected no error for zero int, got: %v", err)
@@ -149,6 +157,7 @@ func TestRequired(t *testing.T) {
t.Run("positive int", func(t *testing.T) {
num := 42
err := Required()(&num)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -157,6 +166,7 @@ func TestRequired(t *testing.T) {
t.Run("nil int pointer", func(t *testing.T) {
var num *int
err := Required()(num)
if err == nil {
t.Error("expected validation error for nil int pointer")
@@ -165,6 +175,7 @@ func TestRequired(t *testing.T) {
t.Run("valid int pointer", func(t *testing.T) {
num := 42
err := Required()(&num)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -173,6 +184,7 @@ func TestRequired(t *testing.T) {
t.Run("empty slice", func(t *testing.T) {
slice := []any{}
err := Required()(slice)
if err == nil {
t.Error("expected validation error for empty slice")
@@ -181,6 +193,7 @@ func TestRequired(t *testing.T) {
t.Run("non-empty slice", func(t *testing.T) {
slice := []any{1, 2, 3}
err := Required()(slice)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -189,6 +202,7 @@ func TestRequired(t *testing.T) {
t.Run("empty string slice", func(t *testing.T) {
slice := []string{}
err := Required()(slice)
if err == nil {
t.Fatal("expected validation error for empty []string slice")
@@ -199,6 +213,7 @@ func TestRequired(t *testing.T) {
t.Run("non-empty string slice", func(t *testing.T) {
slice := []string{"a", "b", "c"}
err := Required()(slice)
if err != nil {
t.Errorf("expected no error for non-empty []string, got: %v", err)
@@ -207,6 +222,7 @@ func TestRequired(t *testing.T) {
t.Run("empty int slice", func(t *testing.T) {
slice := []int{}
err := Required()(slice)
if err == nil {
t.Fatal("expected validation error for empty []int slice")
@@ -217,6 +233,7 @@ func TestRequired(t *testing.T) {
t.Run("non-empty int slice", func(t *testing.T) {
slice := []int{1, 2, 3}
err := Required()(slice)
if err != nil {
t.Errorf("expected no error for non-empty []int, got: %v", err)
@@ -227,7 +244,9 @@ func TestRequired(t *testing.T) {
type CustomType struct {
ID int
}
slice := []CustomType{}
err := Required()(slice)
if err == nil {
t.Fatal("expected validation error for empty custom type slice")
@@ -240,7 +259,9 @@ func TestRequired(t *testing.T) {
type CustomType struct {
ID int
}
slice := []CustomType{{ID: 1}, {ID: 2}}
err := Required()(slice)
if err != nil {
t.Errorf("expected no error for non-empty custom type slice, got: %v", err)
@@ -249,6 +270,7 @@ func TestRequired(t *testing.T) {
t.Run("empty pointer slice", func(t *testing.T) {
slice := []*string{}
err := Required()(slice)
if err == nil {
t.Error("expected validation error for empty []*string slice")
@@ -258,6 +280,7 @@ func TestRequired(t *testing.T) {
t.Run("non-empty pointer slice", func(t *testing.T) {
str1, str2 := "a", "b"
slice := []*string{&str1, &str2}
err := Required()(slice)
if err != nil {
t.Errorf("expected no error for non-empty []*string, got: %v", err)
@@ -268,6 +291,7 @@ func TestRequired(t *testing.T) {
func TestNoDuplicates(t *testing.T) {
t.Run("nil slice", func(t *testing.T) {
var slice []string
err := NoDuplicates()(slice)
if err != nil {
t.Errorf("expected no error for nil slice, got: %v", err)
@@ -276,6 +300,7 @@ func TestNoDuplicates(t *testing.T) {
t.Run("empty slice", func(t *testing.T) {
slice := []string{}
err := NoDuplicates()(slice)
if err != nil {
t.Errorf("expected no error for empty slice, got: %v", err)
@@ -284,6 +309,7 @@ func TestNoDuplicates(t *testing.T) {
t.Run("unique strings", func(t *testing.T) {
slice := []string{"a", "b", "c"}
err := NoDuplicates()(slice)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -292,6 +318,7 @@ func TestNoDuplicates(t *testing.T) {
t.Run("duplicate strings", func(t *testing.T) {
slice := []string{"a", "b", "a"}
err := NoDuplicates()(slice)
if err == nil {
t.Fatal("expected validation error for duplicates")
@@ -302,6 +329,7 @@ func TestNoDuplicates(t *testing.T) {
t.Run("unique ints", func(t *testing.T) {
slice := []int{1, 2, 3}
err := NoDuplicates()(slice)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -310,6 +338,7 @@ func TestNoDuplicates(t *testing.T) {
t.Run("duplicate ints", func(t *testing.T) {
slice := []int{1, 2, 1}
err := NoDuplicates()(slice)
if err == nil {
t.Fatal("expected validation error for duplicates")
@@ -318,6 +347,7 @@ func TestNoDuplicates(t *testing.T) {
t.Run("non-comparable elements", func(t *testing.T) {
slice := []map[string]string{{"a": "b"}}
err := NoDuplicates()(slice)
if err == nil {
t.Fatal("expected validation error for non-comparable elements")

View File

@@ -119,6 +119,7 @@ func GID(entityTypes ...uint16) ValidatorFunc {
if v == nil {
return nil
}
gidValue = *v
default:
return newValidationError(ErrorCodeInvalidGID, "value must be a GID")
@@ -126,6 +127,7 @@ func GID(entityTypes ...uint16) ValidatorFunc {
if len(entityTypes) > 0 {
parsedEntityType := gidValue.EntityType()
valid := slices.Contains(entityTypes, parsedEntityType)
if !valid {
return newValidationError(ErrorCodeInvalidGID, "GID has invalid entity type")

View File

@@ -44,6 +44,7 @@ func TestURL(t *testing.T) {
if (err != nil) != tt.wantError {
t.Errorf("URL() error = %v, wantError %v", err, tt.wantError)
}
if err != nil && err.Code != ErrorCodeInvalidURL {
t.Errorf("Expected error code %s, got %s", ErrorCodeInvalidURL, err.Code)
}
@@ -54,6 +55,7 @@ func TestURL(t *testing.T) {
func TestHTTPSUrl(t *testing.T) {
t.Run("valid https URL", func(t *testing.T) {
str := "https://example.com"
err := HTTPSUrl()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -62,6 +64,7 @@ func TestHTTPSUrl(t *testing.T) {
t.Run("valid https URL with path", func(t *testing.T) {
str := "https://example.com/path/to/resource"
err := HTTPSUrl()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -70,6 +73,7 @@ func TestHTTPSUrl(t *testing.T) {
t.Run("valid https URL with query", func(t *testing.T) {
str := "https://api.example.com/v1/users?page=1"
err := HTTPSUrl()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -78,10 +82,12 @@ func TestHTTPSUrl(t *testing.T) {
t.Run("invalid - http scheme", func(t *testing.T) {
str := "http://example.com"
err := HTTPSUrl()(&str)
if err == nil {
t.Fatal("expected validation error for http")
}
if err.Message != "URL must use https scheme" {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -89,6 +95,7 @@ func TestHTTPSUrl(t *testing.T) {
t.Run("invalid - ftp scheme", func(t *testing.T) {
str := "ftp://example.com"
err := HTTPSUrl()(&str)
if err == nil {
t.Error("expected validation error for ftp")
@@ -97,6 +104,7 @@ func TestHTTPSUrl(t *testing.T) {
t.Run("invalid - no scheme", func(t *testing.T) {
str := "example.com"
err := HTTPSUrl()(&str)
if err == nil {
t.Error("expected validation error for missing scheme")
@@ -105,6 +113,7 @@ func TestHTTPSUrl(t *testing.T) {
t.Run("invalid - no host", func(t *testing.T) {
str := "https://"
err := HTTPSUrl()(&str)
if err == nil {
t.Error("expected validation error for missing host")
@@ -113,6 +122,7 @@ func TestHTTPSUrl(t *testing.T) {
t.Run("empty string", func(t *testing.T) {
str := ""
err := HTTPSUrl()(&str)
if err != nil {
t.Errorf("expected no error for empty string, got: %v", err)
@@ -121,6 +131,7 @@ func TestHTTPSUrl(t *testing.T) {
t.Run("nil pointer", func(t *testing.T) {
var str *string
err := HTTPSUrl()(str)
if err != nil {
t.Errorf("expected no error for nil, got: %v", err)
@@ -157,6 +168,7 @@ func TestOrigin(t *testing.T) {
if (err != nil) != tt.wantError {
t.Errorf("Origin() error = %v, wantError %v", err, tt.wantError)
}
if err != nil && err.Code != ErrorCodeInvalidFormat {
t.Errorf("Expected error code %s, got %s", ErrorCodeInvalidFormat, err.Code)
}
@@ -200,6 +212,7 @@ func TestSlug(t *testing.T) {
if (err != nil) != tt.wantError {
t.Errorf("Slug(%d) error = %v, wantError %v", tt.maxLen, err, tt.wantError)
}
if err != nil && tt.wantCode != "" && err.Code != tt.wantCode {
t.Errorf("Expected error code %s, got %s", tt.wantCode, err.Code)
}
@@ -210,6 +223,7 @@ func TestSlug(t *testing.T) {
func TestDomain(t *testing.T) {
t.Run("valid domain", func(t *testing.T) {
str := "example.com"
err := Domain()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -218,6 +232,7 @@ func TestDomain(t *testing.T) {
t.Run("valid subdomain", func(t *testing.T) {
str := "api.example.com"
err := Domain()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -226,6 +241,7 @@ func TestDomain(t *testing.T) {
t.Run("valid nested subdomain", func(t *testing.T) {
str := "api.v1.example.com"
err := Domain()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -234,6 +250,7 @@ func TestDomain(t *testing.T) {
t.Run("valid domain with hyphens", func(t *testing.T) {
str := "my-api.example-site.com"
err := Domain()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -242,6 +259,7 @@ func TestDomain(t *testing.T) {
t.Run("single word domain", func(t *testing.T) {
str := "localhost"
err := Domain()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -250,6 +268,7 @@ func TestDomain(t *testing.T) {
t.Run("invalid - starts with hyphen", func(t *testing.T) {
str := "-example.com"
err := Domain()(&str)
if err == nil {
t.Error("expected validation error for domain starting with hyphen")
@@ -258,6 +277,7 @@ func TestDomain(t *testing.T) {
t.Run("invalid - ends with hyphen", func(t *testing.T) {
str := "example-.com"
err := Domain()(&str)
if err == nil {
t.Error("expected validation error for domain ending with hyphen")
@@ -266,6 +286,7 @@ func TestDomain(t *testing.T) {
t.Run("invalid - contains underscore", func(t *testing.T) {
str := "example_site.com"
err := Domain()(&str)
if err == nil {
t.Error("expected validation error for underscore")
@@ -274,6 +295,7 @@ func TestDomain(t *testing.T) {
t.Run("invalid - contains spaces", func(t *testing.T) {
str := "example site.com"
err := Domain()(&str)
if err == nil {
t.Error("expected validation error for spaces")
@@ -282,6 +304,7 @@ func TestDomain(t *testing.T) {
t.Run("invalid - empty label", func(t *testing.T) {
str := "example..com"
err := Domain()(&str)
if err == nil {
t.Error("expected validation error for empty label")
@@ -290,10 +313,12 @@ func TestDomain(t *testing.T) {
t.Run("invalid - too long", func(t *testing.T) {
str := strings.Repeat("a", 254)
err := Domain()(&str)
if err == nil {
t.Fatal("expected validation error for domain too long")
}
if err.Message != "domain name too long (max 253 characters)" {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -301,6 +326,7 @@ func TestDomain(t *testing.T) {
t.Run("empty string", func(t *testing.T) {
str := ""
err := Domain()(&str)
if err != nil {
t.Errorf("expected no error for empty string, got: %v", err)
@@ -309,6 +335,7 @@ func TestDomain(t *testing.T) {
t.Run("nil pointer", func(t *testing.T) {
var str *string
err := Domain()(str)
if err != nil {
t.Errorf("expected no error for nil, got: %v", err)
@@ -347,9 +374,11 @@ func TestGID(t *testing.T) {
if err == nil {
t.Fatal("expected validation error for wrong entity type")
}
if err.Code != ErrorCodeInvalidGID {
t.Errorf("expected error code %s, got %s", ErrorCodeInvalidGID, err.Code)
}
if err.Message != "GID has invalid entity type" {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -371,6 +400,7 @@ func TestGID(t *testing.T) {
t.Run("nil GID pointer", func(t *testing.T) {
var gidPtr *gid.GID
err := GID()(gidPtr)
if err != nil {
t.Errorf("expected no error for nil GID pointer, got: %v", err)
@@ -396,6 +426,7 @@ func TestGID(t *testing.T) {
if err == nil {
t.Fatal("expected validation error for non-GID type")
}
if err.Message != "value must be a GID" {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -406,6 +437,7 @@ func TestGID(t *testing.T) {
if err == nil {
t.Fatal("expected validation error for string type")
}
if err.Message != "value must be a GID" {
t.Errorf("unexpected error message: %s", err.Message)
}

View File

@@ -25,6 +25,7 @@ func Min(min int) ValidatorFunc {
}
var num int
switch v := actualValue.(type) {
case int:
num = v
@@ -56,6 +57,7 @@ func Max(max int) ValidatorFunc {
}
var num int
switch v := actualValue.(type) {
case int:
num = v

View File

@@ -30,16 +30,20 @@ func ProseMirrorDocumentContent() ValidatorFunc {
if isNil {
return nil
}
s, ok := actualValue.(string)
if !ok {
return newValidationError(ErrorCodeInvalidFormat, "value must be a string")
}
if strings.TrimSpace(s) == "" {
return nil
}
if err := prosemirror.ValidateDocumentContentJSON(s); err != nil {
return newValidationError(ErrorCodeInvalidFormat, err.Error())
}
return nil
}
}
@@ -55,23 +59,28 @@ func ProseMirrorDocumentMaxTextLength(maxLength int) ValidatorFunc {
if isNil {
return nil
}
s, ok := actualValue.(string)
if !ok {
return newValidationError(ErrorCodeInvalidFormat, "value must be a string")
}
if strings.TrimSpace(s) == "" {
return nil
}
n, err := prosemirror.Parse(s)
if err != nil {
return nil
}
if n.TextLength() > maxLength {
return newValidationError(
ErrorCodeTooLong,
fmt.Sprintf("text content must be at most %d characters", maxLength),
)
}
return nil
}
}

View File

@@ -40,6 +40,7 @@ func TestProseMirrorDocumentContent(t *testing.T) {
}
fn := ProseMirrorDocumentContent()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
@@ -48,6 +49,7 @@ func TestProseMirrorDocumentContent(t *testing.T) {
if (err != nil) != tt.wantError {
t.Errorf("ProseMirrorDocumentContent() error = %v, wantError %v", err, tt.wantError)
}
if err != nil && err.Code != ErrorCodeInvalidFormat {
t.Errorf("expected code %s, got %s", ErrorCodeInvalidFormat, err.Code)
}
@@ -82,6 +84,7 @@ func TestProseMirrorDocumentMaxTextLength(t *testing.T) {
}
fn := ProseMirrorDocumentMaxTextLength(maxLen)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
@@ -90,6 +93,7 @@ func TestProseMirrorDocumentMaxTextLength(t *testing.T) {
if (err != nil) != tt.wantError {
t.Errorf("ProseMirrorDocumentMaxTextLength() error = %v, wantError %v", err, tt.wantError)
}
if err != nil && tt.wantCode != "" && err.Code != tt.wantCode {
t.Errorf("expected code %s, got %s", tt.wantCode, err.Code)
}

View File

@@ -177,6 +177,7 @@ func NoNewLine() ValidatorFunc {
if r == '\n' {
return newValidationError(ErrorCodeInvalidFormat, fmt.Sprintf("contains newline character at position %d", i))
}
if r == '\r' {
return newValidationError(ErrorCodeInvalidFormat, fmt.Sprintf("contains carriage return character at position %d", i))
}
@@ -204,6 +205,7 @@ func SafeText(maxLen int) ValidatorFunc {
return err
}
}
return nil
}
}
@@ -226,6 +228,7 @@ func SafeTextNoNewLine(maxLen int) ValidatorFunc {
return err
}
}
return nil
}
}

View File

@@ -22,6 +22,7 @@ import (
func TestNoHTML(t *testing.T) {
t.Run("valid text without HTML", func(t *testing.T) {
str := "This is a normal text"
err := NoHTML()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -30,6 +31,7 @@ func TestNoHTML(t *testing.T) {
t.Run("valid text with special characters", func(t *testing.T) {
str := "Price: $10.99 - 20% off!"
err := NoHTML()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -38,6 +40,7 @@ func TestNoHTML(t *testing.T) {
t.Run("valid UTF-8 text", func(t *testing.T) {
str := "José García 张伟"
err := NoHTML()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -46,6 +49,7 @@ func TestNoHTML(t *testing.T) {
t.Run("valid text with emojis", func(t *testing.T) {
str := "Hello World 🌍"
err := NoHTML()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -54,10 +58,12 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - script tag XSS", func(t *testing.T) {
str := "<script>alert('xss')</script>"
err := NoHTML()(&str)
if err == nil {
t.Fatal("expected validation error for script tag")
}
if !strings.Contains(err.Message, "HTML tags") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -65,10 +71,12 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - simple bold tag", func(t *testing.T) {
str := "Hello <b>World</b>"
err := NoHTML()(&str)
if err == nil {
t.Fatal("expected validation error for bold tag")
}
if !strings.Contains(err.Message, "HTML tags") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -76,6 +84,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - div tag", func(t *testing.T) {
str := "<div>Content</div>"
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for div tag")
@@ -84,6 +93,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - self-closing tag", func(t *testing.T) {
str := "Line break<br/>here"
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for self-closing tag")
@@ -92,6 +102,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - img tag", func(t *testing.T) {
str := `<img src="x" onerror="alert(1)">`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for img tag")
@@ -100,6 +111,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - anchor tag", func(t *testing.T) {
str := `<a href="javascript:alert(1)">Click</a>`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for anchor tag")
@@ -108,6 +120,7 @@ func TestNoHTML(t *testing.T) {
t.Run("valid - less than symbol", func(t *testing.T) {
str := "5 < 10"
err := NoHTML()(&str)
if err != nil {
t.Errorf("expected no error for bare angle bracket, got: %v", err)
@@ -116,6 +129,7 @@ func TestNoHTML(t *testing.T) {
t.Run("valid - greater than symbol", func(t *testing.T) {
str := "10 > 5"
err := NoHTML()(&str)
if err != nil {
t.Errorf("expected no error for bare angle bracket, got: %v", err)
@@ -124,6 +138,7 @@ func TestNoHTML(t *testing.T) {
t.Run("valid - both angle brackets", func(t *testing.T) {
str := "5 < x > 10"
err := NoHTML()(&str)
if err != nil {
t.Errorf("expected no error for bare angle brackets, got: %v", err)
@@ -132,6 +147,7 @@ func TestNoHTML(t *testing.T) {
t.Run("valid - incomplete tag", func(t *testing.T) {
str := "text <incomplete"
err := NoHTML()(&str)
if err != nil {
t.Errorf("expected no error for incomplete tag, got: %v", err)
@@ -140,6 +156,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - encoded attempt", func(t *testing.T) {
str := "<ScRiPt>alert(1)</ScRiPt>"
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for mixed case script tag")
@@ -148,6 +165,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - svg onload XSS", func(t *testing.T) {
str := `<svg onload=alert(1)>`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for svg tag")
@@ -156,6 +174,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - svg with slash", func(t *testing.T) {
str := `<svg/onload=alert(1)>`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for svg/onload tag")
@@ -164,6 +183,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - iframe tag", func(t *testing.T) {
str := `<iframe src="javascript:alert(1)">`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for iframe tag")
@@ -172,6 +192,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - style tag", func(t *testing.T) {
str := `<style>body{background:url(evil)}</style>`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for style tag")
@@ -180,6 +201,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - HTML comment", func(t *testing.T) {
str := `<!-- comment -->`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for HTML comment")
@@ -188,6 +210,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - DOCTYPE", func(t *testing.T) {
str := `<!DOCTYPE html>`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for DOCTYPE")
@@ -196,6 +219,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - details ontoggle XSS", func(t *testing.T) {
str := `<details open ontoggle=alert(1)>`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for details tag")
@@ -204,6 +228,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - body onload XSS", func(t *testing.T) {
str := `<body onload=alert(1)>`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for body tag")
@@ -212,6 +237,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - object tag", func(t *testing.T) {
str := `<object data="evil.swf">`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for object tag")
@@ -220,6 +246,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - embed tag", func(t *testing.T) {
str := `<embed src="evil.swf">`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for embed tag")
@@ -228,6 +255,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - meta refresh", func(t *testing.T) {
str := `<meta http-equiv="refresh" content="0;url=evil">`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for meta tag")
@@ -236,6 +264,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - input with autofocus XSS", func(t *testing.T) {
str := `<input onfocus=alert(1) autofocus>`
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for input tag")
@@ -244,6 +273,7 @@ func TestNoHTML(t *testing.T) {
t.Run("invalid - tag with newlines in attributes", func(t *testing.T) {
str := "<img\nsrc=x\nonerror=alert(1)>"
err := NoHTML()(&str)
if err == nil {
t.Error("expected validation error for tag with newlines")
@@ -252,6 +282,7 @@ func TestNoHTML(t *testing.T) {
t.Run("valid - math expression", func(t *testing.T) {
str := "if x < 10 then y = 20"
err := NoHTML()(&str)
if err != nil {
t.Errorf("expected no error for math expression, got: %v", err)
@@ -260,6 +291,7 @@ func TestNoHTML(t *testing.T) {
t.Run("valid - arrow notation", func(t *testing.T) {
str := "use -> or => for arrows"
err := NoHTML()(&str)
if err != nil {
t.Errorf("expected no error for arrow notation, got: %v", err)
@@ -268,6 +300,7 @@ func TestNoHTML(t *testing.T) {
t.Run("empty string", func(t *testing.T) {
str := ""
err := NoHTML()(&str)
if err != nil {
t.Errorf("expected no error for empty string, got: %v", err)
@@ -276,6 +309,7 @@ func TestNoHTML(t *testing.T) {
t.Run("nil pointer", func(t *testing.T) {
var str *string
err := NoHTML()(str)
if err != nil {
t.Errorf("expected no error for nil, got: %v", err)
@@ -284,10 +318,12 @@ func TestNoHTML(t *testing.T) {
t.Run("not a string", func(t *testing.T) {
num := 123
err := NoHTML()(&num)
if err == nil {
t.Fatal("expected validation error for non-string")
}
if !strings.Contains(err.Message, "must be a string") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -325,12 +361,14 @@ func TestNoHTML(t *testing.T) {
// Should have error from NoHTML
errors := v.Error().(ValidationErrors)
found := false
for _, err := range errors {
if strings.Contains(err.Message, "HTML tags") {
found = true
break
}
}
if !found {
t.Error("expected error about HTML tags")
}
@@ -355,6 +393,7 @@ func TestNoHTML(t *testing.T) {
func TestPrintableText(t *testing.T) {
t.Run("valid UTF-8 text with accents", func(t *testing.T) {
str := "José García"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for valid UTF-8 name, got: %v", err)
@@ -363,6 +402,7 @@ func TestPrintableText(t *testing.T) {
t.Run("valid text with emojis", func(t *testing.T) {
str := "Hello World 🌍"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for emojis, got: %v", err)
@@ -371,6 +411,7 @@ func TestPrintableText(t *testing.T) {
t.Run("valid Chinese characters", func(t *testing.T) {
str := "张伟"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for Chinese characters, got: %v", err)
@@ -379,6 +420,7 @@ func TestPrintableText(t *testing.T) {
t.Run("valid Arabic text", func(t *testing.T) {
str := "محمد"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for Arabic text, got: %v", err)
@@ -387,6 +429,7 @@ func TestPrintableText(t *testing.T) {
t.Run("valid Cyrillic text", func(t *testing.T) {
str := "Александр"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for Cyrillic text, got: %v", err)
@@ -395,6 +438,7 @@ func TestPrintableText(t *testing.T) {
t.Run("valid text with apostrophe and hyphen", func(t *testing.T) {
str := "O'Brien-Smith"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for apostrophe and hyphen, got: %v", err)
@@ -403,6 +447,7 @@ func TestPrintableText(t *testing.T) {
t.Run("valid text with numbers", func(t *testing.T) {
str := "Product 2024"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for text with numbers, got: %v", err)
@@ -411,6 +456,7 @@ func TestPrintableText(t *testing.T) {
t.Run("valid text with punctuation", func(t *testing.T) {
str := "Hello, World! How are you?"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for punctuation, got: %v", err)
@@ -419,6 +465,7 @@ func TestPrintableText(t *testing.T) {
t.Run("valid text with angle brackets", func(t *testing.T) {
str := "5 < 10 > 3"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for angle brackets (HTML checking is separate), got: %v", err)
@@ -427,10 +474,12 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - RLO character", func(t *testing.T) {
str := "test\u202Eexe.txt"
err := PrintableText()(&str)
if err == nil {
t.Fatal("expected validation error for RLO character")
}
if !strings.Contains(err.Message, "bidirectional override") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -438,6 +487,7 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - LRO character", func(t *testing.T) {
str := "test\u202Dtext"
err := PrintableText()(&str)
if err == nil {
t.Error("expected validation error for LRO character")
@@ -446,10 +496,12 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - zero-width space", func(t *testing.T) {
str := "test\u200Btext"
err := PrintableText()(&str)
if err == nil {
t.Fatal("expected validation error for zero-width space")
}
if !strings.Contains(err.Message, "zero-width") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -457,6 +509,7 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - zero-width non-joiner", func(t *testing.T) {
str := "test\u200Ctext"
err := PrintableText()(&str)
if err == nil {
t.Error("expected validation error for zero-width non-joiner")
@@ -465,6 +518,7 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - zero-width joiner", func(t *testing.T) {
str := "test\u200Dtext"
err := PrintableText()(&str)
if err == nil {
t.Error("expected validation error for zero-width joiner")
@@ -473,6 +527,7 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - BOM character", func(t *testing.T) {
str := "\uFEFFtest"
err := PrintableText()(&str)
if err == nil {
t.Error("expected validation error for BOM character")
@@ -481,10 +536,12 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - null byte", func(t *testing.T) {
str := "test\x00text"
err := PrintableText()(&str)
if err == nil {
t.Fatal("expected validation error for null byte")
}
if !strings.Contains(err.Message, "control character") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -492,6 +549,7 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - tab character", func(t *testing.T) {
str := "test\ttext"
err := PrintableText()(&str)
if err == nil {
t.Error("expected validation error for tab character")
@@ -500,6 +558,7 @@ func TestPrintableText(t *testing.T) {
t.Run("valid - newline character", func(t *testing.T) {
str := "test\ntext"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for newline character, got: %v", err)
@@ -508,6 +567,7 @@ func TestPrintableText(t *testing.T) {
t.Run("valid - carriage return", func(t *testing.T) {
str := "test\rtext"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for carriage return, got: %v", err)
@@ -516,6 +576,7 @@ func TestPrintableText(t *testing.T) {
t.Run("valid - multiple newlines", func(t *testing.T) {
str := "hello foo\nbar\n\njd"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for multiple newlines, got: %v", err)
@@ -524,10 +585,12 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - soft hyphen", func(t *testing.T) {
str := "test\u00ADtext"
err := PrintableText()(&str)
if err == nil {
t.Fatal("expected validation error for soft hyphen")
}
if !strings.Contains(err.Message, "invisible formatting") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -535,6 +598,7 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - word joiner", func(t *testing.T) {
str := "test\u2060text"
err := PrintableText()(&str)
if err == nil {
t.Error("expected validation error for word joiner")
@@ -543,10 +607,12 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - private use area character", func(t *testing.T) {
str := "test\uE000text"
err := PrintableText()(&str)
if err == nil {
t.Fatal("expected validation error for private use area")
}
if !strings.Contains(err.Message, "private use") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -554,10 +620,12 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - replacement character", func(t *testing.T) {
str := "test\uFFFDtext"
err := PrintableText()(&str)
if err == nil {
t.Fatal("expected validation error for replacement character")
}
if !strings.Contains(err.Message, "replacement character") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -565,6 +633,7 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - DEL control character", func(t *testing.T) {
str := "test\x7Ftext"
err := PrintableText()(&str)
if err == nil {
t.Error("expected validation error for DEL control character")
@@ -573,6 +642,7 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - C1 control character", func(t *testing.T) {
str := "test\u0080text"
err := PrintableText()(&str)
if err == nil {
t.Error("expected validation error for C1 control character")
@@ -581,6 +651,7 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - LTR mark", func(t *testing.T) {
str := "test\u200Etext"
err := PrintableText()(&str)
if err == nil {
t.Error("expected validation error for LTR mark")
@@ -589,6 +660,7 @@ func TestPrintableText(t *testing.T) {
t.Run("invalid - RTL mark", func(t *testing.T) {
str := "test\u200Ftext"
err := PrintableText()(&str)
if err == nil {
t.Error("expected validation error for RTL mark")
@@ -597,6 +669,7 @@ func TestPrintableText(t *testing.T) {
t.Run("valid with pointer", func(t *testing.T) {
str := "Valid Name"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -605,6 +678,7 @@ func TestPrintableText(t *testing.T) {
t.Run("empty string", func(t *testing.T) {
str := ""
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for empty string, got: %v", err)
@@ -613,6 +687,7 @@ func TestPrintableText(t *testing.T) {
t.Run("nil pointer", func(t *testing.T) {
var str *string
err := PrintableText()(str)
if err != nil {
t.Errorf("expected no error for nil, got: %v", err)
@@ -621,10 +696,12 @@ func TestPrintableText(t *testing.T) {
t.Run("not a string", func(t *testing.T) {
num := 123
err := PrintableText()(&num)
if err == nil {
t.Fatal("expected validation error for non-string")
}
if !strings.Contains(err.Message, "must be a string") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -642,10 +719,12 @@ func TestPrintableText(t *testing.T) {
t.Run("position reported correctly", func(t *testing.T) {
str := "abc\x00def"
err := PrintableText()(&str)
if err == nil {
t.Fatal("expected validation error")
}
if !strings.Contains(err.Message, "position 3") {
t.Errorf("expected position 3 in error message, got: %s", err.Message)
}
@@ -655,10 +734,12 @@ func TestPrintableText(t *testing.T) {
// Test that position is counted correctly with UTF-8 characters
// The range loop in Go iterates by runes, so position will be rune index
str := "abc\x00"
err := PrintableText()(&str)
if err == nil {
t.Fatal("expected validation error")
}
// The null byte is at rune position 3 (after 'a', 'b', 'c')
if !strings.Contains(err.Message, "position 3") {
t.Errorf("expected position 3 in error message, got: %s", err.Message)
@@ -669,6 +750,7 @@ func TestPrintableText(t *testing.T) {
func TestSafeText(t *testing.T) {
t.Run("valid text", func(t *testing.T) {
str := "Product Name 2024"
err := SafeText(100)(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -677,6 +759,7 @@ func TestSafeText(t *testing.T) {
t.Run("valid UTF-8 text", func(t *testing.T) {
str := "José García"
err := SafeText(50)(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -685,6 +768,7 @@ func TestSafeText(t *testing.T) {
t.Run("valid text with emoji", func(t *testing.T) {
str := "Hello World 🌍"
err := SafeText(50)(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -693,6 +777,7 @@ func TestSafeText(t *testing.T) {
t.Run("valid text with apostrophe and hyphen", func(t *testing.T) {
str := "O'Brien-Smith"
err := SafeText(50)(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -701,10 +786,12 @@ func TestSafeText(t *testing.T) {
t.Run("invalid - empty string", func(t *testing.T) {
str := ""
err := SafeText(100)(&str)
if err == nil {
t.Fatal("expected validation error for empty string")
}
if !strings.Contains(err.Message, "empty") && !strings.Contains(err.Message, "required") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -712,10 +799,12 @@ func TestSafeText(t *testing.T) {
t.Run("invalid - exceeds max length", func(t *testing.T) {
str := "This is a very long string that exceeds the maximum length"
err := SafeText(10)(&str)
if err == nil {
t.Fatal("expected validation error for exceeding max length")
}
if !strings.Contains(err.Message, "at most") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -723,10 +812,12 @@ func TestSafeText(t *testing.T) {
t.Run("invalid - contains HTML tags", func(t *testing.T) {
str := "Hello <b>World</b>"
err := SafeText(100)(&str)
if err == nil {
t.Fatal("expected validation error for HTML tags")
}
if !strings.Contains(err.Message, "HTML tags") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -734,6 +825,7 @@ func TestSafeText(t *testing.T) {
t.Run("invalid - contains script tag", func(t *testing.T) {
str := "<script>alert('xss')</script>"
err := SafeText(100)(&str)
if err == nil {
t.Error("expected validation error for script tag")
@@ -742,6 +834,7 @@ func TestSafeText(t *testing.T) {
t.Run("valid - contains angle brackets", func(t *testing.T) {
str := "5 < 10"
err := SafeText(100)(&str)
if err != nil {
t.Errorf("expected no error for bare angle brackets, got: %v", err)
@@ -750,10 +843,12 @@ func TestSafeText(t *testing.T) {
t.Run("invalid - contains null byte", func(t *testing.T) {
str := "test\x00text"
err := SafeText(100)(&str)
if err == nil {
t.Fatal("expected validation error for null byte")
}
if !strings.Contains(err.Message, "control character") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -761,6 +856,7 @@ func TestSafeText(t *testing.T) {
t.Run("invalid - contains tab character", func(t *testing.T) {
str := "test\ttext"
err := SafeText(100)(&str)
if err == nil {
t.Error("expected validation error for tab character")
@@ -769,6 +865,7 @@ func TestSafeText(t *testing.T) {
t.Run("valid - contains newline", func(t *testing.T) {
str := "test\ntext"
err := SafeText(100)(&str)
if err != nil {
t.Errorf("expected no error for newline, got: %v", err)
@@ -777,6 +874,7 @@ func TestSafeText(t *testing.T) {
t.Run("valid - contains multiple newlines", func(t *testing.T) {
str := "hello foo\nbar\n\njd"
err := SafeText(100)(&str)
if err != nil {
t.Errorf("expected no error for multiple newlines, got: %v", err)
@@ -785,10 +883,12 @@ func TestSafeText(t *testing.T) {
t.Run("invalid - contains zero-width space", func(t *testing.T) {
str := "test\u200Btext"
err := SafeText(100)(&str)
if err == nil {
t.Fatal("expected validation error for zero-width space")
}
if !strings.Contains(err.Message, "zero-width") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -796,10 +896,12 @@ func TestSafeText(t *testing.T) {
t.Run("invalid - contains RLO character", func(t *testing.T) {
str := "test\u202Eexe.txt"
err := SafeText(100)(&str)
if err == nil {
t.Fatal("expected validation error for RLO character")
}
if !strings.Contains(err.Message, "bidirectional override") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -807,10 +909,12 @@ func TestSafeText(t *testing.T) {
t.Run("invalid - contains private use area character", func(t *testing.T) {
str := "test\uE000text"
err := SafeText(100)(&str)
if err == nil {
t.Fatal("expected validation error for private use area")
}
if !strings.Contains(err.Message, "private use") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -818,6 +922,7 @@ func TestSafeText(t *testing.T) {
t.Run("nil pointer", func(t *testing.T) {
var str *string
err := SafeText(100)(str)
if err != nil {
t.Errorf("expected no error for nil pointer, got: %v", err)
@@ -826,10 +931,12 @@ func TestSafeText(t *testing.T) {
t.Run("not a string", func(t *testing.T) {
num := 123
err := SafeText(100)(&num)
if err == nil {
t.Fatal("expected validation error for non-string")
}
if !strings.Contains(err.Message, "must be a string") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -856,12 +963,14 @@ func TestSafeText(t *testing.T) {
errors := v.Error().(ValidationErrors)
found := false
for _, err := range errors {
if strings.Contains(err.Message, "HTML tags") {
found = true
break
}
}
if !found {
t.Error("expected error about HTML tags")
}
@@ -869,6 +978,7 @@ func TestSafeText(t *testing.T) {
t.Run("edge case - exactly at max length", func(t *testing.T) {
str := "12345"
err := SafeText(5)(&str)
if err != nil {
t.Errorf("expected no error for string at max length, got: %v", err)
@@ -877,6 +987,7 @@ func TestSafeText(t *testing.T) {
t.Run("edge case - one character over max length", func(t *testing.T) {
str := "123456"
err := SafeText(5)(&str)
if err == nil {
t.Error("expected validation error for string over max length")
@@ -887,6 +998,7 @@ func TestSafeText(t *testing.T) {
func TestNoNewLine(t *testing.T) {
t.Run("valid text without newlines", func(t *testing.T) {
str := "Product Name 2024"
err := NoNewLine()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -895,10 +1007,12 @@ func TestNoNewLine(t *testing.T) {
t.Run("invalid - contains newline", func(t *testing.T) {
str := "Line 1\nLine 2"
err := NoNewLine()(&str)
if err == nil {
t.Fatal("expected validation error for newline")
}
if !strings.Contains(err.Message, "newline") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -906,10 +1020,12 @@ func TestNoNewLine(t *testing.T) {
t.Run("invalid - contains carriage return", func(t *testing.T) {
str := "Line 1\rLine 2"
err := NoNewLine()(&str)
if err == nil {
t.Fatal("expected validation error for carriage return")
}
if !strings.Contains(err.Message, "carriage return") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -917,6 +1033,7 @@ func TestNoNewLine(t *testing.T) {
t.Run("invalid - contains both newline and carriage return", func(t *testing.T) {
str := "Line 1\n\rLine 3"
err := NoNewLine()(&str)
if err == nil {
t.Error("expected validation error for newline or carriage return")
@@ -925,6 +1042,7 @@ func TestNoNewLine(t *testing.T) {
t.Run("nil pointer", func(t *testing.T) {
var str *string
err := NoNewLine()(str)
if err != nil {
t.Errorf("expected no error for nil pointer, got: %v", err)
@@ -933,6 +1051,7 @@ func TestNoNewLine(t *testing.T) {
t.Run("empty string", func(t *testing.T) {
str := ""
err := NoNewLine()(&str)
if err != nil {
t.Errorf("expected no error for empty string, got: %v", err)
@@ -943,6 +1062,7 @@ func TestNoNewLine(t *testing.T) {
func TestSafeTextNoNewLine(t *testing.T) {
t.Run("valid text", func(t *testing.T) {
str := "Product Name 2024"
err := SafeTextNoNewLine(100)(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -951,6 +1071,7 @@ func TestSafeTextNoNewLine(t *testing.T) {
t.Run("valid UTF-8 text", func(t *testing.T) {
str := "José García"
err := SafeTextNoNewLine(50)(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -959,10 +1080,12 @@ func TestSafeTextNoNewLine(t *testing.T) {
t.Run("invalid - contains newline", func(t *testing.T) {
str := "Line 1\nLine 2"
err := SafeTextNoNewLine(100)(&str)
if err == nil {
t.Fatal("expected validation error for newline")
}
if !strings.Contains(err.Message, "newline") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -970,10 +1093,12 @@ func TestSafeTextNoNewLine(t *testing.T) {
t.Run("invalid - contains carriage return", func(t *testing.T) {
str := "Line 1\rLine 2"
err := SafeTextNoNewLine(100)(&str)
if err == nil {
t.Fatal("expected validation error for carriage return")
}
if !strings.Contains(err.Message, "carriage return") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -981,10 +1106,12 @@ func TestSafeTextNoNewLine(t *testing.T) {
t.Run("invalid - empty string", func(t *testing.T) {
str := ""
err := SafeTextNoNewLine(100)(&str)
if err == nil {
t.Fatal("expected validation error for empty string")
}
if !strings.Contains(err.Message, "empty") && !strings.Contains(err.Message, "required") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -992,10 +1119,12 @@ func TestSafeTextNoNewLine(t *testing.T) {
t.Run("invalid - exceeds max length", func(t *testing.T) {
str := "This is a very long string that exceeds the maximum length"
err := SafeTextNoNewLine(10)(&str)
if err == nil {
t.Fatal("expected validation error for exceeding max length")
}
if !strings.Contains(err.Message, "at most") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -1003,10 +1132,12 @@ func TestSafeTextNoNewLine(t *testing.T) {
t.Run("invalid - contains HTML tags", func(t *testing.T) {
str := "Hello <b>World</b>"
err := SafeTextNoNewLine(100)(&str)
if err == nil {
t.Fatal("expected validation error for HTML tags")
}
if !strings.Contains(err.Message, "HTML tags") {
t.Errorf("unexpected error message: %s", err.Message)
}
@@ -1014,6 +1145,7 @@ func TestSafeTextNoNewLine(t *testing.T) {
t.Run("invalid - contains tab character", func(t *testing.T) {
str := "test\ttext"
err := SafeTextNoNewLine(100)(&str)
if err == nil {
t.Error("expected validation error for tab character")
@@ -1022,6 +1154,7 @@ func TestSafeTextNoNewLine(t *testing.T) {
t.Run("nil pointer", func(t *testing.T) {
var str *string
err := SafeTextNoNewLine(100)(str)
if err != nil {
t.Errorf("expected no error for nil pointer, got: %v", err)
@@ -1030,6 +1163,7 @@ func TestSafeTextNoNewLine(t *testing.T) {
t.Run("edge case - exactly at max length", func(t *testing.T) {
str := "12345"
err := SafeTextNoNewLine(5)(&str)
if err != nil {
t.Errorf("expected no error for string at max length, got: %v", err)

View File

@@ -113,11 +113,13 @@ func OneOfSlice[T any](allowed []T) ValidatorFunc {
// Dereference all pointer levels
actualValue := value
val := reflect.ValueOf(value)
for val.Kind() == reflect.Pointer {
if val.IsNil() {
return nil
}
val = val.Elem()
actualValue = val.Interface()
}

View File

@@ -36,6 +36,7 @@ func TestAfter(t *testing.T) {
if err == nil {
t.Fatal("expected validation error")
}
if err.Code != ErrorCodeOutOfRange {
t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code)
}
@@ -50,6 +51,7 @@ func TestAfter(t *testing.T) {
t.Run("nil pointer", func(t *testing.T) {
var timeVal *time.Time
err := After(now)(timeVal)
if err != nil {
t.Errorf("expected no error for nil, got: %v", err)
@@ -74,6 +76,7 @@ func TestBefore(t *testing.T) {
if err == nil {
t.Fatal("expected validation error")
}
if err.Code != ErrorCodeOutOfRange {
t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code)
}
@@ -88,6 +91,7 @@ func TestBefore(t *testing.T) {
t.Run("nil pointer", func(t *testing.T) {
var timeVal *time.Time
err := Before(now)(timeVal)
if err != nil {
t.Errorf("expected no error for nil, got: %v", err)
@@ -101,6 +105,7 @@ func TestRangeDuration(t *testing.T) {
t.Run("duration within range", func(t *testing.T) {
duration := 30 * time.Minute
err := RangeDuration(minDuration, maxDuration)(&duration)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -109,6 +114,7 @@ func TestRangeDuration(t *testing.T) {
t.Run("duration at minimum", func(t *testing.T) {
duration := 10 * time.Minute
err := RangeDuration(minDuration, maxDuration)(&duration)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -117,6 +123,7 @@ func TestRangeDuration(t *testing.T) {
t.Run("duration at maximum", func(t *testing.T) {
duration := 1 * time.Hour
err := RangeDuration(minDuration, maxDuration)(&duration)
if err != nil {
t.Errorf("expected no error, got: %v", err)
@@ -125,10 +132,12 @@ func TestRangeDuration(t *testing.T) {
t.Run("duration below minimum", func(t *testing.T) {
duration := 5 * time.Minute
err := RangeDuration(minDuration, maxDuration)(&duration)
if err == nil {
t.Fatal("expected validation error")
}
if err.Code != ErrorCodeOutOfRange {
t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code)
}
@@ -136,10 +145,12 @@ func TestRangeDuration(t *testing.T) {
t.Run("duration above maximum", func(t *testing.T) {
duration := 2 * time.Hour
err := RangeDuration(minDuration, maxDuration)(&duration)
if err == nil {
t.Fatal("expected validation error")
}
if err.Code != ErrorCodeOutOfRange {
t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code)
}
@@ -147,6 +158,7 @@ func TestRangeDuration(t *testing.T) {
t.Run("nil pointer", func(t *testing.T) {
var duration *time.Duration
err := RangeDuration(minDuration, maxDuration)(duration)
if err != nil {
t.Errorf("expected no error for nil, got: %v", err)