Skip to content

Commit 28d9d77

Browse files
committed
Major refactoring and testing.
1 parent 60d21ba commit 28d9d77

42 files changed

Lines changed: 2535 additions & 749 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 180 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,207 @@
11
# validate
22

3-
Composable validation helpers for Go with fluent builders, rule tags,
4-
and struct validation. Optional message translation support.
3+
Composable validation for Go with fluent builders, rule tags, struct
4+
validation, and optional message translation.
55

6-
## Install
6+
## Packages
7+
8+
- `github.com/aatuh/validate`: main API (`Validate`, builders)
9+
- `github.com/aatuh/validate/validators`: type-specific rules
10+
- `github.com/aatuh/validate/errors`: error types and codes
11+
- `github.com/aatuh/validate/structvalidator`: struct validation
12+
- `github.com/aatuh/validate/translator`: i18n helpers
13+
14+
## Quick start
15+
16+
### Basic validation
717

818
```go
9-
import "github.com/aatuh/validate"
19+
package main
20+
21+
import (
22+
"fmt"
23+
"github.com/aatuh/validate"
24+
)
25+
26+
func main() {
27+
v := validate.New()
28+
29+
// String: Email() is terminal and returns func(any) error.
30+
emailV := v.String().MinLength(3).MaxLength(50).Email()
31+
if err := emailV("user@example.com"); err != nil {
32+
fmt.Println("validation failed:", err)
33+
}
34+
35+
// Int: call Build() to obtain func(any) error.
36+
ageV := v.Int().MinInt(18).MaxInt(120).Build()
37+
if err := ageV(25); err != nil {
38+
fmt.Println("validation failed:", err)
39+
}
40+
}
1041
```
1142

12-
## Quick start
43+
### Slice validation with element rules
1344

1445
```go
15-
v := validate.NewValidate(nil)
16-
17-
// Direct composition
18-
check := v.WithString(v.MinLength(3), v.MaxLength(10))
19-
if err := check("hello"); err != nil { /* handle */ }
20-
21-
// Builder style
22-
rule := v.StringBuilder().
23-
WithMin(3).
24-
WithMax(10).
25-
WithEmail().
26-
Build()
27-
_ = rule("user@example.com")
46+
v := validate.New()
47+
// tags must be non-empty strings, at least 2 chars each
48+
tagElem := v.String().MinLength(2).Build()
49+
tagsV := v.Slice().MinSliceLength(1).ForEach(tagElem).Build()
50+
if err := tagsV([]string{"go", "lib"}); err != nil {
51+
fmt.Println("validation failed:", err)
52+
}
2853
```
2954

30-
## Rules and builders
55+
### Struct validation
3156

32-
- **string**: `len`, `min`, `max`, `oneof`, `email`, `regex`
33-
- **int/int64**: `min`, `max`
34-
- **bool**: builder `MustBeTrue`, `MustBeFalse`
35-
- **slice**: `len`, `min`, `max`, `ForEach(elemValidator)`
57+
```go
58+
package main
59+
60+
import (
61+
"fmt"
62+
"github.com/aatuh/validate"
63+
verrs "github.com/aatuh/validate/errors"
64+
"github.com/aatuh/validate/structvalidator"
65+
)
66+
67+
type User struct {
68+
Name string `validate:"string;min=3;max=50"`
69+
Email string `validate:"string;email"`
70+
Age int `validate:"int;min=18;max=120"`
71+
}
3672

37-
Builder entry points: `StringBuilder()`, `IntBuilder()`, `BoolBuilder()`,
38-
`SliceBuilder()`.
73+
func main() {
74+
v := validate.New()
75+
sv := structvalidator.NewStructValidator(v)
76+
77+
u := User{Name: "John Doe", Email: "john@example.com", Age: 25}
78+
if err := sv.ValidateStruct(u); err != nil {
79+
if es, ok := err.(verrs.Errors); ok {
80+
fmt.Println("errors:", es.AsMap())
81+
} else {
82+
fmt.Println("validation failed:", err)
83+
}
84+
}
85+
}
86+
```
3987

40-
## Struct tags
88+
### With translation
4189

