From b721b0a15c50aae1d77e50f1211972b03a07f210 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Wed, 10 Dec 2014 16:21:12 -0800 Subject: [PATCH] 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. --- api/errors/descriptors.go | 42 +++++++----- api/errors/errors_test.go | 2 +- cmd/registry-api-doctable-gen/main.go | 95 +++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 19 deletions(-) create mode 100644 cmd/registry-api-doctable-gen/main.go diff --git a/api/errors/descriptors.go b/api/errors/descriptors.go index e3dfd37b..1d71162f 100644 --- a/api/errors/descriptors.go +++ b/api/errors/descriptors.go @@ -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 } diff --git a/api/errors/errors_test.go b/api/errors/errors_test.go index f0712bef..7a68fe90 100644 --- a/api/errors/errors_test.go +++ b/api/errors/errors_test.go @@ -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) } diff --git a/cmd/registry-api-doctable-gen/main.go b/cmd/registry-api-doctable-gen/main.go new file mode 100644 index 00000000..f76c249e --- /dev/null +++ b/cmd/registry-api-doctable-gen/main.go @@ -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") + } +}