Skip to content

Commit 6fc71a8

Browse files
authored
Merge pull request #399 from share/snapshot-throw
Catch errors thrown in `ot.applyOps`
2 parents 4fa0160 + 922062d commit 6fc71a8

File tree

3 files changed

+134
-16
lines changed

3 files changed

+134
-16
lines changed

lib/error.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ ShareDBError.CODES = {
3939
ERR_OP_VERSION_MISMATCH_DURING_TRANSFORM: 'ERR_OP_VERSION_MISMATCH_DURING_TRANSFORM',
4040
ERR_OP_VERSION_NEWER_THAN_CURRENT_SNAPSHOT: 'ERR_OP_VERSION_NEWER_THAN_CURRENT_SNAPSHOT',
4141
ERR_OT_OP_BADLY_FORMED: 'ERR_OT_OP_BADLY_FORMED',
42+
ERR_OT_OP_NOT_APPLIED: 'ERR_OT_OP_NOT_APPLIED',
4243
ERR_OT_OP_NOT_PROVIDED: 'ERR_OT_OP_NOT_PROVIDED',
4344
ERR_PRESENCE_TRANSFORM_FAILED: 'ERR_PRESENCE_TRANSFORM_FAILED',
4445
ERR_PROTOCOL_VERSION_NOT_SUPPORTED: 'ERR_PROTOCOL_VERSION_NOT_SUPPORTED',

lib/ot.js

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -167,23 +167,27 @@ exports.applyOps = function(snapshot, ops) {
167167
if (!type) return new ShareDBError(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED, 'Unknown type');
168168
}
169169

170-
for (var index = 0; index < ops.length; index++) {
171-
var op = ops[index];
172-
173-
snapshot.v = op.v + 1;
174-
175-
if (op.create) {
176-
type = types[op.create.type];
177-
if (!type) return new ShareDBError(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED, 'Unknown type');
178-
snapshot.data = type.create(op.create.data);
179-
snapshot.type = type.uri;
180-
} else if (op.del) {
181-
snapshot.data = undefined;
182-
type = null;
183-
snapshot.type = null;
184-
} else {
185-
snapshot.data = type.apply(snapshot.data, op.op);
170+
try {
171+
for (var index = 0; index < ops.length; index++) {
172+
var op = ops[index];
173+
174+
snapshot.v = op.v + 1;
175+
176+
if (op.create) {
177+
type = types[op.create.type];
178+
if (!type) return new ShareDBError(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED, 'Unknown type');
179+
snapshot.data = type.create(op.create.data);
180+
snapshot.type = type.uri;
181+
} else if (op.del) {
182+
snapshot.data = undefined;
183+
type = null;
184+
snapshot.type = null;
185+
} else {
186+
snapshot.data = type.apply(snapshot.data, op.op);
187+
}
186188
}
189+
} catch (error) {
190+
return new ShareDBError(ERROR_CODE.ERR_OT_OP_NOT_APPLIED, error.message);
187191
}
188192
};
189193

test/ot.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ var ot = require('../lib/ot');
33
var types = require('../lib/types');
44
var type = types.defaultType;
55
var presenceType = require('./client/presence/presence-test-type').type;
6+
var ShareDBError = require('../lib/error');
67

8+
var ERROR_CODE = ShareDBError.CODES;
79
types.register(presenceType);
810

911
describe('ot', function() {
@@ -369,4 +371,115 @@ describe('ot', function() {
369371
});
370372
});
371373
});
374+
375+
describe('applyOps', function() {
376+
it('applies an op to a snapshot', function() {
377+
var snapshot = {
378+
type: 'json0',
379+
data: {title: 'Wee Free Men'}
380+
};
381+
382+
var ops = [
383+
{
384+
v: 1,
385+
op: [{p: ['title', 0], si: 'The '}]
386+
}
387+
];
388+
389+
var error = ot.applyOps(snapshot, ops);
390+
expect(error).to.be.undefined;
391+
expect(snapshot.data).to.eql({title: 'The Wee Free Men'});
392+
expect(snapshot.v).to.equal(2);
393+
});
394+
395+
it('applies multiple ops', function() {
396+
var snapshot = {
397+
type: 'json0',
398+
data: {title: 'Wee Free Men'}
399+
};
400+
401+
var ops = [
402+
{
403+
v: 1,
404+
op: [{p: ['title', 0], si: 'The '}]
405+
},
406+
{
407+
v: 2,
408+
op: [{p: ['author'], oi: 'Terry Pratchett'}]
409+
}
410+
];
411+
412+
ot.applyOps(snapshot, ops);
413+
expect(snapshot.data).to.eql({
414+
author: 'Terry Pratchett',
415+
title: 'The Wee Free Men'
416+
});
417+
expect(snapshot.v).to.equal(3);
418+
});
419+
420+
it('applies a del to a snapshot', function() {
421+
var snapshot = {
422+
type: 'json0',
423+
data: {title: 'Wee Free Men'}
424+
};
425+
426+
var ops = [{v: 1, del: true}];
427+
428+
ot.applyOps(snapshot, ops);
429+
expect(snapshot.data).to.be.undefined;
430+
});
431+
432+
it('applies a create to a snapshot', function() {
433+
var snapshot = {};
434+
var ops = [
435+
{
436+
v: 1,
437+
create: {
438+
type: 'json0',
439+
data: {title: 'Wee Free Men'}
440+
}
441+
}
442+
];
443+
444+
ot.applyOps(snapshot, ops);
445+
expect(snapshot.data).to.eql({title: 'Wee Free Men'});
446+
});
447+
448+
it('returns an error if the snapshot has an unknown type', function() {
449+
var snapshot = {type: 'unknown-type', data: {}};
450+
var ops = [];
451+
var error = ot.applyOps(snapshot, ops);
452+
expect(error.code).to.equal(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED);
453+
});
454+
455+
it('returns an error if a create op has an unknown type', function() {
456+
var snapshot = {};
457+
var ops = [
458+
{
459+
v: 1,
460+
create: {
461+
type: 'unknown-type',
462+
data: {}
463+
}
464+
}
465+
];
466+
var error = ot.applyOps(snapshot, ops);
467+
expect(error.code).to.equal(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED);
468+
});
469+
470+
it('catches and returns an error thrown by type.apply', function() {
471+
var snapshot = {
472+
type: 'json0',
473+
data: {title: 'Wee Free Men'}
474+
};
475+
476+
var ops = [{
477+
v: 1,
478+
op: [{p: ['title'], li: 'not a list'}]
479+
}];
480+
481+
var error = ot.applyOps(snapshot, ops);
482+
expect(error.code).to.equal(ERROR_CODE.ERR_OT_OP_NOT_APPLIED);
483+
});
484+
});
372485
});

0 commit comments

Comments
 (0)