diff --git a/internal/cloudapi/v2/errors.go b/internal/cloudapi/v2/errors.go index 2e6b8df00..79184139c 100644 --- a/internal/cloudapi/v2/errors.go +++ b/internal/cloudapi/v2/errors.go @@ -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) } diff --git a/internal/cloudapi/v2/errors_test.go b/internal/cloudapi/v2/errors_test.go index ebbd57fab..5c1044799 100644 --- a/internal/cloudapi/v2/errors_test.go +++ b/internal/cloudapi/v2/errors_test.go @@ -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) } diff --git a/internal/cloudapi/v2/handler.go b/internal/cloudapi/v2/handler.go index 0079fd543..a5471ee4a 100644 --- a/internal/cloudapi/v2/handler.go +++ b/internal/cloudapi/v2/handler.go @@ -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) diff --git a/internal/cloudapi/v2/middleware.go b/internal/cloudapi/v2/middleware.go index 0909c714f..06ee7f244 100644 --- a/internal/cloudapi/v2/middleware.go +++ b/internal/cloudapi/v2/middleware.go @@ -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) diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go index 009661435..e189987c7 100644 --- a/internal/cloudapi/v2/openapi.v2.gen.go +++ b/internal/cloudapi/v2/openapi.v2.gen.go @@ -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 diff --git a/internal/cloudapi/v2/openapi.v2.yml b/internal/cloudapi/v2/openapi.v2.yml index 377fd18fa..4268a161e 100644 --- a/internal/cloudapi/v2/openapi.v2.yml +++ b/internal/cloudapi/v2/openapi.v2.yml @@ -399,6 +399,7 @@ components: type: string operation_id: type: string + details: {} ErrorList: allOf: diff --git a/internal/cloudapi/v2/v2_koji_test.go b/internal/cloudapi/v2/v2_koji_test.go index ad5361116..3092add74 100644 --- a/internal/cloudapi/v2/v2_koji_test.go +++ b/internal/cloudapi/v2/v2_koji_test.go @@ -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`) } } diff --git a/internal/cloudapi/v2/v2_test.go b/internal/cloudapi/v2/v2_test.go index 1cb18790d..5e9f4d721 100644 --- a/internal/cloudapi/v2/v2_test.go +++ b/internal/cloudapi/v2/v2_test.go @@ -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) {