Skip to content

Commit 0b2254a

Browse files
authored
Implement Restrict String Length (#9)
* Implement [Validator] RestrictStringLength - [+] feat(constant.go): add error messages for fields exceeding maximum length - [+] feat(restrict_string.go): implement RestrictStringLength restrictor for validating string field lengths - [+] test(validator_test.go): add test cases for RestrictStringLength restrictor * Update Benchmark result - [+] docs(README.md): add benchmark results for long description scenarios and JSON/XML encoders - [+] docs(README.md): add observations and conclusion based on benchmark results - [+] test(benchmark_test.go): add benchmarks for long description scenarios with Sonic JSON, standard JSON, default XML, and custom XML encoders * Docs [README] Update Documentation - [+] docs(readme): add new feature to restrict string length for specified fields with configurable maximum length
1 parent 91330fc commit 0b2254a

File tree

5 files changed

+529
-0
lines changed

5 files changed

+529
-0
lines changed

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The middleware currently supports the following features:
1111
- Conditional validation skipping based on custom logic
1212
- Restriction of fields to contain only numbers with an optional maximum value
1313
- Storing validation results in the request context for advanced use cases
14+
- Restriction of string length for specified fields with a configurable maximum length
1415

1516
More features and validation capabilities will be added in the future to enhance the middleware's functionality and cater to a wider range of validation scenarios.
1617

@@ -167,3 +168,35 @@ BenchmarkValidatorWithStandardJSON/Valid_JSON_request-24 42150
167168
BenchmarkValidatorWithDefaultXML/Valid_XML_request-24 27764 43721 ns/op 23244 B/op 212 allocs/op
168169
BenchmarkValidatorWithCustomXML/Valid_XML_request-24 27417 43951 ns/op 23256 B/op 212 allocs/op
169170
```
171+
172+
```sh
173+
goos: windows
174+
goarch: amd64
175+
pkg: github.com/H0llyW00dzZ/FiberValidator
176+
cpu: AMD Ryzen 9 3900X 12-Core Processor
177+
BenchmarkRestrictStringLengthLongDescriptionSonicJSON/Valid_JSON_long_description-24 55225 23263 ns/op 19344 B/op 58 allocs/op
178+
BenchmarkRestrictStringLengthLongDescriptionStandardJSON/Valid_JSON_long_description-24 48288 24452 ns/op 19234 B/op 65 allocs/op
179+
BenchmarkRestrictStringLengthLongDescriptionDefaultXML/Valid_XML_long_description-24 30114 39411 ns/op 25018 B/op 111 allocs/op
180+
BenchmarkRestrictStringLengthLongDescriptionCustomXML/Valid_XML_long_description-24 30322 40180 ns/op 25025 B/op 111 allocs/op
181+
BenchmarkValidatorWithSonicJSONSeafood/Valid_JSON_request-24 51158 23802 ns/op 16421 B/op 86 allocs/op
182+
BenchmarkValidatorWithStandardJSONSeafood/Valid_JSON_request-24 43411 27269 ns/op 16652 B/op 112 allocs/op
183+
BenchmarkValidatorWithDefaultXMLSeafood/Valid_XML_request-24 26984 45736 ns/op 23451 B/op 213 allocs/op
184+
BenchmarkValidatorWithCustomXMLSeafood/Valid_XML_request-24 26899 45503 ns/op 23443 B/op 213 allocs/op
185+
BenchmarkValidatorWithSonicJSON/Valid_JSON_request-24 50936 24697 ns/op 16417 B/op 86 allocs/op
186+
BenchmarkValidatorWithStandardJSON/Valid_JSON_request-24 43172 28678 ns/op 16627 B/op 112 allocs/op
187+
BenchmarkValidatorWithDefaultXML/Valid_XML_request-24 27283 43998 ns/op 23262 B/op 212 allocs/op
188+
BenchmarkValidatorWithCustomXML/Valid_XML_request-24 27990 43546 ns/op 23264 B/op 212 allocs/op
189+
```
190+
191+
> [!NOTE]
192+
> Based on the benchmark results, the following observations can be made:
193+
>
194+
> - The Sonic JSON encoder/decoder consistently outperforms the standard JSON encoder/decoder in terms of execution time, bytes allocated, and allocations per operation.
195+
> - The custom XML encoder/decoder performs slightly slower than the default XML encoder/decoder in most cases, with similar memory usage.
196+
> - The JSON benchmarks generally have better performance compared to the XML benchmarks, with lower execution time, bytes allocated, and allocations per operation.
197+
>
198+
> <p align="center">
199+
> <img src="https://i.imgur.com/PxjZ0Dz.png" alt="gopher run" />
200+
> </p>
201+
>
202+
> Overall, the benchmarks using the Sonic JSON encoder/decoder demonstrate the best performance among the tested scenarios.

benchmark_test.go

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,235 @@ import (
1717
"github.com/gofiber/fiber/v2"
1818
)
1919

20+
func BenchmarkRestrictStringLengthLongDescriptionSonicJSON(b *testing.B) {
21+
app := fiber.New(fiber.Config{
22+
JSONEncoder: sonic.Marshal,
23+
JSONDecoder: sonic.Unmarshal,
24+
})
25+
26+
app.Use(validator.New(validator.Config{
27+
Rules: []validator.Restrictor{
28+
validator.RestrictStringLength{
29+
Fields: []string{"name", "description"},
30+
MaxLength: ptr(1000),
31+
},
32+
},
33+
}))
34+
35+
app.Post("/", func(c *fiber.Ctx) error {
36+
return c.SendString("OK")
37+
})
38+
39+
// Generate a long description string
40+
var longDescription strings.Builder
41+
for i := 0; i < 1000; i++ {
42+
longDescription.WriteString("A")
43+
}
44+
45+
testCase := struct {
46+
name string
47+
contentType string
48+
requestBody string
49+
}{
50+
name: "Valid JSON long description",
51+
contentType: fiber.MIMEApplicationJSON,
52+
requestBody: `{"name":"Gopher","description":"` + longDescription.String() + `"}`,
53+
}
54+
55+
b.Run(testCase.name, func(b *testing.B) {
56+
b.ReportAllocs()
57+
58+
for i := 0; i < b.N; i++ {
59+
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(testCase.requestBody))
60+
req.Header.Set("Content-Type", testCase.contentType)
61+
resp, err := app.Test(req)
62+
if err != nil {
63+
b.Fatalf("Unexpected error: %v", err)
64+
}
65+
defer resp.Body.Close()
66+
67+
if resp.StatusCode != http.StatusOK {
68+
b.Errorf("Expected status %d, got %d", http.StatusOK, resp.StatusCode)
69+
}
70+
71+
_, err = io.ReadAll(resp.Body)
72+
if err != nil {
73+
b.Fatalf("Unexpected error reading response body: %v", err)
74+
}
75+
}
76+
})
77+
}
78+
79+
func BenchmarkRestrictStringLengthLongDescriptionStandardJSON(b *testing.B) {
80+
app := fiber.New()
81+
82+
app.Use(validator.New(validator.Config{
83+
Rules: []validator.Restrictor{
84+
validator.RestrictStringLength{
85+
Fields: []string{"name", "description"},
86+
MaxLength: ptr(1000),
87+
},
88+
},
89+
}))
90+
91+
app.Post("/", func(c *fiber.Ctx) error {
92+
return c.SendString("OK")
93+
})
94+
95+
// Generate a long description string
96+
var longDescription strings.Builder
97+
for i := 0; i < 1000; i++ {
98+
longDescription.WriteString("A")
99+
}
100+
101+
testCase := struct {
102+
name string
103+
contentType string
104+
requestBody string
105+
}{
106+
name: "Valid JSON long description",
107+
contentType: fiber.MIMEApplicationJSON,
108+
requestBody: `{"name":"Gopher","description":"` + longDescription.String() + `"}`,
109+
}
110+
111+
b.Run(testCase.name, func(b *testing.B) {
112+
b.ReportAllocs()
113+
114+
for i := 0; i < b.N; i++ {
115+
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(testCase.requestBody))
116+
req.Header.Set("Content-Type", testCase.contentType)
117+
resp, err := app.Test(req)
118+
if err != nil {
119+
b.Fatalf("Unexpected error: %v", err)
120+
}
121+
defer resp.Body.Close()
122+
123+
if resp.StatusCode != http.StatusOK {
124+
b.Errorf("Expected status %d, got %d", http.StatusOK, resp.StatusCode)
125+
}
126+
127+
_, err = io.ReadAll(resp.Body)
128+
if err != nil {
129+
b.Fatalf("Unexpected error reading response body: %v", err)
130+
}
131+
}
132+
})
133+
}
134+
135+
func BenchmarkRestrictStringLengthLongDescriptionDefaultXML(b *testing.B) {
136+
app := fiber.New()
137+
138+
app.Use(validator.New(validator.Config{
139+
Rules: []validator.Restrictor{
140+
validator.RestrictStringLength{
141+
Fields: []string{"name", "description"},
142+
MaxLength: ptr(1000),
143+
},
144+
},
145+
}))
146+
147+
app.Post("/", func(c *fiber.Ctx) error {
148+
return c.SendString("OK")
149+
})
150+
151+
// Generate a long description string
152+
var longDescription strings.Builder
153+
for i := 0; i < 1000; i++ {
154+
longDescription.WriteString("A")
155+
}
156+
157+
testCase := struct {
158+
name string
159+
contentType string
160+
requestBody string
161+
}{
162+
name: "Valid XML long description",
163+
contentType: fiber.MIMEApplicationXML,
164+
requestBody: `<data><name>Gopher</name><description>` + longDescription.String() + `</description></data>`,
165+
}
166+
167+
b.Run(testCase.name, func(b *testing.B) {
168+
b.ReportAllocs()
169+
170+
for i := 0; i < b.N; i++ {
171+
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(testCase.requestBody))
172+
req.Header.Set("Content-Type", testCase.contentType)
173+
resp, err := app.Test(req)
174+
if err != nil {
175+
b.Fatalf("Unexpected error: %v", err)
176+
}
177+
defer resp.Body.Close()
178+
179+
if resp.StatusCode != http.StatusOK {
180+
b.Errorf("Expected status %d, got %d", http.StatusOK, resp.StatusCode)
181+
}
182+
183+
_, err = io.ReadAll(resp.Body)
184+
if err != nil {
185+
b.Fatalf("Unexpected error reading response body: %v", err)
186+
}
187+
}
188+
})
189+
}
190+
191+
func BenchmarkRestrictStringLengthLongDescriptionCustomXML(b *testing.B) {
192+
app := fiber.New(fiber.Config{
193+
XMLEncoder: customXMLMarshal,
194+
})
195+
196+
app.Use(validator.New(validator.Config{
197+
Rules: []validator.Restrictor{
198+
validator.RestrictStringLength{
199+
Fields: []string{"name", "description"},
200+
MaxLength: ptr(1000),
201+
},
202+
},
203+
}))
204+
205+
app.Post("/", func(c *fiber.Ctx) error {
206+
return c.SendString("OK")
207+
})
208+
209+
// Generate a long description string
210+
var longDescription strings.Builder
211+
for i := 0; i < 1000; i++ {
212+
longDescription.WriteString("A")
213+
}
214+
215+
testCase := struct {
216+
name string
217+
contentType string
218+
requestBody string
219+
}{
220+
name: "Valid XML long description",
221+
contentType: fiber.MIMEApplicationXML,
222+
requestBody: `<data><name>Gopher</name><description>` + longDescription.String() + `</description></data>`,
223+
}
224+
225+
b.Run(testCase.name, func(b *testing.B) {
226+
b.ReportAllocs()
227+
228+
for i := 0; i < b.N; i++ {
229+
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(testCase.requestBody))
230+
req.Header.Set("Content-Type", testCase.contentType)
231+
resp, err := app.Test(req)
232+
if err != nil {
233+
b.Fatalf("Unexpected error: %v", err)
234+
}
235+
defer resp.Body.Close()
236+
237+
if resp.StatusCode != http.StatusOK {
238+
b.Errorf("Expected status %d, got %d", http.StatusOK, resp.StatusCode)
239+
}
240+
241+
_, err = io.ReadAll(resp.Body)
242+
if err != nil {
243+
b.Fatalf("Unexpected error reading response body: %v", err)
244+
}
245+
}
246+
})
247+
}
248+
20249
func BenchmarkValidatorWithSonicJSONSeafood(b *testing.B) {
21250
app := fiber.New(fiber.Config{
22251
JSONEncoder: sonic.Marshal,

constant.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ const (
2626
ErrFieldExceedsMaximumDigits = "The '%s' field must not exceed %d digits"
2727
)
2828

29+
const (
30+
// ErrFieldExceedsMaximumLength represents an error message for a field that exceeds the maximum allowed length.
31+
ErrFieldExceedsMaximumLength = "The '%s' field must not exceed %d characters"
32+
33+
// ErrFieldsExceedMaximumLength represents an error message for fields that exceed the maximum allowed length.
34+
ErrFieldsExceedMaximumLength = "The '%s' fields must not exceed the maximum length"
35+
)
36+
2937
const (
3038
// Define the range of numeric characters
3139
numericStart = '0' + iota

0 commit comments

Comments
 (0)