github.com/nikandfor/tlog@v0.21.5-0.20231108111739-3ef89426a96d/convert/json.go (about)

     1  package convert
     2  
     3  import (
     4  	"encoding/base64"
     5  	"errors"
     6  	"io"
     7  	"path/filepath"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/nikandfor/hacked/hfmt"
    12  	"github.com/nikandfor/loc"
    13  
    14  	"github.com/nikandfor/tlog"
    15  	"github.com/nikandfor/tlog/low"
    16  	"github.com/nikandfor/tlog/tlwire"
    17  )
    18  
    19  type (
    20  	JSON struct {
    21  		io.Writer
    22  
    23  		AppendNewLine bool
    24  		AppendKeySafe bool
    25  		TimeFormat    string
    26  		TimeZone      *time.Location
    27  
    28  		Rename RenameFunc
    29  
    30  		d tlwire.Decoder
    31  
    32  		b low.Buf
    33  	}
    34  
    35  	RenameFunc func(b, p, k []byte, st int) ([]byte, bool)
    36  
    37  	TagSub struct {
    38  		Tag byte
    39  		Sub int64
    40  	}
    41  
    42  	SimpleRenameRule struct {
    43  		Tags []TagSub
    44  		Key  string
    45  	}
    46  
    47  	SimpleRenamer struct {
    48  		tlwire.Decoder
    49  
    50  		Rules map[string]SimpleRenameRule
    51  
    52  		Fallback RenameFunc
    53  	}
    54  )
    55  
    56  func NewJSON(w io.Writer) *JSON {
    57  	return &JSON{
    58  		Writer:        w,
    59  		AppendNewLine: true,
    60  		AppendKeySafe: true,
    61  		TimeFormat:    time.RFC3339Nano,
    62  		TimeZone:      time.Local,
    63  	}
    64  }
    65  
    66  func (w *JSON) Write(p []byte) (i int, err error) {
    67  	b := w.b[:0]
    68  
    69  more:
    70  	tag, els, i := w.d.Tag(p, i)
    71  	if tag != tlwire.Map {
    72  		return i, errors.New("map expected")
    73  	}
    74  
    75  	b = append(b, '{')
    76  
    77  	var k []byte
    78  	for el := 0; els == -1 || el < int(els); el++ {
    79  		if els == -1 && w.d.Break(p, &i) {
    80  			break
    81  		}
    82  
    83  		if el != 0 {
    84  			b = append(b, ',')
    85  		}
    86  
    87  		b = append(b, '"')
    88  
    89  		k, i = w.d.Bytes(p, i)
    90  
    91  		var renamed bool
    92  
    93  		if w.Rename != nil {
    94  			b, renamed = w.Rename(b, p, k, i)
    95  		}
    96  
    97  		if !renamed {
    98  			if w.AppendKeySafe {
    99  				b = low.AppendSafe(b, k)
   100  			} else {
   101  				b = append(b, k...)
   102  			}
   103  		}
   104  
   105  		b = append(b, '"', ':')
   106  
   107  		b, i = w.ConvertValue(b, p, i)
   108  	}
   109  
   110  	b = append(b, '}')
   111  	if w.AppendNewLine {
   112  		b = append(b, '\n')
   113  	}
   114  
   115  	if i < len(p) {
   116  		goto more
   117  	}
   118  
   119  	w.b = b[:0]
   120  
   121  	_, err = w.Writer.Write(b)
   122  	if err != nil {
   123  		return 0, err
   124  	}
   125  
   126  	return len(p), nil
   127  }
   128  
   129  func (w *JSON) ConvertValue(b, p []byte, st int) (_ []byte, i int) {
   130  	tag, sub, i := w.d.Tag(p, st)
   131  
   132  	switch tag {
   133  	case tlwire.Int:
   134  		b = strconv.AppendUint(b, uint64(sub), 10)
   135  	case tlwire.Neg:
   136  		b = strconv.AppendInt(b, sub, 10)
   137  	case tlwire.Bytes:
   138  		b = append(b, '"')
   139  
   140  		m := base64.StdEncoding.EncodedLen(int(sub))
   141  		st := len(b)
   142  
   143  		for st+m < cap(b) {
   144  			b = append(b[:cap(b)], 0, 0, 0, 0)
   145  		}
   146  
   147  		b = b[:st+m]
   148  
   149  		base64.StdEncoding.Encode(b[st:], p[i:])
   150  
   151  		b = append(b, '"')
   152  
   153  		i += int(sub)
   154  	case tlwire.String:
   155  		b = append(b, '"')
   156  
   157  		b = low.AppendSafe(b, p[i:i+int(sub)])
   158  
   159  		b = append(b, '"')
   160  
   161  		i += int(sub)
   162  	case tlwire.Array:
   163  		b = append(b, '[')
   164  
   165  		for el := 0; sub == -1 || el < int(sub); el++ {
   166  			if sub == -1 && w.d.Break(p, &i) {
   167  				break
   168  			}
   169  
   170  			if el != 0 {
   171  				b = append(b, ',')
   172  			}
   173  
   174  			b, i = w.ConvertValue(b, p, i)
   175  		}
   176  
   177  		b = append(b, ']')
   178  	case tlwire.Map:
   179  		var k []byte
   180  
   181  		b = append(b, '{')
   182  
   183  		for el := 0; sub == -1 || el < int(sub); el++ {
   184  			if sub == -1 && w.d.Break(p, &i) {
   185  				break
   186  			}
   187  
   188  			if el != 0 {
   189  				b = append(b, ',')
   190  			}
   191  
   192  			k, i = w.d.Bytes(p, i)
   193  
   194  			b = append(b, '"')
   195  
   196  			if w.AppendKeySafe {
   197  				b = low.AppendSafe(b, k)
   198  			} else {
   199  				b = append(b, k...)
   200  			}
   201  
   202  			b = append(b, '"', ':')
   203  
   204  			b, i = w.ConvertValue(b, p, i)
   205  		}
   206  
   207  		b = append(b, '}')
   208  	case tlwire.Semantic:
   209  		switch sub {
   210  		case tlwire.Time:
   211  			var t time.Time
   212  			t, i = w.d.Time(p, st)
   213  
   214  			if w.TimeZone != nil {
   215  				t = t.In(w.TimeZone)
   216  			}
   217  
   218  			if w.TimeFormat != "" {
   219  				b = append(b, '"')
   220  				b = t.AppendFormat(b, w.TimeFormat)
   221  				b = append(b, '"')
   222  			} else {
   223  				b = strconv.AppendInt(b, t.UnixNano(), 10)
   224  			}
   225  		case tlog.WireID:
   226  			var id tlog.ID
   227  			i = id.TlogParse(p, st)
   228  
   229  			bst := len(b) + 1
   230  			b = append(b, `"123456789_123456789_123456789_12"`...)
   231  
   232  			id.FormatTo(b[bst:], 'x')
   233  		case tlwire.Caller:
   234  			var pc loc.PC
   235  			var pcs loc.PCs
   236  			pc, pcs, i = w.d.Callers(p, st)
   237  
   238  			if pcs != nil {
   239  				b = append(b, '[')
   240  				for i, pc := range pcs {
   241  					if i != 0 {
   242  						b = append(b, ',')
   243  					}
   244  
   245  					_, file, line := pc.NameFileLine()
   246  					b = hfmt.Appendf(b, `"%v:%d"`, filepath.Base(file), line)
   247  				}
   248  				b = append(b, ']')
   249  			} else {
   250  				_, file, line := pc.NameFileLine()
   251  
   252  				b = hfmt.Appendf(b, `"%v:%d"`, filepath.Base(file), line)
   253  			}
   254  		default:
   255  			b, i = w.ConvertValue(b, p, i)
   256  		}
   257  	case tlwire.Special:
   258  		switch sub {
   259  		case tlwire.False:
   260  			b = append(b, "false"...)
   261  		case tlwire.True:
   262  			b = append(b, "true"...)
   263  		case tlwire.Nil, tlwire.Undefined, tlwire.None, tlwire.Hidden, tlwire.SelfRef:
   264  			b = append(b, "null"...)
   265  		case tlwire.Float64, tlwire.Float32, tlwire.Float16, tlwire.Float8:
   266  			var f float64
   267  			f, i = w.d.Float(p, st)
   268  
   269  			b = strconv.AppendFloat(b, f, 'f', -1, 64)
   270  		default:
   271  			panic(sub)
   272  		}
   273  	}
   274  
   275  	return b, i
   276  }
   277  
   278  func (r SimpleRenamer) Rename(b, p, k []byte, i int) ([]byte, bool) {
   279  	rule, ok := r.Rules[string(k)]
   280  	if !ok {
   281  		return r.fallback(b, p, k, i)
   282  	}
   283  
   284  	for _, ts := range rule.Tags {
   285  		tag, sub, j := r.Tag(p, i)
   286  
   287  		if tag != tlwire.Semantic && tag != tlwire.Special {
   288  			sub = 0
   289  		}
   290  
   291  		if ts != (TagSub{tag, sub}) {
   292  			return r.fallback(b, p, k, i)
   293  		}
   294  
   295  		i = j
   296  	}
   297  
   298  	return append(b, rule.Key...), true
   299  }
   300  
   301  func (r SimpleRenamer) fallback(b, p, k []byte, i int) ([]byte, bool) {
   302  	if r.Fallback == nil {
   303  		return b, false
   304  	}
   305  
   306  	return r.Fallback(b, p, k, i)
   307  }