{"version":3,"file":"attributes.cjs","names":[],"sources":["../../src/common/attributes.ts"],"sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\n/**\n * OTel JS attributes handling utilities.\n *\n * In earlier OTel spec versions, attributes (e.g. on spans) were restricted\n * to a relatively \"simple\" set of allowed types:\n * - scalar values: string, number, boolean\n * - homogeneous array of scalar values\n *\n * OTEP 4485 defined a wider, extended, more *complex* set of types, adding:\n * - byte arrays (Uint8Array in JS)\n * - heterogenous arrays\n * - key/value-mappings (`KeyValueList` in proto, a plain `object` in JS)\n *\n * OTel SDKs \"MUST\" or \"MAY\" support for attributes, depending.\n * See https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/4485-extending-attributes-to-support-complex-values.md#sdk\n *\n * In OTel JS SDK 3.0 and `@opentelemetry/api@1.10.0` support was added\n * for those extended attributes types. The `api` was updated to accept\n * TypeScript type `unknown` for attribute values via:\n *    type AnyValue = unknown;\n *\n * The intent is that users of the `api` can (and will, this is JavaScript)\n * pass whatever types as attribute values. SDK implementations need to handle\n * API users passing in extended attribute types.\n * Two options:\n *\n * 1. Support the \"simple\" set of types. Use `cleanSimpleAttributes()` and\n *    `maybeAddSimpleAttribute()`. (Or use `sanitizeAttributes()`, kept for\n *    backward compatibility.)\n * 2. Support the extended types. Use `cleanAttributes()`, `isAnyValue()`, and\n *    `maybeAddAttribute()`; and test the SDK implementation with newer\n *    attribute value types.\n */\n\nimport type { AnyValue, Attributes } from '@opentelemetry/api';\nimport { diag } from '@opentelemetry/api';\n\n/**\n * A simpler attribute value with limited supported types.\n * This is the set of types that `Attributes` supported in versions of\n * `@opentelemetry/api` before v1.10.0.\n */\ntype SimpleAttributeValue =\n  | string\n  | number\n  | boolean\n  | Array<null | undefined | string>\n  | Array<null | undefined | number>\n  | Array<null | undefined | boolean>;\n\nexport enum AddAttributeDecision {\n  DROP_UNDEFINED = 0,\n  DROP_INVALID = 1,\n  DROP_LIMIT_REACHED = 2,\n  ADD_NEW = 3,\n  ADD_OVERWRITE_EXISTING = 4,\n}\n\ntype AttributeLimits = {\n  attributeCountLimit: number;\n  attributeValueLengthLimit: number;\n};\n\n// https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#attribute-limits\nconst DEFAULT_ATTRIBUTE_LIMITS: AttributeLimits = {\n  attributeCountLimit: 128,\n  attributeValueLengthLimit: Infinity,\n};\n\ntype MaybeAddAttributeOpts = {\n  key: string;\n  value: AnyValue;\n  attributes: Attributes;\n  limits: AttributeLimits;\n  currentAttributesCount: number;\n};\n\n/**\n * Maybe add the `key`/`val` attribute to the given `attributes`.\n * - \"Maybe\" because this ensures the `key` is valid (the string is not empty),\n *   the `val` is an accepted spec AnyValue type (see `isAnyValue` below),\n *   and the attribute does not need to be dropped because of a count limit.\n *   Note that the TypeScript `AnyValue` type is *looser* than the spec\n *   `AnyValue`.\n * - This also applies attribute count and value length limits.\n * - This returns an `AddAttributeDecision` enum value indicating whether,\n *   why, and how the attribute was added. This can be relevant for the\n *   caller updating metrics (e.g. `droppedAttributesCount`).\n */\nexport function maybeAddAttribute(\n  opts: MaybeAddAttributeOpts\n): AddAttributeDecision {\n  return maybeAddAttributeInternal(opts, isAnyValue);\n}\n\n/**\n * A version of `maybeAddAttribute` that limits allowed attribute value\n * types to \"simple\" types (`SimpleAttributeValue`).\n */\nexport function maybeAddSimpleAttribute(\n  opts: MaybeAddAttributeOpts\n): AddAttributeDecision {\n  return maybeAddAttributeInternal(opts, isSimpleAttributeValue);\n}\n\nfunction maybeAddAttributeInternal(\n  opts: MaybeAddAttributeOpts,\n  isValidValue: (value: unknown) => boolean\n): AddAttributeDecision {\n  if (opts.value === undefined) {\n    // Silently drop undefined values.\n    return AddAttributeDecision.DROP_UNDEFINED;\n  }\n\n  if (opts.key.length === 0) {\n    diag.warn(`Invalid attribute key: ${opts.key}`);\n    return AddAttributeDecision.DROP_INVALID;\n  }\n\n  const isNewKey = !Object.hasOwn(opts.attributes, opts.key);\n  if (\n    isNewKey &&\n    opts.currentAttributesCount >= opts.limits.attributeCountLimit\n  ) {\n    return AddAttributeDecision.DROP_LIMIT_REACHED;\n  }\n\n  if (!isValidValue(opts.value)) {\n    diag.warn(`Invalid attribute value set for key: ${opts.key}`);\n    return AddAttributeDecision.DROP_INVALID;\n  }\n\n  let attributeValueLengthLimit = opts.limits.attributeValueLengthLimit;\n  if (attributeValueLengthLimit <= 0) {\n    diag.warn(\n      `Invalid attributeValueLengthLimit: must be positive, got ${attributeValueLengthLimit} (ignoring)`\n    );\n    attributeValueLengthLimit = Infinity;\n  }\n  opts.attributes[opts.key] = copyAndTruncAnyValue(\n    opts.value,\n    attributeValueLengthLimit\n  );\n  if (isNewKey) {\n    return AddAttributeDecision.ADD_NEW;\n  }\n  return AddAttributeDecision.ADD_OVERWRITE_EXISTING;\n}\n\n/**\n * Clean a given set of attributes.\n * - `undefined` values, at the top-level and properties of nested objects,\n *   are silently dropped\n * - invalid attribute value types are removed, with a warning\n * - attribute count and value length *limits* are applied\n *\n * The call signature is meant to make it ergonomic for creating SDK things\n * that take `attributes` and `droppedAttributesCount` arguments, e.g.:\n *    this.links({\n *      context: opts.context,\n *      ...cleanAttributes(opts.attributes, attributeLimits)\n *    });\n *\n * If the given `inAttributes` is undefined, then the properties in the\n * return value will be undefined.\n */\nexport function cleanAttributes(\n  inAttributes: unknown,\n  limits: AttributeLimits\n): {\n  attributes?: Attributes;\n  droppedAttributesCount?: number;\n} {\n  return cleanAttributesInternal(inAttributes, limits, isAnyValue);\n}\n\n/**\n * A version of `cleanAttributes` that limits allowed attribute value types to\n * \"simple\" types (`SimpleAttributeValue`).\n */\nexport function cleanSimpleAttributes(\n  inAttributes: unknown,\n  limits: AttributeLimits\n): {\n  attributes?: Attributes;\n  droppedAttributesCount?: number;\n} {\n  return cleanAttributesInternal(inAttributes, limits, isSimpleAttributeValue);\n}\n\nfunction cleanAttributesInternal(\n  inAttributes: unknown,\n  limits: AttributeLimits,\n  isValidValue: (value: unknown) => boolean\n): {\n  attributes?: Attributes;\n  droppedAttributesCount?: number;\n} {\n  if (typeof inAttributes !== 'object' || inAttributes == null) {\n    return {};\n  }\n\n  const attributes: Attributes = {};\n  let droppedAttributesCount = 0;\n\n  let attributeValueLengthLimit = limits.attributeValueLengthLimit;\n  if (attributeValueLengthLimit <= 0) {\n    diag.warn(\n      `Invalid attributeValueLengthLimit: must be positive, got ${attributeValueLengthLimit} (ignoring)`\n    );\n    attributeValueLengthLimit = Infinity;\n  }\n\n  let count = 0;\n  for (const [key, val] of Object.entries(inAttributes)) {\n    if (val === undefined) {\n      // Silently drop undefined values.\n      continue;\n    }\n    if (count >= limits.attributeCountLimit) {\n      droppedAttributesCount++;\n      continue;\n    }\n    if (key.length === 0) {\n      diag.warn(`Invalid attribute key: ${key}`);\n      droppedAttributesCount++;\n      continue;\n    }\n    if (!isValidValue(val)) {\n      diag.warn(`Invalid attribute value set for key: ${key}`);\n      droppedAttributesCount++;\n      continue;\n    }\n    attributes[key] = copyAndTruncAnyValue(val, attributeValueLengthLimit);\n    count++;\n  }\n\n  return { attributes, droppedAttributesCount };\n}\n\n/**\n * Returns true iff the given value is a supported AnyValue per\n * https://opentelemetry.io/docs/specs/otel/common/#anyvalue\n *\n * - Primitives/scalars: string, boolean, number\n *   (Note that this *excludes* BigInt and Symbol)\n * - Byte arrays (Uint8Array, Buffer)\n * - Arrays of AnyValue (heterogeneous arrays allowed)\n * - Maps from string to AnyValue (nested objects)\n * - Empty values (null)\n *\n * Notes:\n * - `undefined` is *not* considered an AnyValue.\n *\n * @param val\n * @returns true if the value is a valid AnyValue, false otherwise\n */\nexport function isAnyValue(val: unknown): boolean {\n  return isAnyValueInternal(val, new WeakSet());\n}\n\nfunction isAnyValueInternal(\n  val: unknown,\n  visited: WeakSet<object>\n): val is AnyValue {\n  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof#description\n  switch (typeof val) {\n    case 'undefined':\n    case 'bigint':\n    case 'symbol':\n    case 'function':\n      return false;\n    case 'string':\n    case 'number':\n    case 'boolean':\n      return true;\n    case 'object':\n      // Handled below.\n      break;\n    default:\n      // JavaScript added a new primitive type!?\n      // Default to *not* supporting this new type as an AnyValue.\n      return false;\n  }\n\n  if (val === null) {\n    return true;\n  }\n\n  // Byte arrays\n  if (val instanceof Uint8Array) {\n    return true;\n  }\n\n  // This leaves objects and arrays. Check for circular references.\n  if (visited.has(val)) {\n    // Circular reference detected - reject it\n    return false;\n  }\n  visited.add(val);\n\n  // Arrays (can contain any AnyValue, including heterogeneous)\n  if (Array.isArray(val)) {\n    for (const item of val) {\n      // Special case: `undefined` in an array is allowed because it has\n      // meaning (the array index of subsequent items). It gets serialized\n      // as an empty value, the same as `null`.\n      if (item !== undefined && !isAnyValueInternal(item, visited)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  // Only accept plain objects (not objects like Date, RegExp, Error, etc.).\n  const obj = val as Record<string, unknown>;\n  if (obj.constructor !== Object && obj.constructor !== undefined) {\n    return false;\n  }\n\n  // Plain objects\n  for (const k of Object.keys(obj)) {\n    const v = obj[k];\n    // Allow undefined properties on an object. This allows the pattern of\n    // building structured objects with optional fields. The undefined\n    // properties will be dropped by `cleanAttributes` and `maybeAddAttribute`.\n    if (v !== undefined && !isAnyValueInternal(v, visited)) {\n      return false;\n    }\n  }\n\n  return true;\n}\n\n/**\n * Attribute value length limiting/truncation per:\n * https://opentelemetry.io/docs/specs/otel/common/#attribute-limits\n *\n * For arrays and objects, this also returns a deep copy to avoid mutating data\n * passed in by calling code.\n *\n * @param {AnyValue} val - The attribute value. We are assuming this has\n *    already been guarded by `isAnyValue`. This filters out non-plain objects,\n *    like RegExp & Date, and circular references.\n */\nfunction copyAndTruncAnyValue(val: AnyValue, limit: number): AnyValue {\n  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof#description\n  switch (typeof val) {\n    // Currently some of these aren't allowed AnyValue's, but having these\n    // here is resilient for later changes (e.g. allowing bigint) and they help\n    // with type narrowing.\n    case 'number':\n    case 'boolean':\n    case 'undefined':\n    case 'bigint':\n    case 'symbol':\n    case 'function':\n      return val;\n    case 'string':\n      if (val.length <= limit) {\n        return val;\n      } else {\n        return val.substring(0, limit);\n      }\n    case 'object':\n      // Handled below.\n      break;\n    default:\n      // JavaScript added a new primitive type!?\n      // Assume we cannot meaningfully copy/trunc this new type.\n      return val;\n  }\n\n  if (val === null) {\n    return val;\n  }\n\n  // Byte arrays\n  if (val instanceof Uint8Array) {\n    if (val.length <= limit) {\n      return val;\n    } else {\n      return val.slice(0, limit);\n    }\n  }\n\n  // Arrays\n  if (Array.isArray(val)) {\n    return val.map(elem => copyAndTruncAnyValue(elem, limit));\n  }\n\n  // Plain objects\n  const truncObj: Record<string, AnyValue> = {};\n  const obj = val as Record<string, AnyValue>;\n  for (const k of Object.keys(obj)) {\n    const v = obj[k];\n    // Silently drop undefined properties. They are allowed by isAnyValue()\n    // with the expectation they will be dropped here.\n    if (v !== undefined) {\n      truncObj[k] = copyAndTruncAnyValue(obj[k], limit);\n    }\n  }\n  return truncObj;\n}\n\n/**\n * Clean the given attributes to just those with \"simple\" attribute types.\n * This remains for backwards compatibility with 2.x.\n *\n * Changes to previous behavior:\n * - a default attribute count limit of 128 is applied\n * - nullish values (except in arrays) are removed\n *\n * @deprecated Use `cleanAttributes` or `cleanSimpleAttributes`.\n */\nexport function sanitizeAttributes(inAttributes: unknown): Attributes {\n  const { attributes } = cleanSimpleAttributes(\n    inAttributes,\n    DEFAULT_ATTRIBUTE_LIMITS\n  );\n  return attributes ?? {};\n}\n\n/**\n * Return true if the given attribute value is a \"simple\" attribute value type.\n *\n * Changes to previous behavior: nullish values now return *false*.\n *\n * @deprecated use `isAnyValue`, or `isSimpleAttributeValue`.\n */\nexport const isAttributeValue = isSimpleAttributeValue;\n\nexport function isSimpleAttributeValue(\n  val: unknown\n): val is SimpleAttributeValue {\n  if (Array.isArray(val)) {\n    return isHomogeneousAttributeValueArray(val);\n  }\n  return isValidPrimitiveAttributeValueType(typeof val);\n}\n\nfunction isHomogeneousAttributeValueArray(arr: unknown[]): boolean {\n  let type: string | undefined;\n\n  for (const element of arr) {\n    // null/undefined elements are allowed\n    if (element == null) continue;\n    const elementType = typeof element;\n\n    if (elementType === type) {\n      continue;\n    }\n\n    if (!type) {\n      if (isValidPrimitiveAttributeValueType(elementType)) {\n        type = elementType;\n        continue;\n      }\n      // encountered an invalid primitive\n      return false;\n    }\n\n    return false;\n  }\n\n  return true;\n}\n\nfunction isValidPrimitiveAttributeValueType(valType: string): boolean {\n  switch (valType) {\n    case 'number':\n    case 'boolean':\n    case 'string':\n      return true;\n  }\n\n  return false;\n}\n"],"mappings":";;AAuDA,IAAY,uBAAL,yBAAA,sBAAA;CACL,qBAAA,qBAAA,oBAAA,KAAA;CACA,qBAAA,qBAAA,kBAAA,KAAA;CACA,qBAAA,qBAAA,wBAAA,KAAA;CACA,qBAAA,qBAAA,aAAA,KAAA;CACA,qBAAA,qBAAA,4BAAA,KAAA;;AACF,EAAA,CAAA,CAAA;AAQA,MAAM,2BAA4C;CAChD,qBAAqB;CACrB,2BAA2B;AAC7B;;;;;;;;;;;;;AAsBA,SAAgB,kBACd,MACsB;CACtB,OAAO,0BAA0B,MAAM,UAAU;AACnD;;;;;AAMA,SAAgB,wBACd,MACsB;CACtB,OAAO,0BAA0B,MAAM,sBAAsB;AAC/D;AAEA,SAAS,0BACP,MACA,cACsB;CACtB,IAAI,KAAK,UAAU,KAAA,GAEjB,OAAA;CAGF,IAAI,KAAK,IAAI,WAAW,GAAG;EACzB,mBAAA,KAAK,KAAK,0BAA0B,KAAK,KAAK;EAC9C,OAAA;CACF;CAEA,MAAM,WAAW,CAAC,OAAO,OAAO,KAAK,YAAY,KAAK,GAAG;CACzD,IACE,YACA,KAAK,0BAA0B,KAAK,OAAO,qBAE3C,OAAA;CAGF,IAAI,CAAC,aAAa,KAAK,KAAK,GAAG;EAC7B,mBAAA,KAAK,KAAK,wCAAwC,KAAK,KAAK;EAC5D,OAAA;CACF;CAEA,IAAI,4BAA4B,KAAK,OAAO;CAC5C,IAAI,6BAA6B,GAAG;EAClC,mBAAA,KAAK,KACH,4DAA4D,0BAA0B,YACxF;EACA,4BAA4B;CAC9B;CACA,KAAK,WAAW,KAAK,OAAO,qBAC1B,KAAK,OACL,yBACF;CACA,IAAI,UACF,OAAA;CAEF,OAAA;AACF;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,gBACd,cACA,QAIA;CACA,OAAO,wBAAwB,cAAc,QAAQ,UAAU;AACjE;;;;;AAMA,SAAgB,sBACd,cACA,QAIA;CACA,OAAO,wBAAwB,cAAc,QAAQ,sBAAsB;AAC7E;AAEA,SAAS,wBACP,cACA,QACA,cAIA;CACA,IAAI,OAAO,iBAAiB,YAAY,gBAAgB,MACtD,OAAO,CAAC;CAGV,MAAM,aAAyB,CAAC;CAChC,IAAI,yBAAyB;CAE7B,IAAI,4BAA4B,OAAO;CACvC,IAAI,6BAA6B,GAAG;EAClC,mBAAA,KAAK,KACH,4DAA4D,0BAA0B,YACxF;EACA,4BAA4B;CAC9B;CAEA,IAAI,QAAQ;CACZ,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,YAAY,GAAG;EACrD,IAAI,QAAQ,KAAA,GAEV;EAEF,IAAI,SAAS,OAAO,qBAAqB;GACvC;GACA;EACF;EACA,IAAI,IAAI,WAAW,GAAG;GACpB,mBAAA,KAAK,KAAK,0BAA0B,KAAK;GACzC;GACA;EACF;EACA,IAAI,CAAC,aAAa,GAAG,GAAG;GACtB,mBAAA,KAAK,KAAK,wCAAwC,KAAK;GACvD;GACA;EACF;EACA,WAAW,OAAO,qBAAqB,KAAK,yBAAyB;EACrE;CACF;CAEA,OAAO;EAAE;EAAY;CAAuB;AAC9C;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,WAAW,KAAuB;CAChD,OAAO,mBAAmB,qBAAK,IAAI,QAAQ,CAAC;AAC9C;AAEA,SAAS,mBACP,KACA,SACiB;CAEjB,QAAQ,OAAO,KAAf;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,YACH,OAAO;EACT,KAAK;EACL,KAAK;EACL,KAAK,WACH,OAAO;EACT,KAAK,UAEH;EACF,SAGE,OAAO;CACX;CAEA,IAAI,QAAQ,MACV,OAAO;CAIT,IAAI,eAAe,YACjB,OAAO;CAIT,IAAI,QAAQ,IAAI,GAAG,GAEjB,OAAO;CAET,QAAQ,IAAI,GAAG;CAGf,IAAI,MAAM,QAAQ,GAAG,GAAG;EACtB,KAAK,MAAM,QAAQ,KAIjB,IAAI,SAAS,KAAA,KAAa,CAAC,mBAAmB,MAAM,OAAO,GACzD,OAAO;EAGX,OAAO;CACT;CAGA,MAAM,MAAM;CACZ,IAAI,IAAI,gBAAgB,UAAU,IAAI,gBAAgB,KAAA,GACpD,OAAO;CAIT,KAAK,MAAM,KAAK,OAAO,KAAK,GAAG,GAAG;EAChC,MAAM,IAAI,IAAI;EAId,IAAI,MAAM,KAAA,KAAa,CAAC,mBAAmB,GAAG,OAAO,GACnD,OAAO;CAEX;CAEA,OAAO;AACT;;;;;;;;;;;;AAaA,SAAS,qBAAqB,KAAe,OAAyB;CAEpE,QAAQ,OAAO,KAAf;EAIE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,YACH,OAAO;EACT,KAAK,UACH,IAAI,IAAI,UAAU,OAChB,OAAO;OAEP,OAAO,IAAI,UAAU,GAAG,KAAK;EAEjC,KAAK,UAEH;EACF,SAGE,OAAO;CACX;CAEA,IAAI,QAAQ,MACV,OAAO;CAIT,IAAI,eAAe,YAAY;EAC7B,IAAI,IAAI,UAAU,OAChB,OAAO;OAEP,OAAO,IAAI,MAAM,GAAG,KAAK;CAE7B;CAGA,IAAI,MAAM,QAAQ,GAAG,GACnB,OAAO,IAAI,KAAI,SAAQ,qBAAqB,MAAM,KAAK,CAAC;CAI1D,MAAM,WAAqC,CAAC;CAC5C,MAAM,MAAM;CACZ,KAAK,MAAM,KAAK,OAAO,KAAK,GAAG,GAI7B,IAHU,IAAI,OAGJ,KAAA,GACR,SAAS,KAAK,qBAAqB,IAAI,IAAI,KAAK;CAGpD,OAAO;AACT;;;;;;;;;;;AAYA,SAAgB,mBAAmB,cAAmC;CACpE,MAAM,EAAE,eAAe,sBACrB,cACA,wBACF;CACA,OAAO,cAAc,CAAC;AACxB;;;;;;;;AASA,MAAa,mBAAmB;AAEhC,SAAgB,uBACd,KAC6B;CAC7B,IAAI,MAAM,QAAQ,GAAG,GACnB,OAAO,iCAAiC,GAAG;CAE7C,OAAO,mCAAmC,OAAO,GAAG;AACtD;AAEA,SAAS,iCAAiC,KAAyB;CACjE,IAAI;CAEJ,KAAK,MAAM,WAAW,KAAK;EAEzB,IAAI,WAAW,MAAM;EACrB,MAAM,cAAc,OAAO;EAE3B,IAAI,gBAAgB,MAClB;EAGF,IAAI,CAAC,MAAM;GACT,IAAI,mCAAmC,WAAW,GAAG;IACnD,OAAO;IACP;GACF;GAEA,OAAO;EACT;EAEA,OAAO;CACT;CAEA,OAAO;AACT;AAEA,SAAS,mCAAmC,SAA0B;CACpE,QAAQ,SAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK,UACH,OAAO;CACX;CAEA,OAAO;AACT"}