{"version":3,"file":"TraceState.mjs","names":[],"sources":["../../src/trace/TraceState.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { TraceState as TraceStateApi } from '@opentelemetry/api';\nimport { validateKey, validateValue } from '../internal/validators';\n\nconst MAX_TRACE_STATE_ITEMS = 32;\nconst MAX_TRACE_STATE_LEN = 512;\nconst LIST_MEMBERS_SEPARATOR = ',';\nconst LIST_MEMBER_KEY_VALUE_SPLITTER = '=';\n\n/**\n * TraceState must be a class and not a simple object type because of the spec\n * requirement (https://www.w3.org/TR/trace-context/#tracestate-field).\n *\n * Here is the list of allowed mutations:\n * - New key-value pair should be added into the beginning of the list\n * - The value of any key can be updated. Modified keys MUST be moved to the\n * beginning of the list.\n */\nexport class TraceState implements TraceStateApi {\n  private _length: number;\n  private _rawTraceState: string;\n  private _internalState: Map<string, string> | undefined;\n\n  constructor(rawTraceState?: string) {\n    this._rawTraceState =\n      typeof rawTraceState === 'string' ? rawTraceState : '';\n    this._length = this._rawTraceState.length;\n  }\n\n  set(key: string, value: string): TraceState {\n    if (!validateKey(key) || !validateValue(value)) {\n      return this;\n    }\n\n    const currState = this._getState();\n    const currValue = currState.get(key);\n    // Get the new length depending if we already have a value or not\n    // - for existing keys we add the difference between the length of the values\n    // - for new keys is the key & value lenght plus\n    //   - +1 for the key/value splitter\n    //   - +1 for the separator if there are other keys\n    let newLength = this._length;\n    if (typeof currValue === 'string') {\n      newLength += value.length - currValue.length;\n    } else {\n      // A list can hold at most 32 list-members, so a new key is only\n      // accepted while there is room for it. Updating an existing key does\n      // not change the count and is always allowed.\n      if (currState.size >= MAX_TRACE_STATE_ITEMS) {\n        return this;\n      }\n      newLength += key.length + value.length + (currState.size > 0 ? 2 : 1);\n    }\n    if (newLength > MAX_TRACE_STATE_LEN) {\n      return this;\n    }\n\n    const newState = new Map(currState);\n    newState.delete(key);\n    newState.set(key, value);\n    return this._fromState(newState, newLength);\n  }\n\n  unset(key: string): TraceState {\n    const currState = this._getState();\n    const currValue = currState.get(key);\n\n    // No need to create a new instance if the key does not exist\n    if (typeof currValue !== 'string') {\n      return this;\n    }\n\n    // Get the new length depending if we already have a value or not\n    // - for existing keys we substract key and value length plus\n    //   - +1 for the key/value splitter\n    //   - +1 for the separator if there are other keys\n    let newLength = this._length - (key.length + currValue.length + 1);\n    if (currState.size > 1) {\n      // remove separator from length if there's no key or only one.\n      newLength = newLength - 1;\n    }\n\n    const newState = new Map(currState);\n    newState.delete(key);\n    return this._fromState(newState, newLength);\n  }\n\n  get(key: string): string | undefined {\n    const currState = this._getState();\n    return currState.get(key);\n  }\n\n  serialize(): string {\n    // Maps put new entries at the end. We prepend the seralized entry\n    // to get the right order according to the spec (updated members go 1st)\n    let serialized = '';\n    let index = 0;\n    for (const entry of this._getState()) {\n      if (index > 0) {\n        serialized = LIST_MEMBERS_SEPARATOR + serialized;\n      }\n      serialized =\n        `${entry[0]}${LIST_MEMBER_KEY_VALUE_SPLITTER}${entry[1]}` + serialized;\n      index++;\n    }\n    return serialized;\n  }\n\n  private _getState(): Map<string, string> {\n    if (this._internalState) {\n      return this._internalState;\n    }\n\n    // Not parsed yet, lets do it\n    const vendorMembers = this._rawTraceState.split(LIST_MEMBERS_SEPARATOR);\n\n    // This Map will have the order reversed\n    const vendorEntries = new Map();\n    let currentLength = 0;\n\n    for (const member of vendorMembers) {\n      const m = member.trim();\n      const idx = m.indexOf(LIST_MEMBER_KEY_VALUE_SPLITTER);\n      if (idx === -1) {\n        continue;\n      }\n\n      const key = m.slice(0, idx);\n      const value = m.slice(idx + 1);\n      if (!validateKey(key) || !validateValue(value)) {\n        continue;\n      }\n\n      // Skip if adding the new member exceeds the length\n      const futureLength =\n        currentLength + m.length + (vendorEntries.size > 0 ? 1 : 0);\n      if (futureLength > MAX_TRACE_STATE_LEN) {\n        continue;\n      }\n\n      // All good, add it\n      vendorEntries.set(key, value);\n      currentLength = futureLength;\n\n      // Check if we reached the max items\n      if (vendorEntries.size >= MAX_TRACE_STATE_ITEMS) {\n        break;\n      }\n    }\n\n    // Now we set the length & the Map in the right order\n    this._length = currentLength;\n    this._internalState = new Map(\n      Array.from(vendorEntries.entries()).reverse()\n    );\n    return this._internalState;\n  }\n\n  private _fromState(state: Map<string, string>, length: number): TraceState {\n    const traceState = Object.create(TraceState.prototype) as TraceState;\n    traceState._internalState = state;\n    traceState._length = length;\n    return traceState;\n  }\n}\n"],"mappings":";;AAQA,MAAM,wBAAwB;AAC9B,MAAM,sBAAsB;AAC5B,MAAM,yBAAyB;AAC/B,MAAM,iCAAiC;;;;;;;;;;AAWvC,IAAa,aAAb,MAAa,WAAoC;CAC/C;CACA;CACA;CAEA,YAAY,eAAwB;EAClC,KAAK,iBACH,OAAO,kBAAkB,WAAW,gBAAgB;EACtD,KAAK,UAAU,KAAK,eAAe;CACrC;CAEA,IAAI,KAAa,OAA2B;EAC1C,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,cAAc,KAAK,GAC3C,OAAO;EAGT,MAAM,YAAY,KAAK,UAAU;EACjC,MAAM,YAAY,UAAU,IAAI,GAAG;EAMnC,IAAI,YAAY,KAAK;EACrB,IAAI,OAAO,cAAc,UACvB,aAAa,MAAM,SAAS,UAAU;OACjC;GAIL,IAAI,UAAU,QAAQ,uBACpB,OAAO;GAET,aAAa,IAAI,SAAS,MAAM,UAAU,UAAU,OAAO,IAAI,IAAI;EACrE;EACA,IAAI,YAAY,qBACd,OAAO;EAGT,MAAM,WAAW,IAAI,IAAI,SAAS;EAClC,SAAS,OAAO,GAAG;EACnB,SAAS,IAAI,KAAK,KAAK;EACvB,OAAO,KAAK,WAAW,UAAU,SAAS;CAC5C;CAEA,MAAM,KAAyB;EAC7B,MAAM,YAAY,KAAK,UAAU;EACjC,MAAM,YAAY,UAAU,IAAI,GAAG;EAGnC,IAAI,OAAO,cAAc,UACvB,OAAO;EAOT,IAAI,YAAY,KAAK,WAAW,IAAI,SAAS,UAAU,SAAS;EAChE,IAAI,UAAU,OAAO,GAEnB,YAAY,YAAY;EAG1B,MAAM,WAAW,IAAI,IAAI,SAAS;EAClC,SAAS,OAAO,GAAG;EACnB,OAAO,KAAK,WAAW,UAAU,SAAS;CAC5C;CAEA,IAAI,KAAiC;EAEnC,OADkB,KAAK,UACR,CAAC,CAAC,IAAI,GAAG;CAC1B;CAEA,YAAoB;EAGlB,IAAI,aAAa;EACjB,IAAI,QAAQ;EACZ,KAAK,MAAM,SAAS,KAAK,UAAU,GAAG;GACpC,IAAI,QAAQ,GACV,aAAa,yBAAyB;GAExC,aACE,GAAG,MAAM,KAAK,iCAAiC,MAAM,OAAO;GAC9D;EACF;EACA,OAAO;CACT;CAEA,YAAyC;EACvC,IAAI,KAAK,gBACP,OAAO,KAAK;EAId,MAAM,gBAAgB,KAAK,eAAe,MAAM,sBAAsB;EAGtE,MAAM,gCAAgB,IAAI,IAAI;EAC9B,IAAI,gBAAgB;EAEpB,KAAK,MAAM,UAAU,eAAe;GAClC,MAAM,IAAI,OAAO,KAAK;GACtB,MAAM,MAAM,EAAE,QAAQ,8BAA8B;GACpD,IAAI,QAAQ,IACV;GAGF,MAAM,MAAM,EAAE,MAAM,GAAG,GAAG;GAC1B,MAAM,QAAQ,EAAE,MAAM,MAAM,CAAC;GAC7B,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,cAAc,KAAK,GAC3C;GAIF,MAAM,eACJ,gBAAgB,EAAE,UAAU,cAAc,OAAO,IAAI,IAAI;GAC3D,IAAI,eAAe,qBACjB;GAIF,cAAc,IAAI,KAAK,KAAK;GAC5B,gBAAgB;GAGhB,IAAI,cAAc,QAAQ,uBACxB;EAEJ;EAGA,KAAK,UAAU;EACf,KAAK,iBAAiB,IAAI,IACxB,MAAM,KAAK,cAAc,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAC9C;EACA,OAAO,KAAK;CACd;CAEA,WAAmB,OAA4B,QAA4B;EACzE,MAAM,aAAa,OAAO,OAAO,WAAW,SAAS;EACrD,WAAW,iBAAiB;EAC5B,WAAW,UAAU;EACrB,OAAO;CACT;AACF"}