All files jsonPath.ts

86.32% Statements 101/117
95% Branches 19/20
75% Functions 3/4
86.32% Lines 101/117

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 1181x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 19x 19x 19x 19x     19x 19x 19x 19x 8x 8x 8x 8x 19x 19x 19x 19x 19x 18x 18x 19x 19x 19x 19x 1x 21x 21x 9x 2x 2x 2x 1x 1x 1x 1x 2x 7x 7x 21x 12x 12x 12x 12x 21x 1x 1x                             1x 1x 1x 1x 1x 1x 1x 9x 9x 9x 9x 9x 9x 1x 1x 7x 7x 9x 1x 1x 1x 1x 1x 9x  
/**
 * Copyright (c) OpenLens Authors. All rights reserved.
 * Licensed under MIT License. See LICENSE in root directory for more information.
 */
 
import { JSONPath } from "@astronautlabs/jsonpath";
import { TypedRegEx } from "typed-regex";
 
const slashDashSearch = /[/\\-]/g;
const pathByBareDots = /(?<=\w)\./;
const textBeforeFirstSquare = /^.*(?=\[)/g;
const backSlash = /\\/g;
const kubectlOptionPrefix = TypedRegEx("^\\$?\\.?(?<pathExpression>.*)");
const sliceVersion = /\[]/g;
const tripleDotName = /\.\.\.(?<trailing>.)/g;
const trailingDotDot = /\.\.$/;
 
/**
 * The GO package that kubectl and kubernetes uses for its JSONpath implementation has some
 * shorthand conveniences that are not part of the official spec. This function tries to convert
 * those shorthands to the official spec.
 *
 * Known shorthands:
 * - Leading `$` is optional (but implied)
 * - The string `/` can be used without a leading `\` escapement
 * - The string `\.` is used to denote the "value of '.'" and not "next key"
 * - The string `-` can be used while not in quotes
 * - `[]` as shorthand for `[0]`
 * - Remove `..` at the end of a path, we will just format it slightly differently
 * - Allow `...foo` as well as `..foo`
 */
export function convertKubectlJsonPathToNodeJsonPath(jsonPath: string) {
  const captures = kubectlOptionPrefix.captures(jsonPath);
  let start = "$";
 
  if (!captures) {
    return start;
  }
 
  let { pathExpression } = captures;
 
  if (pathExpression.match(slashDashSearch)) {
    const [first, ...rest] = pathExpression.split(pathByBareDots);
 
    pathExpression = `${convertToIndexNotation(first, true)}${rest.map(value => convertToIndexNotation(value)).join("")}`;
  }
 
  pathExpression = pathExpression.replace(trailingDotDot, "");
  pathExpression = pathExpression.replace(tripleDotName, "..$<trailing>");
 
  if (!pathExpression.startsWith("[")) {
    start += ".";
  }
 
  // strip '\' characters from the result
  return `${start}${pathExpression.replace(backSlash, "").replace(sliceVersion, "[0]")}`;
}
 
function convertToIndexNotation(key: string, firstItem = false) {
  if (key.match(slashDashSearch)) {
    if (key.includes("[")) { // handle cases where key contains [...]
      const keyToConvert = key.match(textBeforeFirstSquare); // get the text from the key before '['
 
      if (keyToConvert && keyToConvert[0].match(slashDashSearch)) { // check if that part contains illegal characters
        return key.replace(keyToConvert[0], `['${keyToConvert[0]}']`); // surround key with '[' and ']'
      } else {
        return `.${key}`; // otherwise return as is with leading '.'
      }
    }
 
    return `['${key}']`;
  } else { // no illegal characters found, do not touch
    const prefix = firstItem ? "" : ".";
 
    return `${prefix}${key}`;
  }
}
 
export function formatJSONValue(value: unknown): string {
  if (value == null) {
    return "";
  }

  if (Array.isArray(value)) {
    return value.map(formatJSONValue).join(", ");
  }

  if (typeof value === "object") {
    return JSON.stringify(value);
  }

  return String(value);
}
 
/**
 * This function is a safer version of `JSONPath.value(obj, path)` with untrusted jsonpath strings
 *
 * This function will also stringify the value retreived from the object
 */
export function safeJSONPathValue(obj: object, path: string): unknown {
  try {
    const parsedPath = JSONPath.parse(convertKubectlJsonPathToNodeJsonPath(path));
    const isSlice = parsedPath.some((exp: any) => exp.expression.type === "slice" || exp.expression.type === "wildcard");
    const value = JSONPath.query(obj, JSONPath.stringify(parsedPath), isSlice ? Infinity : 1);
 
    if (isSlice) {
      return value;
    }
 
    return value[0];
  } catch (error) {
    // something failed
    console.warn("[JSON-PATH]: failed to parse jsonpath", error);
 
    return undefined;
  }
}