package got import ( "encoding/json" "fmt" "os" "strings" "sync" "time" . "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common" G "github.com/dave/jennifer/jen" "github.com/davecgh/go-spew/spew" ) type Middleware struct { Id string `json:"id"` Type string `json:"type"` Fn func(ctx *MiddlewareContext) error `json:"-"` } type MiddlewareContext struct { Project *Project Method *Method Statement *G.Statement Middleware *Middleware File *G.File } var ( Middlewares = map[string]*Middleware{ "post": GenCreateStmts, "put": GenUpdateStmts, "patch": GenPatchStmts, "delete": GenDeleteStmts, "get_one": GenGetStmtsOne, "filter": GenGetFilter, "get_list": GenGetStmtsList, "undelete": GenUndeleteStmts, "implement": GenImplement, } ResourceWG = sync.WaitGroup{} ) func InitFile(file *G.File, resource *Resource, p *Project) { now := time.Now().UnixNano() // file.ImportAlias( // file.ImportName( // fmt.Sprintf("%s/%s/models", p.Custom["go.package.repository"].(string), p.Package), // "models", // ) if _, defined := p.Custom["go.package.repository"]; !defined { panic("go.package.repository not defined in project.json") } file.Var().Defs( G.Id(fmt.Sprintf("_%s_bson_%d_ *", resource.Entity, now)).Qual(BSON, "M"), G.Id(fmt.Sprintf("_%s_error_%d_ *", resource.Entity, now)).Qual(API_ERROR, "Error"), G.Id(fmt.Sprintf("_%s_sync_%d_ *", resource.Entity, now)).Qual("sync", "WaitGroup"), G.Id(fmt.Sprintf("_%s_primitive_%d_ *", resource.Entity, now)).Qual("go.mongodb.org/mongo-driver/bson/primitive", "ObjectID"), G.Id(fmt.Sprintf("_%s_models_%d_ *", resource.Entity, now)).Qual( fmt.Sprintf("%s/build/%s/models", p.Custom["go.package.repository"].(string), p.Package), "Entity", ), G.Id(fmt.Sprintf("_%s_actions_%d_ *", resource.Entity, now)).Qual( fmt.Sprintf("%s/build/%s/actions", p.Custom["go.package.repository"].(string), p.Package), "Action", ), ) file.Func().Id("init").Params().BlockFunc(func(s *G.Group) { for _, v := range resource.Formats { s.Add(G.Id("FormatSelection").Index(G.Lit(resource.ID + "." + v.Id)).Op("=").Lit(v.Fields)) } }) } func CreateDummy(p *Project, resource *Resource, method *Method) error { // Verifica se existe um arquivo na pasta de include. // Caso o arquivo não exista um novo arquivo é criado. // outputfile := fmt.Sprintf("%s/include/go/api_%s_%s.go", CurrentDirectory, strings.ToLower(resource.ID), strings.ToLower(method.ID)) outputfile := fmt.Sprintf( "../project/include/go/api_%s_%s_gen.go", strings.ToLower(resource.ID), strings.ToLower(method.ID), ) if _, err := os.Stat(outputfile); os.IsNotExist(err) { file := G.NewFile(p.Package) file.Comment(method.Description) if o, err := json.MarshalIndent(method, "", " "); err == nil { file.Comment(string(o)) } file.Func().Params( G.Id("t").Op("*").Id(ResourceStructId(resource)), ).Id(method.ID).Params( G.Id("ctx").Qual(IRIS_CTX, "Context"), ).Params( G.Id("resp").Interface(), G.Id("err").Op("*").Qual(API_ERROR, "Error"), ).Block(G.Return()) // if err = Write(path, file); err != nil { // return err // } return Write(outputfile, file) } return nil } func GenResources(p *Project) (err error) { var ( file *G.File path string ) if err = generateActionCommonFile(p); err != nil { return } for _, resource := range p.Resources { // Registra os campos retornados nos formatos do metodo list // FormatMap = map[string]string{} // Cria um novo arquivo para cada recurso file = G.NewFile(p.Package) InitFile(file, resource, p) // Inseri os comentarios de cada recurso no inicio do arquivo file.Comment(resource.Description).Line() // Define um tipo para registrar os metodos da api file.Type().Id(ResourceStructId(resource)).Struct().Line() for _, method := range resource.Methods { switch method.Template { case "", "implement": CreateDummy(p, resource, method) default: GenMethod(p, file, resource, method) } } // file.Var().DefsFunc(func(s *G.Group) { // values := G.Dict{} // for k, v := range FormatMap { // values[G.Lit(k)] = G.Lit(v) // } // s.Add( // G.Id("Format" + resource.Entity).Op("=").Map(G.String()).String().Values(values), // ) // }) file.Func().Params( G.Id("t").Op("*").Id(ResourceStructId((resource))), ).Id("Fields").Params( G.Id("filter").Op("*").Qual(API_URL, "Filter"), ).Params( G.Id("err").Op("*").Qual(API_ERROR, "Error"), ).Block( G.Id(` if filter.Fields != nil { return } if filter.Format == "full" { filter.Fields = nil return } if fields, has := FormatSelection[`).Lit(fmt.Sprintf("%s.", resource.ID)).Id(`+filter.Format]; has { filter.Fields = api.MgoFields(fields) } else { err = errs.InvalidArgument().Details(&errs.Detail{ Message: `).Qual("fmt", "Sprintf").Id(`("Invalid value for projection '%s'",filter.Format), }) } return `), // G.If( // // G.List(G.Id("fields"), G.Id("has")).Op(":=").Id("FormatSelection"+resource.Entity).Index(G.Id("filter").Dot("Format")), // G.List(G.Id("fields"), G.Id("has")).Op(":=").Id("FormatSelection").Index( // G.Lit(resource.ID+".").Op("+").Id("filter").Dot("Format"), // ), // G.Id("has"), // ).Block( // G.Id("filter").Dot("Fields").Op("=").Id("api").Dot("MgoFields").Call(G.Id("fields")), // ).Else().Block( // G.Id("err").Op("=").Qual(API_URL, "Error").Call( // G.Qual(API_URL, "ERR_INVALID_PARAM_VALUE"), // G.Lit("Invalid value for %s"), // ), // // .Dot("Add").Call( // // G.Op("&").Qual(API_URL, "ErrDescription").Values(G.Dict{ // // G.Id("Message"): G.Lit(""), // // }), // // ), // ), ) // Fim do loop de methods // GenCall(f, resource) path = fmt.Sprintf("%s/%s/api_%s_gen.go", p.OutPath, p.Package, strings.ToLower(resource.ID)) if err = Write(path, file); err != nil { return err } } // Fim do loop de recursos ResourceWG.Add(2) go GenIndexApi(p) ResourceWG.Wait() return nil } func GenQueries(p *Project, resourcesIdMap map[string]bool) { defer func() { ResourceWG.Done() }() if len(p.Queries.Queries) == 0 { return } file := G.NewFile(p.Package) queries := func(s *G.Statement) { var ( query string found bool ) for key, _ := range resourcesIdMap { if query, found = p.Queries.Queries[fmt.Sprintf("go.%s", key)]; !found { query = "{}" } s.Add(G.Qual(API_URL, "RegisterQuery(").Lit(key).Id(",").Lit(query).Id(")").Line()) } } file.Id(` func init() {`).Do(queries).Id(`}`) if err := Write(fmt.Sprintf("%s/%s/%s_gen.go", p.OutPath, p.Package, "queries"), file); err != nil { panic(err) } } func GenIndexApi(p *Project) error { defer func() { ResourceWG.Done() }() var ( stmt *G.Statement params string statments G.Statement idString string middlewares []string Index = G.NewFile(p.Package) RequestParams = G.Id(`RequestParams := `).Qual(CODE_GEN_V2_COMMON, "RequestParams") jwt = G.Id(`JWT := `).Qual(CODE_GEN_V2_AUTHORIZATION, "Handler") addJwt = false queryIndexsMap = map[string]bool{} callActionId = "apply" ) statments = append(statments, G.Id(fmt.Sprintf("%s := ", callActionId)).Qual(API_URL, "CallAction")) statments = append(statments, RequestParams) // statments = append(statments) // Inicializa o mapa de filtros da api Index.Id(` var ( filtersApiReference = map[string]*`).Qual(CODE_GEN_V2_COMMON, "ApiFilter").Id(`{} FormatSelection = map[string]string{} ) func init(){ var ( entity *common.ApiFilter files []`).Qual("os", "FileInfo").Id(` path = "./filters" err error ) files, _ = `).Qual(CODE_GEN_V2_COMMON, "GetFiles").Id(`(path) for _, file := range files { if !file.IsDir() { entity = &common.ApiFilter{} if err = common.ParseJson(`).Qual("path/filepath", "Join").Id(`(path, file.Name()), entity); err != nil { panic(err) } filtersApiReference[entity.Id] = entity } } } func executeAction(ctx context.Context, actions ...func(context.Context) (interface{}, *`).Qual(API_ERROR, "Error").Id(`)) (resp interface{},err *errs.Error){ for _, action := range actions { if resp, err = action(ctx); err != nil || resp != nil { return } } return } `) for rindex, resource := range p.Resources { stmt = G.Line().Id(resource.ID).Op(":=").Id(ResourceStructId(resource)).Values().Line() for k, method := range resource.Methods { // fmt.Println("----------------------------") args := []G.Code{ G.Lit(method.HttpMethod), G.Lit(p.GetPath(method)), } middlewares = []string{} if len(method.ParametersString) > 0 { params = strings.Join(method.ParametersString, ",") // fmt.Println(fmt.Sprintf(`%s|RequestParams("%s", UserRequestParams)`, API_URL, params)) stmt.Add(G.Line().Comment( "Lista de parametros a serem validados durante a requisição", ).Line().Id(fmt.Sprintf(`args%d%d := "%s"`, rindex, k, params)).Line()) middlewares = append(middlewares, fmt.Sprintf(`RequestParams(args%d%d, UserRequestParams)`, rindex, k)) } middlewares = append(middlewares, p.Middlewares...) middlewares = append(middlewares, method.Middlewares...) data := map[string]interface{}{ "ResourceId": resource.ID, "MethodId": method.ID, } idString = fmt.Sprintf("%s:%s", resource.ID, method.ID) queryIndexsMap[idString] = true for _, m := range middlewares { if strings.Contains(m, "JWT") && !addJwt { addJwt = true RequestParams.Line().Add(jwt) } m = ResolveParams(m, data) parts := strings.Split(m, "|") // Quando parts possui tamanho maior que // significa que foi especificado um middleware de outro pacote. if len(parts) > 1 { args = append(args, G.Line().Id(callActionId).Call( G.Id(parts[1]), G.Qual(parts[0], parts[1]), )) } else { args = append(args, G.Line().Id(callActionId).Call( G.Lit(m), G.Id(m), )) } } args = append(args, G.Line().Id(callActionId).Call( G.Lit(fmt.Sprintf("%s.%s", resource.ID, method.ID)), G.Id(resource.ID).Dot(method.ID), )) if len(method.Postresponse) > 0 { args = append(args, G.Line().Id(method.Postresponse[0])) } stmt.Add(G.Line().Comment(method.Description).Line().Id("app").Dot("Handle").Call(args...).Line()) } statments = append(statments, stmt) } // Cria a funcao que trata os filtros // statments = append(statments, G.Line().Comment("Filter request").Line().Id("app").Dot("Handle").Call( // G.Lit("GET"), // G.Lit(p.BasePath+"/filters/{id:string}"), // // G.Func().Params(G.Id("ctx").Qual(IRIS_CTX, "Context")).Block(), // G.Id(fmt.Sprintf(`FilterHandle("../api/%s/filters")`, p.Package)), // )) // Cria a funcao que trata os metodos options statments = append(statments, G.Line().Comment("Options request").Line().Id("app").Dot("Options").Call( G.Lit("/{url:path}"), G.Func().Params(G.Id("ctx").Qual(IRIS_CTX, "Context")).Block( G.Id(`ctx.ResponseWriter().Header().Set("Access-Control-Allow-Origin", ctx.GetHeader("Origin"))`), ), )) // Cria a funcao que registra as urls da api no arquivo api_index_gen.go Index.Func().Id("Register").Params( G.Id("app").Op("*").Qual(IRIS, "Application"), ).Block( statments..., ).Line() go GenQueries(p, queryIndexsMap) return Write(fmt.Sprintf("%s/%s/api_index_gen.go", p.OutPath, p.Package), Index) } func GenMethod(p *Project, f *G.File, r *Resource, method *Method) error { f.Comment(method.Description) if o, err := json.MarshalIndent(method, "", " "); err == nil { f.Comment(string(o)) } stmt := f.Func().Params( // G.Id("t").Op("*").Id(strings.Title(r.ID)), G.Id("t").Op("*").Id(ResourceStructId(r)), ).Id(method.ID).Params( G.Id("ctx").Qual(IRIS_CTX, "Context"), // G.Id("resp").Op("*").Qual(API_URL, "ApiResponse"), ).Params( G.Id("resp").Interface(), G.Id("err").Op("*").Qual(API_ERROR, "Error"), ) generateActionsFiles(p, f, r, method) if middle, found := Middlewares[method.Template]; found { ctx := &MiddlewareContext{ Project: p, Method: method, Middleware: middle, Statement: stmt, File: f, } return middle.Fn(ctx) } return fmt.Errorf("Method '%s' template not defined!", method.ID) } func generateActionCommonFile(p *Project) (err error) { path := fmt.Sprintf( "%s/include/go/actions/index_gen.go", CurrentDirectory, ) if _, fileErr := os.Stat(path); os.IsNotExist(fileErr) { file := G.NewFile("actions") file.Id(` type Action struct { ID string } `).Line() err = Write(path, file) } return } func generateActionsFiles(p *Project, f *G.File, r *Resource, method *Method) { actions := []Action{} if method.Preconditions != nil { actions = append(actions, method.Preconditions...) } if method.BeforeResponse != nil { actions = append(actions, method.BeforeResponse...) } for _, action := range actions { path := fmt.Sprintf( "%s/include/go/actions/%s_gen.go", CurrentDirectory, action.ID, // method.Entity, // method.ID, // hookId, ) if _, fileErr := os.Stat(path); os.IsNotExist(fileErr) { // methodId := fmt.Sprintf("%s%s%s", strings.Title(hookId), method.Entity, strings.Title(method.ID)) file := G.NewFile("actions") context := map[string]interface{}{ "imports": map[string]string{ "errs": "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs", "context": "github.com/kataras/iris/v12/context", }, "function": strings.Title(action.ID), "hasContext": action.Context != nil, } spew.Dump(action.ID) spew.Dump(action.Context) out, _ := TemplateToString(hookStmtsTmpl, context) file.Id(out).Line() Write(path, file) } } // for hookId, _ := range method.Hooks { // // methodId := fmt.Sprintf("%s%s%s", strings.Title(hookId), method.Entity, strings.Title(method.ID)) // path := fmt.Sprintf( // "%s/include/go/actions/%s_%s_%s_gen.go", // CurrentDirectory, // method.Entity, // method.ID, // hookId, // ) // if _, fileErr := os.Stat(path); os.IsNotExist(fileErr) { // file := G.NewFile(p.Package) // context := map[string]interface{}{ // "imports": map[string]string{ // // "api": "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api", // "errs": "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs", // "context": "github.com/kataras/iris/v12/context", // }, // } // out, _ := TemplateToString(hookStmtsTmpl, context) // file.Id(out).Line() // Write(path, file) // } // } } func GenFromGenericModel(p *Project, entity *EntityInfo) { var ( posfix string propertie *G.Statement tproperties G.Statement // file = G.NewFile(p.Package) file = G.NewFile("models") cproperties = map[string]*G.Statement{} properties = G.Statement{} values = G.Dict{} model = p.GetSchema(entity.Name) entityName = entity.NewName // filename = "model_" + strings.ToLower(entityName) filename = "models/" + CamelToUnder(entityName) propName string ) for _, meta := range model.Properties { propName = UpFirst(meta.ID) propertie = G.Id(propName) meta.FillTags(p, propName) posfix = "" // Registra a relaao entre as entidades if meta.Relation { posfix = "Ref" SR.Add(&Relation{ Source: meta.GetType(), Target: model.ID, Attr: strings.Replace(meta.Tags["bson"], ",omitempty", "", 1), DB: model.DB, Collection: model.Collection, IsArray: meta.Array, }) } if meta.Array { propertie.Index() } propertie.Id(entity.TranslateType(meta.Type) + posfix) // propertie.Id(meta.Type + posfix) // Adiciona as tags caso sejam definidas if meta.Tags != nil { propertie.Tag(meta.Tags) // if name, ok := meta.Tags["json"]; ok { // } } // Adiciona a crescricao como comentario if meta.Description != "" { propertie.Comment(meta.Description) } cproperties[meta.ID] = propertie if meta.ID == "ID" { values[G.Id("ID")] = G.Qual(BSON_PRIMITIVE, "NewObjectID").Call() } // Verifica se possui valor padrão if meta.Default != nil { values[G.Id(meta.ID)] = G.Lit(meta.Default) } properties = append(properties, propertie) } if model.Representations != nil { for posfix, rep := range model.Representations { tproperties = G.Statement{} for _, attr := range rep { tproperties = append(tproperties, cproperties[attr]) } file.Comment( "Representação " + posfix, ).Line().Type().Id( model.ID + posfix, ).Struct(tproperties...) } } // Cria a entidade normal file.Line().Comment(model.Description).Line().Comment("Representação Completa") file.Type().Id(entityName).Struct(properties...) file.Comment(fmt.Sprintf("Cria uma instancia de %s.", entityName)) // Cria a função de instanciar um novo elemento com os valores padrão determinados file.Func().Id("New" + entityName).Params().Op("*").Id(entityName).Block( G.Return(G.Op("&").Id(entityName).Values(values)), ) // Salva o arquivo da entidade // if err := f.Save(fmt.Sprintf("%s/%s/%s_gen.go", p.OutPath, p.Package, filename)); err != nil { // fmt.Printf("%s/%s/%s_gen.go", p.OutPath, p.Package, filename) if err := Write(fmt.Sprintf("%s/%s/%s_gen.go", p.OutPath, p.Package, filename), file); err != nil { panic(err) } } func Write(path string, file *G.File) error { // fmt.Println(fmt.Sprintf("Write -> %#v", path)) return FilePutContents(path, fmt.Sprintf("%#v", file), 0777) } func ResourceStructId(resource *Resource) string { return strings.Title(resource.ID) + "Resource" }