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  }