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/jeremywohl/flatten" "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 ProjectDotNotation map[string]interface{} } 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 { 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"` HasMetrics bool `json:"hasMetrics"` Environment Environment `json:"environment"` Variables map[string]interface{} `json:"variables"` Metrics map[string]*Metric `json:"metrics"` 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"` TranslatorBuildOptionsMap map[string]func(*Project, *BuildOptions) (*BuildSet, error) `json:"-"` Paths PathMap `json:"paths"` } type PathMap map[string]string func (paths PathMap) Format(id, format string, arguments ...interface{} ) string { path, found := paths[id] if !found { path = "" } return path + fmt.Sprintf(format, arguments...) } func (paths PathMap) Dist(format string, arguments ...interface{} ) string { return paths.Format("dist", format , arguments...) } func (paths PathMap) Build(format string, arguments ...interface{} ) string { return paths.Format("build", format , arguments...) } func (paths PathMap) Current(format string, arguments ...interface{} ) string { return paths.Format("current", format , arguments...) } func (paths PathMap) Include(format string, arguments ...interface{} ) string { return paths.Format("include", format , arguments...) } func (paths PathMap) Project(format string, arguments ...interface{} ) string { return paths.Format("project", format , arguments...) } type Metric struct { Description string `json:"description"` Props map[string]*Parameter `json:"props"` Return *MetricReturn `json:"return"` } type MetricReturn struct { Type string `json:"type"` Range bool `json:"range"` Props map[string]*Parameter `json:"props"` } 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"` Reference *string `json:"reference,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"` BeforeParseRequest []Action `json:"beforeParseRequest"` 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"` Required bool `json:"required"` 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 = "query." + arg value = api.Q(ctx, arg, param.Default) case "path": id = "path." + arg value = api.P(ctx, arg, param.Default) } sourceValue = value emptyValue := (value == "" || value == nil) if param.Required && emptyValue { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf( "ParamRequired: param '%s' in '%s'", param.ID, param.Location, ) return nil, invalidArgument } if !emptyValue && param.ConvertTo != "" { if value, err = convertValueByType(param.ConvertTo, value); err != nil { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf( "ParamTypeConversionError: param '%s' in '%s' with value '%v'. Waiting a %s ", param.ID, param.Location, value, param.ConvertTo, ) return nil, invalidArgument } } if param.Validation != nil { for validator, args := range param.Validation { if fn, found := validationParamFunctions[validator]; found { ctx.Application().Logger().Info(fmt.Sprintf("validadete[%s][%s][%v]", validator, args, value)) if err = fn(param, value, args); err != nil { return nil, err } } } } values.Set(fmt.Sprintf("%s_conv", id), value) values.Set(id, sourceValue) paramsMap[fmt.Sprintf("%s_conv", arg)] = value paramsMap[arg] = sourceValue } ctx.Next() return } } var ( convertionTypeFunctions = map[string]func(interface{}) (interface{}, *errs.Error){ "ObjectID": stringToObjectId, "array(string)": stringToArrayString, "bool": stringToBool, "int": stringToInt, "number": stringToFloat, } validationParamFunctions = map[string]func(*Parameter, interface{}, interface{}) *errs.Error{ "min": func(param *Parameter, value interface{}, minString interface{}) *errs.Error { var input float64 if v, ok := value.(int64); ok { input = float64(v) } else if v, ok := value.(float64); ok { input = v } else if v, ok := value.(string); ok { input = float64(len(v)) } else { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf( "[%s] ValueRestriction: mim validation requires (int,float,string)", param.ID, ) return invalidArgument } if min, convert := minString.(float64); !convert || input < min { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf( "[%s] ValueRestriction: value > %v. Received (%v)", param.ID, minString, value, ) return invalidArgument } return nil }, "max": func(param *Parameter, value interface{}, maxString interface{}) *errs.Error { var input float64 if v, ok := value.(int64); ok { input = float64(v) } else if v, ok := value.(float64); ok { input = v } else if v, ok := value.(string); ok { input = float64(len(v)) } else { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf( "[%s] ValueRestriction: mim validation requires (int,float,string)", param.ID, ) return invalidArgument } if max, convert := maxString.(float64); !convert || input > max { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf( "[%s] ValueRestriction: value < %v. Received (%v)", param.ID, maxString, value, ) return invalidArgument } return nil }, "accept": func(param *Parameter, input interface{}, accept interface{}) *errs.Error { var ( acceptValues = accept.([]interface{}) acceptValuesString = []string{} value = fmt.Sprintf("%v", input) ) for _, acceptValue := range acceptValues { if value == acceptValue.(string) { return nil } acceptValuesString = append(acceptValuesString, acceptValue.(string)) } invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf( "[%s] ValueRestriction: '%s' isn't accept. Accept [%s]", param.ID, value, strings.Join(acceptValuesString, ","), ) return invalidArgument }, "reject": func(param *Parameter, input interface{}, reject interface{}) *errs.Error { var ( rejectValues = reject.([]interface{}) value = fmt.Sprintf("%v", input) ) for _, rejectValue := range rejectValues { if value == rejectValue.(string) { invalidArgument := errs.InvalidArgument() invalidArgument.Message = fmt.Sprintf( "[%s] ValueRestriction: '%s' isn't accept", param.ID, value, ) return invalidArgument } } return nil }, "regex": func(param *Parameter, 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( "[%s] ValueRestriction: '%s' isn't accept", param.ID, 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 stringToArrayString(value interface{}) (stringArray interface{}, err *errs.Error) { var ( valueString string ok bool ) if valueString, ok = value.(string); !ok { err = errs.InvalidArgument().Details(&errs.Detail{ Reason: "StringToArrayString expect a string value", }) return } stringArray = strings.Split(valueString, ",") return } // 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 (project *Project) Build(options *BuildOptions) (err error) { var ( variablesJson string out []byte ) options.ProjectDotNotation = map[string]interface{}{} if out, err = json.Marshal(project); err != nil { return } if variablesJson, err = flatten.FlattenString( string(out), "project.", flatten.DotStyle, ); err != nil { return } if err = json.Unmarshal( []byte(variablesJson), &options.ProjectDotNotation, ); err != nil { return } for _, client := range project.Clients { if fn, found := project.Translators[client.Id]; found { if err = fn(project); err != nil { return err } if err = runBuildCommads(project, client, options); err != nil { return } } else { err = fmt.Errorf("compiler translation model '%s' not defined", client.Id) return } } return } 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 getCustom(options map[string]interface{}, path string) (resp interface{}) { if options != nil { resp = options[path] } return } 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{}, } }