Merge pull request #132314 from thockin/jp_nicer_api_errors

Nicer value rendering in API errors
This commit is contained in:
Kubernetes Prow Robot
2025-07-03 01:33:33 -07:00
committed by GitHub
12 changed files with 515 additions and 126 deletions

View File

@@ -107,7 +107,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
AdmissionReviewVersions: []string{"invalidVersion"},
},
}, true),
expectedError: `Invalid value: []string{"invalidVersion"}`,
expectedError: `Invalid value: ["invalidVersion"]`,
}, {
name: "should fail on duplicate AdmissionReviewVersion",
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
@@ -313,7 +313,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
}},
},
}, true),
expectedError: `webhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`,
expectedError: `webhooks[0].rules[0].resources: Invalid value: ["*/*","a"]: if '*/*' is present, must not specify other resources`,
}, {
name: "FailurePolicy can only be \"Ignore\" or \"Fail\"",
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
@@ -892,7 +892,7 @@ func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) {
AdmissionReviewVersions: []string{"v1beta1", "invalid-v1"},
},
}, true),
expectedError: `Invalid value: []string{"invalid-v1"}`,
expectedError: `Invalid value: ["invalid-v1"]`,
}, {
name: "should fail on invalid AdmissionReviewVersion with missing previous versions",
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
@@ -908,7 +908,7 @@ func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) {
SideEffects: &unknownSideEffect,
},
}, false),
expectedError: `Invalid value: []string{"invalid-v1"}`,
expectedError: `Invalid value: ["invalid-v1"]`,
}, {
name: "Webhooks must have unique names when old config has unique names",
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
@@ -1084,7 +1084,7 @@ func TestValidateMutatingWebhookConfiguration(t *testing.T) {
AdmissionReviewVersions: []string{"invalidVersion"},
},
}, true),
expectedError: `Invalid value: []string{"invalidVersion"}`,
expectedError: `Invalid value: ["invalidVersion"]`,
}, {
name: "should fail on duplicate AdmissionReviewVersion",
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
@@ -1290,7 +1290,7 @@ func TestValidateMutatingWebhookConfiguration(t *testing.T) {
}},
},
}, true),
expectedError: `webhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`,
expectedError: `webhooks[0].rules[0].resources: Invalid value: ["*/*","a"]: if '*/*' is present, must not specify other resources`,
}, {
name: "FailurePolicy can only be \"Ignore\" or \"Fail\"",
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
@@ -1885,7 +1885,7 @@ func TestValidateMutatingWebhookConfigurationUpdate(t *testing.T) {
AdmissionReviewVersions: []string{"v1beta1", "invalid-v1"},
},
}, true),
expectedError: `Invalid value: []string{"invalid-v1"}`,
expectedError: `Invalid value: ["invalid-v1"]`,
}, {
name: "should fail on invalid AdmissionReviewVersion with missing previous versions",
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
@@ -1901,7 +1901,7 @@ func TestValidateMutatingWebhookConfigurationUpdate(t *testing.T) {
SideEffects: &unknownSideEffect,
},
}, false),
expectedError: `Invalid value: []string{"invalid-v1"}`,
expectedError: `Invalid value: ["invalid-v1"]`,
}, {
name: "Webhooks can have duplicate names when old config has duplicate names",
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
@@ -2592,7 +2592,7 @@ func TestValidateValidatingAdmissionPolicy(t *testing.T) {
},
},
},
expectedError: `spec.matchConstraints.resourceRules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`,
expectedError: `spec.matchConstraints.resourceRules[0].resources: Invalid value: ["*/*","a"]: if '*/*' is present, must not specify other resources`,
}, {
name: "invalid expression",
config: &admissionregistration.ValidatingAdmissionPolicy{
@@ -3900,7 +3900,7 @@ func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
},
},
},
expectedError: `spec.matchResources.resourceRules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`,
expectedError: `spec.matchResources.resourceRules[0].resources: Invalid value: ["*/*","a"]: if '*/*' is present, must not specify other resources`,
}, {
name: "validationActions must be unique",
config: &admissionregistration.ValidatingAdmissionPolicyBinding{
@@ -5055,7 +5055,7 @@ func TestValidateMutatingAdmissionPolicy(t *testing.T) {
},
},
},
expectedError: `spec.matchConstraints.resourceRules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`,
expectedError: `spec.matchConstraints.resourceRules[0].resources: Invalid value: ["*/*","a"]: if '*/*' is present, must not specify other resources`,
}, {
name: "patchType required",
config: &admissionregistration.MutatingAdmissionPolicy{
@@ -5946,7 +5946,7 @@ func TestValidateMutatingAdmissionPolicyBinding(t *testing.T) {
},
},
},
expectedError: `spec.matchResources.resourceRules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`,
expectedError: `spec.matchResources.resourceRules[0].resources: Invalid value: ["*/*","a"]: if '*/*' is present, must not specify other resources`,
}, {
name: "paramRef selector must not be set when name is set",
config: &admissionregistration.MutatingAdmissionPolicyBinding{

View File

@@ -280,7 +280,7 @@ func TestValidateCommonVersion(t *testing.T) {
}},
CommonEncodingVersion: nil,
},
expectedErr: "Invalid value: \"null\": the common encoding version is v1alpha1",
expectedErr: "Invalid value: null: the common encoding version is v1alpha1",
}, {
status: apiserverinternal.StorageVersionStatus{
StorageVersions: []apiserverinternal.ServerStorageVersion{{

View File

@@ -927,7 +927,7 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) {
}},
},
},
msg: "Invalid value: InvalidResource: must be a standard resource type or fully qualified",
msg: "Invalid value: \"InvalidResource\": must be a standard resource type or fully qualified",
}, {
horizontalPodAutoscaler: autoscaling.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{Name: "myautoscaler", Namespace: metav1.NamespaceDefault},
@@ -1429,7 +1429,7 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) {
if len(errs) == 0 {
t.Errorf("expected failure for %q", c.msg)
} else if !strings.Contains(errs[0].Error(), c.msg) {
t.Errorf("unexpected error: %q, expected: %q", errs[0], c.msg)
t.Errorf("unexpected error:\n expected: %q\n got: %q", c.msg, errs[0])
}
}

