From d5c7ac580c544ec93eafaa2b09b25fb8333896de Mon Sep 17 00:00:00 2001 From: Stepan I <2688692+micronull@users.noreply.github.com> Date: Mon, 7 Nov 2022 21:57:59 +0500 Subject: [PATCH] Fix lost error types in oneOf (#658) --- openapi3/errors.go | 20 ++++++++-- openapi3/issue657_test.go | 79 +++++++++++++++++++++++++++++++++++++++ openapi3/schema.go | 11 +----- 3 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 openapi3/issue657_test.go diff --git a/openapi3/errors.go b/openapi3/errors.go index b530101df..74baab9a5 100644 --- a/openapi3/errors.go +++ b/openapi3/errors.go @@ -10,11 +10,15 @@ import ( type MultiError []error func (me MultiError) Error() string { + return spliceErr(" | ", me) +} + +func spliceErr(sep string, errs []error) string { buff := &bytes.Buffer{} - for i, e := range me { + for i, e := range errs { buff.WriteString(e.Error()) - if i != len(me)-1 { - buff.WriteString(" | ") + if i != len(errs)-1 { + buff.WriteString(sep) } } return buff.String() @@ -43,3 +47,13 @@ func (me MultiError) As(target interface{}) bool { } return false } + +type multiErrorForOneOf MultiError + +func (meo multiErrorForOneOf) Error() string { + return spliceErr(" Or ", meo) +} + +func (meo multiErrorForOneOf) Unwrap() error { + return MultiError(meo) +} diff --git a/openapi3/issue657_test.go b/openapi3/issue657_test.go new file mode 100644 index 000000000..195ccd19c --- /dev/null +++ b/openapi3/issue657_test.go @@ -0,0 +1,79 @@ +package openapi3_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/getkin/kin-openapi/openapi3" +) + +func TestOneOf_Warning_Errors(t *testing.T) { + t.Parallel() + + loader := openapi3.NewLoader() + spec := ` +components: + schemas: + Something: + type: object + properties: + field: + title: Some field + oneOf: + - title: First rule + type: string + minLength: 10 + maxLength: 10 + - title: Second rule + type: string + minLength: 15 + maxLength: 15 +`[1:] + + doc, err := loader.LoadFromData([]byte(spec)) + require.NoError(t, err) + + tests := [...]struct { + name string + value string + checkErr require.ErrorAssertionFunc + }{ + { + name: "valid value", + value: "ABCDE01234", + checkErr: require.NoError, + }, + { + name: "valid value", + value: "ABCDE0123456789", + checkErr: require.NoError, + }, + { + name: "no valid value", + value: "ABCDE", + checkErr: func(t require.TestingT, err error, i ...interface{}) { + require.Equal(t, "doesn't match schema due to: minimum string length is 10\nSchema:\n {\n \"maxLength\": 10,\n \"minLength\": 10,\n \"title\": \"First rule\",\n \"type\": \"string\"\n }\n\nValue:\n \"ABCDE\"\n Or minimum string length is 15\nSchema:\n {\n \"maxLength\": 15,\n \"minLength\": 15,\n \"title\": \"Second rule\",\n \"type\": \"string\"\n }\n\nValue:\n \"ABCDE\"\n", err.Error()) + + wErr := &openapi3.MultiError{} + require.ErrorAs(t, err, wErr) + + require.Len(t, *wErr, 2) + + require.Equal(t, "minimum string length is 10", (*wErr)[0].(*openapi3.SchemaError).Reason) + require.Equal(t, "minimum string length is 15", (*wErr)[1].(*openapi3.SchemaError).Reason) + }, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + err = doc.Components.Schemas["Something"].Value.Properties["field"].Value.VisitJSON(test.value) + + test.checkErr(t, err) + }) + } +} diff --git a/openapi3/schema.go b/openapi3/schema.go index ba846d0cd..e5cded877 100644 --- a/openapi3/schema.go +++ b/openapi3/schema.go @@ -926,7 +926,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val var ( ok = 0 - validationErrors = []error{} + validationErrors = multiErrorForOneOf{} matchedOneOfIdx = 0 tempValue = value ) @@ -955,14 +955,7 @@ func (schema *Schema) visitSetOperations(settings *schemaValidationSettings, val if ok != 1 { if len(validationErrors) > 1 { - errorMessage := "" - for _, err := range validationErrors { - if errorMessage != "" { - errorMessage += " Or " - } - errorMessage += err.Error() - } - return errors.New("doesn't match schema due to: " + errorMessage) + return fmt.Errorf("doesn't match schema due to: %w", validationErrors) } if settings.failfast { return errSchema