cloudapi: get specific error from openapi

Error handling is structured in such a way that typically, a ServiceCodeError is passed
through the echo HTTP error, in reference to internally defined errors. We want to be able
to obtain and return specific external errors, for example during validation from openapi3.
Add a 'details' field to the serviceError struct, to contain extra / externally defined
information. Modify HTTPErrorHandler to anticipate either a string or a ServiceErrorCode
from echo, and respond accordingly. Edit the affected tests to expect the appropriate response.
This commit is contained in:
Chloe Kaubisch 2022-05-18 12:52:29 +00:00 committed by Sanne Raymaekers
parent d59d870574
commit 765e93bfc8
8 changed files with 144 additions and 116 deletions

View file

@ -43,7 +43,7 @@ const (
ErrorInvalidOSTreeParams ServiceErrorCode = 27
ErrorTenantNotFound ServiceErrorCode = 28
ErrorNoGPGKey ServiceErrorCode = 29
ErrorInvalidRequest ServiceErrorCode = 30
ErrorValidationFailed ServiceErrorCode = 30
// Internal errors, these are bugs
ErrorFailedToInitializeBlueprint ServiceErrorCode = 1000
@ -111,7 +111,7 @@ func getServiceErrors() serviceErrors {
serviceError{ErrorInvalidOSTreeParams, http.StatusBadRequest, "Invalid OSTree parameters or parameter combination"},
serviceError{ErrorTenantNotFound, http.StatusBadRequest, "Tenant not found in JWT claims"},
serviceError{ErrorNoGPGKey, http.StatusBadRequest, "Invalid repository, when check_gpg is set, gpgkey must be specified"},
serviceError{ErrorInvalidRequest, http.StatusBadRequest, "Request could not be validated"},
serviceError{ErrorValidationFailed, http.StatusBadRequest, "Request could not be validated"},
serviceError{ErrorFailedToInitializeBlueprint, http.StatusInternalServerError, "Failed to initialize blueprint"},
serviceError{ErrorFailedToGenerateManifestSeed, http.StatusInternalServerError, "Failed to generate manifest seed"},
@ -155,7 +155,22 @@ func HTTPError(code ServiceErrorCode) error {
// echo.HTTPError has a message interface{} field, which can be used to include the ServiceErrorCode
func HTTPErrorWithInternal(code ServiceErrorCode, internalErr error) error {
se := find(code)
he := echo.NewHTTPError(se.httpStatus, se.code)
he := echo.NewHTTPError(se.httpStatus, detailsError{code, ""})
if internalErr != nil {
he.Internal = internalErr
}
return he
}
type detailsError struct {
errorCode ServiceErrorCode
details interface{}
}
// instead of sending a ServiceErrorCode as he.Message, send the validation error string (see above)
func HTTPErrorWithDetails(code ServiceErrorCode, internalErr error, details string) error {
se := find(code)
he := echo.NewHTTPError(se.httpStatus, detailsError{code, details})
if internalErr != nil {
he.Internal = internalErr
}
@ -164,7 +179,7 @@ func HTTPErrorWithInternal(code ServiceErrorCode, internalErr error) error {
// Convert a ServiceErrorCode into an Error as defined in openapi.v2.yml
// serviceError is optional, prevents multiple find() calls
func APIError(code ServiceErrorCode, serviceError *serviceError, c echo.Context) *Error {
func APIError(code ServiceErrorCode, serviceError *serviceError, c echo.Context, details *interface{}) *Error {
se := serviceError
if se == nil {
se = find(code)
@ -184,6 +199,7 @@ func APIError(code ServiceErrorCode, serviceError *serviceError, c echo.Context)
Code: fmt.Sprintf("%s%d", ErrorCodePrefix, se.code),
OperationId: operationID, // set operation id from context
Reason: se.reason,
Details: details,
}
}
@ -214,7 +230,7 @@ func APIErrorList(page int, pageSize int, c echo.Context) *ErrorList {
for _, e := range errs {
// Implicit memory alasing doesn't couse any bug in this case
/* #nosec G601 */
list.Items = append(list.Items, *APIError(e.code, &e, c))
list.Items = append(list.Items, *APIError(e.code, &e, c, nil))
}
list.Size = len(list.Items)
return list
@ -235,11 +251,12 @@ func apiErrorFromEchoError(echoError *echo.HTTPError) ServiceErrorCode {
// Convert an echo error into an AOC compliant one so we send a correct json error response
func (s *Server) HTTPErrorHandler(echoError error, c echo.Context) {
doResponse := func(code ServiceErrorCode, c echo.Context, internal error) {
doResponse := func(details *interface{}, code ServiceErrorCode, c echo.Context, internal error) {
// don't anticipate serviceerrorcode, instead check what type it is
if !c.Response().Committed {
var err error
sec := find(code)
apiErr := APIError(code, sec, c)
apiErr := APIError(code, sec, c, details)
if sec.httpStatus == http.StatusInternalServerError {
errMsg := fmt.Sprintf("Internal server error. Code: %s, OperationId: %s", apiErr.Code, apiErr.OperationId)
@ -267,7 +284,7 @@ func (s *Server) HTTPErrorHandler(echoError error, c echo.Context) {
he, ok := echoError.(*echo.HTTPError)
if !ok {
c.Logger().Errorf("ErrorNotHTTPError %v", echoError)
doResponse(ErrorNotHTTPError, c, echoError)
doResponse(nil, ErrorNotHTTPError, c, echoError)
return
}
@ -278,11 +295,15 @@ func (s *Server) HTTPErrorHandler(echoError error, c echo.Context) {
}
}
sec, ok := he.Message.(ServiceErrorCode)
err, ok := he.Message.(detailsError)
if !ok {
// No service code was set, so Echo threw this error
doResponse(apiErrorFromEchoError(he), c, he.Internal)
doResponse(nil, apiErrorFromEchoError(he), c, he.Internal)
return
}
doResponse(sec, c, he.Internal)
var det *interface{}
if err.details != nil {
det = &err.details
}
doResponse(det, err.errorCode, c, he.Internal)
}

View file

@ -14,9 +14,9 @@ func TestHTTPErrorReturnsEchoHTTPError(t *testing.T) {
echoError, ok := err.(*echo.HTTPError)
require.True(t, ok)
require.Equal(t, se.httpStatus, echoError.Code)
serviceErrorCode, ok := echoError.Message.(ServiceErrorCode)
detailsError, ok := echoError.Message.(detailsError)
require.True(t, ok)
require.Equal(t, se.code, serviceErrorCode)
require.Equal(t, se.code, detailsError.errorCode)
}
}
@ -25,7 +25,7 @@ func TestAPIError(t *testing.T) {
for _, se := range getServiceErrors() {
ctx := e.NewContext(nil, nil)
ctx.Set("operationID", "test-operation-id")
apiError := APIError(se.code, nil, ctx)
apiError := APIError(se.code, nil, ctx, nil)
require.Equal(t, fmt.Sprintf("/api/image-builder-composer/v2/errors/%d", se.code), apiError.Href)
require.Equal(t, fmt.Sprintf("%d", se.code), apiError.Id)
require.Equal(t, "Error", apiError.Kind)
@ -38,15 +38,15 @@ func TestAPIError(t *testing.T) {
func TestAPIErrorOperationID(t *testing.T) {
ctx := echo.New().NewContext(nil, nil)
apiError := APIError(ErrorUnauthenticated, nil, ctx)
apiError := APIError(ErrorUnauthenticated, nil, ctx, nil)
require.Equal(t, "IMAGE-BUILDER-COMPOSER-10003", apiError.Code)
ctx.Set("operationID", 5)
apiError = APIError(ErrorUnauthenticated, nil, ctx)
apiError = APIError(ErrorUnauthenticated, nil, ctx, nil)
require.Equal(t, "IMAGE-BUILDER-COMPOSER-10003", apiError.Code)
ctx.Set("operationID", "test-operation-id")
apiError = APIError(ErrorUnauthenticated, nil, ctx)
apiError = APIError(ErrorUnauthenticated, nil, ctx, nil)
require.Equal(t, "IMAGE-BUILDER-COMPOSER-401", apiError.Code)
}

View file

@ -79,7 +79,7 @@ func (h *apiHandlers) GetError(ctx echo.Context, id string) error {
return HTTPError(ErrorInvalidErrorId)
}
apiError := APIError(ServiceErrorCode(errorId), nil, ctx)
apiError := APIError(ServiceErrorCode(errorId), nil, ctx, nil)
// If the service error wasn't found, it's a 404 in this instance
if apiError.Id == fmt.Sprintf("%d", ErrorServiceErrorNotFound) {
return HTTPError(ErrorErrorNotFound)

View file

@ -74,7 +74,12 @@ func (s *Server) ValidateRequest(next echo.HandlerFunc) echo.HandlerFunc {
ctx := request.Context()
if err := openapi3filter.ValidateRequest(ctx, input); err != nil {
return HTTPErrorWithInternal(ErrorInvalidRequest, err)
details := ""
re, ok := err.(*openapi3filter.RequestError)
if ok {
details = re.Error()
}
return HTTPErrorWithDetails(ErrorValidationFailed, err, details)
}
return next(c)

View file

@ -236,9 +236,10 @@ type Error struct {
// Embedded struct due to allOf(#/components/schemas/ObjectReference)
ObjectReference `yaml:",inline"`
// Embedded fields due to inline allOf schema
Code string `json:"code"`
OperationId string `json:"operation_id"`
Reason string `json:"reason"`
Code string `json:"code"`
Details *interface{} `json:"details,omitempty"`
OperationId string `json:"operation_id"`
Reason string `json:"reason"`
}
// ErrorList defines model for ErrorList.
@ -687,84 +688,84 @@ var swaggerSpec = []string{
"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",
"fGs+VHoj0q5NBlp+9lC7Mbp9PsyfdmCdlJpe9mbmVZoNmmmixsMw++rWb26rsK2NW1d537QKdR9N+cmg",
"PyLzu8cBqncW/nOndNN9oje37fmse2cuxlb26bwy85+1tphk9Our3AL62sLlVf/0quWh6ezmtrdwRmT5",
"KibLZ5PRB4wult782ZrdzQUh3UrG6jf8TOthwIZaMec27gfluj4uF6b61cXgwuxOHTK9zIyIZt4Xqj1Y",
"1ApX+cVEm4oxys/a+u0Tvb3x27UHftWfadr95bC6vEX+8qRS1u8zw4bdLU/z/Yf2ZERKqPlsLXH3Rps7",
"2eHlea+t+858yk+rJ74ztbJ0MC7w/Jv7PLvVypd0sHgs5CawXXzsn1zbzwiNSKWkPdEHe6xn217/ZGI+",
"0wlnDfFcuR3fP58MZxeVnseMxyqbXI1b01zL67Wri4G94HdVXrMvsyOidfxF7hF2a5qVaxZv9a7Ryuiv",
"E6pVdJ1Nak8+XjwyXMT+affJq7wOMmb/7drlRtMilczrc3tEcOXOd0y/XPZf7cfMXOTGgmBh9fjrxF50",
"/cnwvvA8LthTcVGx2/eZp6dyIfdqd4rtebVXvavWRkScX1w+P/Zmutuw2ufdbLtfrTy7D9NxvmV3Bt1s",
"56m2hI9ZWydONXquX7Vm0H2YGPXibER0Vz/Bd62bWq1bq1erhQvcaKCrksvsi6uy/8DvOt1uThsW9Web",
"LIaVi6qrdKh+Oa9c1OfT5ojU5s3Lizvaqld5vVYb1qvzRv3KatQvCtVq3ZrerWefXA+rmXJt6FnOsl99",
"Hl7Zk2XbHpHMiVl6uzUfZuOrnNZ4zU+b5ZuL2rVGOk8ntfus68/6J68Dv59/7LBa3s1f+o7w2r1Gq90R",
"brFxPiJZdvn2VKWD7NI7HTYrneq50a3Xb5aT6oTTx/tKeXjv108yYzJhA9TLdXo3dXN5Wy+XHk8rRXzz",
"MCJusX8y5nfn83I912GOUe0Wuuc+XT5n+1hcwudC+67zIE4GDZgtYD7sX9Ynb7R8O6w85Fs306I2Itbr",
"o1XJXWfGbq7x1i8PKvnHxvk468wmhaYzW1jN1zaystm3p+HCZcP+c6tVN2dv5olz3S/5C+tqRCaLTEtb",
"Os+5Dh5fstJltbq8Ob1/ZNXn/rzf1Rr6ZFCZN+pkMe2f+8tX93H+MLuuPfmN5kPlBuWHI9LF91mzdV3h",
"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",
"1dx5t6BZAAA=",
"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",
"AP//e2tK5q1ZAAA=",
}
// GetSwagger returns the content of the embedded swagger specification file

View file

@ -399,6 +399,7 @@ components:
type: string
operation_id:
type: string
details: {}
ErrorList:
allOf:

View file

@ -642,13 +642,13 @@ func TestKojiJobTypeValidation(t *testing.T) {
test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", finalizeID, path), ``, http.StatusOK, "*")
// The other IDs should fail
test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", initID, path), ``, http.StatusNotFound, `{"code":"IMAGE-BUILDER-COMPOSER-26","href":"/api/image-builder-composer/v2/errors/26","id":"26","kind":"Error","reason":"Requested job has invalid type"}`, `operation_id`)
test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", initID, path), ``, http.StatusNotFound, `{"code":"IMAGE-BUILDER-COMPOSER-26", "details": "", "href":"/api/image-builder-composer/v2/errors/26","id":"26","kind":"Error","reason":"Requested job has invalid type"}`, `operation_id`)
for _, buildID := range buildJobIDs {
test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", buildID, path), ``, http.StatusOK, "*")
}
badID := uuid.New()
test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", badID, path), ``, http.StatusNotFound, `{"code":"IMAGE-BUILDER-COMPOSER-15","href":"/api/image-builder-composer/v2/errors/15","id":"15","kind":"Error","reason":"Compose with given id not found"}`, `operation_id`)
test.TestRoute(t, handler, false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%s%s", badID, path), ``, http.StatusNotFound, `{"code":"IMAGE-BUILDER-COMPOSER-15", "details": "", "href":"/api/image-builder-composer/v2/errors/15","id":"15","kind":"Error","reason":"Compose with given id not found"}`, `operation_id`)
}
}

View file

@ -85,7 +85,7 @@ func TestUnknownRoute(t *testing.T) {
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-21",
"reason": "Requested resource doesn't exist"
}`, "operation_id")
}`, "operation_id", "details")
}
func TestGetError(t *testing.T) {
@ -99,7 +99,7 @@ func TestGetError(t *testing.T) {
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-4",
"reason": "Unsupported distribution"
}`, "operation_id")
}`, "operation_id", "details")
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", "/api/image-builder-composer/v2/errors/3000", ``, http.StatusNotFound, `
{
@ -108,7 +108,7 @@ func TestGetError(t *testing.T) {
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-17",
"reason": "Error with given id not found"
}`, "operation_id")
}`, "operation_id", "details")
}
func TestGetErrorList(t *testing.T) {
@ -127,7 +127,7 @@ func TestGetErrorList(t *testing.T) {
"code": "IMAGE-BUILDER-COMPOSER-4",
"reason": "Unsupported distribution"
}]
}`, "operation_id", "total")
}`, "operation_id", "total", "details")
}
func TestCompose(t *testing.T) {
@ -163,7 +163,7 @@ func TestCompose(t *testing.T) {
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-30",
"reason": "Request could not be validated"
}`, "operation_id")
}`, "operation_id", "details")
// unsupported architecture
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
@ -187,7 +187,7 @@ func TestCompose(t *testing.T) {
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-5",
"reason": "Unsupported architecture"
}`, "operation_id")
}`, "operation_id", "details")
// unsupported imagetype
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
@ -211,7 +211,7 @@ func TestCompose(t *testing.T) {
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-30",
"reason": "Request could not be validated"
}`, "operation_id")
}`, "operation_id", "details")
// Returns 404, but should be 405; see https://github.com/labstack/echo/issues/1981
// test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
@ -434,7 +434,7 @@ func TestCompose(t *testing.T) {
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-10",
"reason": "Error resolving OSTree repo"
}`, "operation_id")
}`, "operation_id", "details")
// bad ref
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
@ -461,7 +461,7 @@ func TestCompose(t *testing.T) {
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-9",
"reason": "Invalid OSTree ref"
}`, "operation_id")
}`, "operation_id", "details")
// bad parent ref
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
@ -490,7 +490,7 @@ func TestCompose(t *testing.T) {
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-9",
"reason": "Invalid OSTree ref"
}`, "operation_id")
}`, "operation_id", "details")
// incorrect ref for URL
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
@ -518,7 +518,7 @@ func TestCompose(t *testing.T) {
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-10",
"reason": "Error resolving OSTree repo"
}`, "operation_id")
}`, "operation_id", "details")
// parent ref without URL
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", fmt.Sprintf(`
@ -545,7 +545,7 @@ func TestCompose(t *testing.T) {
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-27",
"reason": "Invalid OSTree parameters or parameter combination"
}`, "operation_id")
}`, "operation_id", "details")
}
func TestComposeStatusSuccess(t *testing.T) {
@ -615,7 +615,7 @@ func TestComposeStatusSuccess(t *testing.T) {
"kind": "Error",
"code": "IMAGE-BUILDER-COMPOSER-1012",
"reason": "OSBuildJobResult does not have expected fields set"
}`, "operation_id")
}`, "operation_id", "details")
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", fmt.Sprintf("/api/image-builder-composer/v2/composes/%v/logs", jobId), ``, http.StatusOK, fmt.Sprintf(`
{
@ -658,7 +658,7 @@ func TestComposeStatusSuccess(t *testing.T) {
"sources": {}
}
]
}`, jobId, jobId))
}`, jobId, jobId), "details")
}
func TestComposeStatusFailure(t *testing.T) {