1// Copyright (c) Jupyter Development Team. 2// Distributed under the terms of the Modified BSD License. 3'use strict'; 4 5import { 6 valueIn 7} from '../common/util'; 8 9import { 10 ChunkSource 11} from '../chunking'; 12 13/** 14 * The different diff operations available 15 */ 16export 17type DiffOp = 'add' | 'remove' | 'replace' | 'patch' | 18 'addrange' | 'removerange'; 19 20 21/** 22 * Base interface for all diff entries 23 */ 24export 25interface IDiffEntryBase { 26 /** 27 * The key of the diff entry: Either the field name in an object, or the 28 * index in a list/string. 29 */ 30 key: string | number; 31 32 /** 33 * A string identifying the diff operation type, as defined by DiffOp. 34 */ 35 op: DiffOp; 36 37 /** 38 * Optional: Source of diff, for use when merging. 39 * 40 * This should not need to be set manually. 41 */ 42 source?: ChunkSource; 43} 44 45 46/** 47 * Diff representing an added sequence of list entries, or an added substring 48 */ 49export 50interface IDiffAddRange extends IDiffEntryBase { 51 op: 'addrange'; 52 key: number; 53 /** 54 * The sequence of values that were added 55 */ 56 valuelist: string | any[]; 57} 58 59/** 60 * Diff representing an added object entry 61 */ 62export 63interface IDiffAdd extends IDiffEntryBase { 64 op: 'add'; 65 key: string; 66 /** 67 * The value that was added 68 */ 69 value: any; 70} 71 72 73/** 74 * Diff representing a removed object entry 75 */ 76export 77interface IDiffRemove extends IDiffEntryBase { 78 op: 'remove'; 79 key: string; 80} 81 82 83/** 84 * Diff representing a replaced object entry 85 */ 86export 87interface IDiffReplace extends IDiffEntryBase { 88 op: 'replace'; 89 key: string; 90 /** 91 * The new value 92 */ 93 value: any; 94} 95 96 97/** 98 * Diff representing a removed sequence of list entries, or a removed substring 99 */ 100export 101interface IDiffRemoveRange extends IDiffEntryBase { 102 op: 'removerange'; 103 key: number; 104 105 /** 106 * The length of the sequence that was deleted 107 */ 108 length: number; 109} 110 111 112/** 113 * Diff representing a patched entry (object entry or list entry) 114 */ 115export 116interface IDiffPatch extends IDiffEntryBase { 117 op: 'patch'; 118 /** 119 * The collection of sub-diffs describing the patch of the object 120 */ 121 diff: IDiffEntry[] | null; 122} 123export 124interface IDiffPatchArray extends IDiffPatch { 125 key: number; 126} 127export 128interface IDiffPatchObject extends IDiffPatch { 129 key: string; 130} 131 132/** 133 * Describes a diff entry of a single JSON value (object, list, string) 134 */ 135export 136type IDiffEntry = IDiffAddRange | IDiffRemoveRange | IDiffPatch | IDiffAdd | IDiffRemove | IDiffReplace; 137 138export 139type IDiffArrayEntry = IDiffAddRange | IDiffRemoveRange | IDiffPatchArray; 140 141export 142type IDiffObjectEntry = IDiffPatchObject | IDiffAdd | IDiffRemove | IDiffReplace; 143 144export 145type IDiffImmutableArrayEntry = IDiffAddRange | IDiffRemoveRange; 146 147export 148type IDiffImmutableObjectEntry = IDiffAdd | IDiffRemove | IDiffReplace; 149 150 151export 152type DiffCollection = (IDiffEntry[] | null)[]; 153 154 155/** Create a replacement diff entry */ 156export 157function opReplace(key: string, value: any): IDiffReplace { 158 return {op: 'replace', key: key, value: value}; 159} 160 161/** Create an addition diff entry */ 162export 163function opAdd(key: string, value: any): IDiffAdd { 164 return {op: 'add', key: key, value: value}; 165} 166 167/** Create a removal diff entry */ 168export 169function opRemove(key: string): IDiffRemove { 170 return {op: 'remove', key: key}; 171} 172 173/** Create a removal diff entry */ 174export 175function opAddRange(key: number, valuelist: string | any[]): IDiffAddRange { 176 return {op: 'addrange', key: key, valuelist: valuelist}; 177} 178 179/** Create a range removal diff entry */ 180export 181function opRemoveRange(key: number, length: number): IDiffRemoveRange { 182 return {op: 'removerange', key: key, length: length}; 183} 184 185/** Create a range removal diff entry */ 186export 187function opPatch(key: string | number, diff: IDiffEntry[] | null): IDiffPatch { 188 return {op: 'patch', key: key, diff: diff}; 189} 190 191 192/** 193 * Validate that a diff operation is valid to apply on a given base sequence 194 */ 195export 196function validateSequenceOp(base: ReadonlyArray<any> | string, entry: IDiffEntry): void { 197 if (typeof entry.key !== 'number') { 198 console.info('Invalid patch details', base, entry); 199 throw new TypeError(`Invalid patch sequence op: Key is not a number: ${entry.key}`); 200 } 201 let index = entry.key; 202 if (entry.op === 'addrange') { 203 if (index < 0 || index > base.length || isNaN(index)) { 204 throw new RangeError('Invalid add range diff op: Key out of range: ' + index); 205 } 206 } else if (entry.op === 'removerange') { 207 if (index < 0 || index >= base.length || isNaN(index)) { 208 throw new RangeError('Invalid remove range diff op: Key out of range: ' + index); 209 } 210 let skip = entry.length; 211 if (index + skip > base.length || isNaN(index)) { 212 throw new RangeError('Invalid remove range diff op: Range too long!'); 213 } 214 } else if (entry.op === 'patch') { 215 if (index < 0 || index >= base.length || isNaN(index)) { 216 throw new RangeError('Invalid patch diff op: Key out of range: ' + index); 217 } 218 } else { 219 throw new Error('Invalid op: ' + entry.op); 220 } 221} 222 223/** 224 * Validate that a diff operation is valid to apply on a given base object 225 */ 226export 227function validateObjectOp(base: any, entry: IDiffEntry, keys: string[]): void { 228 let op = entry.op; 229 if (typeof entry.key !== 'string') { 230 console.info('Invalid patch details', base, entry, keys); 231 throw new TypeError(`Invalid patch object op: Key is not a string: ${entry.key}`); 232 } 233 let key = entry.key; 234 235 if (op === 'add') { 236 if (valueIn(key, keys)) { 237 throw new Error('Invalid add key diff op: Key already present: ' + key); 238 } 239 } else if (op === 'remove') { 240 if (!valueIn(key, keys)) { 241 throw new Error('Invalid remove key diff op: Missing key: ' + key); 242 } 243 } else if (op === 'replace') { 244 if (!valueIn(key, keys)) { 245 throw new Error('Invalid replace key diff op: Missing key: ' + key); 246 } 247 } else if (op === 'patch') { 248 if (!valueIn(key, keys)) { 249 throw new Error('Invalid patch key diff op: Missing key: ' + key); 250 } 251 } else { 252 throw new Error('Invalid op: ' + op); 253 } 254} 255