github.com/machinefi/w3bstream@v1.6.5-rc9.0.20240426031326-b8c7c4876e72/pkg/depends/kit/httptransport/transformer/tsfm.go (about)

     1  package transformer
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"net/textproto"
     7  	"net/url"
     8  	"reflect"
     9  	"sort"
    10  
    11  	"github.com/pkg/errors"
    12  
    13  	"github.com/machinefi/w3bstream/pkg/depends/x/contextx"
    14  	"github.com/machinefi/w3bstream/pkg/depends/x/mapx"
    15  	"github.com/machinefi/w3bstream/pkg/depends/x/typesx"
    16  )
    17  
    18  type Transformer interface {
    19  	Names() []string
    20  	New(context.Context, typesx.Type) (Transformer, error)
    21  	EncodeTo(context.Context, io.Writer, any) error
    22  	DecodeFrom(context.Context, io.Reader, any, ...textproto.MIMEHeader) error
    23  }
    24  
    25  type Option struct {
    26  	Name string
    27  	MIME string
    28  	CommonOption
    29  }
    30  
    31  func (o Option) String() string {
    32  	values := url.Values{}
    33  	if o.Name != "" {
    34  		values.Add("Name", o.Name)
    35  	}
    36  	if o.MIME != "" {
    37  		values.Add("MIME", o.MIME)
    38  	}
    39  	if o.Omitempty {
    40  		values.Add("Omitempty", "true")
    41  	}
    42  	if o.Explode {
    43  		values.Add("Explode", "true")
    44  	}
    45  	return values.Encode()
    46  }
    47  
    48  type CommonOption struct {
    49  	Omitempty bool // Omitempty should be ignored when value is empty
    50  	Explode   bool // Explode for raw uint8/byte slice/array
    51  }
    52  
    53  type TsfmAndOption struct {
    54  	Transformer
    55  	Option Option
    56  }
    57  
    58  type Factory interface {
    59  	NewTransformer(context.Context, typesx.Type, Option) (Transformer, error)
    60  }
    61  
    62  type ckFactory struct{}
    63  
    64  func ContextWithFactory(ctx context.Context, f Factory) context.Context {
    65  	return contextx.WithValue(ctx, ckFactory{}, f)
    66  }
    67  
    68  func FactoryFromContext(ctx context.Context) Factory {
    69  	if f, ok := ctx.Value(ckFactory{}).(Factory); ok {
    70  		return f
    71  	}
    72  	return DefaultFactory
    73  }
    74  
    75  func NewTransformer(ctx context.Context, t typesx.Type, opt Option) (Transformer, error) {
    76  	return FactoryFromContext(ctx).NewTransformer(ctx, t, opt)
    77  }
    78  
    79  type factory struct {
    80  	set   map[string]Transformer
    81  	cache *mapx.Map[string, Transformer]
    82  }
    83  
    84  var DefaultFactory = &factory{}
    85  
    86  func NewFactory() *factory {
    87  	return &factory{
    88  		set:   make(map[string]Transformer),
    89  		cache: mapx.New[string, Transformer](),
    90  	}
    91  }
    92  
    93  func (f *factory) Register(tsfms ...Transformer) {
    94  	if f.set == nil {
    95  		f.set = map[string]Transformer{}
    96  	}
    97  	if f.cache == nil {
    98  		f.cache = mapx.New[string, Transformer]()
    99  	}
   100  	for _, t := range tsfms {
   101  		for _, name := range t.Names() {
   102  			f.set[name] = t
   103  		}
   104  	}
   105  }
   106  
   107  func (f *factory) NewTransformer(ctx context.Context, t typesx.Type, opt Option) (Transformer, error) {
   108  	if ctx == nil {
   109  		ctx = context.Background()
   110  	}
   111  
   112  	key := typesx.FullTypeName(t) + opt.String()
   113  
   114  	if v, ok := f.cache.Load(key); ok {
   115  		return v.(Transformer), nil
   116  	}
   117  
   118  	if opt.MIME == "" {
   119  		indirect := typesx.DeRef(t)
   120  
   121  		switch indirect.Kind() {
   122  		case reflect.Slice:
   123  			elem := indirect.Elem()
   124  			if elem.PkgPath() == "" && elem.Kind() == reflect.Uint8 {
   125  				opt.MIME = "plain" // bytes
   126  			} else {
   127  				opt.MIME = "json"
   128  			}
   129  		case reflect.Struct:
   130  			// *mime/multipart.FileHeader
   131  			if indirect.PkgPath() == "mime/multipart" && indirect.Name() == "FileHeader" {
   132  				opt.MIME = "octet-stream"
   133  			} else {
   134  				opt.MIME = "json"
   135  			}
   136  		case reflect.Map, reflect.Array:
   137  			opt.MIME = "json"
   138  		default:
   139  			opt.MIME = "plain"
   140  		}
   141  
   142  		if _, ok := typesx.EncodingTextMarshalerTypeReplacer(t); ok {
   143  			opt.MIME = "plain"
   144  		}
   145  	}
   146  
   147  	if ct, ok := f.set[opt.MIME]; ok {
   148  		tsf, err := ct.New(ContextWithFactory(ctx, f), t)
   149  		if err != nil {
   150  			return nil, err
   151  		}
   152  		f.cache.Store(key, tsf)
   153  		return tsf, nil
   154  	}
   155  	return nil, errors.Errorf("fmt %s is not supported for content transformer", key)
   156  }
   157  
   158  // Transformers returns all tsfms registered to DefaultFactory
   159  func Transformers() (ret []string) {
   160  	names := make(map[string]bool)
   161  	for _, tf := range DefaultFactory.set {
   162  		name := tf.Names()[0]
   163  		if s, ok := tf.(CanString); ok {
   164  			name = s.String()
   165  		}
   166  		if _, ok := names[name]; !ok {
   167  			names[name] = true
   168  			ret = append(ret, name)
   169  		}
   170  	}
   171  	sort.Strings(ret)
   172  	return ret
   173  }