github.com/mdaxf/iac@v0.0.0-20240519030858-58a061660378/vendor_skip/go.opentelemetry.io/otel/trace/tracestate.go (about)

     1  // Copyright The OpenTelemetry Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package trace // import "go.opentelemetry.io/otel/trace"
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"regexp"
    21  	"strings"
    22  )
    23  
    24  const (
    25  	maxListMembers = 32
    26  
    27  	listDelimiter = ","
    28  
    29  	// based on the W3C Trace Context specification, see
    30  	// https://www.w3.org/TR/trace-context-1/#tracestate-header
    31  	noTenantKeyFormat   = `[a-z][_0-9a-z\-\*\/]*`
    32  	withTenantKeyFormat = `[a-z0-9][_0-9a-z\-\*\/]*@[a-z][_0-9a-z\-\*\/]*`
    33  	valueFormat         = `[\x20-\x2b\x2d-\x3c\x3e-\x7e]*[\x21-\x2b\x2d-\x3c\x3e-\x7e]`
    34  
    35  	errInvalidKey    errorConst = "invalid tracestate key"
    36  	errInvalidValue  errorConst = "invalid tracestate value"
    37  	errInvalidMember errorConst = "invalid tracestate list-member"
    38  	errMemberNumber  errorConst = "too many list-members in tracestate"
    39  	errDuplicate     errorConst = "duplicate list-member in tracestate"
    40  )
    41  
    42  var (
    43  	noTenantKeyRe   = regexp.MustCompile(`^` + noTenantKeyFormat + `$`)
    44  	withTenantKeyRe = regexp.MustCompile(`^` + withTenantKeyFormat + `$`)
    45  	valueRe         = regexp.MustCompile(`^` + valueFormat + `$`)
    46  	memberRe        = regexp.MustCompile(`^\s*((?:` + noTenantKeyFormat + `)|(?:` + withTenantKeyFormat + `))=(` + valueFormat + `)\s*$`)
    47  )
    48  
    49  type member struct {
    50  	Key   string
    51  	Value string
    52  }
    53  
    54  func newMember(key, value string) (member, error) {
    55  	if len(key) > 256 {
    56  		return member{}, fmt.Errorf("%w: %s", errInvalidKey, key)
    57  	}
    58  	if !noTenantKeyRe.MatchString(key) {
    59  		if !withTenantKeyRe.MatchString(key) {
    60  			return member{}, fmt.Errorf("%w: %s", errInvalidKey, key)
    61  		}
    62  		atIndex := strings.LastIndex(key, "@")
    63  		if atIndex > 241 || len(key)-1-atIndex > 14 {
    64  			return member{}, fmt.Errorf("%w: %s", errInvalidKey, key)
    65  		}
    66  	}
    67  	if len(value) > 256 || !valueRe.MatchString(value) {
    68  		return member{}, fmt.Errorf("%w: %s", errInvalidValue, value)
    69  	}
    70  	return member{Key: key, Value: value}, nil
    71  }
    72  
    73  func parseMember(m string) (member, error) {
    74  	matches := memberRe.FindStringSubmatch(m)
    75  	if len(matches) != 3 {
    76  		return member{}, fmt.Errorf("%w: %s", errInvalidMember, m)
    77  	}
    78  	result, e := newMember(matches[1], matches[2])
    79  	if e != nil {
    80  		return member{}, fmt.Errorf("%w: %s", errInvalidMember, m)
    81  	}
    82  	return result, nil
    83  }
    84  
    85  // String encodes member into a string compliant with the W3C Trace Context
    86  // specification.
    87  func (m member) String() string {
    88  	return fmt.Sprintf("%s=%s", m.Key, m.Value)
    89  }
    90  
    91  // TraceState provides additional vendor-specific trace identification
    92  // information across different distributed tracing systems. It represents an
    93  // immutable list consisting of key/value pairs, each pair is referred to as a
    94  // list-member.
    95  //
    96  // TraceState conforms to the W3C Trace Context specification
    97  // (https://www.w3.org/TR/trace-context-1). All operations that create or copy
    98  // a TraceState do so by validating all input and will only produce TraceState
    99  // that conform to the specification. Specifically, this means that all
   100  // list-member's key/value pairs are valid, no duplicate list-members exist,
   101  // and the maximum number of list-members (32) is not exceeded.
   102  type TraceState struct { //nolint:revive // revive complains about stutter of `trace.TraceState`
   103  	// list is the members in order.
   104  	list []member
   105  }
   106  
   107  var _ json.Marshaler = TraceState{}
   108  
   109  // ParseTraceState attempts to decode a TraceState from the passed
   110  // string. It returns an error if the input is invalid according to the W3C
   111  // Trace Context specification.
   112  func ParseTraceState(tracestate string) (TraceState, error) {
   113  	if tracestate == "" {
   114  		return TraceState{}, nil
   115  	}
   116  
   117  	wrapErr := func(err error) error {
   118  		return fmt.Errorf("failed to parse tracestate: %w", err)
   119  	}
   120  
   121  	var members []member
   122  	found := make(map[string]struct{})
   123  	for _, memberStr := range strings.Split(tracestate, listDelimiter) {
   124  		if len(memberStr) == 0 {
   125  			continue
   126  		}
   127  
   128  		m, err := parseMember(memberStr)
   129  		if err != nil {
   130  			return TraceState{}, wrapErr(err)
   131  		}
   132  
   133  		if _, ok := found[m.Key]; ok {
   134  			return TraceState{}, wrapErr(errDuplicate)
   135  		}
   136  		found[m.Key] = struct{}{}
   137  
   138  		members = append(members, m)
   139  		if n := len(members); n > maxListMembers {
   140  			return TraceState{}, wrapErr(errMemberNumber)
   141  		}
   142  	}
   143  
   144  	return TraceState{list: members}, nil
   145  }
   146  
   147  // MarshalJSON marshals the TraceState into JSON.
   148  func (ts TraceState) MarshalJSON() ([]byte, error) {
   149  	return json.Marshal(ts.String())
   150  }
   151  
   152  // String encodes the TraceState into a string compliant with the W3C
   153  // Trace Context specification. The returned string will be invalid if the
   154  // TraceState contains any invalid members.
   155  func (ts TraceState) String() string {
   156  	members := make([]string, len(ts.list))
   157  	for i, m := range ts.list {
   158  		members[i] = m.String()
   159  	}
   160  	return strings.Join(members, listDelimiter)
   161  }
   162  
   163  // Get returns the value paired with key from the corresponding TraceState
   164  // list-member if it exists, otherwise an empty string is returned.
   165  func (ts TraceState) Get(key string) string {
   166  	for _, member := range ts.list {
   167  		if member.Key == key {
   168  			return member.Value
   169  		}
   170  	}
   171  
   172  	return ""
   173  }
   174  
   175  // Insert adds a new list-member defined by the key/value pair to the
   176  // TraceState. If a list-member already exists for the given key, that
   177  // list-member's value is updated. The new or updated list-member is always
   178  // moved to the beginning of the TraceState as specified by the W3C Trace
   179  // Context specification.
   180  //
   181  // If key or value are invalid according to the W3C Trace Context
   182  // specification an error is returned with the original TraceState.
   183  //
   184  // If adding a new list-member means the TraceState would have more members
   185  // then is allowed, the new list-member will be inserted and the right-most
   186  // list-member will be dropped in the returned TraceState.
   187  func (ts TraceState) Insert(key, value string) (TraceState, error) {
   188  	m, err := newMember(key, value)
   189  	if err != nil {
   190  		return ts, err
   191  	}
   192  
   193  	cTS := ts.Delete(key)
   194  	if cTS.Len()+1 <= maxListMembers {
   195  		cTS.list = append(cTS.list, member{})
   196  	}
   197  	// When the number of members exceeds capacity, drop the "right-most".
   198  	copy(cTS.list[1:], cTS.list)
   199  	cTS.list[0] = m
   200  
   201  	return cTS, nil
   202  }
   203  
   204  // Delete returns a copy of the TraceState with the list-member identified by
   205  // key removed.
   206  func (ts TraceState) Delete(key string) TraceState {
   207  	members := make([]member, ts.Len())
   208  	copy(members, ts.list)
   209  	for i, member := range ts.list {
   210  		if member.Key == key {
   211  			members = append(members[:i], members[i+1:]...)
   212  			// TraceState should contain no duplicate members.
   213  			break
   214  		}
   215  	}
   216  	return TraceState{list: members}
   217  }
   218  
   219  // Len returns the number of list-members in the TraceState.
   220  func (ts TraceState) Len() int {
   221  	return len(ts.list)
   222  }