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

     1  package transformer
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"io"
     7  	"net/textproto"
     8  	"reflect"
     9  	"strconv"
    10  
    11  	"github.com/machinefi/w3bstream/pkg/depends/kit/httptransport/httpx"
    12  	vldterr "github.com/machinefi/w3bstream/pkg/depends/kit/validator/errors"
    13  	"github.com/machinefi/w3bstream/pkg/depends/x/typesx"
    14  )
    15  
    16  func init() { DefaultFactory.Register(&JSON{}) }
    17  
    18  type JSON struct{}
    19  
    20  func (JSON) Names() []string { return []string{httpx.MIME_JSON, "json"} }
    21  
    22  func (JSON) NamedByTag() string { return "json" }
    23  
    24  func (t *JSON) String() string { return httpx.MIME_JSON }
    25  
    26  func (JSON) New(context.Context, typesx.Type) (Transformer, error) { return &JSON{}, nil }
    27  
    28  func (t *JSON) EncodeTo(ctx context.Context, w io.Writer, v interface{}) error {
    29  	if rv, ok := v.(reflect.Value); ok {
    30  		v = rv.Interface()
    31  	}
    32  
    33  	httpx.MaybeWriteHeader(ctx, w, t.String(), map[string]string{
    34  		"charset": "utf-8",
    35  	})
    36  
    37  	return json.NewEncoder(w).Encode(v)
    38  }
    39  
    40  func (JSON) DecodeFrom(ctx context.Context, r io.Reader, v interface{}, _ ...textproto.MIMEHeader) error {
    41  	if rv, ok := v.(reflect.Value); ok {
    42  		if rv.Kind() != reflect.Ptr && rv.CanAddr() {
    43  			rv = rv.Addr()
    44  		}
    45  		v = rv.Interface()
    46  	}
    47  
    48  	dec := json.NewDecoder(r)
    49  	if err := dec.Decode(v); err != nil {
    50  		return WrapLocationDecoderError(dec, err)
    51  	}
    52  	return nil
    53  }
    54  
    55  func WrapLocationDecoderError(dec *json.Decoder, err error) error {
    56  	switch e := err.(type) {
    57  	case *json.UnmarshalTypeError:
    58  		r := reflect.ValueOf(dec).Elem()
    59  		errs := vldterr.NewErrorSet()
    60  		errs.AddErr(e, location(r.Field(1 /* .buf */).Bytes(), int(e.Offset)))
    61  		return errs.Err()
    62  	case *json.SyntaxError:
    63  		return e
    64  	default:
    65  		r := reflect.ValueOf(dec).Elem()
    66  		// json.Decoder.d.off
    67  		offset := r.Field(2).Field(1).Int()
    68  		if offset > 0 {
    69  			errs := vldterr.NewErrorSet()
    70  			// json.Decoder.buf
    71  			errs.AddErr(e, location(r.Field(1).Bytes(), int(offset-1)))
    72  			return errs.Err()
    73  		}
    74  		return e
    75  	}
    76  }
    77  
    78  func location(data []byte, offset int) string {
    79  	i := 0
    80  	arrayPaths := map[string]bool{}
    81  	arrayIdxSet := map[string]int{}
    82  	pw := &PathWalker{}
    83  
    84  	markObjectKey := func() {
    85  		jsonKey, l := nextString(data[i:])
    86  		i += l
    87  
    88  		if i < int(offset) && len(jsonKey) > 0 {
    89  			key, _ := strconv.Unquote(string(jsonKey))
    90  			pw.Enter(key)
    91  		}
    92  	}
    93  
    94  	markArrayIdx := func(path string) {
    95  		if arrayPaths[path] {
    96  			arrayIdxSet[path]++
    97  		} else {
    98  			arrayPaths[path] = true
    99  		}
   100  		pw.Enter(arrayIdxSet[path])
   101  	}
   102  
   103  	for i < offset {
   104  		i += nextToken(data[i:])
   105  		char := data[i]
   106  
   107  		switch char {
   108  		case '"':
   109  			_, l := nextString(data[i:])
   110  			i += l
   111  		case '[', '{':
   112  			i++
   113  
   114  			if char == '[' {
   115  				markArrayIdx(pw.String())
   116  			} else {
   117  				markObjectKey()
   118  			}
   119  		case '}', ']', ',':
   120  			i++
   121  			pw.Exit()
   122  
   123  			if char == ',' {
   124  				path := pw.String()
   125  
   126  				if _, ok := arrayPaths[path]; ok {
   127  					markArrayIdx(path)
   128  				} else {
   129  					markObjectKey()
   130  				}
   131  			}
   132  		default:
   133  			i++
   134  		}
   135  	}
   136  
   137  	return pw.String()
   138  }
   139  
   140  func nextToken(data []byte) int {
   141  	for i, c := range data {
   142  		switch c {
   143  		case ' ', '\n', '\r', '\t':
   144  			continue
   145  		default:
   146  			return i
   147  		}
   148  	}
   149  	return -1
   150  }
   151  
   152  func nextString(data []byte) (finalData []byte, l int) {
   153  	quoteStartAt := -1
   154  	for i, c := range data {
   155  		switch c {
   156  		case '"':
   157  			if i > 0 && string(data[i-1]) == "\\" {
   158  				continue
   159  			}
   160  			if quoteStartAt >= 0 {
   161  				return data[quoteStartAt : i+1], i + 1
   162  			} else {
   163  				quoteStartAt = i
   164  			}
   165  		default:
   166  			continue
   167  		}
   168  	}
   169  	return nil, 0
   170  }