Skip to content

Commit

Permalink
feat: add debugger
Browse files Browse the repository at this point in the history
  • Loading branch information
palmcivet committed Apr 4, 2024
1 parent be0da83 commit b7b7764
Show file tree
Hide file tree
Showing 36 changed files with 543 additions and 318 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
build:
runs-on: ubuntu-latest
permissions:
pages: write
contents: write
steps:
- name: Check out repository
uses: actions/checkout@v4
Expand Down
28 changes: 0 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,6 @@ pnpm install @getspectra/spectra-js

## Usage

```ts
import {
Policy,
Spectra,
parseDependences,
ResourceInterface,
DataInterface,
} from '@getspectra/spectra-js';

function loadDataFromDatabase(resources: ResourceInterface): DataInterface {
return {
'user.id': 2,
};
}

const policy = new Policy({
applyFilter: ['user.id', '=', 2],
permissions: ['EDIT_FILE'],
effect: 'DENY',
});

const dataDependencies = parseDependences(policy.getApplyFilter());
const resources = loadDataFromDatabase(dataDependencies);
const result = Spectra.validate([policy], resources);

console.info(result); // false
```

See [exapmle](./example/) for more usage.

## License
Expand Down
38 changes: 12 additions & 26 deletions example/basic-usage.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,26 @@
import {
BinaryExpression,
Policy,
Spectra,
parseDependences,
ResourceInterface,
DataInterface,
} from '../src';
import { BinaryExpression, Policy, Spectra, DataLoaderFunction } from '../src';

const loadDataFromDatabase: DataLoaderFunction = () => {
return {
'user.id': 1,
'user.name': 'Amiya',
'file.name': 'file.txt',
};
};

const ALL_POLICIES = [
new Policy({
applyFilter: new BinaryExpression('user.id', '=', 1),
permissions: ['EDIT_FILE'],
effect: 'ALLOW',
filter: new BinaryExpression('user.id', '=', 1),
}),
new Policy({
applyFilter: new BinaryExpression('user.id', '=', 2),
permissions: ['EDIT_FILE'],
effect: 'DENY',
filter: new BinaryExpression('user.id', '=', 2),
}),
];

const resourcesToLoad = ALL_POLICIES.reduce((memo, p) => {
const dataDependencies = parseDependences(p.getApplyFilter());
return Object.assign(memo, dataDependencies);
}, {} as ResourceInterface);

function loadDataFromDatabase(resources: ResourceInterface): DataInterface {
return {
'user.id': 1,
'user.name': 'Amiya',
'file.name': 'file.txt',
};
}

const resources = loadDataFromDatabase(resourcesToLoad);

const result = Spectra.validate(ALL_POLICIES, resources);
const result = Spectra.validate(ALL_POLICIES, loadDataFromDatabase, 'EDIT_FILE');

console.info(result); // true
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@
"typescript": "^5.2.2"
},
"dependencies": {
"@getspectra/spectra-typings": "^0.0.5"
"@getspectra/spectra-typings": "^0.0.6"
}
}
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 0 additions & 7 deletions src/apply-evaluator/apply-evaluator.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/apply-evaluator/index.ts

This file was deleted.

16 changes: 16 additions & 0 deletions src/debugger/debugger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {
ExpressionDebugReport,
ExpressionInterface,
DebuggerOptions,
DataType,
} from '@/types';

export class Debugger {
public static debug(
data: DataType,
expression: ExpressionInterface,
options: DebuggerOptions
): ExpressionDebugReport {
return expression.debug(data, options);
}
}
1 change: 1 addition & 0 deletions src/debugger/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './debugger';
7 changes: 7 additions & 0 deletions src/evaluator/evaluator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ExpressionInterface, DataType } from '@/types';

export class Evaluator {
public static evaluate(data: DataType, filter: ExpressionInterface): boolean {
return filter.evaluate(data);
}
}
1 change: 1 addition & 0 deletions src/evaluator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './evaluator';
38 changes: 26 additions & 12 deletions src/expressions/and.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { AndExpressionDefinition, FieldName } from '@getspectra/spectra-typings';
import { ExpressionInterface } from '@/types';
import {
DataType,
DebuggerOptions,
ExpressionInterface,
LogicExpressionDebugReport,
LogicOperationName,
} from '@/types';

