arrest
Version:
OpenAPI v3 compliant REST framework for Node.js, with support for MongoDB and JSON-Schema
183 lines • 6.8 kB
JavaScript
import { rulesToQuery } from '@casl/ability/extra';
import { ObjectId } from 'mongodb';
function haveKeysInCommon(a, b) {
return !!Object.keys(a).find((k) => (typeof b[k] !== 'undefined' ? k : undefined));
}
export function addConstraint(query, constraint) {
if (!query) {
query = {};
}
if (typeof constraint === 'object' && constraint !== null) {
if (Array.isArray(query)) {
if (Array.isArray(constraint)) {
query = query.concat(constraint);
}
else if (query.length && query[query.length - 1].$match) {
query[query.length - 1].$match = addConstraint(query[query.length - 1].$match, constraint);
}
else if (Object.keys(constraint).length > 0) {
query.push({ $match: constraint });
}
}
else if (Array.isArray(constraint)) {
query = [{ $match: query }].concat(constraint);
}
else {
if (query.$or && constraint.$or) {
query = { $and: [query, constraint] };
}
else if (query.$and && Object.keys(constraint).length > 0) {
const lastCond = query.$and[query.$and.length - 1];
if (lastCond && !(lastCond.$or && constraint.$or) && !(lastCond.$and && constraint.$and) && !haveKeysInCommon(lastCond, constraint)) {
Object.assign(lastCond, constraint);
}
else {
query.$and.push(constraint);
}
}
else {
if (haveKeysInCommon(query, constraint)) {
query = { $and: [query, constraint] };
}
else {
Object.assign(query, constraint);
}
}
}
}
return query;
}
export function escapeMongoKey(key) {
return key.replace(/%/g, '%25').replace(/\$/g, '%24').replace(/\./g, '%2E');
}
export const unescapeMongoKey = decodeURIComponent;
function translateProperties(val, f) {
if (typeof val !== 'object' || val === null || val instanceof Date || ObjectId.isValid(val)) {
return val;
}
else if (Array.isArray(val)) {
return val.map((k) => translateProperties(k, f));
}
else {
const out = {};
for (let k in val) {
out[f(k)] = translateProperties(val[k], f);
}
return out;
}
}
export function escapeMongoObject(val) {
return translateProperties(val, escapeMongoKey);
}
export function unescapeMongoObject(val) {
return translateProperties(val, unescapeMongoKey);
}
export function patchToMongo(patch, escape = false) {
const out = {};
for (let p of patch) {
const path = (p.path || '')
.split('/')
.map((i) => (escape ? escapeMongoKey(i) : i))
.slice(1);
if (!path.length) {
throw new Error('path cannot be empty');
}
switch (p.op) {
case 'add':
if (!out.doc)
out.doc = {};
if (path.length && path[path.length - 1] == '-') {
if (path.length === 1) {
throw new Error("cannot use '-' index at root of path");
}
path.pop();
if (!out.doc.$push)
out.doc.$push = {};
out.doc.$push[path.join('.')] = p.value;
}
else {
if (!out.doc.$set)
out.doc.$set = {};
out.doc.$set[path.join('.')] = p.value;
}
break;
case 'replace':
if (path.length && path[path.length - 1] == '-') {
throw new Error("cannot use '-' index in path of replace");
}
else {
if (!out.doc)
out.doc = {};
if (!out.doc.$set)
out.doc.$set = {};
out.doc.$set[path.join('.')] = p.value;
if (!out.query)
out.query = {};
out.query[path.join('.')] = { $exists: true };
}
break;
case 'move':
const from = (p.from || '').split('/').slice(1);
if (!from.length) {
throw new Error('from path cannot be empty');
}
if (from.length && from[from.length - 1] == '-') {
throw new Error("cannot use '-' index in from path of move");
}
else if (path.length && path[path.length - 1] == '-') {
throw new Error("cannot use '-' index in path of move");
}
else {
if (!out.doc)
out.doc = {};
if (!out.doc.$rename)
out.doc.$rename = {};
out.doc.$rename[from.join('.')] = path.join('.');
}
break;
case 'remove':
if (path.length && path[path.length - 1] == '-') {
throw new Error("cannot use '-' index in path of remove");
}
else {
if (!out.doc)
out.doc = {};
if (!out.doc.$unset)
out.doc.$unset = {};
out.doc.$unset[path.join('.')] = 1;
}
break;
case 'copy':
throw new Error('copy not supported');
case 'test':
if (path.length && path[path.length - 1] == '-') {
throw new Error("cannot use '-' index in path of test");
}
else {
if (!out.query)
out.query = {};
out.query[path.join('.')] = p.value;
}
break;
}
}
return out;
}
// The following two functions come from @casl/mongoose and were
// copied (and adapted) here to avoid importing mongoose
function convertToMongoQuery(rule) {
const conditions = rule.conditions;
return rule.inverted ? { $nor: [conditions] } : conditions;
}
export function toMongoQuery(ability, subjectType, action) {
// TODO: typescript doesn't like the type of action, so we work around it
const f = rulesToQuery;
const out = f(ability, action, subjectType, convertToMongoQuery);
if (Object.keys(out).length === 1 && out.$or.length === 1) {
return out.$or[0];
}
else {
return out;
}
}
//# sourceMappingURL=util.js.map