Select Git revision
api_documentation.go
api_documentation.go 4.19 KiB
package api_documentation
import (
"fmt"
"reflect"
"strings"
"gitlab.com/uafrica/go-utils/handler_utils"
"gitlab.com/uafrica/go-utils/errors"
)
type NoParams struct{}
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 DocParam struct {
Name string
Type string
Description string
}
func GetDocs(endpointHandlers map[string]map[string]interface{}) (Docs, error) {
docs := Docs{
Paths: map[string]DocPath{},
}
for path, methods := range endpointHandlers {
docPath := DocPath{
Methods: map[string]DocMethod{},
}
for method, methodHandler := range methods {
docMethod := DocMethod{}
if handler, ok := methodHandler.(handler_utils.Handler); !ok {
docMethod.Description = "Not available"
} else {
//purpose
docMethod.Description = "Not available - see request and response structs"
//describe parameters
docMethod.Parameters = map[string]DocParam{}
for i := 0; i < handler.RequestParamsType.NumField(); i++ {
f := handler.RequestParamsType.Field(i)
name := f.Tag.Get("json")
if name == "" {
name = f.Name
}
docMethod.Parameters[f.Name] = 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.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
}
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
}