4290
```go
43-
type Input struct {
44-
Name string `validate:"string;min=3;max=32"`
45-
Age int `validate:"int;min=0;max=130"`
46-
Emails []any `validate:"slice;min=1"`
91+
package main
92+
93+
import (
94+
"fmt"
95+
"github.com/aatuh/validate"
96+
"github.com/aatuh/validate/translator"
97+
)
98+
99+
func main() {
100+
msgs := map[string]string{
101+
"string.minLength": "doit contenir au moins %d caractères",
102+
"string.email.invalid": "adresse email invalide",
103+
}
104+
tr := translator.NewSimpleTranslator(msgs)
105+
106+
v := validate.New().WithTranslator(tr)
107+
108+
check := v.String().MinLength(5).Email()
109+
if err := check("ab"); err != nil {
110+
fmt.Println("fr:", err)
111+
}
47112
}
113+
```
114+
115+
Tip: `translator.DefaultEnglishTranslations()` provides sensible defaults.
116+
117+
## API reference
118+
119+
### Validate
48120

49-
v := validate.NewValidate(nil)
50-
if err := v.ValidateStruct(Input{ Name: "Al", Age: 200 }); err != nil {
51-
// err includes per-field messages
121+
```go
122+
v := validate.New()
123+
v = v.WithTranslator(tr)
124+
v = v.PathSeparator(".")
125+
126+
custom := map[string]func(any) error{
127+
"customRule": func(v any) error { return nil },
52128
}
129+
v2 := validate.NewWithCustomRules(custom)
53130
```
54131

55-
## Translation
132+
### Builders
133+
134+
```go
135+
// String
136+
strV := v.String().MinLength(3).MaxLength(50).Regex(`^[a-z0-9_]+$`).Build()
56137

57-
Provide a `Translator` with a `T(key, ...params)` method.
138+
// Int (accepts any Go int type at call time)
139+
intV := v.Int().MinInt(0).MaxInt(100).Build()
140+
141+
// Int64 (requires exactly int64 at call time)
142+
int64V := v.Int64().MinInt(0).MaxInt(100).Build()
143+
144+
// Slice
145+
elem := v.String().MinLength(2).Build()
146+
sliceV := v.Slice().MinSliceLength(1).ForEach(elem).Build()
147+
148+
// Bool
149+
boolV := v.Bool() // type-check only
150+
```
151+
152+
### Struct tags
153+
154+
```go
155+
type Example struct {
156+
Name string `validate:"string;min=3;max=50"`
157+
Email string `validate:"string;email"`
158+
Age int `validate:"int;min=18;max=120"`
159+
Tags []string `validate:"slice;min=1;max=5"`
160+
Flag bool `validate:"bool"`
161+
}
162+
```
163+
164+
Supported tokens:
165+
166+
- string: `len`, `min`, `max`, `oneof=a b c`, `regex=...`, `email`
167+
- int/int64: `min`, `max` (use `int64;...` to require int64)
168+
- slice: `len`, `min`, `max`
169+
- bool: type only
170+
171+
### Errors
172+
173+
Struct validation returns `errors.Errors` which offers helpers:
58174

59175
```go
60-
type mapTranslator struct{ msgs map[string]string }
61-
func (t mapTranslator) T(k string, p ...any) string {
62-
if m, ok := t.msgs[k]; ok { return fmt.Sprintf(m, p...) }
63-
return fmt.Sprintf(k, p...)
176+
if es, ok := err.(verrs.Errors); ok {
177+
_ = es.Has("Email")
178+
_ = es.Filter("") // or a nested prefix like "Address."
179+
_ = es.AsMap()
64180
}
181+
```
65182

66-
v := validate.NewValidate(nil)
67-
msgs := validate.DefaultEnglishTranslations()
68-
v.WithTranslator(mapTranslator{msgs: msgs})
183+
Note: direct builder calls usually return a single `error`, not an
184+
`errors.Errors` aggregate.
185+
186+
## Error codes
187+
188+
Stable constants for programmatic handling (see `errors/codes.go`):
189+
190+
```go
191+
const (
192+
// String
193+
CodeStringMin = "string.min"
194+
CodeStringMax = "string.max"
195+
CodeStringNonEmpty = "string.nonempty"
196+
CodeStringPattern = "string.pattern"
197+
CodeStringOneOf = "string.oneof"
198+
199+
// Number (ints/floats)
200+
CodeNumberMin = "number.min"
201+
CodeNumberMax = "number.max"
202+
203+
// Slice
204+
CodeSliceMin = "slice.min"
205+
CodeSliceMax = "slice.max"
206+
)
69207
```

bool.go

Lines changed: 0 additions & 32 deletions
This file was deleted.

boolbuilder.go

Lines changed: 0 additions & 44 deletions
This file was deleted.

0 commit comments

Comments
 (0)