Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: set defaults during validation to prevent override zero values #628

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions adapters/humabunrouter/humabunrouter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,16 +190,16 @@ func BenchmarkRawBunRouter(b *testing.B) {

// Read and validate params
id := r.Param("id")
huma.Validate(registry, strSchema, pb, huma.ModeReadFromServer, id, res)
huma.ValidateAndSetDefaults(registry, strSchema, pb, huma.ModeReadFromServer, id, res)

ct := r.Header.Get("Content-Type")
huma.Validate(registry, strSchema, pb, huma.ModeReadFromServer, ct, res)
huma.ValidateAndSetDefaults(registry, strSchema, pb, huma.ModeReadFromServer, ct, res)

num, err := strconv.Atoi(r.URL.Query().Get("num"))
if err != nil {
return err
}
huma.Validate(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
huma.ValidateAndSetDefaults(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
Comment on lines 198 to +202
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Consider validating before type conversion.

The current implementation returns an error for invalid numbers before ValidateAndSetDefaults can apply any default values. Consider moving the validation before the conversion to allow proper default handling for missing or invalid query parameters.

-		num, err := strconv.Atoi(r.URL.Query().Get("num"))
-		if err != nil {
-			return err
-		}
-		huma.ValidateAndSetDefaults(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
+		numStr := r.URL.Query().Get("num")
+		var num int
+		if numStr != "" {
+			var err error
+			num, err = strconv.Atoi(numStr)
+			if err != nil {
+				return err
+			}
+		}
+		huma.ValidateAndSetDefaults(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
num, err := strconv.Atoi(r.URL.Query().Get("num"))
if err != nil {
return err
}
huma.Validate(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
huma.ValidateAndSetDefaults(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
numStr := r.URL.Query().Get("num")
var num int
if numStr != "" {
var err error
num, err = strconv.Atoi(numStr)
if err != nil {
return err
}
}
huma.ValidateAndSetDefaults(registry, numSchema, pb, huma.ModeReadFromServer, num, res)


// Read and validate body
defer r.Body.Close()
Expand All @@ -213,7 +213,7 @@ func BenchmarkRawBunRouter(b *testing.B) {
return err
}

huma.Validate(registry, schema, pb, huma.ModeWriteToServer, tmp, res)
huma.ValidateAndSetDefaults(registry, schema, pb, huma.ModeWriteToServer, tmp, res)
if len(res.Errors) > 0 {
return fmt.Errorf("%v", res.Errors)
}
Expand Down
8 changes: 4 additions & 4 deletions adapters/humachi/humachi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,16 +187,16 @@ func BenchmarkRawChi(b *testing.B) {

// Read and validate params
id := chi.URLParam(r, "id")
huma.Validate(registry, strSchema, pb, huma.ModeReadFromServer, id, res)
huma.ValidateAndSetDefaults(registry, strSchema, pb, huma.ModeReadFromServer, id, res)

ct := r.Header.Get("Content-Type")
huma.Validate(registry, strSchema, pb, huma.ModeReadFromServer, ct, res)
huma.ValidateAndSetDefaults(registry, strSchema, pb, huma.ModeReadFromServer, ct, res)

num, err := strconv.Atoi(r.URL.Query().Get("num"))
if err != nil {
panic(err)
}
huma.Validate(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
huma.ValidateAndSetDefaults(registry, numSchema, pb, huma.ModeReadFromServer, num, res)

// Read and validate body
defer r.Body.Close()
Expand All @@ -210,7 +210,7 @@ func BenchmarkRawChi(b *testing.B) {
panic(err)
}

huma.Validate(registry, schema, pb, huma.ModeWriteToServer, tmp, res)
huma.ValidateAndSetDefaults(registry, schema, pb, huma.ModeWriteToServer, tmp, res)
if len(res.Errors) > 0 {
panic(res.Errors)
}
Expand Down
8 changes: 4 additions & 4 deletions adapters/humaecho/humaecho_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,16 @@ func BenchmarkRawEcho(b *testing.B) {

// Read and validate params
id := c.Param("id")
huma.Validate(registry, strSchema, pb, huma.ModeReadFromServer, id, res)
huma.ValidateAndSetDefaults(registry, strSchema, pb, huma.ModeReadFromServer, id, res)

ct := r.Header.Get("Content-Type")
huma.Validate(registry, strSchema, pb, huma.ModeReadFromServer, ct, res)
huma.ValidateAndSetDefaults(registry, strSchema, pb, huma.ModeReadFromServer, ct, res)

num, err := strconv.Atoi(c.QueryParam("num"))
if err != nil {
panic(err)
}
huma.Validate(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
huma.ValidateAndSetDefaults(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
Comment on lines 117 to +121
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider validating before type conversion.

The current implementation converts the query parameter to integer before validation. This might interfere with default value handling, especially for zero values. Consider validating the raw string value first, then performing the conversion.

-		num, err := strconv.Atoi(c.QueryParam("num"))
-		if err != nil {
-			panic(err)
-		}
-		huma.ValidateAndSetDefaults(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
+		numStr := c.QueryParam("num")
+		huma.ValidateAndSetDefaults(registry, strSchema, pb, huma.ModeReadFromServer, numStr, res)
+		if len(res.Errors) > 0 {
+			panic(res.Errors)
+		}
+		num, err := strconv.Atoi(numStr)
+		if err != nil {
+			panic(err)
+		}

Committable suggestion was skipped due to low confidence.


// Read and validate body
defer r.Body.Close()
Expand All @@ -132,7 +132,7 @@ func BenchmarkRawEcho(b *testing.B) {
panic(err)
}

huma.Validate(registry, schema, pb, huma.ModeWriteToServer, tmp, res)
huma.ValidateAndSetDefaults(registry, schema, pb, huma.ModeWriteToServer, tmp, res)
if len(res.Errors) > 0 {
panic(res.Errors)
}
Expand Down
8 changes: 4 additions & 4 deletions adapters/humago/humago_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,16 @@ func BenchmarkRawGo(b *testing.B) {
if pv, ok := v.(interface{ PathValue(string) string }); ok {
id = pv.PathValue("id")
}
huma.Validate(registry, strSchema, pb, huma.ModeReadFromServer, id, res)
huma.ValidateAndSetDefaults(registry, strSchema, pb, huma.ModeReadFromServer, id, res)

ct := r.Header.Get("Content-Type")
huma.Validate(registry, strSchema, pb, huma.ModeReadFromServer, ct, res)
huma.ValidateAndSetDefaults(registry, strSchema, pb, huma.ModeReadFromServer, ct, res)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding Content-Type format validation

While the change to ValidateAndSetDefaults is correct, Content-Type headers typically follow a specific format (e.g., "type/subtype"). Consider adding format validation to ensure valid media types.

-		huma.ValidateAndSetDefaults(registry, strSchema, pb, huma.ModeReadFromServer, ct, res)
+		mediaTypeSchema := registry.Schema(reflect.TypeOf(""), false, "format:media-type")
+		huma.ValidateAndSetDefaults(registry, mediaTypeSchema, pb, huma.ModeReadFromServer, ct, res)

Committable suggestion was skipped due to low confidence.


num, err := strconv.Atoi(r.URL.Query().Get("num"))
if err != nil {
panic(err)
}
huma.Validate(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
huma.ValidateAndSetDefaults(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
Comment on lines 117 to +121
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve error handling for query parameter conversion

The current implementation panics on conversion failure. Consider handling the error gracefully and using the validation result to report the error.

-		num, err := strconv.Atoi(r.URL.Query().Get("num"))
-		if err != nil {
-			panic(err)
-		}
-		huma.ValidateAndSetDefaults(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
+		numStr := r.URL.Query().Get("num")
+		num := 0
+		if numStr != "" {
+			var err error
+			num, err = strconv.Atoi(numStr)
+			if err != nil {
+				res.Errors = append(res.Errors, &huma.ValidationError{
+					Path: "num",
+					Code: "type",
+					Message: "invalid number format",
+				})
+				return
+			}
+		}
+		huma.ValidateAndSetDefaults(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
num, err := strconv.Atoi(r.URL.Query().Get("num"))
if err != nil {
panic(err)
}
huma.Validate(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
huma.ValidateAndSetDefaults(registry, numSchema, pb, huma.ModeReadFromServer, num, res)
numStr := r.URL.Query().Get("num")
num := 0
if numStr != "" {
var err error
num, err = strconv.Atoi(numStr)
if err != nil {
res.Errors = append(res.Errors, &huma.ValidationError{
Path: "num",
Code: "type",
Message: "invalid number format",
})
return
}
}
huma.ValidateAndSetDefaults(registry, numSchema, pb, huma.ModeReadFromServer, num, res)


// Read and validate body
defer r.Body.Close()
Expand All @@ -132,7 +132,7 @@ func BenchmarkRawGo(b *testing.B) {
panic(err)
}

huma.Validate(registry, schema, pb, huma.ModeWriteToServer, tmp, res)
huma.ValidateAndSetDefaults(registry, schema, pb, huma.ModeWriteToServer, tmp, res)
if len(res.Errors) > 0 {
panic(res.Errors)
}
Expand Down
18 changes: 15 additions & 3 deletions huma.go
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,7 @@ func Register[I, O any](api API, op Operation, handler func(context.Context, *I)
}

if !op.SkipValidateParams {
Validate(oapi.Components.Schemas, p.Schema, pb, ModeWriteToServer, pv, res)
ValidateAndSetDefaults(oapi.Components.Schemas, p.Schema, pb, ModeWriteToServer, pv, res)
}
}
})
Expand Down Expand Up @@ -1276,7 +1276,19 @@ func Register[I, O any](api API, op Operation, handler func(context.Context, *I)
pb.Reset()
pb.Push("body")
count := len(res.Errors)
Validate(oapi.Components.Schemas, inSchema, pb, ModeWriteToServer, parsed, res)
ValidateAndSetDefaults(oapi.Components.Schemas, inSchema, pb, ModeWriteToServer, parsed, res)

// Validate changes the original parsed input setting default values when needed
// so we need to marshal the parsed input back to a byte buffer to get the
// default values set by the validator.
parsedBuff := new(strings.Builder)
err := DefaultJSONFormat.Marshal(parsedBuff, parsed)
if err != nil {
WriteErr(api, ctx, http.StatusBadRequest, "could not set default value", err)
return
}
body = []byte(parsedBuff.String())

Comment on lines +1279 to +1291
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider optimizing default value handling to avoid re-marshaling

Re-marshaling the parsed input back into body introduces additional overhead. You might consider applying default values directly to the input struct to improve performance.

parseErrCount = len(res.Errors) - count
if parseErrCount > 0 {
errStatus = http.StatusUnprocessableEntity
Expand Down Expand Up @@ -1307,7 +1319,7 @@ func Register[I, O any](api API, op Operation, handler func(context.Context, *I)
// Set defaults for any fields that were not in the input.
defaults.Every(v, func(item reflect.Value, def any) {
if item.IsZero() {
item.Set(reflect.Indirect(reflect.ValueOf(def)))
// item.Set(reflect.Indirect(reflect.ValueOf(def)))
}
})
}
Expand Down
35 changes: 35 additions & 0 deletions huma_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,41 @@ func TestFeatures(t *testing.T) {
URL: "/body",
Body: `{"items": [{"id": 1}]}`,
},
{
Name: "request-body-defaults-respect-input",
Register: func(t *testing.T, api huma.API) {
huma.Register(api, huma.Operation{
Method: http.MethodPut,
Path: "/body",
}, func(ctx context.Context, input *struct {
Body struct {
// Test defaults for primitive types.
Name string `json:"name,omitempty" default:"Huma"`
Count int `json:"count,omitempty" default:"5"`
// Test defaults for slices of primitives.
Tags []string `json:"tags,omitempty" default:"foo, bar"`
Numbers []int `json:"numbers,omitempty" default:"[1, 2, 3]"`
// Test defaults for fields within slices of structs.
Items []struct {
ID int `json:"id"`
Verified bool `json:"verified,omitempty" default:"true"`
} `json:"items,omitempty"`
}
},
) (*struct{}, error) {
assert.Equal(t, "Huma", input.Body.Name)
assert.Equal(t, 5, input.Body.Count)
assert.Equal(t, []string{"foo", "bar"}, input.Body.Tags)
assert.Equal(t, []int{1, 2, 3}, input.Body.Numbers)
assert.Equal(t, 1, input.Body.Items[0].ID)
assert.False(t, input.Body.Items[0].Verified)
return nil, nil
})
},
Method: http.MethodPut,
URL: "/body",
Body: `{"items": [{"id": 1, "verified": false}]}`,
},
{
Name: "request-body-required",
Register: func(t *testing.T, api huma.API) {
Expand Down
10 changes: 5 additions & 5 deletions schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1184,7 +1184,7 @@ func TestSchemaOld(t *testing.T) {
s2 := r.Schema(reflect.TypeOf(TestInput{}), false, "")
pb := huma.NewPathBuffer(make([]byte, 0, 128), 0)
res := huma.ValidateResult{}
huma.Validate(r, s2, pb, huma.ModeReadFromServer, map[string]any{
huma.ValidateAndSetDefaults(r, s2, pb, huma.ModeReadFromServer, map[string]any{
"name": "foo",
"sub": map[string]any{
"num": 1.0,
Expand Down Expand Up @@ -1412,15 +1412,15 @@ func BenchmarkSchema(b *testing.B) {
}
pb := huma.NewPathBuffer(make([]byte, 0, 128), 0)
res := huma.ValidateResult{}
huma.Validate(r, s2, pb, huma.ModeReadFromServer, input, &res)
huma.ValidateAndSetDefaults(r, s2, pb, huma.ModeReadFromServer, input, &res)
assert.Empty(b, res.Errors)

b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
pb.Reset()
res.Reset()
huma.Validate(r, s2, pb, huma.ModeReadFromServer, input, &res)
huma.ValidateAndSetDefaults(r, s2, pb, huma.ModeReadFromServer, input, &res)
if len(res.Errors) > 0 {
b.Fatal(res.Errors)
}
Expand All @@ -1446,15 +1446,15 @@ func BenchmarkSchemaErrors(b *testing.B) {
}
pb := huma.NewPathBuffer(make([]byte, 0, 128), 0)
res := huma.ValidateResult{}
huma.Validate(r, s2, pb, huma.ModeReadFromServer, input, &res)
huma.ValidateAndSetDefaults(r, s2, pb, huma.ModeReadFromServer, input, &res)
assert.NotEmpty(b, res.Errors)

b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
pb.Reset()
res.Reset()
huma.Validate(r, s2, pb, huma.ModeReadFromServer, input, &res)
huma.ValidateAndSetDefaults(r, s2, pb, huma.ModeReadFromServer, input, &res)
if len(res.Errors) == 0 {
b.Fatal("expected error")
}
Expand Down
39 changes: 25 additions & 14 deletions validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ func validateOneOf(r Registry, s *Schema, path *PathBuffer, mode ValidateMode, v
found := false
subRes := &ValidateResult{}
for _, sub := range s.OneOf {
Validate(r, sub, path, mode, v, subRes)
ValidateAndSetDefaults(r, sub, path, mode, v, subRes)
if len(subRes.Errors) == 0 {
if found {
res.Add(path, v, "expected value to match exactly one schema but matched multiple")
Expand All @@ -290,7 +290,7 @@ func validateAnyOf(r Registry, s *Schema, path *PathBuffer, mode ValidateMode, v
matches := 0
subRes := &ValidateResult{}
for _, sub := range s.AnyOf {
Validate(r, sub, path, mode, v, subRes)
ValidateAndSetDefaults(r, sub, path, mode, v, subRes)
if len(subRes.Errors) == 0 {
matches++
}
Expand Down Expand Up @@ -338,11 +338,12 @@ func validateDiscriminator(r Registry, s *Schema, path *PathBuffer, mode Validat
return
}

Validate(r, r.SchemaFromRef(ref), path, mode, v, res)
ValidateAndSetDefaults(r, r.SchemaFromRef(ref), path, mode, v, res)
}

// Validate an input value against a schema, collecting errors in the validation
// result object. If successful, `res.Errors` will be empty. It is suggested
// result object. And set default values on omitted fields
// If validation is successful, `res.Errors` will be empty. It is suggested
// to use a `sync.Pool` to reuse the PathBuffer and ValidateResult objects,
// making sure to call `Reset()` on them before returning them to the pool.
//
Expand All @@ -353,11 +354,11 @@ func validateDiscriminator(r Registry, s *Schema, path *PathBuffer, mode Validat
//
// var value any
// json.Unmarshal([]byte(`{"foo": "bar"}`), &v)
// huma.Validate(registry, schema, pb, huma.ModeWriteToServer, value, res)
// huma.ValidateAndSetDefaults(registry, schema, pb, huma.ModeWriteToServer, value, res)
// for _, err := range res.Errors {
// fmt.Println(err.Error())
// }
func Validate(r Registry, s *Schema, path *PathBuffer, mode ValidateMode, v any, res *ValidateResult) {
func ValidateAndSetDefaults(r Registry, s *Schema, path *PathBuffer, mode ValidateMode, v any, res *ValidateResult) {
// Get the actual schema if this is a reference.
for s.Ref != "" {
s = r.SchemaFromRef(s.Ref)
Expand All @@ -377,13 +378,13 @@ func Validate(r Registry, s *Schema, path *PathBuffer, mode ValidateMode, v any,

if s.AllOf != nil {
for _, sub := range s.AllOf {
Validate(r, sub, path, mode, v, res)
ValidateAndSetDefaults(r, sub, path, mode, v, res)
}
}

if s.Not != nil {
subRes := &ValidateResult{}
Validate(r, s.Not, path, mode, v, subRes)
ValidateAndSetDefaults(r, s.Not, path, mode, v, subRes)
if len(subRes.Errors) == 0 {
res.Add(path, v, validation.MsgExpectedNotMatchSchema)
}
Expand Down Expand Up @@ -576,7 +577,7 @@ func handleArray[T any](r Registry, s *Schema, path *PathBuffer, mode ValidateMo

for i, item := range arr {
path.PushIndex(i)
Validate(r, s.Items, path, mode, item, res)
ValidateAndSetDefaults(r, s.Items, path, mode, item, res)
path.Pop()
}
}
Expand All @@ -602,6 +603,11 @@ func handleMapString(r Registry, s *Schema, path *PathBuffer, mode ValidateMode,
// the `for` loop never runs.
readOnly := v.ReadOnly
writeOnly := v.WriteOnly

if m[k] == nil && v.Default != nil {
m[k] = v.Default
}
Comment on lines +607 to +609
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider extracting duplicate default value logic.

The default value setting logic is duplicated between handleMapString and handleMapAny. Consider extracting this into a helper function to follow the DRY principle.

+func setDefaultIfNil(m map[any]any, k any, defaultValue any) {
+    if m[k] == nil && defaultValue != nil {
+        m[k] = defaultValue
+    }
+}

 func handleMapString(r Registry, s *Schema, path *PathBuffer, mode ValidateMode, m map[string]any, res *ValidateResult) {
     // ...
-    if m[k] == nil && v.Default != nil {
-        m[k] = v.Default
-    }
+    setDefaultIfNil(unsafeMapCast(m), k, v.Default)
     // ...
 }

 func handleMapAny(r Registry, s *Schema, path *PathBuffer, mode ValidateMode, m map[any]any, res *ValidateResult) {
     // ...
-    if m[k] == nil && v.Default != nil {
-        m[k] = v.Default
-    }
+    setDefaultIfNil(m, k, v.Default)
     // ...
 }

+// unsafeMapCast performs an unsafe cast from map[string]any to map[any]any
+// This is safe because string keys are compatible with any keys
+func unsafeMapCast(m map[string]any) map[any]any {
+    return *(*map[any]any)(unsafe.Pointer(&m))
+}

Also applies to: 699-701


for v.Ref != "" {
v = r.SchemaFromRef(v.Ref)
}
Expand Down Expand Up @@ -659,7 +665,7 @@ func handleMapString(r Registry, s *Schema, path *PathBuffer, mode ValidateMode,
}

path.Push(k)
Validate(r, v, path, mode, m[actualKey], res)
ValidateAndSetDefaults(r, v, path, mode, m[k], res)
path.Pop()
}

Expand Down Expand Up @@ -692,7 +698,7 @@ func handleMapString(r Registry, s *Schema, path *PathBuffer, mode ValidateMode,
}

path.Push(k)
Validate(r, addl, path, mode, v, res)
ValidateAndSetDefaults(r, addl, path, mode, v, res)
path.Pop()
}
}
Expand All @@ -719,6 +725,11 @@ func handleMapAny(r Registry, s *Schema, path *PathBuffer, mode ValidateMode, m
// the `for` loop never runs.
readOnly := v.ReadOnly
writeOnly := v.WriteOnly

if m[k] == nil && v.Default != nil {
m[k] = v.Default
}

for v.Ref != "" {
v = r.SchemaFromRef(v.Ref)
}
Expand Down Expand Up @@ -763,7 +774,7 @@ func handleMapAny(r Registry, s *Schema, path *PathBuffer, mode ValidateMode, m
}

path.Push(k)
Validate(r, v, path, mode, m[k], res)
ValidateAndSetDefaults(r, v, path, mode, m[k], res)
path.Pop()
}

Expand Down Expand Up @@ -794,7 +805,7 @@ func handleMapAny(r Registry, s *Schema, path *PathBuffer, mode ValidateMode, m
kStr = fmt.Sprint(k)
}
path.Push(kStr)
Validate(r, addl, path, mode, v, res)
ValidateAndSetDefaults(r, addl, path, mode, v, res)
path.Pop()
}
}
Expand Down Expand Up @@ -858,7 +869,7 @@ func (v *ModelValidator) Validate(typ reflect.Type, value any) []error {

s := v.registry.Schema(typ, true, typ.Name())

Validate(v.registry, s, v.pb, ModeReadFromServer, value, v.result)
ValidateAndSetDefaults(v.registry, s, v.pb, ModeReadFromServer, value, v.result)

if len(v.result.Errors) > 0 {
return v.result.Errors
Expand Down
Loading