Change:
With the addition of server side validation in 1.23, it is now possible for a call to successfully decode it’s data and return a non-nil error when there are strict decoding errors (e.g. duplicate/unknown fields). This means that callers of can distinguish between fatal errors (that cause decoding to fail) and strict decoding errors (that still allow the decode to succeed) by inspecting the target object (to see if it was successfully decoded into) and the error itself (to see if it is a ) With the NestedObjectDecoder however, it only returns an error from giving no indication whether a strict decoding error it has returned comes from an internal decode step that short circuited (by not accounting for strict decoding errors) or actually did successfully decode, but also encountered strict decoding errors. We propose taking a few actions to minimize the possibility of erroneous short-circuiting from Document how all nested decoders should only return strict decoding errors if all decoding is successful or return only non-strict decoding (ie. fatal errors). Document this for all known in-tree and out-of-tree . Update in-tree nested decoders to do the above mentioned behavior (which is just the kube-scheduler) Long-term solutions to better guarantee that no erroneous short-circuiting is taking place could include deprecating the current interface in favor of one that returns both the target object that was decoded into along with an error return a special error type specific to indicating that all internal decoding was successful.
/sig api-machinery
/triage accepted

Discussion:
- Score: 2 Utilization: 1 `), <del> wantErr: `decoding .profiles[0].pluginConfig[0]: decoding args for plugin NodeResourcesFit: strict decoding error: unknown field "scoringStrategy.requestedToCapacityRatio.shape[0].Score", unknown field "scoringStrategy.requestedToCapacityRatio.shape[0].Utilization"`, </del> <ins> wantErr: `strict decoding error: decoding .profiles[0].pluginConfig[0]: strict decoding error: decoding args for plugin NodeResourcesFit: strict decoding error: unknown field "scoringStrategy.requestedToCapacityRatio.shape[0].Score", unknown field "scoringStrategy.requestedToCapacityRatio.shape[0].Utilization"`, </ins> }, { name: "v1beta2 NodeResourcesFitArgs resources encoding is strict",

- Score: 2 Utilization: 1 `), <del> wantErr: `decoding .profiles[0].pluginConfig[0]: decoding args for plugin NodeResourcesFit: strict decoding error: unknown field "scoringStrategy.requestedToCapacityRatio.shape[0].Score", unknown field "scoringStrategy.requestedToCapacityRatio.shape[0].Utilization"`, </del> <ins> wantErr: `strict decoding error: decoding .profiles[0].pluginConfig[0]: strict decoding error: decoding args for plugin NodeResourcesFit: strict decoding error: unknown field "scoringStrategy.requestedToCapacityRatio.shape[0].Score", unknown field "scoringStrategy.requestedToCapacityRatio.shape[0].Utilization"`, </ins> }, { name: "v1beta3 NodeResourcesFitArgs resources encoding is strict",

- Name: cpu Weight: 1 `), <del> wantErr: `decoding .profiles[0].pluginConfig[0]: decoding args for plugin NodeResourcesFit: strict decoding error: unknown field "scoringStrategy.resources[0].Name", unknown field "scoringStrategy.resources[0].Weight"`, </del> <ins> wantErr: `strict decoding error: decoding .profiles[0].pluginConfig[0]: strict decoding error: decoding args for plugin NodeResourcesFit: strict decoding error: unknown field "scoringStrategy.resources[0].Name", unknown field "scoringStrategy.resources[0].Weight"`, </ins> }, { name: "out-of-tree plugin args",

// NestedObjectDecoder is an optional interface that objects may implement to be given // an opportunity to decode any nested Objects / RawExtensions during serialization. <ins> // It is possible for DecodeNestedObjects to return a non-nil error but for the decoding // to have succeeded in the case of strict decoding errors (e.g. unknown/duplicate fields). // As such it is important for callers of DecodeNestedObjects to check to confirm whether // an error is a runtime.StrictDecodingError before short circuiting. // Similarly, implementations of DecodeNestedObjects should ensure that a runtime.StrictDecodingError // is only returned when the rest of decoding has succeeded. </ins> type NestedObjectDecoder interface { DecodeNestedObjects(d Decoder) error }

} } <del> var strictDecodingErr error </del> <ins> var strictDecodingErrs []error </ins> obj, gvk, err := c.decoder.Decode(data, defaultGVK, decodeInto) if err != nil { <del> if obj != nil && runtime.IsStrictDecodingError(err) { // save the strictDecodingError and the caller decide what to do with it strictDecodingErr = err </del> <ins> if strictErr, ok := runtime.AsStrictDecodingError(err); obj != nil && ok { // save the strictDecodingError and let the caller decide what to do with it strictDecodingErrs = append(strictDecodingErrs, strictErr.Errors()...) </ins> } else { return nil, gvk, err } } <del> // TODO: look into strict handling of nested object decoding </del> if d, ok := obj.(runtime.NestedObjectDecoder); ok { if err := d.DecodeNestedObjects(runtime.WithoutVersionDecoder{c.decoder}); err != nil { <del> return nil, gvk, err </del> <ins> if strictErr, ok := runtime.AsStrictDecodingError(err); ok { // save the strictDecodingError let and the caller decide what to do with it strictDecodingErrs = append(strictDecodingErrs, strictErr.Errors()...) } else { return nil, gvk, err } </ins> } } <ins> // aggregate the strict decoding errors into one var strictDecodingErr error if len(strictDecodingErrs) > 0 { strictDecodingErr = runtime.NewStrictDecodingError(strictDecodingErrs) } </ins> // if we specify a target, use generic conversion. if into != nil { // perform defaulting if requested

} } <ins> func TestNestedDecodeStrictDecodingError(t *testing.T) { strictErr := runtime.NewStrictDecodingError([]error{fmt.Errorf("duplicate field")}) n := &testNestedDecodable{nestedErr: strictErr} decoder := &mockSerializer{obj: n} codec := NewCodec(nil, decoder, nil, nil, nil, nil, nil, nil, "TestNestedDecode") o, _, err := codec.Decode([]byte(`{}`), nil, n) if strictErr, ok := runtime.AsStrictDecodingError(err); !ok || err != strictErr { t.Errorf("unexpected error: %v", err) } if o != n { t.Errorf("did not successfully decode with strict decoding error: %v", o) } if !n.nestedCalled { t.Errorf("did not invoke nested decoder") } } </ins> func TestNestedEncode(t *testing.T) { n := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode")} n2 := &testNestedDecodable{nestedErr: fmt.Errorf("unable to decode 2")}

// DecodeNestedObjects decodes plugin args for known types. func (c *KubeSchedulerConfiguration) DecodeNestedObjects(d runtime.Decoder) error { <ins> var strictDecodingErrs []error </ins> for i := range c.Profiles { prof := &c.Profiles[i] for j := range prof.PluginConfig { err := prof.PluginConfig[j].decodeNestedObjects(d) if err != nil { <del> return fmt.Errorf("decoding .profiles[%d].pluginConfig[%d]: %w", i, j, err) </del> <ins> decodingErr := fmt.Errorf("decoding .profiles[%d].pluginConfig[%d]: %w", i, j, err) if runtime.IsStrictDecodingError(err) { strictDecodingErrs = append(strictDecodingErrs, decodingErr) } else { return decodingErr } </ins> } } } <ins> if len(strictDecodingErrs) > 0 { return runtime.NewStrictDecodingError(strictDecodingErrs) } </ins> return nil }

return nil } <ins> var strictDecodingErr error </ins> obj, parsedGvk, err := d.Decode(c.Args.Raw, &gvk, nil) if err != nil { <del> return fmt.Errorf("decoding args for plugin %s: %w", c.Name, err) </del> <ins> decodingArgsErr := fmt.Errorf("decoding args for plugin %s: %w", c.Name, err) if obj != nil && runtime.IsStrictDecodingError(err) { strictDecodingErr = runtime.NewStrictDecodingError([]error{decodingArgsErr}) } else { return decodingArgsErr } </ins> } if parsedGvk.GroupKind() != gvk.GroupKind() { return fmt.Errorf("args for plugin %s were not of type %s, got %s", c.Name, gvk.GroupKind(), parsedGvk.GroupKind()) } c.Args.Object = obj <del> return nil </del> <ins> return strictDecodingErr </ins> } func (c *PluginConfig) encodeNestedObjects(e runtime.Encoder) error {