gotsrpc/typereader.go
Cyrill Schumacher 099876a04e feat: Go 1.18 compatibility because of missing any type
also reduce usage of reflect and use type switches
2022-08-16 16:50:05 +02:00

401 lines
11 KiB
Go

package gotsrpc
import (
"fmt"
"go/ast"
"os"
"reflect"
"strings"
"gopkg.in/yaml.v2"
)
var ReaderTrace = false
func readStructs(pkg *ast.Package, packageName string) (structs map[string]*Struct, scalarTypes map[string]*Scalar, err error) {
structs = map[string]*Struct{}
trace("reading files in package", packageName)
scalarTypes = map[string]*Scalar{}
errorTypes := map[string]bool{}
for _, file := range pkg.Files {
err = extractTypes(file, packageName, structs, scalarTypes)
if err != nil {
return
}
err = extractErrorTypes(file, packageName, errorTypes)
if err != nil {
return
}
}
for name, structType := range structs {
_, isErrorType := errorTypes[name]
if isErrorType {
structType.IsError = true
}
}
// jsonDump(errorTypes)
// jsonDump(scalarTypes)
// jsonDump(structs)
return
}
func trace(args ...interface{}) {
if ReaderTrace {
fmt.Fprintln(os.Stderr, args...)
}
}
func traceData(args ...interface{}) {
if ReaderTrace {
for _, arg := range args {
yamlBytes, errMarshal := yaml.Marshal(arg)
if errMarshal != nil {
trace(arg)
continue
}
trace(string(yamlBytes))
}
}
}
func extractJSONInfo(tag string) *JSONInfo {
t := reflect.StructTag(tag)
jsonTagString := t.Get("json")
if len(jsonTagString) == 0 {
return nil
}
jsonTagParts := strings.Split(jsonTagString, ",")
name := ""
ignore := false
omit := false
forceStringType := false
cleanParts := []string{}
for _, jsonTagPart := range jsonTagParts {
cleanParts = append(cleanParts, strings.TrimSpace(jsonTagPart))
}
switch len(cleanParts) {
case 1:
switch cleanParts[0] {
case "-":
ignore = true
default:
name = cleanParts[0]
}
case 2:
if len(cleanParts[0]) > 0 {
name = cleanParts[0]
}
switch cleanParts[1] {
case "omitempty":
omit = true
case "string":
forceStringType = true
}
}
return &JSONInfo{
Name: name,
OmitEmpty: omit,
ForceStringType: forceStringType,
Ignore: ignore,
}
}
func getScalarFromAstIdent(ident *ast.Ident) ScalarType {
switch ident.Name {
case "string":
return ScalarTypeString
case "any":
return ScalarTypeAny
case "bool":
return ScalarTypeBool
case "byte":
return ScalarTypeByte
case "float", "float32", "float64",
"int", "int8", "int16", "int32", "int64",
"uint", "uint8", "uint16", "uint32", "uint64":
return ScalarTypeNumber
default:
if ident.Obj != nil && ident.Obj.Decl != nil && reflect.ValueOf(ident.Obj.Decl).Type().String() == "*ast.TypeSpec" {
typeSpec := ident.Obj.Decl.(*ast.TypeSpec)
if reflect.ValueOf(typeSpec.Type).Type().String() == "*ast.Ident" {
return getScalarFromAstIdent(typeSpec.Type.(*ast.Ident))
}
}
return ScalarTypeNone
}
}
func getTypesFromAstType(ident *ast.Ident) (structType string, scalarType ScalarType) {
scalarType = getScalarFromAstIdent(ident)
switch scalarType {
case ScalarTypeNone:
structType = ident.Name
}
return
}
func readAstType(v *Value, fieldIdent *ast.Ident, fileImports fileImportSpecMap) {
structType, scalarType := getTypesFromAstType(fieldIdent)
v.ScalarType = scalarType
if len(structType) > 0 {
v.StructType = &StructType{
Name: structType,
Package: fileImports.getPackagePath(""),
}
} else {
v.GoScalarType = fieldIdent.Name
if fieldIdent.Obj != nil && fieldIdent.Obj.Decl != nil && reflect.ValueOf(fieldIdent.Obj.Decl).Type().String() == "*ast.TypeSpec" {
// typeSpec := fieldIdent.Obj.Decl.(*ast.TypeSpec)
// fmt.Println("-------------------------------------->", fieldIdent.Name, reflect.ValueOf(typeSpec.Type).Type())
v.Scalar = &Scalar{
Package: fileImports.getPackagePath(""),
Name: fieldIdent.Name,
Type: scalarType,
}
}
}
}
func readAstStarExpr(v *Value, starExpr *ast.StarExpr, fileImports fileImportSpecMap) {
v.IsPtr = true
switch starExprType := starExpr.X.(type) {
case *ast.Ident:
readAstType(v, starExprType, fileImports)
case *ast.StructType:
// nested anonymous
readAstStructType(v, starExprType, fileImports)
case *ast.SelectorExpr:
readAstSelectorExpr(v, starExprType, fileImports)
default:
trace("a pointer on what", reflect.ValueOf(starExpr.X).Type().String())
}
}
func readAstMapType(m *Map, mapType *ast.MapType, fileImports fileImportSpecMap) {
trace(" map key", mapType.Key, reflect.ValueOf(mapType.Key).Type().String())
trace(" map value", mapType.Value, reflect.ValueOf(mapType.Value).Type().String())
// key
switch keyType := mapType.Key.(type) {
case *ast.Ident:
_, scalarType := getTypesFromAstType(keyType)
m.KeyType = string(scalarType)
m.KeyGoType = keyType.Name
default:
// todo: implement support for "*ast.Scalar" type (sca)
// this is important for scalar types in map keys
// Example:
//(*ast.MapType)(0xc420e2cc90)({
//Map: (token.Pos) 276258,
// Key: (*ast.SelectorExpr)(0xc420301900)({
// X: (*ast.Ident)(0xc4203018c0)(constants),
// Sel: (*ast.Ident)(0xc4203018e0)(Site)
// }),
//Value: (*ast.ArrayType)(0xc420e2cc60)({
// Lbrack: (token.Pos) 276277,
// Len: (ast.Expr) <nil>,
// Elt: (*ast.SelectorExpr)(0xc420301960)({
// X: (*ast.Ident)(0xc420301920)(elastic),
// Sel: (*ast.Ident)(0xc420301940)(CategoryDocument)
// })
// })
//})
// fmt.Println("--------------------------->", reflect.ValueOf(mapType.Key).Type().String())
}
// value
m.Value.loadExpr(mapType.Value, fileImports)
}
func readAstSelectorExpr(v *Value, selectorExpr *ast.SelectorExpr, fileImports fileImportSpecMap) {
switch selExpType := selectorExpr.X.(type) {
case *ast.Ident:
// that could be the package name
// selectorIdent := selectorExpr.X.(*ast.Ident)
// fmt.Println(selectorExpr, selectorExpr.X.(*ast.Ident))
readAstType(v, selExpType, fileImports)
if v.StructType != nil {
v.StructType.Package = fileImports.getPackagePath(v.StructType.Name)
v.StructType.Name = selectorExpr.Sel.Name
}
// fmt.Println(selectorExpr.X.(*ast.Ident).Name, ".", selectorExpr.Sel)
// readAstType(v, selectorExpr.Sel, fileImports)
default:
trace("selectorExpr.Sel !?", selectorExpr.X, reflect.ValueOf(selectorExpr.X).Type().String())
}
}
func readAstStructType(v *Value, structType *ast.StructType, fileImports fileImportSpecMap) {
v.Struct = &Struct{}
v.Struct.Fields = readFieldList(structType.Fields.List, fileImports)
}
func readAstInterfaceType(v *Value, interfaceType *ast.InterfaceType, fileImports fileImportSpecMap) {
v.IsInterface = true
}
func (v *Value) loadExpr(expr ast.Expr, fileImports fileImportSpecMap) {
switch expr.(type) {
case *ast.ArrayType:
fieldArray := expr.(*ast.ArrayType)
v.Array = &Array{Value: &Value{}}
switch faEltType := fieldArray.Elt.(type) {
case *ast.ArrayType:
// readAstArrayType(v.Array.Value, fieldArray.Elt.(*ast.ArrayType), fileImports)
v.Array.Value.loadExpr(faEltType, fileImports)
case *ast.Ident:
readAstType(v.Array.Value, faEltType, fileImports)
case *ast.StarExpr:
readAstStarExpr(v.Array.Value, faEltType, fileImports)
case *ast.MapType:
v.Array.Value.Map = &Map{
Value: &Value{},
}
readAstMapType(v.Array.Value.Map, faEltType, fileImports)
case *ast.SelectorExpr:
readAstSelectorExpr(v.Array.Value, faEltType, fileImports)
case *ast.StructType:
readAstStructType(v.Array.Value, faEltType, fileImports)
case *ast.InterfaceType:
readAstInterfaceType(v.Array.Value, faEltType, fileImports)
default:
trace("---------------------> array of", reflect.ValueOf(fieldArray.Elt).Type().String())
}
case *ast.Ident:
fieldIdent := expr.(*ast.Ident)
readAstType(v, fieldIdent, fileImports)
case *ast.StarExpr:
// a pointer on sth
readAstStarExpr(v, expr.(*ast.StarExpr), fileImports)
case *ast.MapType:
v.Map = &Map{
Value: &Value{},
}
readAstMapType(v.Map, expr.(*ast.MapType), fileImports)
case *ast.SelectorExpr:
readAstSelectorExpr(v, expr.(*ast.SelectorExpr), fileImports)
case *ast.StructType:
readAstStructType(v, expr.(*ast.StructType), fileImports)
case *ast.InterfaceType:
readAstInterfaceType(v, expr.(*ast.InterfaceType), fileImports)
default:
trace("what kind of field ident would that be ?!", reflect.ValueOf(expr).Type().String())
}
}
func readField(astField *ast.Field, fileImports fileImportSpecMap) (name string, v *Value, jsonInfo *JSONInfo) {
name = ""
if len(astField.Names) > 0 {
name = astField.Names[0].Name
}
v = &Value{}
v.loadExpr(astField.Type, fileImports)
if astField.Tag != nil {
jsonInfo = extractJSONInfo(astField.Tag.Value[1 : len(astField.Tag.Value)-1])
}
return
}
func readFieldList(fieldList []*ast.Field, fileImports fileImportSpecMap) (fields []*Field) {
for _, field := range fieldList {
name, value, jsonInfo := readField(field, fileImports)
if len(name) == 0 {
trace("i do not understand this one", field, name, value, jsonInfo)
continue
}
// this is not unicode proof
if strings.Compare(strings.ToLower(name[:1]), name[:1]) == 0 {
continue
}
if value != nil {
fields = append(fields, &Field{
Name: name,
Value: value,
JSONInfo: jsonInfo,
})
}
}
return
}
func extractErrorTypes(file *ast.File, packageName string, errorTypes map[string]bool) (err error) {
for _, d := range file.Decls {
if funcDecl, ok := d.(*ast.FuncDecl); ok {
if funcDecl.Recv != nil && len(funcDecl.Recv.List) == 1 {
firstReceiverField := funcDecl.Recv.List[0]
if starExpr, ok := firstReceiverField.Type.(*ast.StarExpr); ok {
if ident, ok := starExpr.X.(*ast.Ident); ok {
if funcDecl.Name.Name == "Error" && funcDecl.Type.Params.NumFields() == 0 && funcDecl.Type.Results.NumFields() == 1 {
returnValueField := funcDecl.Type.Results.List[0]
if returnValueIdent, ok := returnValueField.Type.(*ast.Ident); ok {
if returnValueIdent.Name == "string" {
errorTypes[packageName+"."+ident.Name] = true
}
// fmt.Println("error for:", ident.Name, returnValueIdent.Name)
}
}
}
}
}
}
}
return
}
func extractTypes(file *ast.File, packageName string, structs map[string]*Struct, scalarTypes map[string]*Scalar) error {
fileImports := getFileImports(file, packageName)
for name, obj := range file.Scope.Objects {
if obj.Kind == ast.Typ && obj.Decl != nil {
structName := packageName + "." + name
if typeSpec, ok := obj.Decl.(*ast.TypeSpec); ok {
switch tst := typeSpec.Type.(type) {
case *ast.StructType:
structs[structName] = &Struct{
Name: name,
Fields: []*Field{},
Package: packageName,
}
structType := typeSpec.Type.(*ast.StructType)
trace("StructType", obj.Name)
structs[structName].Fields = readFieldList(structType.Fields.List, fileImports)
case *ast.Ident:
trace("Scalar", obj.Name)
scalarIdent := typeSpec.Type.(*ast.Ident)
scalarTypes[structName] = &Scalar{
Name: structName,
Package: packageName,
Type: getScalarFromAstIdent(scalarIdent),
}
case *ast.ArrayType:
arrayValue := &Value{}
arrayValue.loadExpr(typeSpec.Type, fileImports)
structs[structName] = &Struct{
Name: name,
Package: packageName,
Array: arrayValue.Array,
}
case *ast.MapType:
mapValue := &Value{}
mapValue.loadExpr(typeSpec.Type, fileImports)
structs[structName] = &Struct{
Name: name,
Package: packageName,
Map: mapValue.Map,
}
default:
fmt.Printf(" ignoring %s %T\n", obj.Name, tst)
}
}
}
}
return nil
}