-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ociregistry: implement MarshalError and use it in ociserver
This provides a standard way for code to convert from a regular Go error to the form appropriate for OCI error response bodies. It seems to make sense to put this inside the top level ociregistry package because the JSON-oriented `WireError` types are already implemented there, and the logic for stripping error code prefixes is directly related to the logic in the same package that adds them. Also in passing fix an unintended use of the deprecated `io/ioutil` package to use `io` instead. Also add a test for error stuttering to `ociclient` and fix the code to avoid stuttering the HTTP status code as exposed by that test. Fixes #31. Signed-off-by: Roger Peppe <rogpeppe@gmail.com> Change-Id: I22408dd3320b73bc52f37181987f79003dbaa115 Dispatch-Trailer: {"type":"trybot","CL":1191146,"patchset":5,"ref":"refs/changes/46/1191146/5","targetBranch":"main"}
- Loading branch information
1 parent
25a0bf5
commit d296834
Showing
7 changed files
with
278 additions
and
111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package ociregistry | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/go-quicktest/qt" | ||
) | ||
|
||
var errorTests = []struct { | ||
testName string | ||
err error | ||
wantMsg string | ||
wantMarshalData rawJSONMessage | ||
wantMarshalHTTPStatus int | ||
}{{ | ||
testName: "RegularGoError", | ||
err: fmt.Errorf("unknown error"), | ||
wantMsg: "unknown error", | ||
wantMarshalData: `{"errors":[{"code":"UNKNOWN","message":"unknown error"}]}`, | ||
wantMarshalHTTPStatus: http.StatusInternalServerError, | ||
}, { | ||
testName: "RegistryError", | ||
err: ErrBlobUnknown, | ||
wantMsg: "blob unknown: blob unknown to registry", | ||
wantMarshalData: `{"errors":[{"code":"BLOB_UNKNOWN","message":"blob unknown to registry"}]}`, | ||
wantMarshalHTTPStatus: http.StatusNotFound, | ||
}, { | ||
testName: "WrappedRegistryErrorWithContextAtStart", | ||
err: fmt.Errorf("some context: %w", ErrBlobUnknown), | ||
wantMsg: "some context: blob unknown: blob unknown to registry", | ||
wantMarshalData: `{"errors":[{"code":"BLOB_UNKNOWN","message":"some context: blob unknown: blob unknown to registry"}]}`, | ||
wantMarshalHTTPStatus: http.StatusNotFound, | ||
}, { | ||
testName: "WrappedRegistryErrorWithContextAtEnd", | ||
err: fmt.Errorf("%w: some context", ErrBlobUnknown), | ||
wantMsg: "blob unknown: blob unknown to registry: some context", | ||
wantMarshalData: `{"errors":[{"code":"BLOB_UNKNOWN","message":"blob unknown to registry: some context"}]}`, | ||
wantMarshalHTTPStatus: http.StatusNotFound, | ||
}, { | ||
testName: "HTTPStatusIgnoredWithKnownCode", | ||
err: NewHTTPError(fmt.Errorf("%w: some context", ErrBlobUnknown), http.StatusUnauthorized, nil, nil), | ||
wantMsg: "401 Unauthorized: blob unknown: blob unknown to registry: some context", | ||
// Note: the "401 Unauthorized" remains intact because it's not redundant with respect | ||
// to the 404 HTTP response code. | ||
wantMarshalData: `{"errors":[{"code":"BLOB_UNKNOWN","message":"401 Unauthorized: blob unknown: blob unknown to registry: some context"}]}`, | ||
wantMarshalHTTPStatus: http.StatusNotFound, | ||
}, { | ||
testName: "HTTPStatusUsedWithUnknownCode", | ||
err: NewHTTPError(NewError("a message with a code", "SOME_CODE", nil), http.StatusUnauthorized, nil, nil), | ||
wantMsg: "401 Unauthorized: some code: a message with a code", | ||
wantMarshalData: `{"errors":[{"code":"SOME_CODE","message":"a message with a code"}]}`, | ||
wantMarshalHTTPStatus: http.StatusUnauthorized, | ||
}, { | ||
testName: "ErrorWithDetail", | ||
err: NewError("a message with some detail", "SOME_CODE", json.RawMessage(`{"foo": true}`)), | ||
wantMsg: `some code: a message with some detail`, | ||
wantMarshalData: `{"errors":[{"code":"SOME_CODE","message":"a message with some detail","detail":{"foo":true}}]}`, | ||
wantMarshalHTTPStatus: http.StatusInternalServerError, | ||
}} | ||
|
||
func TestError(t *testing.T) { | ||
for _, test := range errorTests { | ||
t.Run(test.testName, func(t *testing.T) { | ||
qt.Check(t, qt.ErrorMatches(test.err, test.wantMsg)) | ||
data, httpStatus := MarshalError(test.err) | ||
qt.Check(t, qt.Equals(httpStatus, test.wantMarshalHTTPStatus)) | ||
qt.Check(t, qt.JSONEquals(data, test.wantMarshalData), qt.Commentf("marshal data: %s", data)) | ||
|
||
// Check that the marshaled error unmarshals into WireError OK and | ||
// that the code matches appropriately. | ||
var errs *WireErrors | ||
err := json.Unmarshal(data, &errs) | ||
qt.Assert(t, qt.IsNil(err)) | ||
if ociErr := Error(nil); errors.As(test.err, &ociErr) { | ||
qt.Assert(t, qt.IsTrue(errors.Is(errs, NewError("something", ociErr.Code(), nil)))) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
type rawJSONMessage string | ||
|
||
func (m rawJSONMessage) MarshalJSON() ([]byte, error) { | ||
return []byte(m), nil | ||
} | ||
|
||
func (m *rawJSONMessage) UnmarshalJSON(data []byte) error { | ||
*m = rawJSONMessage(data) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.