docs/vendor/github.com/lightstep/tracecontext.go/tracestate/package.go

124 lines
3.5 KiB
Go

package tracestate
import (
"errors"
"fmt"
"regexp"
"strings"
)
var (
// ErrInvalidListMember occurs if at least one list member is invalid, e.g., contains an unexpected character.
ErrInvalidListMember = errors.New("tracecontext: Invalid tracestate list member")
// ErrDuplicateListMemberKey occurs if at least two list members contain the same vendor-tenant pair.
ErrDuplicateListMemberKey = errors.New("tracecontext: Duplicate list member key in tracestate")
// ErrTooManyListMembers occurs if the list contains more than the maximum number of members per the spec, i.e., 32.
ErrTooManyListMembers = errors.New("tracecontext: Too many list members in tracestate")
)
const (
maxMembers = 32
delimiter = ","
)
var (
re = regexp.MustCompile(`^\s*(?:([a-z0-9_\-*/]{1,241})@([a-z0-9_\-*/]{1,14})|([a-z0-9_\-*/]{1,256}))=([\x20-\x2b\x2d-\x3c\x3e-\x7e]*[\x21-\x2b\x2d-\x3c\x3e-\x7e])\s*$`)
)
// Member contains vendor-specific data that should be propagated across all new spans started within a given trace.
type Member struct {
// Vendor is a key representing a particular trace vendor.
Vendor string
// Tenant is a key used to distinguish between tenants of a multi-tenant trace vendor.
Tenant string
// Value is the particular data that the vendor intents to pass to child spans.
Value string
}
// String encodes a `Member` into a string formatted according to the W3C spec.
// The string may be invalid if any fields are invalid, e.g, the vendor contains a non-compliant character.
func (m Member) String() string {
if m.Tenant == "" {
return fmt.Sprintf("%s=%s", m.Vendor, m.Value)
}
return fmt.Sprintf("%s@%s=%s", m.Vendor, m.Tenant, m.Value)
}
// TraceState represents a list of `Member`s that should be propagated to new spans started in a trace.
type TraceState []Member
// String encodes all `Member`s of the `TraceState` into a single string, formatted according to the W3C spec.
// The string may be invalid if any `Member`s are invalid, e.g., containing a non-compliant character.
func (ts TraceState) String() string {
var members []string
for _, member := range ts {
members = append(members, member.String())
}
return strings.Join(members, ",")
}
// Parse attempts to decode a `TraceState` from a byte array.
// It returns an error if the byte array is invalid, e.g., it contains an incorrectly formatted list member.
func Parse(traceState []byte) (TraceState, error) {
return parse(string(traceState))
}
// ParseString attempts to decode a `TraceState` from a string.
// It returns an error if the string is invalid, e.g., it contains an incorrectly formatted list member.
func ParseString(traceState string) (TraceState, error) {
return parse(traceState)
}
func parse(traceState string) (ts TraceState, err error) {
found := make(map[string]interface{})
members := strings.Split(traceState, delimiter)
for _, member := range members {
if len(member) == 0 {
continue
}
var m Member
m, err = parseMember(member)
if err != nil {
return
}
key := fmt.Sprintf("%s%s", m.Vendor, m.Tenant)
if _, ok := found[key]; ok {
err = ErrDuplicateListMemberKey
return
}
found[key] = nil
ts = append(ts, m)
if len(ts) > maxMembers {
err = ErrTooManyListMembers
return
}
}
return
}
func parseMember(s string) (Member, error) {
matches := re.FindStringSubmatch(s)
if len(matches) != 5 {
return Member{}, ErrInvalidListMember
}
vendor := matches[1]
if vendor == "" {
vendor = matches[3]
}
return Member{
Vendor: vendor,
Tenant: matches[2],
Value: matches[4],
}, nil
}