Skip to content

Commit 68a3cef

Browse files
feat(validation): implement custom validation for redundancy level with improved error messaging
1 parent aaf1753 commit 68a3cef

7 files changed

Lines changed: 52 additions & 9 deletions

File tree

pkg/api/api.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,9 @@ type Service struct {
214214

215215
whitelistedWithdrawalAddress []common.Address
216216

217-
preMapHooks map[string]func(v string) (string, error)
218-
validate *validator.Validate
217+
preMapHooks map[string]func(v string) (string, error)
218+
validationCustomErrorMessages map[string]func(err validator.FieldError) error
219+
validate *validator.Validate
219220

220221
redistributionAgent *storageincentives.Agent
221222

@@ -321,6 +322,7 @@ func New(
321322
}
322323
return name
323324
})
325+
s.setupValidation()
324326
s.stamperStore = stamperStore
325327

326328
for _, v := range whitelistedWithdrawalAddress {
@@ -709,11 +711,17 @@ func (s *Service) mapStructure(input, output any) func(string, log.Logger, http.
709711
case []byte:
710712
val = string(v)
711713
}
714+
var cause error
715+
if msgFn, ok := s.validationCustomErrorMessages[err.Tag()]; ok {
716+
cause = msgFn(err)
717+
} else {
718+
cause = fmt.Errorf("want %s:%s", err.Tag(), err.Param())
719+
}
712720
vErrs = multierror.Append(vErrs,
713721
&validationError{
714722
Entry: strings.ToLower(err.Field()),
715723
Value: val,
716-
Cause: fmt.Errorf("want %s:%s", err.Tag(), err.Param()),
724+
Cause: cause,
717725
})
718726
}
719727
return response(vErrs.ErrorOrNil())

pkg/api/bytes.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) {
4040
Pin bool `map:"Swarm-Pin"`
4141
Deferred *bool `map:"Swarm-Deferred-Upload"`
4242
Encrypt bool `map:"Swarm-Encrypt"`
43-
RLevel redundancy.Level `map:"Swarm-Redundancy-Level" validate:"lte=4"`
43+
RLevel redundancy.Level `map:"Swarm-Redundancy-Level" validate:"rLevel"`
4444
Act bool `map:"Swarm-Act"`
4545
HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"`
4646
}{}

pkg/api/bytes_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,7 @@ func TestBytesRedundancyLevel(t *testing.T) {
449449
Reasons: []jsonhttp.Reason{
450450
{
451451
Field: "swarm-redundancy-level",
452-
Error: fmt.Sprintf("want lte:%d", maxValidLevel),
452+
Error: fmt.Sprintf("want redundancy level to be in the range of %d and %d", int(redundancy.NONE), int(redundancy.PARANOID)),
453453
},
454454
},
455455
},

pkg/api/bzz.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func (s *Service) bzzUploadHandler(w http.ResponseWriter, r *http.Request) {
7272
Deferred *bool `map:"Swarm-Deferred-Upload"`
7373
Encrypt bool `map:"Swarm-Encrypt"`
7474
IsDir bool `map:"Swarm-Collection"`
75-
RLevel redundancy.Level `map:"Swarm-Redundancy-Level" validate:"lte=4"`
75+
RLevel redundancy.Level `map:"Swarm-Redundancy-Level" validate:"rLevel"`
7676
Act bool `map:"Swarm-Act"`
7777
HistoryAddress swarm.Address `map:"Swarm-Act-History-Address"`
7878
}{}
@@ -387,7 +387,7 @@ func (s *Service) serveReference(logger log.Logger, address swarm.Address, pathV
387387
Cache *bool `map:"Swarm-Cache"`
388388
Strategy *getter.Strategy `map:"Swarm-Redundancy-Strategy"`
389389
FallbackMode *bool `map:"Swarm-Redundancy-Fallback-Mode"`
390-
RLevel *redundancy.Level `map:"Swarm-Redundancy-Level" validate:"omitempty,lte=4"`
390+
RLevel *redundancy.Level `map:"Swarm-Redundancy-Level" validate:"omitempty,rLevel"`
391391
ChunkRetrievalTimeout *string `map:"Swarm-Chunk-Retrieval-Timeout"`
392392
}{}
393393

@@ -599,7 +599,7 @@ func (s *Service) serveManifestEntry(
599599
func (s *Service) downloadHandler(logger log.Logger, w http.ResponseWriter, r *http.Request, reference swarm.Address, additionalHeaders http.Header, etag, headersOnly bool, rootCh swarm.Chunk) {
600600
headers := struct {
601601
Strategy *getter.Strategy `map:"Swarm-Redundancy-Strategy"`
602-
RLevel *redundancy.Level `map:"Swarm-Redundancy-Level" validate:"omitempty,lte=4"`
602+
RLevel *redundancy.Level `map:"Swarm-Redundancy-Level" validate:"omitempty,rLevel"`
603603
FallbackMode *bool `map:"Swarm-Redundancy-Fallback-Mode"`
604604
ChunkRetrievalTimeout *string `map:"Swarm-Chunk-Retrieval-Timeout"`
605605
LookaheadBufferSize *int `map:"Swarm-Lookahead-Buffer-Size"`

pkg/api/bzz_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1300,7 +1300,7 @@ func TestBzzDownloadRedundancyLevel(t *testing.T) {
13001300
Reasons: []jsonhttp.Reason{
13011301
{
13021302
Field: "swarm-redundancy-level",
1303-
Error: fmt.Sprintf("want lte:%d", maxValidLevel),
1303+
Error: fmt.Sprintf("want redundancy level to be in the range of %d and %d", int(redundancy.NONE), int(redundancy.PARANOID)),
13041304
},
13051305
},
13061306
},

pkg/api/validation.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2026 The Swarm Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package api
6+
7+
import (
8+
"fmt"
9+
10+
"github.com/ethersphere/bee/v2/pkg/file/redundancy"
11+
"github.com/go-playground/validator/v10"
12+
)
13+
14+
const (
15+
RedundancyLevelTag = "rLevel"
16+
)
17+
18+
// setupValidation configures custom validation rules and their custom error messages.
19+
func (s *Service) setupValidation() {
20+
s.validate.RegisterValidation(RedundancyLevelTag, func(fl validator.FieldLevel) bool {
21+
level := redundancy.Level(fl.Field().Uint())
22+
return level.Validate()
23+
})
24+
25+
s.validationCustomErrorMessages = map[string]func(err validator.FieldError) error{
26+
RedundancyLevelTag: func(err validator.FieldError) error {
27+
return fmt.Errorf("want redundancy level to be in the range of %d and %d", int(redundancy.NONE), int(redundancy.PARANOID))
28+
},
29+
}
30+
}

pkg/file/redundancy/level.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ const (
2929
PARANOID
3030
)
3131

32+
// Validate validates the redundancy level
33+
func (l Level) Validate() bool {
34+
return l >= NONE && l <= PARANOID
35+
}
36+
3237
// GetParities returns number of parities based on appendix F table 5
3338
func (l Level) GetParities(shards int) int {
3439
et, err := l.getErasureTable()

0 commit comments

Comments
 (0)