Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move ErrorCode logic to new errcode package #548

Merged
merged 4 commits into from
Jun 15, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion cmd/registry-api-descriptor-template/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"regexp"
"text/template"

"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/api/v2"
)

Expand All @@ -44,7 +45,15 @@ func main() {

tmpl := template.Must(template.New(filename).Funcs(funcMap).ParseFiles(path))

if err := tmpl.Execute(os.Stdout, v2.APIDescriptor); err != nil {
data := struct {
RouteDescriptors []v2.RouteDescriptor
ErrorDescriptors []errcode.ErrorDescriptor
}{
RouteDescriptors: v2.APIDescriptor.RouteDescriptors,
ErrorDescriptors: errcode.GetErrorCodeGroup("registry.api.v2"),
}

if err := tmpl.Execute(os.Stdout, data); err != nil {
log.Fatalln(err)
}
}
Expand Down
22 changes: 11 additions & 11 deletions docs/spec/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -734,20 +734,20 @@ The error codes encountered via the API are enumerated in the following table:

|Code|Message|Description|
-------|----|------|------------
`UNKNOWN` | unknown error | Generic error returned when the error does not have an API classification.
`UNSUPPORTED` | The operation is unsupported. | The operation was unsupported due to a missing implementation or invalid set of parameters.
`UNAUTHORIZED` | access to the requested resource is not authorized | The access controller denied access for the operation on a resource. Often this will be accompanied by a 401 Unauthorized response status.
`BLOB_UNKNOWN` | blob unknown to registry | This error may be returned when a blob is unknown to the registry in a specified repository. This can be returned with a standard get or if a manifest references an unknown layer during upload.
`BLOB_UPLOAD_INVALID` | blob upload invalid | The blob upload encountered an error and can no longer proceed.
`BLOB_UPLOAD_UNKNOWN` | blob upload unknown to registry | If a blob upload has been cancelled or was never started, this error code may be returned.
`DIGEST_INVALID` | provided digest did not match uploaded content | When a blob is uploaded, the registry will check that the content matches the digest provided by the client. The error may include a detail structure with the key "digest", including the invalid digest string. This error may also be returned when a manifest includes an invalid layer digest.
`SIZE_INVALID` | provided length did not match content length | When a layer is uploaded, the provided size will be checked against the uploaded content. If they do not match, this error will be returned.
`NAME_INVALID` | invalid repository name | Invalid repository name encountered either during manifest validation or any API operation.
`TAG_INVALID` | manifest tag did not match URI | During a manifest upload, if the tag in the manifest does not match the uri tag, this error will be returned.
`NAME_UNKNOWN` | repository name not known to registry | This is returned if the name used during an operation is unknown to the registry.
`MANIFEST_UNKNOWN` | manifest unknown | This error is returned when the manifest, identified by name and tag is unknown to the repository.
`MANIFEST_BLOB_UNKNOWN` | blob unknown to registry | This error may be returned when a manifest blob is unknown to the registry.
`MANIFEST_INVALID` | manifest invalid | During upload, manifests undergo several checks ensuring validity. If those checks fail, this error may be returned, unless a more specific error is included. The detail will contain information the failed validation.
`MANIFEST_UNKNOWN` | manifest unknown | This error is returned when the manifest, identified by name and tag is unknown to the repository.
`MANIFEST_UNVERIFIED` | manifest failed signature verification | During manifest upload, if the manifest fails signature verification, this error will be returned.
`BLOB_UNKNOWN` | blob unknown to registry | This error may be returned when a blob is unknown to the registry in a specified repository. This can be returned with a standard get or if a manifest references an unknown layer during upload.
`BLOB_UPLOAD_UNKNOWN` | blob upload unknown to registry | If a blob upload has been cancelled or was never started, this error code may be returned.
`BLOB_UPLOAD_INVALID` | blob upload invalid | The blob upload encountered an error and can no longer proceed.
`NAME_INVALID` | invalid repository name | Invalid repository name encountered either during manifest validation or any API operation.
`NAME_UNKNOWN` | repository name not known to registry | This is returned if the name used during an operation is unknown to the registry.
`SIZE_INVALID` | provided length did not match content length | When a layer is uploaded, the provided size will be checked against the uploaded content. If they do not match, this error will be returned.
`TAG_INVALID` | manifest tag did not match URI | During a manifest upload, if the tag in the manifest does not match the uri tag, this error will be returned.
`UNAUTHORIZED` | access to the requested resource is not authorized | The access controller denied access for the operation on a resource. Often this will be accompanied by a 401 Unauthorized response status.
`UNSUPPORTED` | The operation is unsupported. | The operation was unsupported due to a missing implementation or invalid set of parameters.



Expand Down
225 changes: 225 additions & 0 deletions registry/api/errcode/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
package errcode

import (
"encoding/json"
"fmt"
"strings"
)

// ErrorCoder is the base interface for ErrorCode and Error allowing
// users of each to just call ErrorCode to get the real ID of each
type ErrorCoder interface {
ErrorCode() ErrorCode
}

// 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ErrorCode is missing Error and ErrorCode method. Error makes it implement error and ErrorCode implements ErrorCoder interface:

type ErrorCoder interface {
    ErrorCode() Code
}


// ErrorCode just returns itself
func (ec ErrorCode) ErrorCode() ErrorCode {
return ec
}

// Error returns the ID/Value
func (ec ErrorCode) Error() string {
return ec.Descriptor().Value
}

// Descriptor returns the descriptor for the error code.
func (ec ErrorCode) Descriptor() ErrorDescriptor {
d, ok := errorCodeToDescriptors[ec]

if !ok {
return ErrorCodeUnknown.Descriptor()
}

return d
}

// String returns the canonical identifier for this error code.
func (ec ErrorCode) String() string {
return ec.Descriptor().Value
}

// Message returned the human-readable error message for this error code.
func (ec ErrorCode) Message() string {
return ec.Descriptor().Message
}

// MarshalText encodes the receiver into UTF-8-encoded text and returns the
// result.
func (ec ErrorCode) MarshalText() (text []byte, err error) {
return []byte(ec.String()), nil
}

// UnmarshalText decodes the form generated by MarshalText.
func (ec *ErrorCode) UnmarshalText(text []byte) error {
desc, ok := idToDescriptors[string(text)]

if !ok {
desc = ErrorCodeUnknown.Descriptor()
}

*ec = desc.Code

return nil
}

// WithDetail creates a new Error struct based on the passed-in info and
// set the Detail property appropriately
func (ec ErrorCode) WithDetail(detail interface{}) Error {
if err, ok := detail.(error); ok {
detail = err.Error()
}

return Error{
Code: ec,
Detail: detail,
}
}

// Error provides a wrapper around ErrorCode with extra Details provided.
type Error struct {
Code ErrorCode `json:"code"`
Detail interface{} `json:"detail,omitempty"`
}

// ErrorCode returns the ID/Value of this Error
func (e Error) ErrorCode() ErrorCode {
return e.Code
}

// Error returns a human readable representation of the error.
func (e Error) Error() string {
return fmt.Sprintf("%s: %s",
strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)),
e.Code.Message())
}

