package api_documentation import ( "fmt" "reflect" "strings" "gitlab.com/uafrica/go-utils/handler_utils" ) type NoParams struct{} type DocPath map[string]DocMethodInfo type Docs struct { Paths map[string]DocPath `json:"paths"` Definitions map[string]interface{} } 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{} for method, methodHandler := range methods { docMethod := DocMethodInfo{} if handler, ok := methodHandler.(handler_utils.Handler); !ok { docMethod.Summary = "Not available" } else { //purpose docMethod.Summary = "Not available - see request and response structs" docMethod.Tags = []string{path} //describe parameters docMethod.Parameters = []DocParam{} for i := 0; i < handler.RequestParamsType.NumField(); i++ { f := handler.RequestParamsType.Field(i) name := f.Tag.Get("json") if name == "" { name = f.Name } parameter := DocParam{ Name: name, Type: fmt.Sprintf("%v", f.Type), Description: f.Tag.Get("doc"), } docMethod.Parameters = append(docMethod.Parameters, parameter) } // Request if handler.RequestBodyType != nil { body := reflect.New(handler.RequestBodyType).Interface() bodyTypeString := getType(body) parameter := DocParam{ Name: "body", In: "body", Schema: DocSchema{Ref: "#/definitions/"+bodyTypeString}, } docMethod.Parameters = append(docMethod.Parameters, parameter) docs.Definitions[bodyTypeString] = "" } // Response if handler.ResponseType != nil { responses := map[string]DocResponseValue{} responseBody := reflect.New(handler.RequestBodyType).Interface() responseBodyTypeString := getType(responseBody) responses["200"] = DocResponseValue{ Description: "successful operation", Schema: &DocSchemaResponse{ Items: DocSchema{Ref: "#/definitions/"+responseBodyTypeString}, }, } docMethod.Responses = responses docs.Definitions[responseBodyTypeString] = "" } //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 getType(myvar interface{}) string { if t := reflect.TypeOf(myvar); t.Kind() == reflect.Ptr { return t.Elem().Name() } else { return t.Name() } } // // 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 // }