Skip to content

Commit

Permalink
Merge pull request #26 from phenyl-js/validate-update-operation
Browse files Browse the repository at this point in the history
Validate update operation
  • Loading branch information
shinout committed Jun 1, 2019
2 parents 0910f4c + 7f32d00 commit f34b971
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 0 deletions.
4 changes: 4 additions & 0 deletions modules/format/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,7 @@ export { visitFindOperation } from "./retrieving/visit-find-operation";
export { visitUpdateOperation } from "./updating/visit-update-operation";
export { toMongoFindOperation } from "./retrieving/to-mongo-find-operation";
export { toMongoUpdateOperation } from "./updating/to-mongo-update-operation";
export {
validateUpdateOperation,
UpdateOperationValidationResult,
} from "./updating/validate-update-operation";
70 changes: 70 additions & 0 deletions modules/format/src/updating/validate-update-operation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import {
DocumentPath,
createDocumentPath,
parseDocumentPath,
} from "../common/document-path";

import { UpdateOperationOrSetOperand } from "./update-operation";
import { normalizeUpdateOperation } from "./normalize-update-operation";

export type UpdateOperationValidationResult =
| {
valid: true;
errors: [];
}
| {
valid: false;
errors: Error[];
};
/**
* Check if the given update operation is valid.
*/
export function validateUpdateOperation(
operation: UpdateOperationOrSetOperand
): UpdateOperationValidationResult {
const errors: Error[] = [];
const normalizedOperation = normalizeUpdateOperation(operation);
const docPaths: { [path: string]: DocumentPath } = Object.values(
normalizedOperation
)
.filter(isNotUndefined)
.map(operand => Object.keys(operand))
.reduce(
(acc, paths) => {
paths.forEach(path => {
if (acc[path] != null) {
errors.push(
new Error(
`Updating the path '${path}' would create a conflict at '${path}'`
)
);
}
acc[path] = path;
});
return acc;
},
{} as { [path: string]: DocumentPath }
);
Object.keys(docPaths).forEach(path => {
const attributes = parseDocumentPath(path);
for (let i = 1; i <= attributes.length - 1; i++) {
const partialPath = createDocumentPath(...attributes.slice(0, i));
if (docPaths[partialPath] != null) {
errors.push(
new Error(
`Updating the path '${path}' would create a conflict at '${
docPaths[partialPath]
}'`
)
);
}
}
});
return errors.length === 0
? { valid: true, errors: [] }
: { valid: false, errors };
}

function isNotUndefined<T>(v: T | undefined): v is T {
return v != null;
}
127 changes: 127 additions & 0 deletions modules/format/test/updating/validate-update-operation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/* eslint-env mocha */
import assert from "assert";
import { validateUpdateOperation } from "../../src/updating/validate-update-operation";

describe("validateUpdateOperation", () => {
it("returns valid when an operation has no conflicting document paths", () => {
const operation = {
$set: { "foo.bar": 12, bar: { foo: "abc" }, "foo.biz": true },
$push: { "foo.baz": "xyz" },
$mul: { abc: 123 },
$pull: { xyz: "foo" },
};
assert.deepEqual(validateUpdateOperation(operation), {
valid: true,
errors: [],
});
});

it("returns invalid when an operation has the same simple document path in two different operators", () => {
const operation = {
$inc: { foo: 12 },
$addToSet: { foo: "xyz" },
};
assert.deepEqual(validateUpdateOperation(operation), {
valid: false,
errors: [
new Error("Updating the path 'foo' would create a conflict at 'foo'"),
],
});
});

it("returns invalid when an operation has the same dotted document path in two different operators", () => {
const operation = {
$set: { "foo.bar": 12 },
$push: { "foo.bar": "xyz" },
};
assert.deepEqual(validateUpdateOperation(operation), {
valid: false,
errors: [
new Error(
"Updating the path 'foo.bar' would create a conflict at 'foo.bar'"
),
],
});
});

it("returns invalid when an operation has the same complex document path in two different operators", () => {
const operation = {
$set: { "foo.bar[1].baz": 12 },
$push: { "foo.bar[1].baz": "xyz" },
};
assert.deepEqual(validateUpdateOperation(operation), {
valid: false,
errors: [
new Error(
"Updating the path 'foo.bar[1].baz' would create a conflict at 'foo.bar[1].baz'"
),
],
});
});

it("returns invalid when an operation has two document paths, one of which is contained by the other", () => {
const operation = {
$set: { foo: [1] },
$inc: { "foo[0]": 1 },
};
assert.deepEqual(validateUpdateOperation(operation), {
valid: false,
errors: [
new Error(
"Updating the path 'foo[0]' would create a conflict at 'foo'"
),
],
});
});

it("returns invalid when an operation has two document paths, one of which is contained by the other", () => {
const operation = {
$set: { foo: [1] },
$inc: { "foo[0]": 1 },
};
assert.deepEqual(validateUpdateOperation(operation), {
valid: false,
errors: [
new Error(
"Updating the path 'foo[0]' would create a conflict at 'foo'"
),
],
});
});

it("returns invalid when an operation has two nested document paths, one of which is contained by the other", () => {
const operation = {
$set: { "foo.bar.baz": [1] },
$inc: { "foo.bar": 1 },
};
assert.deepEqual(validateUpdateOperation(operation), {
valid: false,
errors: [
new Error(
"Updating the path 'foo.bar.baz' would create a conflict at 'foo.bar'"
),
],
});
});
it("returns multiple errors when an operation has two or more conflicts", () => {
const operation = {
$set: { foo: { bar: [1] } },
$inc: { "foo.bar[0]": 1 },
$addToSet: { "foo.bar": 4 },
};
assert.deepEqual(validateUpdateOperation(operation), {
valid: false,
errors: [
new Error(
"Updating the path 'foo.bar[0]' would create a conflict at 'foo'"
),
new Error(
"Updating the path 'foo.bar[0]' would create a conflict at 'foo.bar'"
),
new Error(
"Updating the path 'foo.bar' would create a conflict at 'foo'"
),
],
});
});
});
2 changes: 2 additions & 0 deletions modules/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ export {
UpdateOperation,
UpdateOperationReducer,
UpdateOperationOrSetOperand,
UpdateOperationValidationResult,
UpdateOperator,
validateUpdateOperation,
visitFindOperation,
visitUpdateOperation,
} from "@sp2/format";
Expand Down

0 comments on commit f34b971

Please sign in to comment.