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 }