model/marshal.go (598 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package model // import "go.elastic.co/apm/v2/model"
import (
"encoding/hex"
"encoding/json"
"net/http"
"net/url"
"sort"
"strings"
"time"
"github.com/pkg/errors"
"go.elastic.co/apm/v2/internal/apmstrings"
"go.elastic.co/fastjson"
)
//go:generate sh generate.sh
// MarshalFastJSON writes the JSON representation of t to w.
func (t Time) MarshalFastJSON(w *fastjson.Writer) error {
w.Int64(time.Time(t).UnixNano() / int64(time.Microsecond))
return nil
}
// UnmarshalJSON unmarshals the JSON data into t.
func (t *Time) UnmarshalJSON(data []byte) error {
var usec int64
if err := json.Unmarshal(data, &usec); err != nil {
return err
}
*t = Time(time.Unix(usec/1000000, (usec%1000000)*1000).UTC())
return nil
}
// UnmarshalJSON unmarshals the JSON data into v.
func (v *HTTPSpanContext) UnmarshalJSON(data []byte) error {
var httpSpanContext struct {
URL string
StatusCode int `json:"status_code"`
}
if err := json.Unmarshal(data, &httpSpanContext); err != nil {
return err
}
u, err := url.Parse(httpSpanContext.URL)
if err != nil {
return err
}
v.URL = u
v.StatusCode = httpSpanContext.StatusCode
return nil
}
// MarshalFastJSON writes the JSON representation of v to w.
func (v *HTTPSpanContext) MarshalFastJSON(w *fastjson.Writer) error {
w.RawByte('{')
first := true
if v.URL != nil {
beforeURL := w.Size()
w.RawString(`"url":"`)
if v.marshalURL(w) {
w.RawByte('"')
first = false
} else {
w.Rewind(beforeURL)
}
}
if v.StatusCode > 0 {
if !first {
w.RawByte(',')
}
w.RawString(`"status_code":`)
w.Int64(int64(v.StatusCode))
}
w.RawByte('}')
return nil
}
func (v *HTTPSpanContext) marshalURL(w *fastjson.Writer) bool {
if v.URL.Scheme != "" {
if !marshalScheme(w, v.URL.Scheme) {
return false
}
w.RawString("://")
} else {
w.RawString("http://")
}
w.StringContents(v.URL.Host)
if v.URL.Path == "" {
w.RawByte('/')
} else {
if v.URL.Path[0] != '/' {
w.RawByte('/')
}
w.StringContents(v.URL.Path)
}
if v.URL.RawQuery != "" {
w.RawByte('?')
w.StringContents(v.URL.RawQuery)
}
if v.URL.Fragment != "" {
w.RawByte('#')
w.StringContents(v.URL.Fragment)
}
return true
}
// MarshalFastJSON writes the JSON representation of v to w.
func (v *URL) MarshalFastJSON(w *fastjson.Writer) error {
w.RawByte('{')
first := true
if v.Hash != "" {
const prefix = ",\"hash\":"
if first {
first = false
w.RawString(prefix[1:])
} else {
w.RawString(prefix)
}
w.String(v.Hash)
}
if v.Hostname != "" {
const prefix = ",\"hostname\":"
if first {
first = false
w.RawString(prefix[1:])
} else {
w.RawString(prefix)
}
w.String(v.Hostname)
}
if v.Path != "" {
const prefix = `,"pathname":"`
if first {
first = false
w.RawString(prefix[1:])
} else {
w.RawString(prefix)
}
if v.Path[0] != '/' {
w.RawByte('/')
}
w.StringContents(v.Path)
w.RawByte('"')
}
if v.Port != "" {
const prefix = ",\"port\":"
if first {
first = false
w.RawString(prefix[1:])
} else {
w.RawString(prefix)
}
w.String(v.Port)
}
schemeBegin := -1
schemeEnd := -1
if v.Protocol != "" {
before := w.Size()
const prefix = ",\"protocol\":\""
if first {
first = false
w.RawString(prefix[1:])
} else {
w.RawString(prefix)
}
schemeBegin = w.Size()
if marshalScheme(w, v.Protocol) {
schemeEnd = w.Size()
w.RawByte('"')
} else {
w.Rewind(before)
}
}
if v.Search != "" {
const prefix = ",\"search\":"
if first {
first = false
w.RawString(prefix[1:])
} else {
w.RawString(prefix)
}
w.String(v.Search)
}
if schemeEnd != -1 && v.Hostname != "" {
before := w.Size()
w.RawString(",\"full\":")
if !v.marshalFullURL(w, w.Bytes()[schemeBegin:schemeEnd]) {
w.Rewind(before)
}
}
w.RawByte('}')
return nil
}
func marshalScheme(w *fastjson.Writer, scheme string) bool {
// Canonicalize the scheme to lowercase. Don't use
// strings.ToLower, as it's too general and requires
// additional memory allocations.
//
// The scheme should start with a letter, and may
// then be followed by letters, digits, '+', '-',
// and '.'. We don't validate the scheme here, we
// just use those restrictions as a basis for
// optimization; anything not in that set will
// mean the full URL is omitted.
for i := 0; i < len(scheme); i++ {
c := scheme[i]
switch {
case c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '+' || c == '-' || c == '.':
w.RawByte(c)
case c >= 'A' && c <= 'Z':
w.RawByte(c + 'a' - 'A')
default:
return false
}
}
return true
}
func (v *URL) marshalFullURL(w *fastjson.Writer, scheme []byte) bool {
w.RawByte('"')
before := w.Size()
w.RawBytes(scheme)
w.RawString("://")
const maxRunes = 1024
runes := w.Size() - before // scheme is known to be all single-byte runes
if runes >= maxRunes {
// Pathological case, scheme >= 1024 runes.
w.Rewind(before + maxRunes)
w.RawByte('"')
return true
}
// Track how many runes we encode, and stop once we've hit the limit.
rawByte := func(v byte) {
if runes == maxRunes {
return
}
w.RawByte(v)
runes++
}
stringContents := func(v string) {
remaining := maxRunes - runes
truncated, n := apmstrings.Truncate(v, remaining)
if n > 0 {
w.StringContents(truncated)
runes += n
}
}
if strings.IndexByte(v.Hostname, ':') == -1 {
stringContents(v.Hostname)
} else {
rawByte('[')
stringContents(v.Hostname)
rawByte(']')
}
if v.Port != "" {
rawByte(':')
stringContents(v.Port)
}
if v.Path != "" {
if !strings.HasPrefix(v.Path, "/") {
rawByte('/')
}
stringContents(v.Path)
}
if v.Search != "" {
rawByte('?')
stringContents(v.Search)
}
if v.Hash != "" {
rawByte('#')
stringContents(v.Hash)
}
w.RawByte('"')
return true
}
func (l *Log) isZero() bool {
return l.Message == ""
}
func (e *Exception) isZero() bool {
return e.Message == ""
}
func (c Cookies) isZero() bool {
return len(c) == 0
}
// MarshalFastJSON writes the JSON representation of c to w.
func (c Cookies) MarshalFastJSON(w *fastjson.Writer) error {
w.RawByte('{')
first := true
outer:
for i := len(c) - 1; i >= 0; i-- {
for j := i + 1; j < len(c); j++ {
if c[i].Name == c[j].Name {
continue outer
}
}
if first {
first = false
} else {
w.RawByte(',')
}
w.String(c[i].Name)
w.RawByte(':')
w.String(c[i].Value)
}
w.RawByte('}')
return nil
}
// UnmarshalJSON unmarshals the JSON data into c.
func (c *Cookies) UnmarshalJSON(data []byte) error {
m := make(map[string]string)
if err := json.Unmarshal(data, &m); err != nil {
return err
}
*c = make([]*http.Cookie, 0, len(m))
for k, v := range m {
*c = append(*c, &http.Cookie{
Name: k,
Value: v,
})
}
sort.Slice(*c, func(i, j int) bool {
return (*c)[i].Name < (*c)[j].Name
})
return nil
}
func (hs Headers) isZero() bool {
return len(hs) == 0
}
// MarshalFastJSON writes the JSON representation of h to w.
func (hs Headers) MarshalFastJSON(w *fastjson.Writer) error {
w.RawByte('{')
for i, h := range hs {
if i != 0 {
w.RawByte(',')
}
w.String(h.Key)
w.RawByte(':')
if len(h.Values) == 1 {
// Just one item, add the item directly.
w.String(h.Values[0])
} else {
// Zero or multiple items, include them all.
w.RawByte('[')
for i, v := range h.Values {
if i != 0 {
w.RawByte(',')
}
w.String(v)
}
w.RawByte(']')
}
}
w.RawByte('}')
return nil
}
// MarshalFastJSON writes the JSON representation of h to w.
func (*Header) MarshalFastJSON(w *fastjson.Writer) error {
panic("unreachable")
}
// UnmarshalJSON unmarshals the JSON data into c.
func (hs *Headers) UnmarshalJSON(data []byte) error {
var m map[string]interface{}
if err := json.Unmarshal(data, &m); err != nil {
return err
}
for k, v := range m {
switch v := v.(type) {
case string:
*hs = append(*hs, Header{Key: k, Values: []string{v}})
case []interface{}:
var values []string
for _, v := range v {
switch v := v.(type) {
case string:
values = append(values, v)
default:
return errors.Errorf("expected string, got %T", v)
}
}
*hs = append(*hs, Header{Key: k, Values: values})
default:
return errors.Errorf("expected string or []string, got %T", v)
}
}
sort.Slice(*hs, func(i, j int) bool {
return (*hs)[i].Key < (*hs)[j].Key
})
return nil
}
// MarshalFastJSON writes the JSON representation of c to w.
func (c *ExceptionCode) MarshalFastJSON(w *fastjson.Writer) error {
if c.String != "" {
w.String(c.String)
} else {
w.Float64(c.Number)
}
return nil
}
// UnmarshalJSON unmarshals the JSON data into c.
func (c *ExceptionCode) UnmarshalJSON(data []byte) error {
var v interface{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
switch v := v.(type) {
case string:
c.String = v
case float64:
c.Number = v
default:
return errors.Errorf("expected string or number, got %T", v)
}
return nil
}
// isZero is used by fastjson to implement omitempty.
func (c *ExceptionCode) isZero() bool {
return c.String == "" && c.Number == 0
}
// MarshalFastJSON writes the JSON representation of b to w.
func (b *RequestBody) MarshalFastJSON(w *fastjson.Writer) error {
if b.Form != nil {
w.RawByte('{')
first := true
for k, v := range b.Form {
if first {
first = false
} else {
w.RawByte(',')
}
w.String(k)
w.RawByte(':')
if len(v) == 1 {
// Just one item, add the item directly.
w.String(v[0])
} else {
// Zero or multiple items, include them all.
w.RawByte('[')
first := true
for _, v := range v {
if first {
first = false
} else {
w.RawByte(',')
}
w.String(v)
}
w.RawByte(']')
}
}
w.RawByte('}')
} else {
w.String(b.Raw)
}
return nil
}
// UnmarshalJSON unmarshals the JSON data into b.
func (b *RequestBody) UnmarshalJSON(data []byte) error {
var v interface{}
if err := json.Unmarshal(data, &v); err != nil {
return err
}
switch v := v.(type) {
case string:
b.Raw = v
return nil
case map[string]interface{}:
form := make(url.Values, len(v))
for k, v := range v {
switch v := v.(type) {
case string:
form.Set(k, v)
case []interface{}:
for _, v := range v {
switch v := v.(type) {
case string:
form.Add(k, v)
default:
return errors.Errorf("expected string, got %T", v)
}
}
default:
return errors.Errorf("expected string or []string, got %T", v)
}
}
b.Form = form
default:
return errors.Errorf("expected string or map, got %T", v)
}
return nil
}
func (m StringMap) isZero() bool {
return len(m) == 0
}
// MarshalFastJSON writes the JSON representation of m to w.
func (m StringMap) MarshalFastJSON(w *fastjson.Writer) (firstErr error) {
w.RawByte('{')
first := true
for _, item := range m {
if first {
first = false
} else {
w.RawByte(',')
}
w.String(item.Key)
w.RawByte(':')
if err := fastjson.Marshal(w, item.Value); err != nil && firstErr == nil {
firstErr = err
}
}
w.RawByte('}')
return nil
}
// UnmarshalJSON unmarshals the JSON data into m.
func (m *StringMap) UnmarshalJSON(data []byte) error {
var mm map[string]string
if err := json.Unmarshal(data, &mm); err != nil {
return err
}
*m = make(StringMap, 0, len(mm))
for k, v := range mm {
*m = append(*m, StringMapItem{Key: k, Value: v})
}
sort.Slice(*m, func(i, j int) bool {
return (*m)[i].Key < (*m)[j].Key
})
return nil
}
// MarshalFastJSON exists to prevent code generation for StringMapItem.
func (*StringMapItem) MarshalFastJSON(*fastjson.Writer) error {
panic("unreachable")
}
func (m IfaceMap) isZero() bool {
return len(m) == 0
}
// MarshalFastJSON writes the JSON representation of m to w.
func (m IfaceMap) MarshalFastJSON(w *fastjson.Writer) (firstErr error) {
w.RawByte('{')
first := true
for _, item := range m {
if first {
first = false
} else {
w.RawByte(',')
}
w.String(item.Key)
w.RawByte(':')
if err := fastjson.Marshal(w, item.Value); err != nil && firstErr == nil {
firstErr = err
}
}
w.RawByte('}')
return nil
}
// UnmarshalJSON unmarshals the JSON data into m.
func (m *IfaceMap) UnmarshalJSON(data []byte) error {
var mm map[string]interface{}
if err := json.Unmarshal(data, &mm); err != nil {
return err
}
*m = make(IfaceMap, 0, len(mm))
for k, v := range mm {
*m = append(*m, IfaceMapItem{Key: k, Value: v})
}
sort.Slice(*m, func(i, j int) bool {
return (*m)[i].Key < (*m)[j].Key
})
return nil
}
// MarshalFastJSON exists to prevent code generation for IfaceMapItem.
func (*IfaceMapItem) MarshalFastJSON(*fastjson.Writer) error {
panic("unreachable")
}
func (id *TraceID) isZero() bool {
return *id == TraceID{}
}
// MarshalFastJSON writes the JSON representation of id to w.
func (id *TraceID) MarshalFastJSON(w *fastjson.Writer) error {
w.RawByte('"')
writeHex(w, id[:])
w.RawByte('"')
return nil
}
// UnmarshalJSON unmarshals the JSON data into id.
func (id *TraceID) UnmarshalJSON(data []byte) error {
_, err := hex.Decode(id[:], data[1:len(data)-1])
return err
}
func (id *SpanID) isZero() bool {
return *id == SpanID{}
}
// UnmarshalJSON unmarshals the JSON data into id.
func (id *SpanID) UnmarshalJSON(data []byte) error {
_, err := hex.Decode(id[:], data[1:len(data)-1])
return err
}
// MarshalFastJSON writes the JSON representation of id to w.
func (id *SpanID) MarshalFastJSON(w *fastjson.Writer) error {
w.RawByte('"')
writeHex(w, id[:])
w.RawByte('"')
return nil
}
func (t *ErrorTransaction) isZero() bool {
return *t == ErrorTransaction{}
}
func (t *MetricsTransaction) isZero() bool {
return *t == MetricsTransaction{}
}
func (s *MetricsSpan) isZero() bool {
return *s == MetricsSpan{}
}
func writeHex(w *fastjson.Writer, v []byte) {
const hextable = "0123456789abcdef"
for _, v := range v {
w.RawByte(hextable[v>>4])
w.RawByte(hextable[v&0x0f])
}
}
// MarshalFastJSON writes the JSON representation of v to w.
func (v *Metric) MarshalFastJSON(w *fastjson.Writer) error {
w.RawByte('{')
if len(v.Counts) > 0 {
w.RawString("\"values\":")
w.RawByte('[')
for i, v := range v.Values {
if i != 0 {
w.RawByte(',')
}
w.Float64(v)
}
w.RawByte(']')
w.RawString(",\"counts\":")
w.RawByte('[')
for i, v := range v.Counts {
if i != 0 {
w.RawByte(',')
}
w.Uint64(v)
}
w.RawByte(']')
} else {
w.RawString("\"value\":")
w.Float64(v.Value)
}
if v.Type != "" {
w.RawString(",\"type\":")
w.String(v.Type)
}
w.RawByte('}')
return nil
}