All files / json-expression/src/operators patch.ts

80.35% Statements 45/56
16.66% Branches 2/12
100% Functions 6/6
83.67% Lines 41/49

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 1163x   3x   3x 3x 3x   3x 4x     4x         3x 6x             3x 2x 2x                                 2x           3x   3x                                                 3x 3x 3x 3x 3x 4x 4x 4x 4x 4x 4x               3x     1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 2x   1x        
import {Expression, type ExpressionResult} from '../codegen-steps';
import type * as types from '../types';
import {toPath} from '@jsonjoy.com/json-pointer/lib/util';
import type {Path} from '@jsonjoy.com/json-pointer/lib/types';
import {type JavaScript, type JavaScriptLinked, compileClosure} from '@jsonjoy.com/codegen';
import {$findRef} from '@jsonjoy.com/json-pointer/lib/codegen/findRef';
import {find} from '@jsonjoy.com/json-pointer/lib/find';
 
const validateAddOperandCount = (count: number) => {
  Iif (count < 3) {
    throw new Error('Not enough operands for "jp.add" operand.');
  }
  Iif (count % 2 !== 0) {
    throw new Error('Invalid number of operands for "jp.add" operand.');
  }
};
 
const validateAddPath = (path: unknown) => {
  Iif (typeof path !== 'string') {
    throw new Error('The "path" argument for "jp.add" must be a const string.');
  }
};
 
type AddFn = (doc: unknown, value: unknown) => unknown;
 
export const $$add = (path: Path): JavaScriptLinked<AddFn> => {
  const find = $findRef(path);
  const js = /* js */ `
(function(find, path){
  return function(doc, value){
    var f = find(doc);
    var obj = f.obj, key = f.key, val = f.val;
    if (!obj) doc = value;
    else if (typeof key === 'string') obj[key] = value;
    else {
      var length = obj.length;
      if (key < length) obj.splice(key, 0, value);
      else if (key > length) throw new Error('INVALID_INDEX');
      else obj.push(value);
    }
    return doc;
  };
})`;
 
  return {
    deps: [find] as unknown[],
    js: js as JavaScript<(...deps: unknown[]) => AddFn>,
  };
};
 
export const $add = (path: Path): AddFn => compileClosure($$add(path));
 
export const patchOperators: types.OperatorDefinition<any>[] = [
  [
    'jp.add',
    [],
    -1,
    /**
     * Applies JSON Patch "add" operations to the input value.
     *
     * ```
     * ['add', {},
     *   '/a', 1,
     *   '/b', ['+', 2, 3],
     * ]
     * ```
     *
     * Results in:
     *
     * ```
     * {
     *   a: 1,
     *   b: 5,
     * }
     * ```
     */
    (expr: types.JsonPatchAdd, ctx) => {
      let i = 1;
      const length = expr.length;
      validateAddOperandCount(length);
      let doc = ctx.eval(expr[i++], ctx);
      while (i < length) {
        const path = expr[i++];
        validateAddPath(path);
        const value = ctx.eval(expr[i++], ctx);
        const {obj, key} = find(doc, toPath(path));
        Iif (!obj) doc = value;
        else if (typeof key === 'string') (obj as any)[key] = value;
        else IEif (obj instanceof Array) {
          const length = obj.length;
          if ((key as number) < length) obj.splice(key as number, 0, value);
          else if ((key as number) > length) throw new Error('INVALID_INDEX');
          else obj.push(value);
        }
      }
      return doc;
    },
    (ctx: types.OperatorCodegenCtx<types.JsonPatchAdd>): ExpressionResult => {
      const expr = ctx.expr;
      const length = ctx.operands.length;
      validateAddOperandCount(length + 1);
      let i = 0;
      let curr = ctx.operands[i++];
      while (i < length) {
        const path = expr[1 + i++];
        validateAddPath(path);
        const value = ctx.operands[i++];
        const addCompiled = $add(toPath(path));
        const dAdd = ctx.link(addCompiled);
        curr = new Expression(`${dAdd}(${curr}, ${value})`);
      }
      return curr;
    },
  ] as types.OperatorDefinition<types.JsonPatchAdd>,
];