github.com/cloudwego/dynamicgo@v0.2.6-0.20240519101509-707f41b6b834/thrift/annotation.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 thrift 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 24 "github.com/cloudwego/dynamicgo/http" 25 "github.com/cloudwego/dynamicgo/meta" 26 "github.com/cloudwego/thriftgo/parser" 27 ) 28 29 //--------------------------------- Annotation Interface ---------------------------------- 30 31 // Annotation idl annotation interface 32 type Annotation interface { 33 // unique id of the Annotation 34 ID() AnnoID 35 36 // Make makes the handler function under specific values and idl description 37 // 38 // desc is specific type to its registered AnnoScope: 39 // AnnoScopeService: desc is *parser.Service 40 // AnnoScopeFunction: desc is *parser.Function 41 // AnnoScopeStruct: desc is *parser.StructLike 42 // AnnoScopeField: desc is *parser.Field 43 // 44 // returned handler SHOULD BE one interface according to its AnnoKind: 45 // AnnoKindHttpMappping: HTTPMapping interface 46 // AnnoKindKeyMapping: KeyMapping interface 47 // AnnoKindKeyMapping: ValueMapping interface 48 // AnnoKindOptionMapping: OptionMapping interface 49 Make(ctx context.Context, values []parser.Annotation, desc interface{}) (handler interface{}, err error) 50 } 51 52 // AnnoID is the unique id of an annotation, which is composed of kind, scope and type: 53 // 54 // 0xff000000: AnnoKind 55 // 0x00ff0000: AnnoScope 56 // 0x0000ffff: AnnoType 57 type AnnoID uint32 58 59 func MakeAnnoID(kind AnnoKind, scope AnnoScope, typ AnnoType) AnnoID { 60 return AnnoID((uint32(kind) << 24) | (uint32(scope) << 16) | uint32(typ)) 61 } 62 63 // AnnoKind is the kind of annotation, 64 // which defines the result handler of Annotation.Make() 65 type AnnoKind uint8 66 67 const ( 68 // AnnoKindHttpMappping is the kind of http mapping annotation 69 // These annotations Make() will return HTTPMapping 70 AnnoKindHttpMappping AnnoKind = iota + 1 71 72 // AnnotationKindKeyMapping is the kind of key mapping annotation 73 // These annotations Make() will return KeyMapping 74 AnnoKindValueMapping 75 76 // AnnotationKindValueMapping is the kind of value mapping annotation 77 // These annotations Make() will return ValueMapping 78 AnnoKindOptionMapping 79 80 // AnnotationKindOptionMapping is the kind of option mapping annotation 81 // These annotations Make() will return OptionMapping 82 AnnoKindKeyMapping 83 ) 84 85 // Kind returns the kind of the annotation 86 func (t AnnoID) Kind() AnnoKind { 87 return AnnoKind(t >> 24) 88 } 89 90 // AnnoScope is effective scope of annotation 91 type AnnoScope uint8 92 93 const ( 94 // AnnoScopeService works on service description 95 AnnoScopeService AnnoScope = iota + 1 96 97 // AnnoScopeFunction works on function description 98 AnnoScopeFunction 99 100 // AnnoScopeStruct works on struct description 101 AnnoScopeStruct 102 103 // AnnoScopeField works on field description 104 AnnoScopeField 105 ) 106 107 // Scope returns the scope of the annotation 108 func (t AnnoID) Scope() AnnoScope { 109 return AnnoScope((t >> 16) & (0x00ff)) 110 } 111 112 // AnnoType is the specific type of an annotation 113 type AnnoType uint16 114 115 // Type returns the type of the annotation 116 func (t AnnoID) Type() AnnoType { 117 return AnnoType(t & 0xffff) 118 } 119 120 // OptionMapping is used to convert thrift.Options while parsing idl. 121 // See also: thrift/annotation/option_mapping.go 122 type OptionMapping interface { 123 // Map options to new options 124 Map(ctx context.Context, opts Options) Options 125 } 126 127 // ValueMapping is used to convert thrift value while running convertion. 128 // See also: thrift/annotation/value_mapping.go 129 type ValueMapping interface { 130 // Read thrift value from p and convert it into out 131 Read(ctx context.Context, p *BinaryProtocol, field *FieldDescriptor, out *[]byte) error 132 133 // Write thrift value into p, which is converted from in 134 Write(ctx context.Context, p *BinaryProtocol, field *FieldDescriptor, in []byte) error 135 } 136 137 // HTTPMapping is used to convert http value while running convertion. 138 // See also: thrift/annotation/http_mapping.go 139 type HttpMapping interface { 140 // Request get a http value from req 141 Request(ctx context.Context, req http.RequestGetter, field *FieldDescriptor) (string, error) 142 143 // Response set a http value into resp 144 Response(ctx context.Context, resp http.ResponseSetter, field *FieldDescriptor, val string) error 145 146 // RawEncoding indicates the encoding of the value, it should be meta.EncodingText by default 147 Encoding() meta.Encoding 148 } 149 150 // KeyMapping is used to convert field key while parsing idl. 151 // See also: thrift/annotation/key_mapping.go 152 type KeyMapping interface { 153 // Map key to new key 154 Map(ctx context.Context, key string) string 155 } 156 157 //--------------------------------- Thrid-party Register ---------------------------------- 158 159 var annotations = map[string]map[AnnoScope]Annotation{} 160 161 // RegisterAnnotation register an annotation on specific AnnoScope 162 func RegisterAnnotation(an Annotation, keys ...string) { 163 for _, key := range keys { 164 key = strings.ToLower(key) 165 m := annotations[key] 166 if m == nil { 167 m = make(map[AnnoScope]Annotation) 168 annotations[key] = m 169 } 170 m[an.ID().Scope()] = an 171 } 172 } 173 174 func FindAnnotation(key string, scope AnnoScope) Annotation { 175 key = strings.ToLower(key) 176 m := annotations[key] 177 if m == nil { 178 return nil 179 } 180 return m[scope] 181 } 182 183 // makeAnnotation search an annotation by given key+scope and make its handler 184 func makeAnnotation(ctx context.Context, anns []parser.Annotation, scope AnnoScope, desc interface{}) (interface{}, AnnoID, error) { 185 if len(anns) == 0 { 186 return nil, 0, nil 187 } 188 ann := FindAnnotation(anns[0].Key, scope) 189 if ann == nil { 190 return nil, 0, nil 191 } 192 if handler, err := ann.Make(ctx, anns, desc); err != nil { 193 return nil, 0, err 194 } else { 195 return handler, ann.ID(), nil 196 } 197 } 198 199 // AnnotationMapper is used to convert a annotation to equivalent annotations 200 // desc is specific to its registered AnnoScope: 201 // 202 // AnnoScopeService: desc is *parser.Service 203 // AnnoScopeFunction: desc is *parser.Function 204 // AnnoScopeStruct: desc is *parser.StructLike 205 // AnnoScopeField: desc is *parser.Field 206 type AnnotationMapper interface { 207 // Map map a annotation to equivalent annotations 208 Map(ctx context.Context, ann []parser.Annotation, desc interface{}, opt Options) (cur []parser.Annotation, next []parser.Annotation, err error) 209 } 210 211 var annotationMapper = map[string]map[AnnoScope]AnnotationMapper{} 212 213 // RegisterAnnotationMapper register a annotation mapper on specific scope 214 func RegisterAnnotationMapper(scope AnnoScope, mapper AnnotationMapper, keys ...string) { 215 for _, key := range keys { 216 m := annotationMapper[key] 217 if m == nil { 218 m = make(map[AnnoScope]AnnotationMapper) 219 annotationMapper[key] = m 220 } 221 m[scope] = mapper 222 } 223 } 224 225 func FindAnnotationMapper(key string, scope AnnoScope) AnnotationMapper { 226 m := annotationMapper[key] 227 if m == nil { 228 return nil 229 } 230 return m[scope] 231 } 232 233 func RemoveAnnotationMapper(scope AnnoScope, keys ...string) { 234 for _, key := range keys { 235 m := annotationMapper[key] 236 if m != nil { 237 if _, ok := m[scope]; ok { 238 delete(m, scope) 239 } 240 } 241 } 242 } 243 244 //------------------------------- IDL processing logic ------------------------------- 245 246 const ( 247 // AnnoKeyDynamicGoDeprecated is used to mark a description as deprecated 248 AnnoKeyDynamicGoDeprecated = "dynamicgo.deprecated" 249 // AnnoKeyDynamicGoApiNone is used to deal with http response field with api.none annotation 250 AnnoKeyDynamicGoApiNone = "api.none" 251 ) 252 253 type amPair struct { 254 cont []parser.Annotation 255 inter AnnotationMapper 256 } 257 258 type amPairs []amPair 259 260 func (p *amPairs) Add(con parser.Annotation, inter AnnotationMapper) { 261 for i := range *p { 262 if (*p)[i].inter == inter { 263 (*p)[i].cont = append((*p)[i].cont, con) 264 return 265 } 266 } 267 *p = append(*p, amPair{ 268 cont: []parser.Annotation{con}, 269 inter: inter, 270 }) 271 } 272 273 func mapAnnotations(ctx context.Context, as parser.Annotations, scope AnnoScope, desc interface{}, opt Options) (ret []annoPair, left []parser.Annotation, next []parser.Annotation, err error) { 274 con := make(amPairs, 0, len(as)) 275 cur := make([]parser.Annotation, 0, len(as)) 276 // try find mapper 277 for _, a := range as { 278 if mapper := FindAnnotationMapper(a.Key, scope); mapper != nil { 279 con.Add(*a, mapper) 280 } else { 281 // no mapper found, just append it to the result 282 cur = append(cur, *a) 283 } 284 } 285 // process all the annotations under the mapper 286 for _, a := range con { 287 if c, n, err := a.inter.Map(ctx, a.cont, desc, opt); err != nil { 288 return nil, nil, nil, err 289 } else { 290 cur = append(cur, c...) 291 next = n 292 } 293 } 294 m, left := mergeAnnotations(cur, scope) 295 return m, left, next, nil 296 } 297 298 type annoPair struct { 299 cont []parser.Annotation 300 inter Annotation 301 } 302 303 type annoPairs []annoPair 304 305 func (p *annoPairs) Add(con parser.Annotation, inter Annotation) { 306 for i := range *p { 307 if (*p)[i].inter == inter { 308 (*p)[i].cont = append((*p)[i].cont, con) 309 return 310 } 311 } 312 *p = append(*p, annoPair{ 313 cont: []parser.Annotation{con}, 314 inter: inter, 315 }) 316 } 317 318 func mergeAnnotations(in []parser.Annotation, scope AnnoScope) ([]annoPair, []parser.Annotation) { 319 m := make(annoPairs, 0, len(in)) 320 left := make([]parser.Annotation, 0, len(in)) 321 for _, a := range in { 322 if ann := FindAnnotation(a.Key, scope); ann != nil { 323 m.Add(a, ann) 324 } else { 325 left = append(left, a) 326 } 327 } 328 return m, left 329 } 330 331 func handleAnnotation(ctx context.Context, scope AnnoScope, ann Annotation, values []parser.Annotation, opts *Options, desc interface{}) error { 332 handle, err := ann.Make(ctx, values, desc) 333 if err != nil { 334 return err 335 } 336 if handle != nil { 337 switch ann.ID().Kind() { 338 case AnnoKindOptionMapping: 339 om, ok := handle.(OptionMapping) 340 if !ok { 341 return fmt.Errorf("annotation %#v for %d is not OptionMaker", handle, ann.ID()) 342 } 343 *opts = om.Map(ctx, *opts) 344 return nil 345 default: 346 //NOTICE: ignore unsupported annotations 347 return fmt.Errorf("unsupported annotation %q", ann.ID()) 348 } 349 } 350 return nil 351 } 352 353 func handleFieldAnnotation(ctx context.Context, ann Annotation, values []parser.Annotation, opts *Options, field *FieldDescriptor, st *StructDescriptor, desc *parser.Field) error { 354 handle, err := ann.Make(ctx, values, desc) 355 if err != nil { 356 return err 357 } 358 if handle != nil { 359 switch ann.ID().Kind() { 360 case AnnoKindOptionMapping: 361 om, ok := handle.(OptionMapping) 362 if !ok { 363 return fmt.Errorf("annotation %#v for %d is not OptionMaker", handle, ann.ID()) 364 } 365 *opts = om.Map(ctx, *opts) 366 return nil 367 case AnnoKindHttpMappping: 368 hm, ok := handle.(HttpMapping) 369 if !ok { 370 return fmt.Errorf("annotation %#v for %d is not HTTPMapping", handle, ann.ID()) 371 } 372 field.httpMappings = append(field.httpMappings, hm) 373 st.addHttpMappingField(field) 374 return nil 375 case AnnoKindValueMapping: 376 vm, ok := handle.(ValueMapping) 377 if !ok { 378 return fmt.Errorf("annotation %#v for %d is not ValueMapping", handle, ann.ID()) 379 } 380 if field.valueMapping != nil { 381 return fmt.Errorf("one field can only has one ValueMapping!") 382 } 383 field.valueMapping = vm 384 field.valueMappingType = ann.ID().Type() 385 case AnnoKindKeyMapping: 386 km, ok := handle.(KeyMapping) 387 if !ok { 388 return fmt.Errorf("annotation %#v for %d is not KeyMapping", handle, ann.ID()) 389 } 390 field.alias = km.Map(ctx, field.alias) 391 default: 392 //NOTICE: ignore unsupported annotations 393 return fmt.Errorf("unsupported annotation %d", ann.ID()) 394 } 395 } 396 return nil 397 } 398 399 func handleNativeFieldAnnotation(ann parser.Annotation, f *FieldDescriptor, parseTarget ParseTarget) (skip bool, err error) { 400 switch ann.GetKey() { 401 case AnnoKeyDynamicGoDeprecated: 402 { 403 return true, nil 404 } 405 case AnnoKeyDynamicGoApiNone: 406 if parseTarget == Response { 407 return true, nil 408 } 409 } 410 return false, nil 411 } 412 413 // copyAnnotations copy annotations from parser to descriptor 414 // WARN: this MUST be used before passing any annotation from parser to descriptor 415 func copyAnnotationValues(anns []*parser.Annotation) []parser.Annotation { 416 ret := make([]parser.Annotation, len(anns)) 417 for i, ann := range anns { 418 tmp := make([]string, len(ann.Values)) 419 copy(tmp, ann.Values) 420 ret[i].Values = tmp 421 ret[i].Key = ann.Key 422 } 423 return ret 424 } 425 426 // NameSpaceAnnotationKey is used for Option.PutNameSpaceToAnnotation 427 const NameSpaceAnnotationKey = "thrift.name_space" 428 429 func extractNameSpaceToAnnos(ast *parser.Thrift) parser.Annotation { 430 ret := parser.Annotation{ 431 Key: NameSpaceAnnotationKey, 432 } 433 for _, s := range ast.Namespaces { 434 if s == nil { 435 continue 436 } 437 ret.Values = append(ret.Values, s.Language) 438 ret.Values = append(ret.Values, s.Name) 439 } 440 return ret 441 } 442 443 // injectAnnotation injects next annotation by appending. 444 // NOTICE: the next annotation will be appended to the end of the current annotation. 445 func injectAnnotations(origin *[]*parser.Annotation, next []parser.Annotation) error { 446 if len(next) > 0 { 447 off := len(*origin) 448 tmp := make([]*parser.Annotation, off+len(next)) 449 copy(tmp, *origin) 450 for i := off; i < len(tmp); i++ { 451 tmp[i] = &next[i-off] 452 } 453 *origin = tmp 454 } 455 return nil 456 } 457 458 var ( 459 ctxIsBodyRoot = "isBodyRoot" 460 CtxKeyIsBodyRoot = &ctxIsBodyRoot 461 )