github.com/cloudwego/dynamicgo@v0.2.6-0.20240519101509-707f41b6b834/thrift/annotation/anno_mapping.go (about)

     1  /**
     2   * Copyright 2023 CloudWeGo Authors.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package annotation
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"strconv"
    24  	"strings"
    25  
    26  	"github.com/cloudwego/dynamicgo/internal/util"
    27  	"github.com/cloudwego/dynamicgo/meta"
    28  	"github.com/cloudwego/dynamicgo/thrift"
    29  	"github.com/cloudwego/thriftgo/parser"
    30  )
    31  
    32  type goTagMapper struct{}
    33  
    34  func (m goTagMapper) Map(ctx context.Context, anns []parser.Annotation, desc interface{}, opts thrift.Options) (ret []parser.Annotation, next []parser.Annotation, err error) {
    35  	for _, ann := range anns {
    36  		out := make([]string, 0, len(ann.Values))
    37  		for _, v := range ann.Values {
    38  			out = append(out, util.SplitGoTags(v)...)
    39  		}
    40  		for _, v := range out {
    41  			kv, err := util.SplitTagOptions(v)
    42  			if err != nil {
    43  				return nil, nil, fmt.Errorf("invalid go.tag: %s", v)
    44  			}
    45  			switch kv[0] {
    46  			case "json":
    47  				if err := handleGoJSON(kv[1], &ret); err != nil {
    48  					return nil, nil, err
    49  				}
    50  			}
    51  		}
    52  	}
    53  	return
    54  }
    55  
    56  func handleGoJSON(name string, ret *[]parser.Annotation) error {
    57  	*ret = append(*ret, parser.Annotation{
    58  		Key:    APIKeyName,
    59  		Values: []string{name},
    60  	})
    61  	return nil
    62  }
    63  
    64  type apiBodyMapper struct{}
    65  
    66  func (m apiBodyMapper) Map(ctx context.Context, anns []parser.Annotation, desc interface{}, opts thrift.Options) (ret []parser.Annotation, next []parser.Annotation, err error) {
    67  	if len(anns) > 1 {
    68  		return nil, nil, errors.New("api.body must be unique")
    69  	}
    70  	for _, ann := range anns {
    71  		if len(ann.Values) != 1 {
    72  			return nil, nil, errors.New("api.body must have a value")
    73  		}
    74  		ret = append(ret, parser.Annotation{
    75  			Key:    APIKeyName,
    76  			Values: []string{ann.Values[0]},
    77  		})
    78  		isRoot := ctx.Value(thrift.CtxKeyIsBodyRoot)
    79  		// special fast-path: if the field is at body root, we don't need to add api.body
    80  		if isRoot != nil && isRoot.(bool) {
    81  			continue
    82  		} else {
    83  			ret = append(ret, parser.Annotation{
    84  				Key:    "api.body",
    85  				Values: []string{ann.Values[0]},
    86  			})
    87  		}
    88  	}
    89  	return
    90  }
    91  
    92  type sourceMapper struct{}
    93  
    94  func (m sourceMapper) Map(ctx context.Context, anns []parser.Annotation, desc interface{}, opts thrift.Options) (ret []parser.Annotation, next []parser.Annotation, err error) {
    95  	field, ok := desc.(*parser.Field)
    96  	if !ok || field == nil {
    97  		return nil, nil, errors.New("target must be used on field")
    98  	}
    99  	name := m.decideNameCase(field)
   100  	for _, ann := range anns {
   101  		if len(ann.Values) != 1 {
   102  			return nil, nil, errors.New("source must have a value")
   103  		}
   104  		var val = strings.ToLower(ann.Values[0])
   105  		var key = "api."
   106  		switch val {
   107  		case "query":
   108  			key += "query"
   109  		case "header":
   110  			key += "header"
   111  		case "body":
   112  			key = "raw.body"
   113  		case "cookie":
   114  			key += "cookie"
   115  		case "post":
   116  			key += "form"
   117  		case "path":
   118  			key += "path"
   119  		case "raw_uri":
   120  			key += "raw_uri"
   121  		case "raw_body":
   122  			key += "raw_body"
   123  			// case "body_dynamic":
   124  			// 	key = "agw.body_dynamic"
   125  		case "not_body_struct":
   126  			key = "api.no_body_struct"
   127  		default:
   128  			continue
   129  		}
   130  		ret = append(ret, parser.Annotation{
   131  			Key:    key,
   132  			Values: []string{name},
   133  		})
   134  	}
   135  	return
   136  }
   137  
   138  func (self sourceMapper) decideNameCase(field *parser.Field) string {
   139  	name := field.Name
   140  	flag := false
   141  	// firstly check if there is api.keu annotation
   142  	for _, ann := range FindAnnotations(field.Annotations, APIKeyName, "agw.key") {
   143  		if len(ann.Values) == 1 && ann.Values[0] != "" {
   144  			name = ann.Values[0]
   145  			flag = true
   146  			break
   147  		}
   148  	}
   149  	// if not found, try use agw.to_snake or agw.to_lower_camel_case
   150  	if !flag {
   151  		anns := FindAnnotations(field.Annotations, NameCaseKeys...)
   152  		if len(anns) > 2 || len(anns) == 0 {
   153  			return name
   154  		}
   155  		var pre = ""
   156  		if len(anns) == 2 {
   157  			pre = anns[1].Key
   158  		}
   159  		c, _ := nameCaseMapper{}.decideNameCase(pre, anns[0].Key, anns[0].Values[0])
   160  		name = util.ConvertNameCase(c, name)
   161  	}
   162  	return name
   163  }
   164  
   165  type targetMapper struct{}
   166  
   167  func (m targetMapper) Map(ctx context.Context, anns []parser.Annotation, desc interface{}, opts thrift.Options) (ret []parser.Annotation, next []parser.Annotation, err error) {
   168  	field, ok := desc.(*parser.Field)
   169  	if !ok || field == nil {
   170  		return nil, nil, errors.New("target must be used on field")
   171  	}
   172  	name := sourceMapper{}.decideNameCase(field)
   173  	// handle normal xxx.target
   174  	for _, ann := range anns {
   175  		if len(ann.Values) != 1 {
   176  			return nil, nil, errors.New("target must have a value")
   177  		}
   178  
   179  		var val = strings.ToLower(ann.Values[0])
   180  		var key = "api."
   181  		switch val {
   182  		case "header":
   183  			key += "header"
   184  		case "body":
   185  			// directy set as body or write body content
   186  			key = "raw.body"
   187  		case "cookie":
   188  			key += "cookie"
   189  		case "http_code":
   190  			key += "http_code"
   191  		// case "body_dynamic":
   192  		// 	key = "agw.body_dynamic"
   193  		case "ignore":
   194  			key = thrift.AnnoKeyDynamicGoDeprecated
   195  		default:
   196  			continue
   197  		}
   198  		ret = append(ret, parser.Annotation{
   199  			Key:    key,
   200  			Values: []string{name},
   201  		})
   202  	}
   203  	return
   204  }
   205  
   206  func FindAnnotations(anns []*parser.Annotation, keys ...string) (ret []*parser.Annotation) {
   207  	for _, ann := range anns {
   208  		for _, key := range keys {
   209  			if ann.Key == key {
   210  				ret = append(ret, ann)
   211  			}
   212  		}
   213  	}
   214  	return
   215  }
   216  
   217  type nameCaseMapper struct{}
   218  
   219  func (m nameCaseMapper) Map(ctx context.Context, anns []parser.Annotation, desc interface{}, opts thrift.Options) (ret []parser.Annotation, next []parser.Annotation, err error) {
   220  	if len(anns) > 2 {
   221  		return nil, nil, errors.New("name case must be unique")
   222  	}
   223  	if len(anns[0].Values) != 1 {
   224  		return nil, nil, errors.New("name case must have one value")
   225  	}
   226  
   227  	// decide the name case based on both the previous and current annotations
   228  	cur := anns[0].Key
   229  	curval := anns[0].Values[0]
   230  	var pre string
   231  	if len(anns) == 2 {
   232  		pre = anns[1].Key
   233  	}
   234  	final, pkg := m.decideNameCase(pre, cur, curval)
   235  
   236  	var r parser.Annotation
   237  	if f, ok := desc.(*parser.Field); ok {
   238  		r.Key = APIKeyName
   239  		r.Values = []string{util.ConvertNameCase(final, f.Name)}
   240  		return []parser.Annotation{r}, nil, nil
   241  	} else {
   242  		r.Key = pkg + "." + m.caseToKey(final)
   243  		r.Values = []string{"true"}
   244  		return nil, []parser.Annotation{r}, nil
   245  	}
   246  }
   247  
   248  func (m nameCaseMapper) keyToCase(key string) (meta.NameCase, string) {
   249  	pkg, cur := util.SplitPrefix(key)
   250  	switch cur {
   251  	case "to_upper_camel_case":
   252  		return meta.CaseUpperCamel, pkg
   253  	case "to_lower_camel_case":
   254  		return meta.CaseLowerCamel, pkg
   255  	case "to_snake", "to_snake_case":
   256  		return meta.CaseSnake, pkg
   257  	default:
   258  		return meta.CaseDefault, pkg
   259  	}
   260  }
   261  
   262  func (m nameCaseMapper) caseToKey(c meta.NameCase) string {
   263  	switch c {
   264  	case meta.CaseUpperCamel:
   265  		return "to_upper_camel_case"
   266  	case meta.CaseLowerCamel:
   267  		return "to_lower_camel_case"
   268  	case meta.CaseSnake:
   269  		return "to_snake"
   270  	default:
   271  		return ""
   272  	}
   273  }
   274  
   275  func (m nameCaseMapper) valToEnable(val string) bool {
   276  	var enable = true
   277  	if val != "" {
   278  		enable, _ = strconv.ParseBool(val)
   279  	}
   280  	return enable
   281  }
   282  
   283  // decideNameCase decides the final name case based on the previous and current annotations.
   284  // current has higher priority than previous when it is true or is the same key.
   285  // otherwise, the previous annotation is used.
   286  func (m nameCaseMapper) decideNameCase(pre string, cur string, curval string) (meta.NameCase, string) {
   287  	pc, _ := m.keyToCase(pre)
   288  	cc, pkg := m.keyToCase(cur)
   289  	enable := m.valToEnable(curval)
   290  	if pc == cc {
   291  		if enable {
   292  			return cc, pkg
   293  		}
   294  		return meta.CaseDefault, pkg
   295  	} else {
   296  		if enable {
   297  			return cc, pkg
   298  		}
   299  		return pc, pkg
   300  	}
   301  }