// Message returned the human-readable error message for this Error
func (e Error) Message() string {
return e.Code.Message()
}

// ErrorDescriptor provides relevant information about a given error code.
type ErrorDescriptor struct {
// Code is the error code that this descriptor describes.
Code ErrorCode

// Value provides a unique, string key, often captilized with
// underscores, to identify the error code. This value is used as the
// keyed value when serializing api errors.
Value string

// Message is a short, human readable decription of the error condition
// included in API responses.
Message string

// Description provides a complete account of the errors purpose, suitable
// for use in documentation.
Description string

// HTTPStatusCode provides the http status code that is associated with
// this error condition.
HTTPStatusCode int
}

// ParseErrorCode returns the value by the string error code.
// `ErrorCodeUnknown` will be returned if the error is not known.
func ParseErrorCode(value string) ErrorCode {
ed, ok := idToDescriptors[value]
if ok {
return ed.Code
}

return ErrorCodeUnknown
}

// Errors provides the envelope for multiple errors and a few sugar methods
// for use within the application.
type Errors []error

func (errs Errors) Error() string {
switch len(errs) {
case 0:
return "<nil>"
case 1:
return errs[0].Error()
default:
msg := "errors:\n"
for _, err := range errs {
msg += err.Error() + "\n"
}
return msg
}
}

// Len returns the current number of errors.
func (errs Errors) Len() int {
return len(errs)
}

// jsonError extends Error with 'Message' so that we can include the
// error text, just in case the receiver of the JSON doesn't have this
// particular ErrorCode registered
type jsonError struct {
Code ErrorCode `json:"code"`
Message string `json:"message"`
Detail interface{} `json:"detail,omitempty"`
}

// MarshalJSON converts slice of error, ErrorCode or Error into a
// slice of Error - then serializes
func (errs Errors) MarshalJSON() ([]byte, error) {
var tmpErrs []jsonError

for _, daErr := range errs {
var err Error

switch daErr.(type) {
case ErrorCode:
err = daErr.(ErrorCode).WithDetail(nil)
case Error:
err = daErr.(Error)
default:
err = ErrorCodeUnknown.WithDetail(daErr)

}

tmpErrs = append(tmpErrs, jsonError{
Code: err.Code,
Message: err.Message(),
Detail: err.Detail,
})
}

return json.Marshal(tmpErrs)
}

// UnmarshalJSON deserializes []Error and then converts it into slice of
// Error or ErrorCode
func (errs *Errors) UnmarshalJSON(data []byte) error {
var tmpErrs []jsonError

if err := json.Unmarshal(data, &tmpErrs); err != nil {
return err
}

var newErrs Errors
for _, daErr := range tmpErrs {
if daErr.Detail == nil {
// Error's w/o details get converted to ErrorCode
newErrs = append(newErrs, daErr.Code)
} else {
// Error's w/ details are untouched
newErrs = append(newErrs, Error{
Code: daErr.Code,
Detail: daErr.Detail,
})
}
}

*errs = newErrs
return nil
}
Loading