Initial implementation of API errors data structure
							parent
							
								
									364b11e876
								
							
						
					
					
						commit
						af0411420a
					
				| 
						 | 
					@ -0,0 +1,157 @@
 | 
				
			||||||
 | 
					package registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrorCode represents the error type. The errors are serialized via strings
 | 
				
			||||||
 | 
					// and the integer format may change and should *never* be exported.
 | 
				
			||||||
 | 
					type ErrorCode int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						ErrorCodeUnknown ErrorCode = iota
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The following errors can happen during a layer upload.
 | 
				
			||||||
 | 
						ErrorCodeInvalidChecksum
 | 
				
			||||||
 | 
						ErrorCodeInvalidLength
 | 
				
			||||||
 | 
						ErrorCodeInvalidTarsum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The following errors can happen during manifest upload.
 | 
				
			||||||
 | 
						ErrorCodeInvalidName
 | 
				
			||||||
 | 
						ErrorCodeInvalidTag
 | 
				
			||||||
 | 
						ErrorCodeUnverifiedManifest
 | 
				
			||||||
 | 
						ErrorCodeUnknownLayer
 | 
				
			||||||
 | 
						ErrorCodeUntrustedSignature
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var errorCodeStrings = map[ErrorCode]string{
 | 
				
			||||||
 | 
						ErrorCodeUnknown:            "UNKNOWN",
 | 
				
			||||||
 | 
						ErrorCodeInvalidChecksum:    "INVALID_CHECKSUM",
 | 
				
			||||||
 | 
						ErrorCodeInvalidLength:      "INVALID_LENGTH",
 | 
				
			||||||
 | 
						ErrorCodeInvalidTarsum:      "INVALID_TARSUM",
 | 
				
			||||||
 | 
						ErrorCodeInvalidName:        "INVALID_NAME",
 | 
				
			||||||
 | 
						ErrorCodeInvalidTag:         "INVALID_TAG",
 | 
				
			||||||
 | 
						ErrorCodeUnverifiedManifest: "UNVERIFIED_MANIFEST",
 | 
				
			||||||
 | 
						ErrorCodeUnknownLayer:       "UNKNOWN_LAYER",
 | 
				
			||||||
 | 
						ErrorCodeUntrustedSignature: "UNTRUSTED_SIGNATURE",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var errorCodesMessages = map[ErrorCode]string{
 | 
				
			||||||
 | 
						ErrorCodeUnknown:            "unknown error",
 | 
				
			||||||
 | 
						ErrorCodeInvalidChecksum:    "provided checksum did not match uploaded content",
 | 
				
			||||||
 | 
						ErrorCodeInvalidLength:      "provided length did not match content length",
 | 
				
			||||||
 | 
						ErrorCodeInvalidTarsum:      "provided tarsum did not match binary content",
 | 
				
			||||||
 | 
						ErrorCodeInvalidName:        "Manifest name did not match URI",
 | 
				
			||||||
 | 
						ErrorCodeInvalidTag:         "Manifest tag did not match URI",
 | 
				
			||||||
 | 
						ErrorCodeUnverifiedManifest: "Manifest failed signature validation",
 | 
				
			||||||
 | 
						ErrorCodeUnknownLayer:       "Referenced layer not available",
 | 
				
			||||||
 | 
						ErrorCodeUntrustedSignature: "Manifest signed by untrusted source",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var stringToErrorCode map[string]ErrorCode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						stringToErrorCode = make(map[string]ErrorCode, len(errorCodeStrings))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Build up reverse error code map
 | 
				
			||||||
 | 
						for k, v := range errorCodeStrings {
 | 
				
			||||||
 | 
							stringToErrorCode[v] = k
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ParseErrorCode attempts to parse the error code string, returning
 | 
				
			||||||
 | 
					// ErrorCodeUnknown if the error is not known.
 | 
				
			||||||
 | 
					func ParseErrorCode(s string) ErrorCode {
 | 
				
			||||||
 | 
						ec, ok := stringToErrorCode[s]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return ErrorCodeUnknown
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return ec
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// String returns the canonical identifier for this error code.
 | 
				
			||||||
 | 
					func (ec ErrorCode) String() string {
 | 
				
			||||||
 | 
						s, ok := errorCodeStrings[ec]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return errorCodeStrings[ErrorCodeUnknown]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return s
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ec ErrorCode) Message() string {
 | 
				
			||||||
 | 
						m, ok := errorCodesMessages[ec]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return errorCodesMessages[ErrorCodeUnknown]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return m
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ec ErrorCode) MarshalText() (text []byte, err error) {
 | 
				
			||||||
 | 
						return []byte(ec.String()), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ec *ErrorCode) UnmarshalText(text []byte) error {
 | 
				
			||||||
 | 
						*ec = stringToErrorCode[string(text)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Error struct {
 | 
				
			||||||
 | 
						Code    ErrorCode   `json:"code,omitempty"`
 | 
				
			||||||
 | 
						Message string      `json:"message,omitempty"`
 | 
				
			||||||
 | 
						Detail  interface{} `json:"detail,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Error returns a human readable representation of the error.
 | 
				
			||||||
 | 
					func (e Error) Error() string {
 | 
				
			||||||
 | 
						return fmt.Sprintf("%s: %s",
 | 
				
			||||||
 | 
							strings.Title(strings.Replace(e.Code.String(), "_", " ", -1)),
 | 
				
			||||||
 | 
							e.Message)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Errors provides the envelope for multiple errors and a few sugar methods
 | 
				
			||||||
 | 
					// for use within the application.
 | 
				
			||||||
 | 
					type Errors struct {
 | 
				
			||||||
 | 
						Errors []Error `json:"errors,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Push pushes an error on to the error stack, with the optional detail
 | 
				
			||||||
 | 
					// argument. It is a programming error (ie panic) to push more than one
 | 
				
			||||||
 | 
					// detail at a time.
 | 
				
			||||||
 | 
					func (errs *Errors) Push(code ErrorCode, details ...interface{}) {
 | 
				
			||||||
 | 
						if len(details) > 1 {
 | 
				
			||||||
 | 
							panic("please specify zero or one detail items for this error")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var detail interface{}
 | 
				
			||||||
 | 
						if len(details) > 0 {
 | 
				
			||||||
 | 
							detail = details[0]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errs.Errors = append(errs.Errors, Error{
 | 
				
			||||||
 | 
							Code:    code,
 | 
				
			||||||
 | 
							Message: code.Message(),
 | 
				
			||||||
 | 
							Detail:  detail,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// detailUnknownLayer provides detail for unknown layer errors, returned by
 | 
				
			||||||
 | 
					// image manifest push for layers that are not yet transferred. This intended
 | 
				
			||||||
 | 
					// to only be used on the backend to return detail for this specific error.
 | 
				
			||||||
 | 
					type DetailUnknownLayer struct {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Unknown should contain the contents of a layer descriptor, which is a
 | 
				
			||||||
 | 
						// single json object with the key "blobSum" currently.
 | 
				
			||||||
 | 
						Unknown struct {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// BlobSum contains the uniquely identifying tarsum of the layer.
 | 
				
			||||||
 | 
							BlobSum string `json:"blobSum"`
 | 
				
			||||||
 | 
						} `json:"unknown"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,77 @@
 | 
				
			||||||
 | 
					package registry
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestErrorCodes ensures that error code format, mappings and
 | 
				
			||||||
 | 
					// marshaling/unmarshaling. round trips are stable.
 | 
				
			||||||
 | 
					func TestErrorCodes(t *testing.T) {
 | 
				
			||||||
 | 
						for ec, _ := range errorCodeStrings {
 | 
				
			||||||
 | 
							if ec.String() != errorCodeStrings[ec] {
 | 
				
			||||||
 | 
								t.Fatalf("error code string incorrect: %q != %q", ec.String(), errorCodeStrings[ec])
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if ec.Message() != errorCodesMessages[ec] {
 | 
				
			||||||
 | 
								t.Fatalf("incorrect message for error code %v: %q != !q", ec, ec.Message(), errorCodesMessages[ec])
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Serialize the error code using the json library to ensure that we
 | 
				
			||||||
 | 
							// get a string and it works round trip.
 | 
				
			||||||
 | 
							p, err := json.Marshal(ec)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("error marshaling error code %v: %v", ec, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if len(p) <= 0 {
 | 
				
			||||||
 | 
								t.Fatalf("expected content in marshaled before for error code %v: %v", ec)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// First, unmarshal to interface and ensure we have a string.
 | 
				
			||||||
 | 
							var ecUnspecified interface{}
 | 
				
			||||||
 | 
							if err := json.Unmarshal(p, &ecUnspecified); err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("error unmarshaling error code %v: %v", ec, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if _, ok := ecUnspecified.(string); !ok {
 | 
				
			||||||
 | 
								t.Fatalf("expected a string for error code %v on unmarshal got a %T", ec, ecUnspecified)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Now, unmarshal with the error code type and ensure they are equal
 | 
				
			||||||
 | 
							var ecUnmarshaled ErrorCode
 | 
				
			||||||
 | 
							if err := json.Unmarshal(p, &ecUnmarshaled); err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("error unmarshaling error code %v: %v", ec, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if ecUnmarshaled != ec {
 | 
				
			||||||
 | 
								t.Fatalf("unexpected error code during error code marshal/unmarshal: %v != %v", ecUnmarshaled, ec)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TestErrorsManagement does a quick check of the Errors type to ensure that
 | 
				
			||||||
 | 
					// members are properly pushed and marshaled.
 | 
				
			||||||
 | 
					func TestErrorsManagement(t *testing.T) {
 | 
				
			||||||
 | 
						var errs Errors
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errs.Push(ErrorCodeInvalidChecksum)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var detail DetailUnknownLayer
 | 
				
			||||||
 | 
						detail.Unknown.BlobSum = "sometestblobsumdoesntmatter"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errs.Push(ErrorCodeUnknownLayer, detail)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p, err := json.Marshal(errs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("error marashaling errors: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						expectedJSON := "{\"errors\":[{\"code\":\"INVALID_CHECKSUM\",\"message\":\"provided checksum did not match uploaded content\"},{\"code\":\"UNKNOWN_LAYER\",\"message\":\"Referenced layer not available\",\"detail\":{\"unknown\":{\"blobSum\":\"sometestblobsumdoesntmatter\"}}}]}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if string(p) != expectedJSON {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue