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  )