export class AndExpression implements ExpressionInterface {
private expressions: Array<ExpressionInterface>;
Expand All @@ -8,14 +14,8 @@ export class AndExpression implements ExpressionInterface {
this.expressions = expressions;
}

public getOperation(): string {
return 'AND';
}

public getExpression(): AndExpressionDefinition {
return {
and: this.expressions.map((expression) => expression.getExpression()),
};
public getName(): string {
return LogicOperationName.AND;
}

public getFields(): Array<FieldName> {
Expand All @@ -24,11 +24,25 @@ export class AndExpression implements ExpressionInterface {
}, [] as Array<FieldName>);
}

public evaluate(data: object): boolean {
return this.expressions.every((expression) => expression.evaluate(data));
public getDefinition(): AndExpressionDefinition {
return {
and: this.expressions.map((expression) => expression.getDefinition()),
};
}

public jsonSerialize(): string {
return JSON.stringify(this.getExpression());
return JSON.stringify(this.getDefinition());
}

public evaluate(data: DataType): boolean {
return this.expressions.every((expression) => expression.evaluate(data));
}

public debug(data: DataType, options: DebuggerOptions): LogicExpressionDebugReport {
return {
name: this.getName(),
value: this.evaluate(data),
expressions: this.expressions.map((expression) => expression.debug(data, options)),
};
}
}
78 changes: 64 additions & 14 deletions src/expressions/binary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@ import {
FieldValue,
Operation,
} from '@getspectra/spectra-typings';
import { ExpressionInterface, DataInterface } from '@/types';
import { compareValue, getValueFromKey, isArgumentRef } from '@/utils';
import {
ExpressionInterface,
DataType,
OperationName,
ExpressionDebugReport,
DebuggerOptions,
} from '@/types';
import { compareValue, getValueFromKey, isRefValue } from '@/utils';

export class BinaryExpression implements ExpressionInterface {
private left: FieldName;
Expand All @@ -18,31 +24,75 @@ export class BinaryExpression implements ExpressionInterface {
this.operation = operation;
}

public getOperation(): string {
return this.operation;
}

public getExpression(): BinaryExpressionDefinition {
return [this.left, this.operation, this.right];
public getName(): string {
return OperationName[this.operation];
}

public getFields(): Array<FieldName> {
const fields = [this.left];

if (isArgumentRef(this.right)) {
if (isRefValue(this.right)) {
fields.push(this.right.ref);
}

return fields;
}

public getDefinition(): BinaryExpressionDefinition {
return [this.left, this.operation, this.right];
}

public jsonSerialize(): string {
return JSON.stringify(this.getExpression());
return JSON.stringify(this.getDefinition());
}

public evaluate(data: DataInterface): boolean {
const leftValue = getValueFromKey(data, this.left);
const rightValue = getValueFromKey(data, this.right);
return compareValue(leftValue, this.operation, rightValue);
public getValue(data: DataType) {
return {
left: {
name: this.left,
value: getValueFromKey(data, this.left),
},
right: isRefValue(this.right)
? {
name: this.right.ref,
value: getValueFromKey(data, this.right.ref),
}
: {
name: null,
value: this.right,
},
};
}

public evaluate(data: DataType): boolean {
const { left, right } = this.getValue(data);
return compareValue(left.value, this.operation, right.value);
}

public debug(data: DataType, options: DebuggerOptions): ExpressionDebugReport {
const { left, right } = this.getValue(data);

let value: boolean;
if (
options.strict &&
isRefValue(this.right) &&
left.value === null &&
right.value === null
) {
value = false;
console.warn(
'[spectra] strict mode: left and right are null, expression is false.'
);
} else {
value = compareValue(left.value, this.operation, right.value);
}

return {
name: this.getName(),
value,
left,
right,
operation: this.operation,
};
}
}
38 changes: 28 additions & 10 deletions src/expressions/not.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { FieldName, NotExpressionDefinition } from '@getspectra/spectra-typings';
import { ExpressionInterface } from '@/types';
import {
DataType,
DebuggerOptions,
ExpressionInterface,
LogicExpressionDebugReport,
LogicOperationName,
} from '@/types';

export class NotExpression implements ExpressionInterface {
private expression: ExpressionInterface;
Expand All @@ -8,25 +14,37 @@ export class NotExpression implements ExpressionInterface {
this.expression = expression;
}

public getOperation(): string {
return 'NOT';
public getName(): string {
return LogicOperationName.NOT;
}

public getExpression(): NotExpressionDefinition {
public getFields(): Array<FieldName> {
return this.expression.getFields();
}

public getExpressons(): Array<ExpressionInterface> {
return [this.expression];
}

public getDefinition(): NotExpressionDefinition {
return {
not: this.expression.getExpression(),
not: this.expression.getDefinition(),
};
}

public getFields(): Array<FieldName> {
return this.expression.getFields();
public jsonSerialize(): string {
return JSON.stringify(this.getDefinition());
}

public evaluate(data: object): boolean {
public evaluate(data: DataType): boolean {
return !this.expression.evaluate(data);
}

public jsonSerialize(): string {
return JSON.stringify(this.getExpression());
public debug(data: DataType, options: DebuggerOptions): LogicExpressionDebugReport {
return {
name: this.getName(),
value: this.evaluate(data),
expressions: [this.expression.debug(data, options)],
};
}
}
Loading

0 comments on commit b7b7764

Please sign in to comment.