func parseObject(d *ast.Object, k string, m *swagger.Schema, realTypes *[]string, astPkgs map[string]*ast.Package) { ts, ok := d.Decl.(*ast.TypeSpec) if !ok { ColorLog("Unknown type without TypeSec: %v\n", d) os.Exit(1) } // TODO support other types, such as `ArrayType`, `MapType`, `InterfaceType` etc... st, ok := ts.Type.(*ast.StructType) if !ok { return } m.Title = k if st.Fields.List != nil { m.Properties = make(map[string]swagger.Propertie) for _, field := range st.Fields.List { isSlice, realType, sType := typeAnalyser(field) *realTypes = append(*realTypes, realType) mp := swagger.Propertie{} if isSlice { mp.Type = "array" if isBasicType(realType) { typeFormat := strings.Split(sType, ":") mp.Items = &swagger.Propertie{ Type: typeFormat[0], Format: typeFormat[1], } } else { mp.Items = &swagger.Propertie{ Ref: "#/definitions/" + realType, } } } else { if sType == "object" { mp.Ref = "#/definitions/" + realType } else if isBasicType(realType) { typeFormat := strings.Split(sType, ":") mp.Type = typeFormat[0] mp.Format = typeFormat[1] } else if realType == "map" { typeFormat := strings.Split(sType, ":") mp.AdditionalProperties = &swagger.Propertie{ Type: typeFormat[0], Format: typeFormat[1], } } } if field.Names != nil { // set property name as field name var name = field.Names[0].Name // if no tag skip tag processing if field.Tag == nil { m.Properties[name] = mp continue } var tagValues []string stag := reflect.StructTag(strings.Trim(field.Tag.Value, "`")) tag := stag.Get("json") if tag != "" { tagValues = strings.Split(tag, ",") } // dont add property if json tag first value is "-" if len(tagValues) == 0 || tagValues[0] != "-" { // set property name to the left most json tag value only if is not omitempty if len(tagValues) > 0 && tagValues[0] != "omitempty" { name = tagValues[0] } if thrifttag := stag.Get("thrift"); thrifttag != "" { ts := strings.Split(thrifttag, ",") if ts[0] != "" { name = ts[0] } } if required := stag.Get("required"); required != "" { m.Required = append(m.Required, name) } if desc := stag.Get("description"); desc != "" { mp.Description = desc } m.Properties[name] = mp } if ignore := stag.Get("ignore"); ignore != "" { continue } } else { for _, pkg := range astPkgs { for _, fl := range pkg.Files { for nameOfObj, obj := range fl.Scope.Objects { if obj.Name == fmt.Sprint(field.Type) { parseObject(obj, nameOfObj, m, realTypes, astPkgs) } } } } } } } }
// parse the func comments func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpath string) error { var routerPath string var HTTPMethod string opts := swagger.Operation{ Responses: make(map[string]swagger.Response), } if comments != nil && comments.List != nil { for _, c := range comments.List { t := strings.TrimSpace(strings.TrimLeft(c.Text, "//")) if strings.HasPrefix(t, "@router") { elements := strings.TrimSpace(t[len("@router"):]) e1 := strings.SplitN(elements, " ", 2) if len(e1) < 1 { return errors.New("you should has router infomation") } routerPath = e1[0] if len(e1) == 2 && e1[1] != "" { e1 = strings.SplitN(e1[1], " ", 2) HTTPMethod = strings.ToUpper(strings.Trim(e1[0], "[]")) } else { HTTPMethod = "GET" } } else if strings.HasPrefix(t, "@Title") { opts.OperationID = controllerName + "." + strings.TrimSpace(t[len("@Title"):]) } else if strings.HasPrefix(t, "@Description") { opts.Description = strings.TrimSpace(t[len("@Description"):]) } else if strings.HasPrefix(t, "@Summary") { opts.Summary = strings.TrimSpace(t[len("@Summary"):]) } else if strings.HasPrefix(t, "@Success") { ss := strings.TrimSpace(t[len("@Success"):]) rs := swagger.Response{} respCode, pos := peekNextSplitString(ss) ss = strings.TrimSpace(ss[pos:]) respType, pos := peekNextSplitString(ss) if respType == "{object}" || respType == "{array}" { isArray := respType == "{array}" ss = strings.TrimSpace(ss[pos:]) schemaName, pos := peekNextSplitString(ss) if schemaName == "" { ColorLog("[ERRO][%s.%s] Schema must follow {object} or {array}\n", controllerName, funcName) os.Exit(-1) } if strings.HasPrefix(schemaName, "[]") { schemaName = schemaName[2:] isArray = true } schema := swagger.Schema{} if sType, ok := basicTypes[schemaName]; ok { typeFormat := strings.Split(sType, ":") schema.Type = typeFormat[0] schema.Format = typeFormat[1] } else { cmpath, m, mod, realTypes := getModel(schemaName) schema.Ref = "#/definitions/" + m if _, ok := modelsList[pkgpath+controllerName]; !ok { modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0) } modelsList[pkgpath+controllerName][schemaName] = mod appendModels(cmpath, pkgpath, controllerName, realTypes) } if isArray { rs.Schema = &swagger.Schema{ Type: "array", Items: &schema, } } else { rs.Schema = &schema } rs.Description = strings.TrimSpace(ss[pos:]) } else { rs.Description = strings.TrimSpace(ss) } opts.Responses[respCode] = rs } else if strings.HasPrefix(t, "@Param") { para := swagger.Parameter{} p := getparams(strings.TrimSpace(t[len("@Param "):])) if len(p) < 4 { panic(controllerName + "_" + funcName + "'s comments @Param at least should has 4 params") } para.Name = p[0] switch p[1] { case "query": fallthrough case "header": fallthrough case "path": fallthrough case "formData": fallthrough case "body": break default: ColorLog("[WARN][%s.%s] Unknow param location: %s, Possible values are `query`, `header`, `path`, `formData` or `body`.\n", controllerName, funcName, p[1]) } para.In = p[1] pp := strings.Split(p[2], ".") typ := pp[len(pp)-1] if len(pp) >= 2 { cmpath, m, mod, realTypes := getModel(p[2]) para.Schema = &swagger.Schema{ Ref: "#/definitions/" + m, } if _, ok := modelsList[pkgpath+controllerName]; !ok { modelsList[pkgpath+controllerName] = make(map[string]swagger.Schema, 0) } modelsList[pkgpath+controllerName][typ] = mod appendModels(cmpath, pkgpath, controllerName, realTypes) } else { isArray := false paraType := "" paraFormat := "" if strings.HasPrefix(typ, "[]") { typ = typ[2:] isArray = true } if typ == "string" || typ == "number" || typ == "integer" || typ == "boolean" || typ == "array" || typ == "file" { paraType = typ } else if sType, ok := basicTypes[typ]; ok { typeFormat := strings.Split(sType, ":") paraType = typeFormat[0] paraFormat = typeFormat[1] } else { ColorLog("[WARN][%s.%s] Unknow param type: %s\n", controllerName, funcName, typ) } if isArray { para.Type = "array" para.Items = &swagger.ParameterItems{ Type: paraType, Format: paraFormat, } } else { para.Type = paraType para.Format = paraFormat } } if len(p) > 4 { para.Required, _ = strconv.ParseBool(p[3]) para.Description = strings.Trim(p[4], `" `) } else { para.Description = strings.Trim(p[3], `" `) } opts.Parameters = append(opts.Parameters, para) } else if strings.HasPrefix(t, "@Failure") { rs := swagger.Response{} st := strings.TrimSpace(t[len("@Failure"):]) var cd []rune var start bool for i, s := range st { if unicode.IsSpace(s) { if start { rs.Description = strings.TrimSpace(st[i+1:]) break } else { continue } } start = true cd = append(cd, s) } opts.Responses[string(cd)] = rs } else if strings.HasPrefix(t, "@Deprecated") { opts.Deprecated, _ = strconv.ParseBool(strings.TrimSpace(t[len("@Deprecated"):])) } else if strings.HasPrefix(t, "@Accept") { accepts := strings.Split(strings.TrimSpace(strings.TrimSpace(t[len("@Accept"):])), ",") for _, a := range accepts { switch a { case "json": opts.Consumes = append(opts.Consumes, ajson) opts.Produces = append(opts.Produces, ajson) case "xml": opts.Consumes = append(opts.Consumes, axml) opts.Produces = append(opts.Produces, axml) case "plain": opts.Consumes = append(opts.Consumes, aplain) opts.Produces = append(opts.Produces, aplain) case "html": opts.Consumes = append(opts.Consumes, ahtml) opts.Produces = append(opts.Produces, ahtml) } } } } } if routerPath != "" { var item *swagger.Item if itemList, ok := controllerList[pkgpath+controllerName]; ok { if it, ok := itemList[routerPath]; !ok { item = &swagger.Item{} } else { item = it } } else { controllerList[pkgpath+controllerName] = make(map[string]*swagger.Item) item = &swagger.Item{} } switch HTTPMethod { case "GET": item.Get = &opts case "POST": item.Post = &opts case "PUT": item.Put = &opts case "PATCH": item.Patch = &opts case "DELETE": item.Delete = &opts case "HEAD": item.Head = &opts case "OPTIONS": item.Options = &opts } controllerList[pkgpath+controllerName][routerPath] = item } return nil }