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 }