From f2a69c27c2ee01e07ea3b123c3e4d0830ea74ac0 Mon Sep 17 00:00:00 2001 From: Johan de Klerk <jdeklerk00@gmail.com> Date: Fri, 11 Mar 2022 11:48:13 +0200 Subject: [PATCH] docs: Populated response --- api_documentation/api_documentation.go | 261 +++++++++++++++---------- handler_utils/handler.go | 5 + 2 files changed, 159 insertions(+), 107 deletions(-) diff --git a/api_documentation/api_documentation.go b/api_documentation/api_documentation.go index ba8a9b5..57517c7 100644 --- a/api_documentation/api_documentation.go +++ b/api_documentation/api_documentation.go @@ -2,55 +2,74 @@ package api_documentation import ( "fmt" - "reflect" "strings" "gitlab.com/uafrica/go-utils/handler_utils" - - "gitlab.com/uafrica/go-utils/errors" ) type NoParams struct{} +type DocPath map[string]DocMethodInfo + type Docs struct { Paths map[string]DocPath `json:"paths"` } -type DocPath struct { - Methods map[string]DocMethod `json:"methods"` -} -type DocMethod struct { - Description string `json:"description"` - Parameters map[string]DocParam `json:"parameters,omitempty"` - Request interface{} `json:"request,omitempty"` - Response interface{} `json:"response,omitempty"` +type DocMethodInfo struct { + Summary string `json:"summary"` + Tags []string `json:"tags"` + Parameters []DocParam `json:"parameters,omitempty"` + Responses map[string]DocResponseValue `json:"responses,omitempty"` } type DocParam struct { Name string + In string Type string Description string + Schema DocSchema } +type DocSchema struct { + Ref string `json:"$ref"` +} + +type DocResponseValue struct { + Description string `json:"description"` + Schema *DocSchemaResponse `json:"schema,omitempty"` +} + +type DocSchemaResponse struct { + Type *string `json:"type,omitempty"` + Items DocSchema `json:"items"` +} + + func GetDocs(endpointHandlers map[string]map[string]interface{}) (Docs, error) { docs := Docs{ Paths: map[string]DocPath{}, } + + var validationError error + if endpointHandlers, validationError = handler_utils.ValidateAPIEndpoints(endpointHandlers); validationError != nil { + return Docs{}, validationError + } + + for path, methods := range endpointHandlers { - docPath := DocPath{ - Methods: map[string]DocMethod{}, - } + docPath := DocPath{} for method, methodHandler := range methods { - docMethod := DocMethod{} + docMethod := DocMethodInfo{} if handler, ok := methodHandler.(handler_utils.Handler); !ok { - docMethod.Description = "Not available" + docMethod.Summary = "Not available" } else { //purpose - docMethod.Description = "Not available - see request and response structs" + docMethod.Summary = "Not available - see request and response structs" + docMethod.Tags = []string{path} //describe parameters - docMethod.Parameters = map[string]DocParam{} + docMethod.Parameters = []DocParam{} for i := 0; i < handler.RequestParamsType.NumField(); i++ { f := handler.RequestParamsType.Field(i) @@ -59,108 +78,136 @@ func GetDocs(endpointHandlers map[string]map[string]interface{}) (Docs, error) { name = f.Name } - docMethod.Parameters[f.Name] = DocParam{ + parameter := DocParam{ Name: name, Type: fmt.Sprintf("%v", f.Type), Description: f.Tag.Get("doc"), } - } - //describe request schema - var err error - docMethod.Request, err = DocSchema(fmt.Sprintf("%s %s %s", method, path, "request"), handler.RequestBodyType) - if err != nil { - return Docs{}, errors.Wrapf(err, "failed to document request") + docMethod.Parameters = append(docMethod.Parameters, parameter) } - docMethod.Response, err = DocSchema(fmt.Sprintf("%s %s %s", method, path, "response"), handler.ResponseType) - if err != nil { - return Docs{}, errors.Wrapf(err, "failed to document response") - } - } - docPath.Methods[method] = docMethod - } - docs.Paths[path] = docPath - } - return docs, nil -} + if handler.RequestBodyType != nil { + parameter := DocParam{ + Name: "body", + In: "body", + Schema: DocSchema{Ref: "#/definitions/Pet"}, + } -func DocSchema(description string, t reflect.Type) (interface{}, error) { - if t == nil { - return nil, nil - } - schema := map[string]interface{}{ - "description": description, - } + docMethod.Parameters = append(docMethod.Parameters, parameter) + } - if t.Kind() == reflect.Ptr { - schema["optional"] = true - t = t.Elem() - } + if handler.ResponseType != nil { + responses := map[string]DocResponseValue{} - switch t.Kind() { - case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int, - reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint, - reflect.Float64, reflect.Float32, - reflect.Bool, - reflect.String: - schema["type"] = fmt.Sprintf("%v", t) - - case reflect.Interface: - schema["type"] = "interface{}" //any value...? - - case reflect.Struct: - schema["type"] = "object" - properties := map[string]interface{}{} - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - if !f.Anonymous { - fieldName := f.Tag.Get("json") - if fieldName == "" { - fieldName = f.Name - } - if fieldName == "-" { - continue //json does not marshal these - } - fieldName = strings.Replace(fieldName, ",omitempty", "", -1) + responses["200"] = DocResponseValue{ + Description: "successful operation", + Schema: &DocSchemaResponse{ + Items: DocSchema{Ref: "#/definitions/Pet"}, + }, + } - var err error - fieldDesc := f.Tag.Get("doc") - if fieldDesc == "" { - fieldDesc = description + "." + fieldName - } - properties[fieldName], err = DocSchema(fieldDesc, f.Type) - if err != nil { - return nil, errors.Wrapf(err, "failed to document %v.%s", t, fieldName) - } - } - } - schema["properties"] = properties + docMethod.Responses = responses - case reflect.Map: - schema["type"] = "map" - keySchema, err := DocSchema("key", t.Key()) - if err != nil { - return nil, errors.Wrapf(err, "cannot make schema for %v map key", t) - } - schema["key"] = keySchema - elemSchema, err := DocSchema("items", t.Elem()) - if err != nil { - return nil, errors.Wrapf(err, "cannot make schema for %v map elem", t) - } - schema["items"] = elemSchema + } - case reflect.Slice: - schema["type"] = "array" - elemSchema, err := DocSchema("items", t.Elem()) - if err != nil { - return nil, errors.Wrapf(err, "cannot make schema for %v slice elem", t) - } - schema["items"] = elemSchema - default: - return nil, errors.Errorf("cannot generate schema for %v kind=%v", t, t.Kind()) - } - return schema, nil + //describe request schema + // var err error + // docMethod.Request, err = DocSchema(fmt.Sprintf("%s %s %s", method, path, "request"), handler.RequestBodyType) + // if err != nil { + // return Docs{}, errors.Wrapf(err, "failed to document request") + // } + // docMethod.Response, err = DocSchema(fmt.Sprintf("%s %s %s", method, path, "response"), handler.ResponseType) + // if err != nil { + // return Docs{}, errors.Wrapf(err, "failed to document response") + // } + } + + docPath[strings.ToLower(method)] = docMethod + } + docs.Paths[path] = docPath + } + return docs, nil } +// +// func DocSchema(description string, t reflect.Type) (interface{}, error) { +// if t == nil { +// return nil, nil +// } +// schema := map[string]interface{}{ +// "description": description, +// } +// +// if t.Kind() == reflect.Ptr { +// schema["optional"] = true +// t = t.Elem() +// } +// +// switch t.Kind() { +// case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int, +// reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint, +// reflect.Float64, reflect.Float32, +// reflect.Bool, +// reflect.String: +// schema["type"] = fmt.Sprintf("%v", t) +// +// case reflect.Interface: +// schema["type"] = "interface{}" //any value...? +// +// case reflect.Struct: +// schema["type"] = "object" +// properties := map[string]interface{}{} +// for i := 0; i < t.NumField(); i++ { +// f := t.Field(i) +// if !f.Anonymous { +// fieldName := f.Tag.Get("json") +// if fieldName == "" { +// fieldName = f.Name +// } +// if fieldName == "-" { +// continue //json does not marshal these +// } +// fieldName = strings.Replace(fieldName, ",omitempty", "", -1) +// +// var err error +// fieldDesc := f.Tag.Get("doc") +// if fieldDesc == "" { +// fieldDesc = description + "." + fieldName +// } +// properties[fieldName], err = DocSchema(fieldDesc, f.Type) +// if err != nil { +// return nil, errors.Wrapf(err, "failed to document %v.%s", t, fieldName) +// } +// } +// } +// schema["properties"] = properties +// +// case reflect.Map: +// schema["type"] = "map" +// // keySchema, err := DocSchema("key", t.Key()) +// // if err != nil { +// // return nil, errors.Wrapf(err, "cannot make schema for %v map key", t) +// // } +// // schema["key"] = keySchema +// // // elemSchema, err := DocSchema("items", t.Elem()) +// // if err != nil { +// // return nil, errors.Wrapf(err, "cannot make schema for %v map elem", t) +// // } +// // schema["items"] = elemSchema +// +// case reflect.Slice: +// schema["type"] = "array" +// // elemSchema, err := DocSchema("items", t.Elem()) +// // if err != nil { +// // return nil, errors.Wrapf(err, "cannot make schema for %v slice elem", t) +// // } +// // schema["items"] = elemSchema +// +// default: +// return nil, errors.Errorf("cannot generate schema for %v kind=%v", t, t.Kind()) +// } +// +// return schema, nil +// } diff --git a/handler_utils/handler.go b/handler_utils/handler.go index 89c8c8a..b1e6cad 100644 --- a/handler_utils/handler.go +++ b/handler_utils/handler.go @@ -62,6 +62,11 @@ func NewHandler(handlerFunction interface{}) (Handler, error) { return h, errors.Errorf("last result %v is not error type", handlerFunctionType.Out(handlerFunctionType.NumOut()-1)) } + // First result is the response object + if handlerFunctionType.NumOut() > 0 { + h.ResponseType = handlerFunctionType.Out(0) + } + h.FuncValue = reflect.ValueOf(handlerFunction) return h, nil } -- GitLab