Export error descriptors and provide tool generate markdown table

To support accurate specification generation, this changeset includes a quick
and dirty tool to generate a markdown table of error codes generated by the
registry API. Equivalent supports for routes will likely follow.

Exported descriptors could be used to generate other documentation, as well.
pull/4/head
Stephen J Day 2014-12-10 16:21:12 -08:00
parent 87c10960d2
commit b721b0a15c
3 changed files with 120 additions and 19 deletions

View File

@ -20,16 +20,21 @@ type ErrorDescriptor struct {
// for use in documentation. // for use in documentation.
Description string Description string
// DefaultStatusCode should to be returned via the HTTP API. Some error // HTTPStatusCodes provides a list of status under which this error
// may have different status codes depending on the situation. // condition may arise. If it is empty, the error condition may be seen
DefaultStatusCode int // for any status code.
HTTPStatusCodes []int
} }
var descriptors = []ErrorDescriptor{ // Descriptors provides a list of HTTP API Error codes that may be encountered
// when interacting with the registry API.
var Descriptors = []ErrorDescriptor{
{ {
Code: ErrorCodeUnknown, Code: ErrorCodeUnknown,
Value: "UNKNOWN", Value: "UNKNOWN",
Message: "unknown error", Message: "unknown error",
Description: `Generic error returned when the error does not have an
API classification.`,
}, },
{ {
Code: ErrorCodeDigestInvalid, Code: ErrorCodeDigestInvalid,
@ -40,7 +45,7 @@ var descriptors = []ErrorDescriptor{
include a detail structure with the key "digest", including the include a detail structure with the key "digest", including the
invalid digest string. This error may also be returned when a manifest invalid digest string. This error may also be returned when a manifest
includes an invalid layer digest.`, includes an invalid layer digest.`,
DefaultStatusCode: http.StatusBadRequest, HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
}, },
{ {
Code: ErrorCodeSizeInvalid, Code: ErrorCodeSizeInvalid,
@ -49,7 +54,7 @@ var descriptors = []ErrorDescriptor{
Description: `When a layer is uploaded, the provided size will be Description: `When a layer is uploaded, the provided size will be
checked against the uploaded content. If they do not match, this error checked against the uploaded content. If they do not match, this error
will be returned.`, will be returned.`,
DefaultStatusCode: http.StatusBadRequest, HTTPStatusCodes: []int{http.StatusBadRequest},
}, },
{ {
Code: ErrorCodeNameInvalid, Code: ErrorCodeNameInvalid,
@ -57,7 +62,7 @@ var descriptors = []ErrorDescriptor{
Message: "manifest name did not match URI", Message: "manifest name did not match URI",
Description: `During a manifest upload, if the name in the manifest Description: `During a manifest upload, if the name in the manifest
does not match the uri name, this error will be returned.`, does not match the uri name, this error will be returned.`,
DefaultStatusCode: http.StatusBadRequest, HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
}, },
{ {
Code: ErrorCodeTagInvalid, Code: ErrorCodeTagInvalid,
@ -65,7 +70,7 @@ var descriptors = []ErrorDescriptor{
Message: "manifest tag did not match URI", Message: "manifest tag did not match URI",
Description: `During a manifest upload, if the tag in the manifest Description: `During a manifest upload, if the tag in the manifest
does not match the uri tag, this error will be returned.`, does not match the uri tag, this error will be returned.`,
DefaultStatusCode: http.StatusBadRequest, HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
}, },
{ {
Code: ErrorCodeNameUnknown, Code: ErrorCodeNameUnknown,
@ -73,7 +78,7 @@ var descriptors = []ErrorDescriptor{
Message: "repository name not known to registry", Message: "repository name not known to registry",
Description: `This is returned if the name used during an operation is Description: `This is returned if the name used during an operation is
unknown to the registry.`, unknown to the registry.`,
DefaultStatusCode: http.StatusNotFound, HTTPStatusCodes: []int{http.StatusNotFound},
}, },
{ {
Code: ErrorCodeManifestUnknown, Code: ErrorCodeManifestUnknown,
@ -81,7 +86,7 @@ var descriptors = []ErrorDescriptor{
Message: "manifest unknown", Message: "manifest unknown",
Description: `This error is returned when the manifest, identified by Description: `This error is returned when the manifest, identified by
name and tag is unknown to the repository.`, name and tag is unknown to the repository.`,
DefaultStatusCode: http.StatusNotFound, HTTPStatusCodes: []int{http.StatusNotFound},
}, },
{ {
Code: ErrorCodeManifestInvalid, Code: ErrorCodeManifestInvalid,
@ -89,8 +94,9 @@ var descriptors = []ErrorDescriptor{
Message: "manifest invalid", Message: "manifest invalid",
Description: `During upload, manifests undergo several checks ensuring Description: `During upload, manifests undergo several checks ensuring
validity. If those checks fail, this error may be returned, unless a validity. If those checks fail, this error may be returned, unless a
more specific error is included.`, more specific error is included. The detail will contain information
DefaultStatusCode: http.StatusBadRequest, the failed validation.`,
HTTPStatusCodes: []int{http.StatusBadRequest},
}, },
{ {
Code: ErrorCodeManifestUnverified, Code: ErrorCodeManifestUnverified,
@ -98,7 +104,7 @@ var descriptors = []ErrorDescriptor{
Message: "manifest failed signature verification", Message: "manifest failed signature verification",
Description: `During manifest upload, if the manifest fails signature Description: `During manifest upload, if the manifest fails signature
verification, this error will be returned.`, verification, this error will be returned.`,
DefaultStatusCode: http.StatusBadRequest, HTTPStatusCodes: []int{http.StatusBadRequest},
}, },
{ {
Code: ErrorCodeBlobUnknown, Code: ErrorCodeBlobUnknown,
@ -108,7 +114,7 @@ var descriptors = []ErrorDescriptor{
registry in a specified repository. This can be returned with a registry in a specified repository. This can be returned with a
standard get or if a manifest references an unknown layer during standard get or if a manifest references an unknown layer during
upload.`, upload.`,
DefaultStatusCode: http.StatusNotFound, HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
}, },
{ {
@ -117,7 +123,7 @@ var descriptors = []ErrorDescriptor{
Message: "blob upload unknown to registry", Message: "blob upload unknown to registry",
Description: `If a blob upload has been cancelled or was never Description: `If a blob upload has been cancelled or was never
started, this error code may be returned.`, started, this error code may be returned.`,
DefaultStatusCode: http.StatusNotFound, HTTPStatusCodes: []int{http.StatusNotFound},
}, },
} }
@ -125,10 +131,10 @@ var errorCodeToDescriptors map[ErrorCode]ErrorDescriptor
var idToDescriptors map[string]ErrorDescriptor var idToDescriptors map[string]ErrorDescriptor
func init() { func init() {
errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(descriptors)) errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(Descriptors))
idToDescriptors = make(map[string]ErrorDescriptor, len(descriptors)) idToDescriptors = make(map[string]ErrorDescriptor, len(Descriptors))
for _, descriptor := range descriptors { for _, descriptor := range Descriptors {
errorCodeToDescriptors[descriptor.Code] = descriptor errorCodeToDescriptors[descriptor.Code] = descriptor
idToDescriptors[descriptor.Value] = descriptor idToDescriptors[descriptor.Value] = descriptor
} }

View File

@ -11,7 +11,7 @@ import (
// TestErrorCodes ensures that error code format, mappings and // TestErrorCodes ensures that error code format, mappings and
// marshaling/unmarshaling. round trips are stable. // marshaling/unmarshaling. round trips are stable.
func TestErrorCodes(t *testing.T) { func TestErrorCodes(t *testing.T) {
for _, desc := range descriptors { for _, desc := range Descriptors {
if desc.Code.String() != desc.Value { if desc.Code.String() != desc.Value {
t.Fatalf("error code string incorrect: %q != %q", desc.Code.String(), desc.Value) t.Fatalf("error code string incorrect: %q != %q", desc.Code.String(), desc.Value)
} }

View File

@ -0,0 +1,95 @@
// registry-api-doctable-gen uses various descriptors within the registry code
// base to generate markdown tables for use in documentation. This is only
// meant to facilitate updates to documentation and not as an automated tool.
//
// For now, this only includes support for error codes:
//
// $ registry-api-doctable-gen errors
//
package main
import (
"fmt"
"io"
"log"
"os"
"reflect"
"strings"
"text/tabwriter"
"github.com/docker/docker-registry/api/errors"
)
func main() {
if len(os.Args) < 2 {
log.Fatalln("please specify a table to generate: (errors)")
}
switch os.Args[1] {
case "errors":
dumpErrors(os.Stdout)
default:
log.Fatalln("unknown descriptor table:", os.Args[1])
}
}
func dumpErrors(wr io.Writer) {
writer := tabwriter.NewWriter(os.Stdout, 8, 8, 0, '\t', 0)
defer writer.Flush()
fmt.Fprint(writer, "|")
dtype := reflect.TypeOf(errors.ErrorDescriptor{})
var fieldsPrinted int
for i := 0; i < dtype.NumField(); i++ {
field := dtype.Field(i)
if field.Name == "Value" {
continue
}
fmt.Fprint(writer, field.Name, "|")
fieldsPrinted++
}
divider := strings.Repeat("-", 8)
var parts []string
for i := 0; i < fieldsPrinted; i++ {
parts = append(parts, divider)
}
divider = strings.Join(parts, "|")
fmt.Fprintln(writer, "\n"+divider)
for _, descriptor := range errors.Descriptors {
fmt.Fprint(writer, "|")
v := reflect.ValueOf(descriptor)
for i := 0; i < dtype.NumField(); i++ {
value := v.Field(i).Interface()
field := v.Type().Field(i)
if field.Name == "Value" {
continue
} else if field.Name == "Description" {
value = strings.Replace(value.(string), "\n", " ", -1)
} else if field.Name == "Code" {
value = fmt.Sprintf("`%s`", value)
} else if field.Name == "HTTPStatusCodes" {
if len(value.([]int)) > 0 {
var codes []string
for _, code := range value.([]int) {
codes = append(codes, fmt.Sprint(code))
}
value = strings.Join(codes, ", ")
} else {
value = "Any"
}
}
fmt.Fprint(writer, value, "|")
}
fmt.Fprint(writer, "\n")
}
}