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 }