package common import ( "bytes" "encoding/json" "fmt" "regexp" "strconv" "strings" "text/template" "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api" "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs" ts "git.eugeniocarvalho.dev/eugeniucarvalho/gg/generators/typescript" "github.com/kataras/iris/v12/context" "go.mongodb.org/mongo-driver/bson/primitive" ) const ( BSON = "go.mongodb.org/mongo-driver/bson" BSONX = "go.mongodb.org/mongo-driver/x/bsonx" MONGO = "go.mongodb.org/mongo-driver/mongo" BSON_PRIMITIVE = "go.mongodb.org/mongo-driver/bson/primitive" IRIS_CTX = "github.com/kataras/iris/v12/context" IRIS = "github.com/kataras/iris/v12" UPDATE_RELATION = "UpdateRelation" BASE_HAS_DEPENDE = "HasDep" ) var ( API_URL = "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api" API_ERROR = "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/api/errs" CODE_GEN_V2_COMMON = "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/common" CODE_GEN_V2_AUTHORIZATION = "git.eugeniocarvalho.dev/eugeniucarvalho/apicodegen/authorization" // Variavel de controle de acesso aos models da API. Models = &api.Mongo{} camelToUnderRegex = regexp.MustCompile(`([^[:lower:]])`) //Generic e Generic = regexp.MustCompile("(?P[\\w-_]+)<(?P[\\w\\*]+)>") //GenericPart e GenericPart = regexp.MustCompile("<(?P[\\w\\*]+)>") //ImportMap e importMap = map[string]string{ "bson": BSON, "primitive": BSON_PRIMITIVE, } SR = SchemasRelations{ R: map[string][]*Relation{}, } ) type BuildOptions struct { Mode string IgnoreBuildSteps string IgnoreBuildStepsValues map[int]bool } func (b *BuildOptions) IgnoreStep(step int) bool { _, ok := b.IgnoreBuildStepsValues[step] return ok } func (b *BuildOptions) Parse() error { var ( value int err error ) if b.IgnoreBuildStepsValues == nil { b.IgnoreBuildStepsValues = map[int]bool{} } for _, v := range strings.Split(b.IgnoreBuildSteps, ",") { if value, err = strconv.Atoi(v); err != nil { return err } b.IgnoreBuildStepsValues[value] = true } return nil } func ImportMap(base string) string { if v, ok := importMap[base]; ok { return v } panic(fmt.Sprintf("Import %s não definido", base)) } type Project struct { OutPath string `json:"outPath"` Package string `json:"package"` Kind string `json:"kind"` Etag string `json:"etag"` Version string `json:"version"` BuildVersion string `json:"buildVersion"` ID string `json:"id"` Name string `json:"name"` DataBaseSufix string `json:"dataBaseSufix"` Mode string `json:"mode"` Revision string `json:"revision"` Title string `json:"title"` Description string `json:"description"` OwnerDomain string `json:"ownerDomain"` OwnerName string `json:"ownerName"` DocumentationLink string `json:"documentationLink"` Protocol string `json:"protocol"` BaseURL string `json:"baseUrl"` BasePath string `json:"basePath"` Middlewares []string `json:"middlewares"` ServicePath string `json:"servicePath"` GitRepository string `json:"git.repository"` Environment Environment `json:"environment"` Variables map[string]interface{} `json:"variables"` Resource *Resource `json:"-"` Schemas []*Entity `json:"schemas"` SchemasRef map[string]*Entity `json:"-"` Resources []*Resource `json:"resources"` Auth Auth `json:"auth"` TypeScriptSource *ts.File `json:"-"` Icons map[string]string `json:"icons"` ReplaceWhenEmpty map[string]bool `json:"ReplaceWhenEmpty"` OmitEmpty map[string]bool `json:"omitempty"` Clients []*Client `json:"clients,omitempty"` Translators map[string]TranslationFn `json:"-"` FormatMap map[string]string `json:"-"` Queries *QueryDef `json:"queries"` ACL *ACL `json:"acl"` Custom map[string]interface{} `json:"custom"` } type ACL struct { Roles []*Role `json:"roles"` Permissions []*Permission `json:"permissions"` } type QueryDef struct { Blacklistwords map[string][]string `json:"blacklistwords"` Queries map[string]string `json:"queries"` Common map[string]string `json:"common"` } type Role struct { Title string `json:"title"` Description string `json:"description"` ID string `json:"id"` AllowRemove bool `json:"allowRemove,omitempty"` Permissions []string `json:"permissions"` } type Permission struct { Title string `json:"title"` Description string `json:"description"` ID string `json:"id"` } type Client struct { Id string `json:"id,omitempty"` OutputDir string `json:"outputDir,omitempty"` } type Auth struct { AuthCookieDomain string `json:"authCookieDomain"` AuthTokenID string `json:"authTokenId"` Oauth2 Oauth2 `json:"oauth2"` } type Oauth2 struct { URI string `json:"uri"` Client Oauth2Client `json:"client"` Scopes []Scope `json:"scopes"` } type Oauth2Client struct { RedirectURI string `json:"redirect_uri"` ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` Scope []string `json:"scope"` } type Scope struct { ID string `json:"id"` PromptToUser []string `json:"promptToUser"` Description string `json:"description"` } type EnvironmentVariable struct { ID string `json:"id"` CamelID string `json:"-"` Default string `json:"default"` Required bool `json:"required,omitempty"` Description string `json:"description"` } type Environment map[string]*EnvironmentVariable type Entity struct { HasMode bool `json:"hasMode"` ID string `json:"id"` Type string `json:"type"` Description string `json:"description"` Collection string `json:"collection"` DB string `json:"db"` Extends []string `json:"extends"` Properties []*Propertie `json:"properties"` Representations map[string][]string `json:"representations"` Custom map[string]interface{} `json:"custom"` } type Resource struct { ID string `json:"id"` Description string `json:"description"` Entity string `json:"entity"` Formats []*Value `json:"formats"` Methods []*Method `json:"methods"` CommonParams map[string]*Parameter `json:"commonParams"` Custom map[string]interface{} `json:"custom"` } type Method struct { ID string `json:"id"` Entity string `json:"entity"` Type string `json:"type"` // Assume valores {one, list, implement} Path string `json:"path"` Template string `json:"template"` BeforePersistAction bool `json:"beforePersistAction"` HttpMethod string `json:"httpMethod"` Description string `json:"description"` Response string `json:"response"` Request string `json:"request"` Scopes []string `json:"scopes"` Middlewares []string `json:"middlewares"` Postresponse []string `json:"postresponse"` ParameterOrder []string `json:"parameterOrder"` ParametersString []string `json:"parameters"` Resource *Resource `json:"-"` Hooks map[string]bool `json:"hooks"` Parameters map[string]*Parameter `json:"parametersmap"` Preconditions []Action `json:"preconditions"` BeforeResponse []Action `json:"beforeResponse"` Custom map[string]interface{} `json:"custom"` // Parameters map[string]*Parameter `json:"parameters"` } type Action struct { ID string `json:"id"` Context map[string]interface{} `json:"context"` } type Parameter struct { ID string `json:"id"` Type string `json:"type"` Required bool `json:"required"` Description string `json:"description"` Default string `json:"default"` Location string `json:"location"` ConvertTo string `json:"convertTo"` Custom map[string]interface{} `json:"custom"` // Validation *ValidationRule `json:"validation"` Validation map[string]interface{} `json:"validation"` } // type ValidationRule struct { // Accept []*Value `json:"-"` // AcceptRef []string `json:"accept"` // Reject []*Value `json:"reject"` // RejectRef []string `json:"-"` // In []string `json:"in"` // Contains string `json:"contains"` // Regex string `json:"regex"` // Min string `json:"min"` // Max string `json:"max"` // Type string `json:"type"` // } type Value struct { Id string `json:"id"` Value string `json:"value"` Default bool `json:"default"` Fields string `json:"fields"` Description string `json:"description"` } type Propertie struct { ID string `json:"id"` Name string `json:"name"` Type string `json:"type"` Description string `json:"description"` AutogenerateInput string `json:"autogenerate"` Autogenerate map[string]AutoGenDef `json:"-"` Targets string `json:"targets"` Array bool `json:"array"` Relation bool `json:"relation"` TagVisited bool `json:"-"` Reference bool `json:"reference"` Readonly bool `json:"readonly"` Unique bool `json:"uniq"` Default interface{} `json:"default"` Enum []string `json:"enum"` Values []interface{} `json:"values"` EnumDescriptions []string `json:"enumDescriptions"` Tags map[string]string `json:"tags"` Filter []*Filter `json:"filter"` Custom map[string]interface{} `json:"custom"` } type AutoGenDef struct { Type string Args []string } type Filter struct { Path string `json:"path"` Type string `json:"type"` Label string `json:"label"` UserEnumAsOptions bool `json:"userEnumAsOptions"` Multiples bool `json:"multiples"` Options []FilterOption `json:"options,omitempty"` } type FilterOption struct { Value interface{} `json:"value"` Label string `json:"label"` } type ApiFilter struct { Id string `json:"id"` Date int64 `json:"date"` Fields []*Filter `json:"fields"` } func NewApiFilter(id string) *ApiFilter { return &ApiFilter{ Id: id, Fields: []*Filter{}, } } func RequestParams(args string, params map[string]*Parameter) func(ctx context.Context) (resp interface{}, err *errs.Error) { argsList := strings.Split(args, ",") return func(ctx context.Context) (resp interface{}, err *errs.Error) { var ( values = ctx.Values() id string value interface{} sourceValue interface{} param *Parameter paramsMap = map[string]interface{}{} ) values.Set("$params", paramsMap) for _, arg := range argsList { param = params[arg] switch param.Location { case "query": id = "q." + arg value = api.Q(ctx, arg, param.Default) case "path": id = "p." + arg value = api.P(ctx, arg, param.Default) } sourceValue = value if param.Required && (value == "" || value == nil) { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf("ParamRequired:'%s'", param.ID) return nil, invalidArgument } if param.ConvertTo != "" { if value, err = convertValueByType(param.ConvertTo, value); err != nil { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf("TypeConversionError:'%v'. Waiting a %s ", value, param.ConvertTo) return nil, invalidArgument } } if param.Validation != nil { for validator, args := range param.Validation { if fn, found := validationParamFunctions[validator]; found { if err = fn(value, args); err != nil { return nil, err } } } } values.Set(id, value) paramsMap[fmt.Sprintf("%s_conv", arg)] = value paramsMap[arg] = sourceValue } ctx.Next() return } } var ( convertionTypeFunctions = map[string]func(interface{}) (interface{}, *errs.Error){ "ObjectID": stringToObjectId, "bool": stringToBool, "int": stringToInt, "number": stringToFloat, } validationParamFunctions = map[string]func(interface{}, interface{}) *errs.Error{ "min": func(value interface{}, minString interface{}) *errs.Error { min, err := strconv.Atoi(minString.(string)) if err != nil || value.(int) < min { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf("ValueRestriction: value > %s. Received (%d)", minString, value) return invalidArgument } return nil }, "max": func(value interface{}, maxString interface{}) *errs.Error { max, err := strconv.Atoi(maxString.(string)) if err != nil || value.(int) > max { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf("ValueRestriction: value < %s. Received (%d)", maxString, value) return invalidArgument } return nil }, "accept": func(input interface{}, accept interface{}) *errs.Error { var ( acceptValues = accept.([]string) value = input.(string) ) for _, acceptValue := range acceptValues { if value == acceptValue { return nil } } invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf( "ValueRestriction: '%s' isn't accept. Accept [%s]", value, strings.Join(acceptValues, ","), ) return invalidArgument }, "reject": func(input interface{}, reject interface{}) *errs.Error { var ( rejectValues = reject.([]string) value = input.(string) ) for _, rejectValue := range rejectValues { if value == rejectValue { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf( "ValueRestriction: '%s' isn't accept. Rejected terms [%s]", value, strings.Join(rejectValues, ","), ) return invalidArgument } } return nil }, "regex": func(input interface{}, regex interface{}) *errs.Error { var ( regexString = regex.(string) value = input.(string) ) regexInstance := regexp.MustCompile(regexString) if !regexInstance.Match([]byte(value)) { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf( "ValueRestriction: '%s' isn't accept", value, ) return invalidArgument } return nil }, } ) func stringToObjectId(value interface{}) (interface{}, *errs.Error) { var ( valueString = value.(string) valueObjectID primitive.ObjectID err error ) if valueObjectID, err = primitive.ObjectIDFromHex(valueString); err != nil { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf("The value '%s' is'nt a valid ObjectId", valueString) return nil, invalidArgument } return valueObjectID, nil } func stringToBool(value interface{}) (interface{}, *errs.Error) { var ( valueBool bool err error ) if valueBool, err = strconv.ParseBool(value.(string)); err != nil { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf("The value '%s' is'nt a valid boolean. Accept [true,1,T,false,0,F]", valueBool) return nil, invalidArgument } return valueBool, nil } func stringToInt(value interface{}) (interface{}, *errs.Error) { var ( valueInt int64 err error ) if valueInt, err = strconv.ParseInt(value.(string), 10, 64); err != nil { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf("The value '%s' is'nt a valid int", valueInt) return nil, invalidArgument } return valueInt, nil } func stringToFloat(value interface{}) (interface{}, *errs.Error) { var ( valueFloat float64 err error ) if valueFloat, err = strconv.ParseFloat(value.(string), 64); err != nil { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf("The value '%s' is'nt a valid number", valueFloat) return nil, invalidArgument } return valueFloat, nil } func convertValueByType(typ string, value interface{}) (interface{}, *errs.Error) { var err *errs.Error if fn, found := convertionTypeFunctions[typ]; found { if value, err = fn(value); err != nil { return nil, err } } return value, nil } // func validateParam(param *Parameter, value interface{}) (interface{}, *errs.Error) { // var err *errs.Error // return value, nil // } func (t *Method) Hook(id string) bool { // active := t.Hooks[id] // return active return t.Hooks[id] } func (t *Propertie) ParseAutogenerate() error { if t.AutogenerateInput != "" { parts := strings.Split(t.AutogenerateInput, ":") if len(parts) < 2 { return fmt.Errorf("Invalid autogenerate input '%s' in attribute '%s'.", t.AutogenerateInput, t.ID) } if t.Autogenerate == nil { t.Autogenerate = map[string]AutoGenDef{} } args := strings.Split(parts[1], "#") for _, k := range strings.Split(parts[0], ",") { t.Autogenerate[k] = AutoGenDef{ Type: args[0], Args: args[1:], } } } return nil } type SchemasRelations struct { R map[string][]*Relation } type Relation struct { Source string Target string Attr string Collection string DB string IsArray bool } type EntityInfo struct { Name string Origin string NewName string DynamicType string DynamicTypeId string IsGeneric bool } type TranslationFn func(p *Project) error func (p *Project) Build(b *BuildOptions) error { var err error for _, c := range p.Clients { if fn, found := p.Translators[c.Id]; found { if err = fn(p); err != nil { fmt.Println("error on ", c.Id) return err } } else { return fmt.Errorf("Middleware '%s' not defined!", c.Id) } } // fmt.Println("--- RunBuildCommads") return RunBuildCommads(p, b) } func (p *Project) OutDirectory(path string) { p.OutPath = path } func (p *Project) Client(id string) *Client { for _, c := range p.Clients { if c.Id == id { return c } } return nil } func (p *Project) Save(path string) error { data, err := json.MarshalIndent(p, "", " ") if err == nil { err = FilePutContentsBytes(path, data, 0777) } return err } func (p *Project) GetCollection(entity string) string { for _, e := range p.Schemas { if e.ID == entity { return e.Collection } } return "undefined" } func (p *Project) GetEntityDB(entity string) string { if en, found := p.SchemasRef[entity]; found { return en.DB + p.DataBaseSufix } panic(fmt.Sprintf("DB attribute is empty in entity '%s'", entity)) } func (p *Project) EntityDesc(ID string) *Entity { if _, y := p.SchemasRef[ID]; !y { fmt.Println("EntityDesc(ID)", ID) return nil } return p.SchemasRef[ID] } func (m *Method) HasPathParams() bool { return len(m.ParameterOrder) > 0 } func (m *Method) HasFormatParam() (bool, *Parameter) { for id, param := range m.Parameters { // param = m.Parameters[id] // fmt.Println("param:", param.ID) if id == "format" { return true, param } } return false, nil } func (p *Project) GetUrlFromMethod(method *Method) string { return p.BaseURL + method.Path } func (p *Project) ResponseEntity(property string) *EntityInfo { var ( pi = &EntityInfo{ Origin: property, } ) match := Generic.FindStringSubmatch(property) if len(match) == 0 { return pi } for i, name := range Generic.SubexpNames() { switch name { case "type": pi.Name = match[i] case "dtype": pi.DynamicType = match[i] pi.IsGeneric = true } } if pi.IsGeneric { entity := p.GetSchema(pi.Name) match = GenericPart.FindStringSubmatch(entity.ID) for i, name := range GenericPart.SubexpNames() { switch name { case "id": pi.DynamicTypeId = match[i] } } } pi.NewName = pi.Name + UpFirst(strings.Replace(pi.DynamicType, "*", "", -1)) return pi } func (p *Project) GetPath(m *Method) string { path := []byte(p.BasePath + m.Path) for attr, param := range m.Parameters { path = regexp.MustCompile("{"+attr+"}").ReplaceAll(path, []byte("{"+attr+":"+param.Type+"}")) } return string(path) } func (p *Project) GetSchema(id string) *Entity { id = strings.Replace(id, "*", "", -1) if model, ok := p.SchemasRef[id]; ok { return model } panic(fmt.Sprintf("Entity '%s' not defined!", id)) } // Metodos das propriedades func (p *Propertie) FillTags(project *Project, propName string) { if p.TagVisited { return } if propName == "Id" { } if p.Tags != nil { for k, v := range p.Tags { if _, found := project.ReplaceWhenEmpty[k]; found && v == "" { p.Tags[k] = LcFirst(p.ID) } if _, found := project.OmitEmpty[k]; found { if p.Tags[k] != "-" { p.Tags[k] += ",omitempty" } } } } p.TagVisited = true } func (p *Propertie) GetType() string { return strings.Replace(p.Type, "*", "", 1) } // Metodos das informacoes da entidade func (p *EntityInfo) TranslateType(typ string) string { if typ == p.DynamicTypeId { return p.DynamicType } return typ } // Metodos do esquema de relacoes // Add adiciona uma relação ao esquema func (s *SchemasRelations) Has(entity string) bool { // spew.Dump(s) _, found := s.R[entity] return found } // Add adiciona uma relação ao esquema func (s *SchemasRelations) Get(entity string) []*Relation { if e, found := s.R[entity]; found { return e } return []*Relation{} } // Add adiciona uma relação ao esquema func (s *SchemasRelations) Add(r *Relation) { if _, found := s.R[r.Source]; !found { s.R[r.Source] = []*Relation{} } s.R[r.Source] = append(s.R[r.Source], r) } func ParseTemplate(input string, name ...string) (*template.Template, error) { var tmpl, err = template.New(strings.Join(name, "")).Parse(input) return tmpl, err } func TemplateToString(template *template.Template, data interface{}) (string, error) { var result bytes.Buffer if err := template.Execute(&result, data); err != nil { return "", err } return result.String(), nil } func NewProject() *Project { return &Project{ Mode: "", SchemasRef: map[string]*Entity{}, Icons: map[string]string{}, ReplaceWhenEmpty: map[string]bool{}, OmitEmpty: map[string]bool{}, FormatMap: map[string]string{}, Queries: &QueryDef{}, Schemas: []*Entity{}, Resources: []*Resource{}, Translators: map[string]TranslationFn{}, } }