All files / json-patch/op OpExtend.ts

84.44% Statements 38/45
70% Branches 14/20
87.5% Functions 7/8
92.3% Lines 36/39

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  15x   15x 15x     15x         15x     65x 65x   65x       7x               35x 35x       35x 30x   5x   35x       35x 35x 35x 35x 80x 80x 20x 20x   60x   35x       15x         15x 15x       12x 12x       6x 6x 6x 6x 6x 6x      
import type {CompactExtendOp, OPCODE_EXTEND} from '../codec/compact/types';
import {AbstractOp} from './AbstractOp';
import type {OperationExtend} from '../types';
import {find, isArrayReference, isObjectReference, type Path, formatJsonPointer} from '@jsonjoy.com/json-pointer';
import {OPCODE} from '../constants';
import type {IMessagePackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack';
 
const {isArray} = Array;
 
/**
 * @category JSON Patch Extended
 */
export class OpExtend extends AbstractOp<'extend'> {
  constructor(
    path: Path,
    public readonly props: Record<string, unknown>,
    public readonly deleteNull: boolean,
  ) {
    super(path);
  }
 
  public op() {
    return 'extend' as const;
  }
 
  public code() {
    return OPCODE.extend;
  }
 
  public apply(doc: unknown) {
    const ref = find(doc, this.path);
    Iif (isArrayReference(ref)) {
      Iif (ref.val !== undefined) {
        ref.obj[ref.key] = this.extend(ref.val);
      }
    } else if (isObjectReference(ref)) {
      ref.obj[ref.key] = this.extend(ref.val);
    } else {
      doc = this.extend(doc);
    }
    return {doc};
  }
 
  private extend<T>(value: T): T {
    Iif (isArray(value)) return value;
    Iif (typeof value !== 'object') return value;
    Iif (!value) return value;
    for (const [key, v] of Object.entries(this.props)) {
      Iif (key === '__proto__') throw new Error('NO_PROTO');
      if (v === null && this.deleteNull) {
        delete (value as any)[key];
        continue;
      }
      (value as any)[key] = v;
    }
    return value;
  }
 
  public toJson(parent?: AbstractOp): OperationExtend {
    const op: OperationExtend = {
      op: 'extend',
      path: formatJsonPointer(this.path),
      props: this.props,
    };
    if (this.deleteNull) op.deleteNull = this.deleteNull;
    return op;
  }
 
  public toCompact(parent: undefined | AbstractOp, verbose: boolean): CompactExtendOp {
    const opcode: OPCODE_EXTEND = verbose ? 'extend' : OPCODE.extend;
    return this.deleteNull ? [opcode, this.path, this.props, 1] : [opcode, this.path, this.props];
  }
 
  public encode(encoder: IMessagePackEncoder, parent?: AbstractOp) {
    const {deleteNull} = this;
    encoder.encodeArrayHeader(deleteNull ? 4 : 3);
    encoder.writer.u8(OPCODE.extend);
    encoder.encodeArray(this.path as unknown[]);
    encoder.encodeObject(this.props);
    if (deleteNull) encoder.writer.u8(1);
  }
}