View File

@@ -458,7 +458,7 @@ func TestValidateJob(t *testing.T) {
},
},
},
`spec.successPolicy: Invalid value: batch.SuccessPolicy{Rules:[]batch.SuccessPolicyRule{}}: requires indexed completion mode`: {
`spec.successPolicy: Invalid value: {"Rules":[]}: requires indexed completion mode`: {
job: batch.Job{
ObjectMeta: validJobObjectMeta,
Spec: batch.JobSpec{
@@ -739,7 +739,7 @@ func TestValidateJob(t *testing.T) {
},
opts: JobValidationOptions{RequirePrefixedLabels: true},
},
`spec.podFailurePolicy.rules[0].onExitCodes.values: Invalid value: []int32{19, 11}: must be ordered`: {
`spec.podFailurePolicy.rules[0].onExitCodes.values: Invalid value: [19,11]: must be ordered`: {
job: batch.Job{
ObjectMeta: validJobObjectMeta,
Spec: batch.JobSpec{
@@ -758,7 +758,7 @@ func TestValidateJob(t *testing.T) {
},
opts: JobValidationOptions{RequirePrefixedLabels: true},
},
`spec.podFailurePolicy.rules[0].onExitCodes.values: Invalid value: []int32{}: at least one value is required`: {
`spec.podFailurePolicy.rules[0].onExitCodes.values: Invalid value: []: at least one value is required`: {
job: batch.Job{
ObjectMeta: validJobObjectMeta,
Spec: batch.JobSpec{
@@ -1239,7 +1239,7 @@ func TestValidateJob(t *testing.T) {
},
opts: JobValidationOptions{RequirePrefixedLabels: true},
},
"spec.template.metadata.labels: Invalid value: map[string]string{\"y\":\"z\"}: `selector` does not match template `labels`": {
"spec.template.metadata.labels: Invalid value: {\"y\":\"z\"}: `selector` does not match template `labels`": {
job: batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "myjob",
@@ -1259,7 +1259,7 @@ func TestValidateJob(t *testing.T) {
},
opts: JobValidationOptions{RequirePrefixedLabels: true},
},
"spec.template.metadata.labels: Invalid value: map[string]string{\"controller-uid\":\"4d5e6f\"}: `selector` does not match template `labels`": {
"spec.template.metadata.labels: Invalid value: {\"controller-uid\":\"4d5e6f\"}: `selector` does not match template `labels`": {
job: batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "myjob",
@@ -1407,7 +1407,7 @@ func TestValidateJob(t *testing.T) {
},
opts: JobValidationOptions{},
},
"spec.selector: Invalid value: v1.LabelSelector{MatchLabels:map[string]string{\"a\":\"b\"}, MatchExpressions:[]v1.LabelSelectorRequirement(nil)}: `selector` not auto-generated": {
"spec.selector: Invalid value: {\"matchLabels\":{\"a\":\"b\"}}: `selector` not auto-generated": {
job: batch.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "myjob",

View File

@@ -175,7 +175,7 @@ func validateCredentialProviderConfig(config *kubeletconfig.CredentialProviderCo
}
if provider.DefaultCacheDuration != nil && provider.DefaultCacheDuration.Duration < 0 {
allErrs = append(allErrs, field.Invalid(fieldPath.Child("defaultCacheDuration"), provider.DefaultCacheDuration.Duration, "defaultCacheDuration must be greater than or equal to 0"))
allErrs = append(allErrs, field.Invalid(fieldPath.Child("defaultCacheDuration"), provider.DefaultCacheDuration, "defaultCacheDuration must be greater than or equal to 0"))
}
if provider.TokenAttributes != nil {

View File

@@ -889,7 +889,7 @@ func Test_validateCredentialProviderConfig(t *testing.T) {
},
},
},
expectErr: "providers.defaultCacheDuration: Invalid value: -1m0s: defaultCacheDuration must be greater than or equal to 0",
expectErr: "providers.defaultCacheDuration: Invalid value: \"-1m0s\": defaultCacheDuration must be greater than or equal to 0",
},
{
name: "invalid match image",
@@ -1073,7 +1073,7 @@ func Test_validateCredentialProviderConfig(t *testing.T) {
},
},
saTokenForCredentialProviders: true,
expectErr: `providers.tokenAttributes: Invalid value: []string{"now-with-dashes/simple-2"}: annotation keys cannot be both required and optional`,
expectErr: `providers.tokenAttributes: Invalid value: ["now-with-dashes/simple-2"]: annotation keys cannot be both required and optional`,
},
{
name: "required annotation keys set when requireServiceAccount is false",

View File

@@ -11034,7 +11034,7 @@ func TestValidateCustomResourceDefinitionStoredVersions(t *testing.T) {
storageVersion: "v1",
storedVersions: []string{},
errors: []validationMatch{
invalid("status", "storedVersions").contains("Invalid value: []string{}: must have at least one stored version"),
invalid("status", "storedVersions").contains("Invalid value: []: must have at least one stored version"),
},
},
{
@@ -11058,7 +11058,7 @@ func TestValidateCustomResourceDefinitionStoredVersions(t *testing.T) {
storageVersion: "v1",
storedVersions: []string{"v1alpha", "v1beta1"},
errors: []validationMatch{
invalid("status", "storedVersions").contains("Invalid value: []string{\"v1alpha\", \"v1beta1\"}: must have the storage version v1"),
invalid("status", "storedVersions").contains("Invalid value: [\"v1alpha\",\"v1beta1\"]: must have the storage version v1"),
},
},
}

View File

@@ -328,19 +328,19 @@ func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) {
Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: 1970-01-01 00:16:40 +0000 UTC: field is immutable"},
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: \"1970-01-01T00:16:40Z\": field is immutable"},
},
"invalid clear deletionTimestamp": {
Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: \"null\": field is immutable"},
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: null: field is immutable"},
},
"invalid change deletionTimestamp": {
Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now},
New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &later},
ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &later},
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: 1970-01-01 00:33:20 +0000 UTC: field is immutable"},
ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: \"1970-01-01T00:33:20Z\": field is immutable"},
},
"invalid set deletionGracePeriodSeconds": {
@@ -353,7 +353,7 @@ func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) {
Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
New: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
ExpectedNew: metav1.ObjectMeta{Name: "test", ResourceVersion: "1"},
ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: \"null\": field is immutable"},
ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: null: field is immutable"},
},
"invalid change deletionGracePeriodSeconds": {
Old: metav1.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort},
@@ -373,7 +373,7 @@ func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) {
}
for i := range errs {
if errs[i].Error() != tc.ExpectedErrs[i] {
t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errs[i].Error())
t.Errorf("%s: error #%d:\n expected: %q\n got: %q", k, i, tc.ExpectedErrs[i], errs[i].Error())
}
}
if !reflect.DeepEqual(tc.New, tc.ExpectedNew) {
@@ -419,7 +419,7 @@ func TestObjectMetaGenerationUpdate(t *testing.T) {
}
for i := range errList {
if errList[i] != tc.ExpectedErrs[i] {
t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errList[i])
t.Errorf("%s: error #%d:\n expected: %q\n got: %q", k, i, tc.ExpectedErrs[i], errs[i].Error())
}
}
}

