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

View file

@ -11,7 +11,7 @@ import (
// TestErrorCodes ensures that error code format, mappings and
// marshaling/unmarshaling. round trips are stable.
func TestErrorCodes(t *testing.T) {
for _, desc := range descriptors {
for _, desc := range Descriptors {
if 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")
}
}