Newer
Older
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/logs"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/string_utils"
"go/doc"
"go/parser"
"go/token"
"os"
"path/filepath"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/handler_utils"
Paths map[string]DocPath `json:"paths"`
Components DocSchemas `json:"components"`
type DocSchemas struct {
Schemas map[string]interface{} `json:"schemas"`
}
Tags []string `json:"tags"`
RequestBody *DocRequestBody `json:"requestBody,omitempty"`
Parameters []DocParam `json:"parameters,omitempty"`
Responses *map[string]DocResponseValue `json:"responses,omitempty"`
Description string `json:"description"`
Required bool `json:"required"`
Content map[string]interface{} `json:"content"`
Name string `json:"name"`
In string `json:"in"`
Description string `json:"description"`
type DocSchema struct {
Ref string `json:"$ref"`
}
type DocResponseValue struct {
Description string `json:"description"`
Content *map[string]interface{} `json:"content,omitempty"`
Type *string `json:"type,omitempty"`
Items DocSchema `json:"items"`
func GetDocs(endpointHandlers map[string]map[string]interface{}, corePath string) (Docs, error) {
Paths: map[string]DocPath{},
Components: DocSchemas{
Schemas: map[string]interface{}{},
},
}
// Add default error
var validationError error
if endpointHandlers, validationError = handler_utils.ValidateAPIEndpoints(endpointHandlers); validationError != nil {
return Docs{}, validationError
}
functionDocs := GetStructDocs(corePath)
if handler, ok := methodHandler.(handler_utils.Handler); !ok {
functionName := GetFunctionName(handler.FuncValue.Interface())
docMethod.Description = functionDocs[functionName]
docMethod.Summary = functionNameToSummary(functionName, method)
docParameters, err := FillParameters(&docs, handler)
bodyTypeString, requestBody := GetRequestBody(handler, functionDocs)
docMethod.RequestBody = &requestBody
if handler.RequestBodyType.Kind() == reflect.Struct && handler.RequestBodyType.NumField() > 0 {
err := FillStructSchema(&docs, handler.RequestBodyType.Field(0).Type, bodyTypeString)
if err != nil {
return Docs{}, err
}
}
}
response, responseBodyTypeString := GetResponse(handler)
docMethod.Responses = &response
if handler.ResponseType.Kind() == reflect.Struct && handler.ResponseType.NumField() > 0 {
err := FillStructSchema(&docs, handler.ResponseType, responseBodyTypeString)
if err != nil {
return Docs{}, err
}
}
}
}
docPath[strings.ToLower(method)] = docMethod
}
docs.Paths[path] = docPath
}
func functionNameToSummary(name string, method string) string {
name = string_utils.ReplaceCaseInsensitive(name, "GET", "")
name = string_utils.ReplaceCaseInsensitive(name, "POST", "")
name = string_utils.ReplaceCaseInsensitive(name, "PATCH", "")
name = string_utils.ReplaceCaseInsensitive(name, "DELETE", "")
var cleanMethod string
if method == "GET" {
cleanMethod = "Fetch"
} else if method == "POST" {
cleanMethod = "Create"
} else if method == "DELETE" {
cleanMethod = "Delete"
} else if method == "PATCH" {
cleanMethod = "Update"
}
summary := string_utils.PascalCaseToSentence(name)
summary = cleanMethod + " " + strings.ToLower(summary)
return summary
}
func addDefaultSchemas(docs Docs) {
docs.Components.Schemas["error"] = map[string]string{
"type": "string",
"format": "string",
}
docs.Components.Schemas["string"] = map[string]string{
"type": "string",
"format": "string",
}
}
func FillParameters(docs *Docs, handler handler_utils.Handler) ([]DocParam, error) {
docParameters := []DocParam{}
for i := 0; i < handler.RequestParamsType.NumField(); i++ {
structField := handler.RequestParamsType.Field(i)
shouldAddDoc := structField.Tag.Get("doc")
if shouldAddDoc == "-" {
continue
}
schema, err := StructSchema(docs, structField.Type)
name := StructFieldName(structField)
if name == "tableName" {
continue
}
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
In: "query",
Description: structField.Tag.Get("doc"),
Schema: schema,
}
if structField.Tag.Get("doc_required") == "true" {
parameter.Required = true
}
docParameters = append(docParameters, parameter)
}
return docParameters, nil
}
func GetRequestBody(handler handler_utils.Handler, functionDocs map[string]string) (string, DocRequestBody) {
body := reflect.New(handler.RequestBodyType).Interface()
bodyTypeString := getType(body)
requestBody := DocRequestBody{
Description: functionDocs[bodyTypeString],
Required: true,
Content: map[string]interface{}{
"application/json": map[string]interface{}{
"schema": DocSchema{Ref: "#/components/schemas/" + bodyTypeString},
},
},
}
return bodyTypeString, requestBody
}
func GetResponse(handler handler_utils.Handler) (map[string]DocResponseValue, string) {
responses := map[string]DocResponseValue{}
responseBody := reflect.New(handler.ResponseType).Interface()
responseBodyTypeString := getType(responseBody)
response := DocResponseValue{}
if responseBodyTypeString != "" {
response.Content = &map[string]interface{}{
"application/json": map[string]interface{}{
"schema": DocSchema{Ref: "#/components/schemas/" + responseBodyTypeString},
},
}
}
responses["200"] = response
return responses, responseBodyTypeString
}
func StructFieldName(structField reflect.StructField) string {
name := structField.Tag.Get("json")
if name == "" {
name = structField.Name
}
name = strings.Replace(name, ",omitempty", "", -1)
return name
}
func getType(myvar interface{}) string {
if t := reflect.TypeOf(myvar); t.Kind() == reflect.Ptr {
return t.Elem().Name()
} else {
return t.Name()
}
}
func StructSchema(docs *Docs, t reflect.Type) (interface{}, error) {
if t == nil {
return nil, nil
}
schema := map[string]interface{}{}
description := ""
if t.Kind() == reflect.Ptr {
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:
schema["type"] = "integer"
case reflect.Float64, reflect.Float32:
schema["type"] = "number"
if t.String() == "time.Time" {
schema["type"] = "string"
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 == "-" || fieldName == "tableName" {
}
fieldName = strings.Replace(fieldName, ",omitempty", "", -1)
var err error
fieldDesc := f.Tag.Get("doc")
fieldDesc = description + "." + fieldName
}
properties[fieldName], err = StructSchema(docs, f.Type)
if err != nil {
return nil, errors.Wrapf(err, "failed to document %v.%s", t, fieldName)
}
}
}
schema["properties"] = properties
case reflect.Map:
case reflect.Slice:
schema["type"] = "array"
element := t.Elem()
if element.Kind() == reflect.Struct || element.Kind() == reflect.Ptr {
if elementName == "" || elementName == "error" {
return schema, nil
}
schema["items"] = map[string]string{"$ref": "#/components/schemas/" + elementName}
// Check if object is already in the struct schema
_, containsValue := docs.Components.Schemas[elementName]
if !containsValue {
err := FillStructSchema(docs, element, elementName)
if err != nil {
return nil, err
}
}
} else {
items, err := StructSchema(docs, element)
if err != nil {
return nil, errors.Wrapf(err, "failed to document")
}
schema["items"] = items
default:
return nil, errors.Errorf("cannot generate schema for %v kind=%v", t, t.Kind())
}
return schema, nil
}
func FillStructSchema(docs *Docs, element reflect.Type, elementName string) error {
schema, err := StructSchema(docs, element)
if err != nil {
return errors.Wrapf(err, "failed to fill struct schema for %v", elementName)
}
docs.Components.Schemas[elementName] = schema
return nil
}
func GetStructDocs(corePath string) map[string]string {
docs := map[string]string{}
walkFunction := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
return nil
}
fset := token.NewFileSet()
d, err := parser.ParseDir(fset, path, nil, parser.ParseComments)
if err != nil {
fmt.Println(err)
return nil
}
p := doc.New(f, "./", 2)
for _, objectTypes := range p.Types {
doc := strings.ReplaceAll(objectTypes.Doc, objectTypes.Name, "")
docs[objectTypes.Name] = doc
}
for _, function := range p.Funcs {
doc := strings.ReplaceAll(function.Doc, function.Name, "")
docs[function.Name] = doc
}
for _, n := range p.Notes {
fmt.Println("body", n[0].Body)
}
}
return nil
}
err := filepath.Walk(corePath, walkFunction)
if err != nil {
logs.ErrorWithMsg("Failed to upload files to s3", err)
}
return docs
}
func GetFunctionName(temp interface{}) string {
strs := strings.Split((runtime.FuncForPC(reflect.ValueOf(temp).Pointer()).Name()), ".")
return strs[len(strs)-1]
}