View File

@@ -17,8 +17,8 @@ limitations under the License.
package field
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
@@ -72,45 +72,43 @@ var omitValue = OmitValueType{}
// for building nice-looking higher-level error reporting.
func (e *Error) ErrorBody() string {
var s string
switch {
case e.Type == ErrorTypeRequired:
switch e.Type {
case ErrorTypeRequired, ErrorTypeForbidden, ErrorTypeTooLong, ErrorTypeInternal:
s = e.Type.String()
case e.Type == ErrorTypeForbidden:
s = e.Type.String()
case e.Type == ErrorTypeTooLong:
s = e.Type.String()
case e.Type == ErrorTypeInternal:
s = e.Type.String()
case e.BadValue == omitValue:
s = e.Type.String()
default:
value := e.BadValue
valueType := reflect.TypeOf(value)
if value == nil || valueType == nil {
value = "null"
} else if valueType.Kind() == reflect.Pointer {
if reflectValue := reflect.ValueOf(value); reflectValue.IsNil() {
value = "null"
} else {
value = reflectValue.Elem().Interface()
}
case ErrorTypeInvalid, ErrorTypeTypeInvalid, ErrorTypeNotSupported,
ErrorTypeNotFound, ErrorTypeDuplicate, ErrorTypeTooMany:
if e.BadValue == omitValue {
s = e.Type.String()
break
}
switch t := value.(type) {
switch t := e.BadValue.(type) {
case int64, int32, float64, float32, bool:
// use simple printer for simple types
s = fmt.Sprintf("%s: %v", e.Type, value)
s = fmt.Sprintf("%s: %v", e.Type, t)
case string:
s = fmt.Sprintf("%s: %q", e.Type, t)
case fmt.Stringer:
// anything that defines String() is better than raw struct
s = fmt.Sprintf("%s: %s", e.Type, t.String())
default:
// fallback to raw struct
// TODO: internal types have panic guards against json.Marshalling to prevent
// accidental use of internal types in external serialized form. For now, use
// %#v, although it would be better to show a more expressive output in the future
s = fmt.Sprintf("%s: %#v", e.Type, value)
// use more complex techniques to render more complex types
valstr := ""
jb, err := json.Marshal(e.BadValue)
if err == nil {
// best case
valstr = string(jb)
} else if stringer, ok := e.BadValue.(fmt.Stringer); ok {
// anything that defines String() is better than raw struct
valstr = stringer.String()
} else {
// worst case - fallback to raw struct
// TODO: internal types have panic guards against json.Marshalling to prevent
// accidental use of internal types in external serialized form. For now, use
// %#v, although it would be better to show a more expressive output in the future
valstr = fmt.Sprintf("%#v", e.BadValue)
}
s = fmt.Sprintf("%s: %s", e.Type, valstr)
}
default:
internal := InternalError(nil, fmt.Errorf("unhandled error code: %s: please report this", e.Type))
s = internal.ErrorBody()
}
if len(e.Detail) != 0 {
s += fmt.Sprintf(": %s", e.Detail)
@@ -197,7 +195,7 @@ func (t ErrorType) String() string {
case ErrorTypeTypeInvalid:
return "Invalid value"
default:
panic(fmt.Sprintf("unrecognized validation error: %q", string(t)))
return fmt.Sprintf("<unknown error %q>", string(t))
}
}
@@ -258,10 +256,14 @@ func Forbidden(field *Path, detail string) *Error {
// the given value is too long. This is similar to Invalid, but the returned
// error will not include the too-long value. If maxLength is negative, it will
// be included in the message. The value argument is not used.
func TooLong(field *Path, value interface{}, maxLength int) *Error {
func TooLong(field *Path, _ interface{}, maxLength int) *Error {
var msg string
if maxLength >= 0 {
msg = fmt.Sprintf("may not be more than %d bytes", maxLength)
bs := "bytes"
if maxLength == 1 {
bs = "byte"
}
msg = fmt.Sprintf("may not be more than %d %s", maxLength, bs)
} else {
msg = "value is too long"
}
@@ -281,7 +283,11 @@ func TooMany(field *Path, actualQuantity, maxQuantity int) *Error {
var msg string
if maxQuantity >= 0 {
msg = fmt.Sprintf("must have at most %d items", maxQuantity)
is := "items"
if maxQuantity == 1 {
is = "item"
}
msg = fmt.Sprintf("must have at most %d %s", maxQuantity, is)
} else {
msg = "has too many items"
}

View File

@@ -19,8 +19,10 @@ package field
import (
"fmt"
"reflect"
"strings"
"testing"
"time"
"k8s.io/utils/ptr"
)
func TestMakeFuncs(t *testing.T) {
@@ -62,52 +64,6 @@ func TestMakeFuncs(t *testing.T) {
}
}
func TestErrorUsefulMessage(t *testing.T) {
{
s := Invalid(nil, nil, "").Error()
t.Logf("message: %v", s)
if !strings.Contains(s, "null") {
t.Errorf("error message did not contain 'null': %s", s)
}
}
s := Invalid(NewPath("foo"), "bar", "deet").Error()
t.Logf("message: %v", s)
for _, part := range []string{"foo", "bar", "deet", ErrorTypeInvalid.String()} {
if !strings.Contains(s, part) {
t.Errorf("error message did not contain expected part '%v'", part)
}
}
type complicated struct {
Baz int
Qux string
Inner interface{}
KV map[string]int
}
s = Invalid(
NewPath("foo"),
&complicated{
Baz: 1,
Qux: "aoeu",
Inner: &complicated{Qux: "asdf"},
KV: map[string]int{"Billy": 2},
},
"detail",
).Error()
t.Logf("message: %v", s)
for _, part := range []string{
"foo", ErrorTypeInvalid.String(),
"Baz", "Qux", "Inner", "KV", "detail",
"1", "aoeu", "Billy", "2",
// "asdf", TODO: re-enable once we have a better nested printer
} {
if !strings.Contains(s, part) {
t.Errorf("error message did not contain expected part '%v'", part)
}
}
}
func TestToAggregate(t *testing.T) {
testCases := struct {
ErrList []ErrorList
@@ -167,14 +123,6 @@ func TestErrListFilter(t *testing.T) {
}
}
func TestNotSupported(t *testing.T) {
notSupported := NotSupported(NewPath("f"), "v", []string{"a", "b", "c"})
expected := `Unsupported value: "v": supported values: "a", "b", "c"`
if notSupported.ErrorBody() != expected {
t.Errorf("Expected: %s\n, but got: %s\n", expected, notSupported.ErrorBody())
}
}
func TestErrorOrigin(t *testing.T) {
err := Invalid(NewPath("field"), "value", "detail")
@@ -301,3 +249,438 @@ func TestErrorListRemoveCoveredByDeclarative(t *testing.T) {
}
}
}
func TestErrorFormatting(t *testing.T) {
cases := []struct {
name string
input *Error
expect string
}{{
name: "required",
input: &Error{
Type: ErrorTypeRequired,
Field: "path.to.field",
BadValue: "the value",
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Required value: the details`,
}, {
name: "required func",
input: Required(NewPath("path.to.field"), "the details"),
expect: `path.to.field: Required value: the details`,
}, {
name: "forbidden",
input: &Error{
Type: ErrorTypeForbidden,
Field: "path.to.field",
BadValue: "the value",
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Forbidden: the details`,
}, {
name: "forbidden func",
input: Forbidden(NewPath("path.to.field"), "the details"),
expect: `path.to.field: Forbidden: the details`,
}, {
name: "too long",
input: &Error{
Type: ErrorTypeTooLong,
Field: "path.to.field",
BadValue: "the value",
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Too long: the details`,
}, {
name: "too long func(1)",
input: TooLong(NewPath("path.to.field"), "the value", 1),
expect: `path.to.field: Too long: may not be more than 1 byte`,
}, {
name: "too long func(2)",
input: TooLong(NewPath("path.to.field"), "the value", 2),
expect: `path.to.field: Too long: may not be more than 2 bytes`,
}, {
name: "too long func(-1)",
input: TooLong(NewPath("path.to.field"), "the value", -1),
expect: `path.to.field: Too long: value is too long`,
}, {
name: "too many",
input: &Error{
Type: ErrorTypeTooMany,
Field: "path.to.field",
BadValue: "the value",
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Too many: "the value": the details`,
}, {
name: "too many func(2, 1)",
input: TooMany(NewPath("path.to.field"), 2, 1),
expect: `path.to.field: Too many: 2: must have at most 1 item`,
}, {
name: "too many func(3, 2)",
input: TooMany(NewPath("path.to.field"), 3, 2),
expect: `path.to.field: Too many: 3: must have at most 2 items`,
}, {
name: "too many func(2, -1)",
input: TooMany(NewPath("path.to.field"), 2, -1),
expect: `path.to.field: Too many: 2: has too many items`,
}, {
name: "too many func(-1, 1)",
input: TooMany(NewPath("path.to.field"), -1, 1),
expect: `path.to.field: Too many: must have at most 1 item`,
}, {
name: "internal error",
input: &Error{
Type: ErrorTypeInternal,
Field: "path.to.field",
BadValue: "the value",
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Internal error: the details`,
}, {
name: "internal error func",
input: InternalError(NewPath("path.to.field"), fmt.Errorf("the error")),
expect: `path.to.field: Internal error: the error`,
}, {
name: "invalid string",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: "the value",
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: "the value": the details`,
}, {
name: "invalid string type",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: StringType("the value"),
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: "the value": the details`,
}, {
name: "invalid int",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: -42,
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: -42: the details`,
}, {
name: "invalid bool",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: true,
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: true: the details`,
}, {
name: "invalid struct",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: mkTinyStruct(),
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: {"stringField":"stringval","intField":9376,"boolField":true}: the details`,
}, {
name: "invalid list",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: []string{"one", "two", "three"},
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: ["one","two","three"]: the details`,
}, {
name: "invalid map",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: map[string]int{"one": 1, "two": 2, "three": 3},
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: {"one":1,"three":3,"two":2}: the details`,
}, {
name: "invalid time",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: time.Time{},
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: "0001-01-01T00:00:00Z": the details`,
}, {
name: "invalid omitValue",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: omitValue,
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: the details`,
}, {
name: "invalid untyped nil",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: nil,
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: null: the details`,
}, {
name: "invalid typed nil",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: (*string)(nil),
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: null: the details`,
}, {
name: "invalid string ptr",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: ptr.To("the value"),
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: "the value": the details`,
}, {
name: "invalid string type ptr",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: ptr.To(StringType("the value")),
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: "the value": the details`,
}, {
name: "invalid int ptr",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: ptr.To(-42),
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: -42: the details`,
}, {
name: "invalid bool ptr",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: ptr.To(true),
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: true: the details`,
}, {
name: "invalid struct ptr",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: ptr.To(mkTinyStruct()),
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: {"stringField":"stringval","intField":9376,"boolField":true}: the details`,
}, {
name: "invalid list ptr",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: ptr.To([]string{"one", "two", "three"}),
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: ["one","two","three"]: the details`,
}, {
name: "invalid map ptr",
input: &Error{
Type: ErrorTypeInvalid,
Field: "path.to.field",
BadValue: ptr.To(map[string]int{"one": 1, "two": 2, "three": 3}),
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: {"one":1,"three":3,"two":2}: the details`,
}, {
name: "invalid func",
input: Invalid(NewPath("path.to.field"), "the value", "the details"),
expect: `path.to.field: Invalid value: "the value": the details`,
}, {
name: "not found",
input: &Error{
Type: ErrorTypeNotFound,
Field: "path.to.field",
BadValue: "the value",
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Not found: "the value": the details`,
}, {
name: "not supported",
input: &Error{
Type: ErrorTypeNotSupported,
Field: "path.to.field",
BadValue: "the value",
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Unsupported value: "the value": the details`,
}, {
name: "not supported func",
input: NotSupported(NewPath("path.to.field"), "the value", []string{"val1", "val2"}),
expect: `path.to.field: Unsupported value: "the value": supported values: "val1", "val2"`,
}, {
name: "duplicate",
input: &Error{
Type: ErrorTypeDuplicate,
Field: "path.to.field",
BadValue: "the value",
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Duplicate value: "the value": the details`,
}, {
name: "duplicate func",
input: Duplicate(NewPath("path.to.field"), "the value"),
expect: `path.to.field: Duplicate value: "the value"`,
}, {
name: "type invalid",
input: &Error{
Type: ErrorTypeTypeInvalid,
Field: "path.to.field",
BadValue: "the value",
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: "the value": the details`,
}, {
name: "type invalid func",
input: TypeInvalid(NewPath("path.to.field"), "the value", "the details"),
expect: `path.to.field: Invalid value: "the value": the details`,
}, {
name: "failed marshal stringer",
input: &Error{
Type: ErrorTypeTypeInvalid,
Field: "path.to.field",
BadValue: SelfMarshalerStringer{"invisible"},
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: magic: the details`,
}, {
name: "failed marshal non-stringer",
input: &Error{
Type: ErrorTypeTypeInvalid,
Field: "path.to.field",
BadValue: SelfMarshalerNonStringer{"visible"},
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Invalid value: field.SelfMarshalerNonStringer{S:"visible"}: the details`,
}, {
name: "unknown error type",
input: &Error{
Type: "not real",
Field: "path.to.field",
BadValue: SelfMarshalerNonStringer{"visible"},
Detail: "the details",
Origin: "theOrigin",
CoveredByDeclarative: true,
},
expect: `path.to.field: Internal error: unhandled error code: <unknown error "not real">: please report this: the details`,
}}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
result := tc.input.Error()
if want := tc.expect; result != want {
t.Errorf("wrong error string:\n expected: %q\n got: %q", want, result)
}
})
}
}
type StringType string
type TinyStruct struct {
StringField string `json:"stringField"`
IntField int `json:"intField"`
BoolField bool `json:"boolField"`
}
func mkTinyStruct() TinyStruct {
return TinyStruct{
StringField: "stringval",
IntField: 9376,
BoolField: true,
}
}
type SelfMarshalerStringer struct{ S string }
func (SelfMarshalerStringer) MarshalJSON() ([]byte, error) {
return nil, fmt.Errorf("this always fails")
}
func (SelfMarshalerStringer) String() string {
return "magic"
}
type SelfMarshalerNonStringer struct{ S string }
func (SelfMarshalerNonStringer) MarshalJSON() ([]byte, error) {
return nil, fmt.Errorf("this always fails")
}

View File

@@ -900,7 +900,7 @@ func TestValidateAudiences(t *testing.T) {
name: "multiple audiences set when structured authn feature is disabled",
in: []string{"audience1", "audience2"},
matchPolicy: "MatchAny",
want: `issuer.audiences: Invalid value: []string{"audience1", "audience2"}: multiple audiences are not supported when StructuredAuthenticationConfiguration feature gate is disabled`,
want: `issuer.audiences: Invalid value: ["audience1","audience2"]: multiple audiences are not supported when StructuredAuthenticationConfiguration feature gate is disabled`,
},
}

View File

@@ -77,7 +77,7 @@ func TestValidation(t *testing.T) {
Format: "text",
Verbosity: math.MaxInt32 + 1,
},
expectErrors: `verbosity: Invalid value: 0x80000000: Must be <= 2147483647`,
expectErrors: `verbosity: Invalid value: 2147483648: Must be <= 2147483647`,
},
"vmodule-verbosity-overflow": {
config: LoggingConfiguration{
@@ -89,7 +89,7 @@ func TestValidation(t *testing.T) {
},
},
},
expectErrors: `vmodule[0]: Invalid value: 0x80000000: Must be <= 2147483647`,
expectErrors: `vmodule[0]: Invalid value: 2147483648: Must be <= 2147483647`,
},
"vmodule-empty-pattern": {
config: LoggingConfiguration{