github.com/alimy/mir/v4@v4.1.0/internal/reflex/reflex.go (about) 1 // Copyright 2020 Michael Li <alimy@gility.net>. All rights reserved. 2 // Use of this source code is governed by Apache License 2.0 that 3 // can be found in the LICENSE file. 4 5 package reflex 6 7 import ( 8 "reflect" 9 "strings" 10 11 "github.com/alimy/mir/v4/core" 12 "github.com/alimy/mir/v4/internal/naming" 13 ) 14 15 // reflex real parser 16 type reflex struct { 17 engineInfo *core.EngineInfo 18 ns naming.NamingStrategy 19 tagName string 20 watchCtxDone bool 21 noneQuery bool 22 } 23 24 // Parse get Descriptors from parse entries 25 // Notice: Descriptors may be an empty if no actual item and is not routine safe 26 func (r *reflex) Parse(entries []any) (core.Descriptors, error) { 27 ds := make(core.Descriptors) 28 for _, entry := range entries { 29 iface, err := r.IfaceFrom(entry) 30 if err != nil { 31 return nil, err 32 } 33 // no actual fields so just continue 34 if len(iface.Fields) == 0 { 35 continue 36 } 37 if err = ds.Put(iface); err != nil { 38 return nil, err 39 } 40 } 41 return ds, nil 42 } 43 44 func (r *reflex) IfaceFrom(entry any) (*core.IfaceDescriptor, error) { 45 // used to find tagInfo 46 entryType := reflect.TypeOf(entry) 47 if entryType == nil { 48 return nil, errNilType 49 } 50 51 // get real entry type 52 isPtr := false 53 if entryType.Kind() == reflect.Ptr { 54 isPtr = true 55 entryType = entryType.Elem() 56 } 57 58 // entry must struct type 59 if entryType.Kind() != reflect.Struct { 60 return nil, errNotValideType 61 } 62 63 // used to find method from T and lookup struct tag string of mir tag information 64 var entryValue, entryPtrValue reflect.Value 65 if isPtr { 66 entryPtrValue = reflect.ValueOf(entry) 67 entryValue = entryPtrValue.Elem() 68 } else { 69 entryValue = reflect.ValueOf(entry) 70 entryPtrValue = entryValue.Addr() 71 } 72 73 var groupSetuped, chainSetuped bool 74 pkgPath := entryType.PkgPath() 75 // get IfaceDescriptor from entryType and entryPtrType 76 iface := &core.IfaceDescriptor{ 77 Imports: make(map[string]string), 78 EngineInfo: r.engineInfo, 79 VerInfo: core.VerInfo, 80 TypeName: entryType.Name(), 81 PkgPath: pkgPath, 82 PkgName: "api", // set default pkg name 83 Fields: make([]*core.FieldDescriptor, 0), 84 WatchCtxDone: r.watchCtxDone, 85 } 86 for i := entryType.NumField() - 1; i >= 0; i-- { 87 field := entryType.Field(i) 88 switch tagInfo, err := r.tagInfoFrom(field, pkgPath); err { 89 case nil: 90 // group field so just parse group info.group info only have one field 91 if tagInfo.isGroup { 92 if !groupSetuped { 93 groupSetuped = true 94 r.inflateGroupInfo(iface, entryValue, tagInfo) 95 break 96 } else { 97 return nil, errMultGroupInfo 98 } 99 } 100 // chain field so just parse chain info only have one field 101 if tagInfo.isChain { 102 if !chainSetuped { 103 iface.Chain = tagInfo.fieldName 104 chainSetuped = true 105 break 106 } else { 107 return nil, errMultChainInfo 108 } 109 } 110 iface.Fields = append(iface.Fields, r.fieldFrom(tagInfo, pkgPath)) 111 case errNotExist: 112 // normal field but had no mir tag info so just break to continue process next field 113 default: 114 return nil, err 115 } 116 } 117 return iface, nil 118 } 119 120 // inflateGroupInfo setup tag group info to TagMir instance 121 func (r *reflex) inflateGroupInfo(d *core.IfaceDescriptor, v reflect.Value, t *tagInfo) { 122 // group field value assign to m.group first or tag group string info assigned 123 d.Group = t.group 124 if d.Group != "" { 125 names := strings.Split(d.Group, "/") 126 pkgName := r.ns.Naming(names[len(names)-1]) 127 d.SetPkgName(pkgName) 128 } 129 } 130 131 // fieldFrom build tagField from entry and tagInfo 132 func (r *reflex) fieldFrom(t *tagInfo, pkgPath string) *core.FieldDescriptor { 133 return &core.FieldDescriptor{ 134 PkgPath: pkgPath, 135 IsAnyMethod: t.isAnyMethod, 136 IsFieldChain: t.isFieldChain, 137 IsUseContext: t.isUseContext, 138 HttpMethods: t.methods.List(), 139 IsBindIn: t.isBindIn, 140 IsRenderOut: t.isRenderOut, 141 In: t.in, 142 Out: t.out, 143 InOuts: t.inOuts, 144 Host: t.host, 145 Path: t.path, 146 Queries: t.queries, 147 MethodName: t.fieldName, 148 } 149 } 150 151 func NewReflex(info *core.EngineInfo, tagName string, watchCtxDone bool, noneQuery bool) *reflex { 152 return &reflex{ 153 engineInfo: info, 154 ns: naming.NewSnakeNamingStrategy(), 155 tagName: tagName, 156 watchCtxDone: watchCtxDone, 157 noneQuery: noneQuery, 158 } 159 }