// Basic expressions
has_permissions('profile:read')
// Basic combinations
has_permissions('transactions:read' || 'financials:manage')
// Compound expressions, with parentheses
has_permissions(('transactions:read' && 'expenses:read') || 'financials:manage')
(Domain Specific Language)
class UnaryExpr {
value = undefined;
constructor(value) {
this.value = value;
}
or(other) {
return new BinaryExpr(this, other, '||');
}
and(other) {
return new BinaryExpr(this, other, '&&');
}
toString() {
return `${this.value}`;
}
}
class BinaryExpr {
left = undefined;
right = undefined;
op = undefined;
constructor(left, right, op) {
this.left = left;
this.right = right;
if (op !== '||' && op !== '&&') {
throw Error(`invalid operator value: ${op}`);
}
this.op = op;
}
or(other) {
return new BinaryExpr(this, other, '||')
}
and(other) {
return new BinaryExpr(this, other, '&&')
}
toString() {
return `(${this.left} ${this.op} ${this.right})`;
}
}
const expr = new BinaryExpr('left', 'right', '||');
const e2 = expr.and(new BinaryExpr('lhs', 'rhs', '&&'));
console.log(e2.toString());
> (left && (lhs && rhs))
class Permission extends UnaryExpr {}
const trxRead = new Permission('transactions:read');
const trxWrite = new Permission('transactions:write');
const finManage = new Permission('financials:manage');
const perms = trxRead.and(trxWrite).or(finManage);
console.log((perms.toString()));
> (transactions:read || financials:manage)
const perms = trxRead.and(trxWrite).or(finManage);
console.log((perms.toString()));
> ((transactions:read && transactions:write) || financials:manage)
We don't need no stinkin' AST!
Wait, actually, we do need one
Oh, we already have one! kinda
Just don't look too closely...
class Interpreter {
constructor(permissions) {
this.permissions = permissions || [];
}
exec(expr) {
if (expr instanceof lang.UnaryExpr) {
return this.permissions.includes(expr.value);
}
if (expr instanceof lang.BinaryExpr) {
switch (expr.op) {
case '&&':
return this.exec(expr.left) && this.exec(expr.right);
case '||':
return this.exec(expr.left) || this.exec(expr.right);
default:
throw Error(`RuntimeError: invalid operator ${expr.op}`);
}
}
throw Error(`RuntimeError: unsupported expression type ${expr}`);
}
}
const lang = require('./minilang');
const runtime = require('./runtime');
const trxRead = new lang.Permission('transactions:read');
const trxWrite = new lang.Permission('transactions:read');
const finManage = new lang.Permission('financials:manage');
const userPerms = ['transactions:read', 'expenses:read'];
const int = new runtime.Interpreter(userPerms);
const trxReadAndWrite = trxRead.and(trxWrite);
console.log(`User can read/write transactions: ${int.exec(trxReadAndWrite)}`);
> User can read/write transactions: true
console.log(`User can manage financials: ${int.exec(finManage)}`);
> User can manage financials: false
int.permissions = ['financials:manage'];
console.log(`User can manage financials: ${int.exec(perms)}`);
> User can manage financials: true