package got import ( "fmt" . "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common" // . "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/gen" G "github.com/dave/jennifer/jen" "regexp" "strings" "time" // "github.com/davecgh/go-spew/spew" ) var ( filled = map[string]*Entity{} numberRegex = regexp.MustCompile("(int|float).*") // bool // string // int int8 int16 int32 int64 // uint uint8 uint16 uint32 uint64 uintptr // byte // alias for uint8 // rune // alias for int32 // // represents a Unicode code point // float32 float64 // complex64 complex128 primitiveRegex = regexp.MustCompile(`^(bool|string|u?int\d{0,2}|byte|rune|float\d{0,2}|interface|complex\d{2,3})$`) ) func hasReplaceMethod(typ string) bool{ typ = strings.Replace(typ, "*","", 1) return !primitiveRegex.MatchString(typ) } func replaceAccessType(typ string) (accessType string) { typ = strings.Replace(typ, "*","", 1) if strings.Contains(typ, "map"){ accessType = "map" } else { accessType = "id" } return } func GenSchemas(p *Project) error { var ( // requiredOnCreate bool hasAddOrRemove bool filename string inputName string typ string extName string f *G.File propertie *G.Statement extend *G.Statement base = "models/" posfix = "" properties G.Statement // values G.Dict cproperties map[string]*G.Statement addUpdate map[string]string replaceUpdate map[string]map[string]string removeUpdate map[string]map[string]string entityName string entityPatchName string propName string entityInfo *EntityInfo inputProperties G.Statement patchProperties G.Statement referenceProperties G.Statement entities, err = Schemas(p) // entities = p.Schemas // err error ) if err != nil { return err } go GenModelIndex(p) if err = GenFilterEntityDocument(p); err != nil { return err } if err = GenEntityHelpers(p); err != nil { return err } for _, entity := range entities { entityInfo = p.ResponseEntity(entity.ID) if entityInfo.IsGeneric { continue } inputName = entity.ID entityPatchName = "" hasAddOrRemove = false // requiredOnCreate = false entityName = GenericPart.ReplaceAllString(entity.ID, "") filename = base + strings.ToLower(entityName) // f = G.NewFile(p.Package) f = G.NewFile("models") generateIndexsEntity(f, entity) // fmt.Println("Gerando entity: ", entityName) cproperties = map[string]*G.Statement{} // f.Comment(entity.Description) properties = G.Statement{} patchProperties = G.Statement{ G.Qual(API_URL, "PatchHistoryRegister").Tag(map[string]string{ "json": "-", "bson": ",inline", }), } entityModel := G.Qual(API_URL, "EntityModel").Tag(map[string]string{ "json": "-", "bson": "-", }) patchProperties = append(patchProperties, entityModel) // values = G.Dict{} // entityInfo = p.IsGenericEntity(entity.ID) inputProperties = G.Statement{ // G.Qual(API_URL, "DotNotation").Tag(map[string]string{ // "json": "-", // "bson": ",inline", // }), G.Id("DotNotation map[string]interface{}").Tag(map[string]string{ "json": "-", "bson": ",inline", }), } // inputProperties = append(inputProperties, entityModel) referenceProperties = G.Statement{} addUpdate = map[string]string{} replaceUpdate = map[string]map[string]string{} removeUpdate = map[string]map[string]string{} for _, meta := range entity.Properties { // fmt.Println("Gerando ---------- ", entityName, "---------", propName, "----", meta.Array) if meta.Targets != "" && !strings.Contains(meta.Targets, "go") { continue } propName = strings.Title(meta.ID) propertie = G.Id(propName) meta.FillTags(p, propName) posfix = "" // Registra a relaao entre as entidades if meta.Relation { posfix = "Reference" SR.Add(&Relation{ Source: meta.GetType(), Target: entity.ID, Attr: strings.Replace(meta.Tags["bson"], ",omitempty", "", 1), DB: entity.DB, Collection: entity.Collection, IsArray: meta.Array, }) } typ = meta.Type + posfix if typ == "any" { typ = "interface{}" } if meta.Array { hasAddOrRemove = true propertie.Op("*") propertie.Index() if !meta.Readonly { extName = "Add" + propName addUpdate[extName] = meta.ID extend = G.Id(extName).Index().Id(ConvType(typ)).Tag(map[string]string{ "json": extName + ",omitempty", "bson": extName + ",omitempty", }) patchProperties = append(patchProperties, extend) if hasReplaceMethod(typ) { extName = "Replace" + propName replaceUpdate[extName] = map[string]string{ "name": meta.ID, "type": replaceAccessType(typ), } extend = G.Id(extName).Index().Id(ConvType(typ)).Tag(map[string]string{ "json": extName + ",omitempty", "bson": extName + ",omitempty", }) patchProperties = append(patchProperties, extend) } extName = "Remove" + propName removeUpdate[extName] = map[string]string{ "type": ConvType(typ), "propId": meta.ID, } extend = G.Id(extName).Index().Interface().Tag(map[string]string{ "json": extName + ",omitempty", "bson": extName + ",omitempty", }) patchProperties = append(patchProperties, extend) } } // propertie.Id(entityInfo.TranslateType(meta.Type) + posfix) if strings.Contains(typ, ".") { typt := strings.Split(typ, ".") propertie.Qual(ImportMap(typt[0]), ConvType(typt[1])) } else { propertie.Id(ConvType(typ)) } entity.HasMode = true // Adiciona as tags caso sejam definidas if meta.Tags != nil { // if name, ok := meta.Tags["valid"]; ok && strings.Contains(name, "requiredOnCreate") { // entity.HasMode = true // } propertie.Tag(meta.Tags) // if name, ok := meta.Tags["json"]; ok { // // tsPropertieName = strings.Split(name, ",")[0] // } } else { // tsPropertieName = meta.ID } // Adiciona a crescricao como comentario // tsPropertie = &ts.Group{} if meta.Description != "" { propertie.Comment(meta.Description) // tsPropertie.Comment(meta.Description) } // if tsPropertieName != "" { // tsPropertie.Add(ts.Public().Id(tsPropertieName).Op(":").Id(ts.ConvType(meta.Type))) // if meta.Array { // tsPropertie.Add(ts.Index()) // } // tsPropertie.Endl() // tsProperties = append(tsProperties, tsPropertie) // } cproperties[meta.ID] = propertie // Adiciona o valor padrao de inicializacao // spew.Dump(meta) // if tvalue, ok := meta.Autogenerate["create"]; ok { // switch tvalue { // case "objectId": // values[G.Id(propName)] = G.Qual(BSON_PRIMITIVE, "NewObjectID").Call() // case "now": // values[G.Id(propName)] = G.Qual("time", "Now").Call().Id(".Unix()") // case "time:now": // case "user:current": // default: // // Verifica se possui valor padrão // if meta.Default != nil { // values[G.Id(propName)] = G.Lit(meta.Default) // } // } // } // Adiciona as propriedades readonly na entidade principal // e as que tem permissao de escrita na entidade input if !meta.Readonly { inputProperties = append(inputProperties, propertie) } else { properties = append(properties, propertie) } // Adiciona a propriedade que é referencia a lista de propriedades // da entidade reference if meta.Reference { if propName == "Id" { propertie = G.Id("Id").Interface().Tag(map[string]string{ "json": "_id", "bson": "_id", }) } referenceProperties = append(referenceProperties, propertie) } } extend = G.Qual(API_URL, "EntityModel").Tag(map[string]string{ "json": "-", "bson": ",inline", }) inputProperties = append(G.Statement{extend}, inputProperties...) if len(inputProperties) > 0 { // if entity.HasMode { // values[G.Id("Mode")] = G.Qual(API_URL, "Mode").Values(G.Dict{ // G.Id("M"): G.Lit("create"), // }) // } if len(properties) == 0 { properties = inputProperties patchProperties = append(patchProperties, G.Id("Set").Op("*").Id(entityName).Tag(map[string]string{ "json": "$set,omitempty", "bson": "$set,omitempty", })) } else { inputName = entity.ID + "Input" extend = G.Id(inputName).Tag(map[string]string{ "json": ",inline", "bson": ",inline", }) properties = append(G.Statement{extend}, properties...) f.Comment("Representação Input aplicada em operações de update.") f.Type().Id(inputName).Struct(inputProperties...) patchProperties = append(patchProperties, G.Id("Set").Op("*").Id(inputName).Tag(map[string]string{ "json": "$set,omitempty", "bson": "-", })) } } if len(referenceProperties) > 0 { f.Comment("Representação reference aplicada quando a entidade é associada a outra.") f.Type().Id(entity.ID + "Reference").Struct(referenceProperties...) } // if entity.Representations != nil { // for k, rep := range entity.Representations { // tproperties = G.Statement{} // for _, attr := range rep { // tproperties = append(tproperties, cproperties[attr]) // } // f.Comment("Representação " + k) // f.Type().Id(entity.ID + k).Struct(tproperties...) // } // } if len(patchProperties) > 1 { entityPatchName = fmt.Sprintf("%sPatchs", entityName) f.Comment(entity.Description).Line().Comment("Representação de atualização") // patchProperties = append(G.Statement{*entityModel}, patchProperties) f.Type().Id(entityPatchName).Struct(patchProperties...) } // Cria a entidade normal f.Comment(entity.Description).Line().Comment("Representação Completa") f.Type().Id(entityName).Struct(properties...) // Cria a entidade em typescript // tsModels.Line().Export().Class().Id(strings.Title(entity.ID)).Block(tsProperties...).Line() f.Comment("Cria uma instancia de " + entity.ID + ".") // Cria a função de instanciar um novo elemento com os valores padrão determinados f.Func().Id("New"+entityName).Params().Op("*").Id(entityName).Block( G.Id("entity").Op(":=").Op("&").Id(entityName).Values(), G.Do(func(x *G.Statement) { if entity.HasMode { x.Add(G.Id(`entity.SetMode("create")`)) } }), G.Do(func(part *G.Statement) { // user := false for _, prop := range entity.Properties { typ := ConvType(prop.Type) isMap := strings.Contains(typ, "map") if prop.Relation { typ = typ + "Reference" } if def, ok := prop.Autogenerate["create"]; ok { switch def.Type { case "objectId": part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Qual(BSON_PRIMITIVE, "NewObjectID()").Line()) case "now": part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Qual("time", "Now").Call().Id(".Unix()").Line()) case "default": part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Lit(prop.Default).Line()) } } else if prop.Array { part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("= &").Index().Id(typ).Values().Line()) } else if isMap { part.Add(G.Id("entity").Dot(strings.Title(prop.ID)).Op("=").Id(typ).Values().Line()) } } }).Line(), G.Return(G.Id("entity")).Line(), ) // update = bson.M{"$set": f.Entity} // if data, ok = f.Entity.Push(); ok { // update["$push"] = data // } // if data, ok = f.Entity.Pull(); ok { // update["$pull"] = data // } if entityPatchName != "" { f.Func().Params( G.Id("t").Op("*").Id(entityPatchName), ).Id("Patch").Params().Params( G.Op("*").Qual(BSON, "A"), G.Op("*").Qual("go.mongodb.org/mongo-driver/mongo/options", "UpdateOptions"), ).Block( G.Id("updt").Op(":=").Qual(BSON, "A").Values(), G.Id("patchOptions := ").Qual("go.mongodb.org/mongo-driver/mongo/options","Update").Call(), G.Do(func(y *G.Statement) { if !hasAddOrRemove { return } stmts := G.Statement{ G.Id("hasAdd").Op(":=").Lit(0).Line(), G.Id("hasReplace").Op(":=").Lit(0).Line(), G.Id("hasRemove").Op(":=").Lit(0).Line(), G.Id("pull").Op(":=").Qual(BSON, "M").Values().Line(), G.Id("push").Op(":=").Qual(BSON, "M").Values().Line(), G.Id("dotNotation := map[string]interface{}{}").Line(), G.Id("arrayFilters := []interface{}{}").Line(), } // add entitys // spew.Dump(addUpdate) for prop, value := range addUpdate { stmts = append(stmts, G.If(G.Len(G.Id("t").Dot(prop)).Op(">").Lit(0)).Block( G.Id("hasAdd").Op("++"), G.Id("push").Index(G.Lit(value)).Op("=").Qual(BSON, "M").Values(G.Dict{ G.Lit("$each"): G.Id("t").Dot(prop), }), ).Line()) } // replace entitys // spew.Dump(addUpdate) for prop, value := range replaceUpdate { prop = fmt.Sprintf("t.%s", prop) stmts = append(stmts, G.If(G.Len(G.Id(prop)).Op(">").Lit(0)).Block( G.Id("hasReplace").Op("++"), G.Do(func(replace *G.Statement) { name := value["name"] typi := value["type"] arrayFiltersTemplates := map[string]string{ "id": `arrayFilters = append(arrayFilters, bson.M{ api.Format("%s._id", key): value.Id, })`, "map": `arrayFilters = append(arrayFilters, bson.M{ api.Format("%s._id", key): value["_id"], })`, } arrayFilters := arrayFiltersTemplates[typi] template := fmt.Sprintf(`for idx, value := range %s { key := api.Format("%s%%d", idx) dotNotation[api.Format("%s.$[%%s]", key)] = value %s }`, prop, name, name, arrayFilters, ) replace.Add(G.Id(template)) }), ).Line()) } // remove entitys for prop, value := range removeUpdate { stmts = append(stmts, G.If(G.Len(G.Id("t").Dot(prop)).Op(">").Lit(0)).Block( G.Id("hasRemove").Op("++"), G.Do(func(hasRemove *G.Statement) { var assign G.Dict // Se o array for de um atributo de tipo primitivo if primitiveRegex.MatchString(value["type"]) { assign = G.Dict{ G.Lit("$in"): G.Id("t").Dot(prop), } } else { assign = G.Dict{ G.Lit("_id"): G.Qual(BSON, "M").Values(G.Dict{ G.Lit("$in"): G.Id("t").Dot(prop), }), } } hasRemove.Id("pull").Index(G.Lit(value["propId"])).Op("=").Qual(BSON, "M").Values(assign) }), ).Line()) } // fmt.Println("Remove:", inputName, prop, value) // fmt.Printf("%#v", G.If(G.Len(G.Id("t").Dot(prop)).Op(">").Lit(0)).Block( // G.Id("hasRemove").Op("++"), // G.Id("pull").Index(G.Lit(value)).Op("=").Qual(BSON, "M").Values(G.Dict{ // G.Lit("$in"): G.Id("t").Dot(prop), // }), // ).Line()) // x.Add(G.If(G.Len(G.Id("t").Dot(prop)).Op(">").Lit(0)).Block( // G.Id("hasRemove").Op("++"), // G.Id("pull").Index(G.Lit(value)).Op("=").Qual(BSON, "M").Values(G.Dict{ // G.Lit("$in"): G.Id("t").Dot(prop), // }), // ).Line()) stmts = append(stmts, G.If(G.Id("hasAdd").Op(">").Lit(0)).Block( // G.Id("updt").Index(G.Lit("$push")).Op("=").Id("push"), G.Id("updt").Op("=").Append( G.Id("updt"), G.Qual(BSON, "M").Values(G.Lit("$push").Id(": push")), ), // .Index(G.Lit("$push")).Op("=").Id("push"), ).Line()) stmts = append(stmts, G.If(G.Id("hasRemove").Op(">").Lit(0)).Block( // G.Id("updt").Index(G.Lit("$pull")).Op("=").Id("pull"), G.Id("updt").Op("=").Append( G.Id("updt"), G.Qual(BSON, "M").Values(G.Lit("$pull").Id(": pull")), ), ).Line()) stmts = append(stmts, G.If(G.Id("hasReplace").Op(">").Lit(0)).Block( G.Id(fmt.Sprintf(`if t.Set == nil { t.Set = &%s{} } t.Set.DotNotation = dotNotation patchOptions.SetArrayFilters(options.ArrayFilters{ Filters: arrayFilters, }) `, inputName, )), ).Line()) y.Add(stmts...) }), G.Id(`if t.Set != nil { updt = append(updt, bson.M{"$set": t.Set}) }`).Line(), // []interface{}{bson.D{ // {"elem._id", 1}, // }} G.Return( G.Op("&").Id("updt"), G.Id("patchOptions"), ), ) } // Salva o arquivo da entidade if err := Write(p.Paths.Build("/%s/%s_gen.go", p.Package, filename), f); err != nil { return err } } // Fim do for de schema return nil } func ConvType(ntype string) string { if len(ntype) < 3 { fmt.Printf("Invalid type name '%s':\n", ntype) } if ntype[0:3] == "map" { parts := strings.Split(ntype, "|") ntype = fmt.Sprintf("map[%s]%s", parts[1], parts[2]) } return ntype } func GenModelIndex(p *Project) error { var ( // Index = G.NewFile(p.Package) Index = G.NewFile("models") ) Index.Id(`type Entity struct {}`).Line() Index.Comment("") Index.Var().Defs( G.Id("Api").Op("=").Op("&").Qual(API_URL, "Mongo").Values(), ).Line() return Write(p.Paths.Build("/%s/models/index_gen.go", p.Package), Index) } func fill(p *Project, schema *Entity) (*Entity, error) { var ( // found, ok bool found bool cls *Entity // newSchema = &(*schema) err error ) // Se o esquema ainda não esta completo com as classes extendidas caso possua if _, found = filled[schema.ID]; !found { // fEntity = schema // // fmt.Println("Fill ............ ", schema.ID) // fmt.Println("Nao estava cheio ", schema.ID) for _, entity := range schema.Extends { if cls, err = fill(p, p.GetSchema(entity)); err != nil { return nil, err } // fmt.Println("extends ", entity) // if cls, ok = filled[entity]; !ok { // cls = p.GetSchema(entity) // // fmt.Println("Não tava cheio", entity) // // fmt.Println("fill:", schema) // // fmt.Println(cls) // if err = fill(p, cls); err != nil { // return err // } // } // else { // fmt.Println("estava cheio ", entity) // fmt.Println("Merge ", schema.ID, cls.ID, ok) schema.Properties = append(schema.Properties, cls.Properties...) } // for _, x := range schema.Properties { // fmt.Println("fill----------------", schema.ID, x.ID, len(schema.Properties)) // } filled[schema.ID] = schema } return filled[schema.ID], nil } func Schemas(p *Project) ([]*Entity, error) { var ( err error entities = []*Entity{} ) for _, schema := range p.Schemas { if schema, err = fill(p, schema); err != nil { return nil, err } if schema.Type != "abstract" { entities = append(entities, schema) } // for _, x := range schema.Properties { // fmt.Printf("%s , %s, %p\n", schema.ID, x.ID, x) // } } // for _, s := range entities { // for _, x := range s.Properties { // fmt.Printf("final-schemas....%s , %s, %p\n", s.ID, x.ID, x) // } // } return entities, nil } func GenFilterEntityDocument(p *Project) error { var ( content string path string err error ) for _, s := range p.Schemas { if s.Type == "nested.object" { continue } // fmt.Println("gerando filtro ", s.ID) filter := &ApiFilter{ Id: strings.ToLower(s.ID), Date: time.Now().Unix(), } for _, attr := range s.Properties { if attr.Filter == nil { continue } // Atualiza o tipo do filtro na estrutura do campo for _, filterItem := range attr.Filter { FilterTypeMap(attr, filterItem) // Atualiza o path da query if filterItem.Type == "nested" { FilterPath(filter, p, attr, attr.ID) } else { if filterItem.Path == "" { filterItem.Path = attr.ID } if filterItem.UserEnumAsOptions { filterItem.Options = []FilterOption{} // if len(attr.Values) == 0 { // attr.Values = make([]string, len(attr.Enum)) // } // values = make() // } for key, value := range attr.Enum { filterItem.Options = append(filterItem.Options, FilterOption{ Value: attr.Values[key], Label: value, }) } } filter.Fields = append(filter.Fields, filterItem) } } } if filter.Fields == nil { continue } if content, err = JSONStringfy(filter); err != nil { return err } path = p.Paths.Dist("/filters/%s.json", strings.ToLower(s.ID)) if err = FilePutContents(path, content, 0777); err != nil { return err } } return nil } func FilterPath(filter *ApiFilter, p *Project, attr *Propertie, path string) { var ( nfil Filter npath string typ = attr.Type ) if attr.Relation { typ += "Reference" } entity := p.GetSchema(typ) for _, x := range entity.Properties { if x.Filter == nil { continue } npath = fmt.Sprintf("%s.%s", path, x.ID) for _, item := range x.Filter { if item.Type != "nested" { nfil = *item if nfil.Path == "" { nfil.Path = npath } FilterTypeMap(x, item) filter.Fields = append(filter.Fields, &nfil) } else { FilterPath(filter, p, x, npath) } } } } func FilterTypeMap(attr *Propertie, f *Filter) { typ := f.Type if typ != "" { return } switch { // Se o tipo for numerico case numberRegex.Match([]byte(typ)): f.Type = "number" default: f.Type = attr.Type } } func generateIndexsEntity(file *G.File, entity *Entity) { // keys := G.{} // options := G.Dict{ // G.Id("Unique"): G.Lit(true), // // G.Id("Key"): G.Lit("unique"), // // G.Id("Value"): G.Lit(true), // } var create = false indexOptions := G.Dict{ G.Id("Keys"): G.Qual(BSONX, "Doc").ValuesFunc(func(keys *G.Group) { for _, propertie := range entity.Properties { if propertie.Unique { keys.Add(G.Values(G.Lit(propertie.ID), G.Qual(BSONX, "Int32").Call(G.Lit(1)))) create = true // keys[G.Lit(propertie.ID)] = G.Lit(1) } } }), // G.Id("Options"): G.Qual(BSON, "M").Values(options), G.Id("Options"): G.Id("opts.SetUnique(true)"), } if create { file.Line().Func().Id("init").Call().Block( G.Id("models.Api.Ready().Subscribe").Call( G.Func().Call(G.Id("value").Id("...interface{}")).Block( G.Id("opts := &").Qual("go.mongodb.org/mongo-driver/mongo/options", "IndexOptions").Values(), G.Id(`index := `).Qual("go.mongodb.org/mongo-driver/mongo", "IndexModel").Values(indexOptions), // G.Qual("fmt", "Println").Call(G.Lit("create index for "), G.Lit(entity.ID)), G.Id(`if err := models.Api.CreateIndex(`).Lit(entity.DB).Id(`, `).Lit(entity.Collection).Id(`, index); err != nil`). Block( G.Id("panic").Call( G.Qual("fmt", "Sprintf").Call( // G.Lit(fmt.Sprintf("Index %s creation error %s", entity.ID, err.Error())), G.Id(`"Index`).Id(entity.ID).Id(`creation error %s", err.Error()`), ), ).Line(), // (`). // Call(). // Line(). ), ), ), ) // .BlockFunc(func(statement *G.Group) { // statement.Add( // , // ) // statement.Add(G.Line().Id("})")) // }) } } func GenEntityHelpers(project *Project) error { file := G.NewFile("models") entities, err := Schemas(project) if err != nil { return err } file.Add(G.Id(` import( "reflect" "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs" context "github.com/kataras/iris/v12/context" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) `)) getStmtsTmpl, getStmtsErr := ParseTemplate(` func Get{{.entity}}ByID( ctx context.Context, id interface{}, ) ( entity *{{.entity}}, err *errs.Error, ) { var errd error entity = &{{.entity}}{} value := reflect.ValueOf(id) if value.Kind() == reflect.Ptr { id = value.Elem() } if idString, ok := id.(string); ok { if id, errd = primitive.ObjectIDFromHex(idString); errd != nil { return } } options := Filter{{.entity}}Options(ctx) options.Query = &bson.M{"_id": id} options.Entity = entity if _, err = Api.FindOne(options); err != nil { return } return } `) if getStmtsErr != nil { return getStmtsErr } for _, entity := range entities { entityInfo := project.ResponseEntity(entity.ID) if entityInfo.IsGeneric || entity.Type != "object" { continue } context := map[string]interface{}{ "entity": strings.Title(entity.ID), } out, _ := TemplateToString(getStmtsTmpl, context) file.Add(G.Id(out).Line()) } if err := Write( fmt.Sprintf("../project/include/go/models/get_helpers.go"), file, ); err != nil { return err } return nil }