Files
cgrates/data/scripts/generate_dispatchers/generator.go
2022-10-12 18:14:10 +02:00

447 lines
12 KiB
Go

//go: build ignore
/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
package main
import (
"fmt"
"go/ast"
"go/printer"
"go/token"
"io"
"log"
"os"
"path"
"reflect"
"sort"
"strconv"
"unicode"
"github.com/cgrates/cgrates/accounts"
"github.com/cgrates/cgrates/actions"
"github.com/cgrates/cgrates/analyzers"
"github.com/cgrates/cgrates/apis"
"github.com/cgrates/cgrates/config"
"github.com/cgrates/cgrates/cores"
"github.com/cgrates/cgrates/ees"
"github.com/cgrates/cgrates/efs"
"github.com/cgrates/cgrates/engine"
"github.com/cgrates/cgrates/guardian"
"github.com/cgrates/cgrates/loaders"
"github.com/cgrates/cgrates/rates"
"github.com/cgrates/cgrates/sessions"
"github.com/cgrates/cgrates/tpes"
"github.com/cgrates/cgrates/utils"
)
func main() {
type genFile struct {
path string
subsystem string // the name of the constant
obj interface{}
customName string
}
fmt.Println("Generating dispatcher files ...")
for _, file := range []genFile{
{"accounts.go", "MetaAccounts", new(accounts.AccountS), utils.EmptyString},
{"actions.go", "MetaActions", new(actions.ActionS), utils.EmptyString},
{"attributes.go", "MetaAttributes", new(engine.AttributeS), utils.EmptyString},
{"caches.go", "MetaCaches", engine.Cache, utils.EmptyString},
{"cdrs.go", "MetaCDRs", new(engine.CDRServer), utils.CDRs},
{"chargers.go", "MetaChargers", new(engine.ChargerS), utils.EmptyString},
{"config.go", "MetaConfig", new(config.CGRConfig), utils.ConfigS},
{"rates.go", "RateS", new(rates.RateS), utils.EmptyString},
{"replicator.go", "MetaReplicator", new(apis.ReplicatorSv1), utils.EmptyString},
{"resources.go", "MetaResources", new(engine.ResourceS), utils.EmptyString},
{"routes.go", "MetaRoutes", new(engine.RouteS), utils.EmptyString},
{"sessions.go", "MetaSessionS", new(sessions.SessionS), utils.SessionS},
{"stats.go", "MetaStats", new(engine.StatS), utils.EmptyString},
{"thresholds.go", "MetaThresholds", new(engine.ThresholdS), utils.EmptyString},
{"loaders.go", "MetaLoaders", new(loaders.LoaderS), utils.EmptyString},
{"ees.go", "MetaEEs", new(ees.EeS), utils.EmptyString},
{"analyzers.go", "MetaAnalyzer", new(analyzers.AnalyzerS), utils.EmptyString},
{"admins.go", "MetaAdminS", new(apis.AdminSv1), utils.EmptyString},
{"cores.go", "MetaCore", new(cores.CoreS), utils.EmptyString},
{"guardian.go", "MetaGuardian", guardian.Guardian, utils.GuardianS},
{"efs.go", "MetaEFs", new(efs.EfS), utils.EmptyString},
{"tpes.go", "MetaTpes", new(tpes.TPeS), utils.EmptyString},
// {"servicemanager.go", "MetaServiceManager", new(servmanager.ServiceManager), utils.EmptyString},
} {
if err := createFile(file.path, file.subsystem, file.customName, file.obj); err != nil {
log.Fatal(err)
}
}
}
func createFile(filePath, subsystem, customName string, obj interface{}) (err error) {
var f io.WriteCloser
if f, err = os.Create(filePath); err != nil {
return
}
defer f.Close()
return writeFile(f, subsystem, customName, obj)
}
func writeFile(w io.Writer, subsystem, customName string, obj interface{}) (err error) {
if _, err = w.Write([]byte(`/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
// do not modify this code because it's generated
`)); err != nil {
return
}
srv, _ := engine.NewServiceWithName(obj, customName, len(customName) != 0)
fs := token.NewFileSet()
f := generateService(subsystem, srv)
ast.SortImports(fs, f)
return printer.Fprint(w, fs, f)
}
func generateService(subsystem string, srvs engine.IntService) *ast.File {
imports := utils.NewStringSet([]string{
"github.com/cgrates/cgrates/utils",
"github.com/cgrates/birpc/context",
})
decl := make([]ast.Decl, 0)
for k, srv := range srvs {
if unicode.IsLetter(rune(k[len(k)-1])) {
continue
}
methods := make([]string, 0, len(srv.Methods))
for n := range srv.Methods {
methods = append(methods, n)
}
sort.Strings(methods)
for _, n := range methods {
m := srv.Methods[n]
decl = append(decl, generateFunc(srv.Name+n, subsystem, m.ArgType, m.ReplyType))
imports.AddSlice(getImports(m.ArgType))
imports.AddSlice(getImports(m.ReplyType))
}
}
imports.Remove("")
imps := make([]ast.Spec, imports.Size())
for i, k := range imports.AsOrderedSlice() {
imps[i] = &ast.ImportSpec{Path: &ast.BasicLit{
// Kind: token.STRING,
Value: strconv.Quote(k),
}}
}
decl = append([]ast.Decl{&ast.GenDecl{
Tok: token.IMPORT,
Specs: imps,
}}, decl...)
return &ast.File{
Name: ast.NewIdent("dispatchers"),
Decls: decl,
}
}
func generateFunc(service, subsystem string, arg, reply reflect.Type) *ast.FuncDecl {
defer func() {
val := recover()
if val != nil {
log.Println(service)
panic(val)
}
}()
return &ast.FuncDecl{
Recv: &ast.FieldList{
List: []*ast.Field{{
Names: []*ast.Ident{ast.NewIdent("dS")},
Type: &ast.StarExpr{X: ast.NewIdent("DispatcherService")},
}},
},
Name: ast.NewIdent(service),
Type: &ast.FuncType{
Params: &ast.FieldList{List: []*ast.Field{
{
Names: []*ast.Ident{ast.NewIdent("ctx")},
Type: &ast.StarExpr{X: &ast.SelectorExpr{
X: ast.NewIdent("context"),
Sel: ast.NewIdent("Context"),
}},
},
{
Names: []*ast.Ident{ast.NewIdent("args")},
Type: getArgType(arg),
},
{
Names: []*ast.Ident{ast.NewIdent("reply")},
Type: getArgType(reply),
},
}},
Results: &ast.FieldList{List: []*ast.Field{{
Names: []*ast.Ident{ast.NewIdent("err")},
Type: ast.NewIdent("error"),
}}},
},
Body: &ast.BlockStmt{List: generateFuncBody(arg, service, subsystem)},
}
}
type fldPath struct {
Name string
IsPointer bool
}
func generatePath(arg reflect.Type, field string, kind reflect.Kind) (p []fldPath) {
if arg.Kind() == reflect.Ptr {
arg = arg.Elem()
}
if arg.Kind() != reflect.Struct {
return
}
fld, has := arg.FieldByName(field)
if !has {
nf := arg.NumField()
for i := 0; i < nf; i++ {
fld := arg.Field(i)
if fld.Type.Kind() != reflect.Struct ||
fld.Type.Kind() != reflect.Ptr {
continue
}
if p = generatePath(fld.Type, field, kind); p != nil {
return append([]fldPath{{fld.Name, fld.Type.Kind() == reflect.Ptr}}, p...)
}
}
return
}
p = make([]fldPath, len(fld.Index))
cur := arg
for i, idx := range fld.Index {
f := cur.Field(idx)
p[i] = fldPath{f.Name, f.Type.Kind() == reflect.Ptr}
cur = f.Type
if cur.Kind() == reflect.Ptr {
cur = cur.Elem()
}
}
if cur.Kind() != kind {
return nil
}
return
}
func newCond(conds []*ast.BinaryExpr) *ast.BinaryExpr {
if len(conds) == 1 {
return conds[0]
}
return &ast.BinaryExpr{
X: conds[0],
Op: token.LAND,
Y: newCond(conds[1:]),
}
}
func generateCond(arg reflect.Type, obj, dftVal ast.Expr, field string, kind reflect.Kind) (p []ast.Stmt) {
p = make([]ast.Stmt, 0, 2)
p = append(p, &ast.AssignStmt{
Lhs: []ast.Expr{obj},
Tok: token.DEFINE,
Rhs: []ast.Expr{dftVal},
})
paths := generatePath(arg, field, kind)
if len(paths) == 0 {
return
}
paths = append([]fldPath{{"args", arg.Kind() == reflect.Ptr}}, paths...)
conds := make([]*ast.BinaryExpr, 0, len(paths)+1)
curPath := ""
nilI := ast.NewIdent("nil")
for i, p := range paths {
if i != 0 {
curPath += "."
}
curPath += p.Name
if !p.IsPointer {
continue
}
conds = append(conds, &ast.BinaryExpr{
X: ast.NewIdent(curPath),
Op: token.NEQ,
Y: nilI,
})
}
if kind == reflect.String {
conds = append(conds, &ast.BinaryExpr{
X: ast.NewIdent("len(" + curPath + ")"),
Op: token.NEQ,
Y: ast.NewIdent("0"),
})
}
if len(conds) == 0 {
return []ast.Stmt{
&ast.AssignStmt{
Lhs: []ast.Expr{obj},
Tok: token.DEFINE,
Rhs: []ast.Expr{ast.NewIdent(curPath)},
},
}
}
p = append(p, &ast.IfStmt{
Cond: newCond(conds),
Body: &ast.BlockStmt{List: []ast.Stmt{
&ast.AssignStmt{
Lhs: []ast.Expr{obj},
Tok: token.ASSIGN,
Rhs: []ast.Expr{ast.NewIdent(curPath)},
},
}},
})
return
}
func generateFuncBody(arg reflect.Type, funcName, subsystem string) (p []ast.Stmt) {
tnt := ast.NewIdent("tnt")
p = append(p, generateCond(arg, tnt, ast.NewIdent("dS.cfg.GeneralCfg().DefaultTenant"), utils.Tenant, reflect.String)...)
ev := ast.NewIdent("ev")
p = append(p, generateCond(arg, ev, ast.NewIdent("make(map[string]interface{})"), utils.Event, reflect.Map)...)
opts := ast.NewIdent("opts")
p = append(p, generateCond(arg, opts, ast.NewIdent("make(map[string]interface{})"), "APIOpts", reflect.Map)...)
p = append(p, &ast.ReturnStmt{Results: []ast.Expr{&ast.CallExpr{
Fun: &ast.SelectorExpr{
X: ast.NewIdent("dS"),
Sel: ast.NewIdent("Dispatch"),
},
Args: []ast.Expr{
ast.NewIdent("ctx"),
&ast.UnaryExpr{
Op: token.AND,
X: &ast.CompositeLit{
Type: ast.NewIdent("utils.CGREvent"),
Elts: []ast.Expr{
&ast.KeyValueExpr{
Key: ast.NewIdent(utils.Tenant),
Value: tnt,
},
&ast.KeyValueExpr{
Key: ast.NewIdent(utils.Event),
Value: ev,
},
&ast.KeyValueExpr{
Key: ast.NewIdent("APIOpts"),
Value: opts,
},
},
},
},
&ast.SelectorExpr{
X: ast.NewIdent("utils"),
Sel: ast.NewIdent(subsystem),
},
&ast.SelectorExpr{
X: ast.NewIdent("utils"),
Sel: ast.NewIdent(funcName),
},
ast.NewIdent("args"),
ast.NewIdent("reply"),
},
}},
})
return
}
func getArgType(args reflect.Type) ast.Expr {
if name := args.Name(); len(name) != 0 {
pkgpath := args.PkgPath()
if len(pkgpath) == 0 {
return ast.NewIdent(name)
}
return &ast.SelectorExpr{
X: ast.NewIdent(path.Base(pkgpath)),
Sel: ast.NewIdent(name),
}
}
switch args.Kind() {
default:
panic("unsuported argument")
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.Complex64,
reflect.Complex128, reflect.String:
return ast.NewIdent(args.Name())
case reflect.Interface:
name := args.Name()
if len(name) == 0 {
name = "interface{}"
}
return ast.NewIdent(name)
case reflect.Ptr:
return &ast.StarExpr{
X: getArgType(args.Elem()),
}
case reflect.Struct:
pkgpath := args.PkgPath()
if len(pkgpath) == 0 {
return ast.NewIdent(args.Name())
}
return &ast.SelectorExpr{
X: ast.NewIdent(path.Base(pkgpath)),
Sel: ast.NewIdent(args.Name()),
}
case reflect.Array, reflect.Slice:
return &ast.ArrayType{
Elt: getArgType(args.Elem()),
}
case reflect.Map:
return &ast.MapType{
Key: getArgType(args.Key()),
Value: getArgType(args.Elem()),
}
}
}
func getImports(args reflect.Type) []string {
switch args.Kind() {
default:
panic("unsuported argument")
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Uintptr, reflect.Float32, reflect.Float64, reflect.Complex64,
reflect.Complex128, reflect.String, reflect.Interface:
return nil
case reflect.Ptr, reflect.Array, reflect.Slice:
return append(getImports(args.Elem()), args.PkgPath())
case reflect.Struct:
return []string{args.PkgPath()}
case reflect.Map:
args.PkgPath()
key := append(getImports(args.Key()), args.PkgPath())
return append(key, getImports(args.Elem())...)
}
}