Skip to content
This repository was archived by the owner on Jul 14, 2026. It is now read-only.

Commit d061a2e

Browse files
authored
Merge pull request #144 from bright-room/feat/96-validator-composition-utility-all-any
Close #96: Add All and Any validator composition utilities
2 parents afe6a19 + 3a8ed2b commit d061a2e

5 files changed

Lines changed: 373 additions & 1 deletion

File tree

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ make shell # Interactive bash in container
3333
Key interfaces:
3434
- `Storage` — `Get`/`Set`/`Delete` for cached responses (pluggable backend)
3535
- `Locker` — optional per-key mutual exclusion for concurrent request handling. Storage implementations that also implement `Locker` enable automatic lock acquisition in the middleware. Returns 409 Conflict on lock failure.
36-
- `Validator` — `Validate(Config) error` for configuration validation. `ValidatorFunc` is a function adapter (like `http.HandlerFunc`). Preset validators (`MaxTTL`, `MinTTL`, etc.) return `*PresetValidator` which supports `.WithMessage()` for custom error messages.
36+
- `Validator` — `Validate(Config) error` for configuration validation. `ValidatorFunc` is a function adapter (like `http.HandlerFunc`). Preset validators (`MaxTTL`, `MinTTL`, etc.) return `*PresetValidator` which supports `.WithMessage()` for custom error messages. Composition functions `All` (AND) and `Any` (OR) combine multiple validators and also return `*PresetValidator`.
3737

3838
Configuration uses the **Functional Options** pattern (`WithXxx()` functions).
3939

‎README.md‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,37 @@ mw, err := idem.New(
138138
)
139139
```
140140

141+
#### Validator Composition
142+
143+
Use `All` and `Any` to combine validators with AND / OR logic:
144+
145+
```go
146+
mw, err := idem.New(
147+
idem.WithTTL(1 * time.Hour),
148+
idem.WithValidation(
149+
// All: every validator must pass (AND)
150+
idem.All(
151+
idem.MinTTL(1 * time.Minute),
152+
idem.MaxTTL(24 * time.Hour),
153+
),
154+
// Any: at least one validator must pass (OR)
155+
idem.Any(
156+
idem.AllowedKeyHeaders("Idempotency-Key"),
157+
idem.KeyHeaderPattern(regexp.MustCompile(`^X-`)),
158+
),
159+
),
160+
)
161+
```
162+
163+
`All` and `Any` return `*PresetValidator`, so they support `.WithMessage()` and can be nested arbitrarily:
164+
165+
```go
166+
idem.All(
167+
idem.Any(v1, v2),
168+
v3,
169+
).WithMessage("validation failed")
170+
```
171+
141172
### Metrics
142173

143174
Use `WithMetrics` to observe cache hits, misses, lock contention, and errors — for example, to export to Prometheus:

‎errors.go‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,7 @@ var (
1616

1717
// ErrInvalidTTLRange is returned by TTLRange when min exceeds max.
1818
ErrInvalidTTLRange = errors.New("idem: TTLRange min must not exceed max")
19+
20+
// ErrAllValidatorsFailed is returned by Any when every validator fails.
21+
ErrAllValidatorsFailed = errors.New("idem: all validators failed")
1922
)

‎validator.go‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,37 @@ func KeyHeaderPattern(pattern *regexp.Regexp) *PresetValidator {
9292
}
9393
}
9494

95+
// All returns a Validator that succeeds only when every given validator
96+
// succeeds. Validators run in order; validation stops at the first error.
97+
func All(validators ...Validator) *PresetValidator {
98+
return &PresetValidator{
99+
validate: func(cfg Config) error {
100+
for _, v := range validators {
101+
if err := v.Validate(cfg); err != nil {
102+
return err
103+
}
104+
}
105+
return nil
106+
},
107+
}
108+
}
109+
110+
// Any returns a Validator that succeeds when at least one of the given
111+
// validators succeeds. Validators run in order; validation stops at the
112+
// first success. If all validators fail, an error is returned.
113+
func Any(validators ...Validator) *PresetValidator {
114+
return &PresetValidator{
115+
validate: func(cfg Config) error {
116+
for _, v := range validators {
117+
if err := v.Validate(cfg); err == nil {
118+
return nil
119+
}
120+
}
121+
return ErrAllValidatorsFailed
122+
},
123+
}
124+
}
125+
95126
// AllowedKeyHeaders returns a Validator that requires the key header
96127
// name to be one of the specified allowed values.
97128
func AllowedKeyHeaders(allowed ...string) *PresetValidator {

0 commit comments

Comments
 (0)