Skip to content

Commit

Permalink
feat(stringequals): adding new predefined string operator
Browse files Browse the repository at this point in the history
This operator could be used as a conditionResolver
  • Loading branch information
roggervalf committed Feb 16, 2021
1 parent 4613d5d commit 6fd5766
Show file tree
Hide file tree
Showing 12 changed files with 285 additions and 34 deletions.
1 change: 1 addition & 0 deletions dist/main.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ declare abstract class Statement<T extends object> {
effect: EffectBlock;
constructor({ sid, effect, condition }: StatementInterface);
matchConditions(this: Statement<T>, { context, conditionResolver }: MatchConditionInterface<T>): boolean;
private evaluateCondition;
}

declare class ActionBased<T extends object> extends Statement<T> {
Expand Down
57 changes: 51 additions & 6 deletions dist/main.es.js

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

2 changes: 1 addition & 1 deletion dist/main.es.js.map

Large diffs are not rendered by default.

57 changes: 51 additions & 6 deletions dist/main.js

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

2 changes: 1 addition & 1 deletion dist/main.js.map

Large diffs are not rendered by default.

80 changes: 80 additions & 0 deletions src/ActionBasedPolicy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,86 @@ describe('ActionBasedPolicy Class', () => {
});
});

describe('when condition does not exist', () => {
it('returns false', () => {
class User {
constructor(public info = { id: 456, age: 19 }) {}
get user() {
return this.info;
}
}

const policy = new ActionBasedPolicy({
statements: [
{
action: ['read']
},
{
action: ['write', 'update'],
condition: {
greaterThan: {
'user.age': 18
}
}
}
]
});

expect(
policy.evaluate({
action: 'write',
context: { user: { id: 123, age: 17 } }
})
).toBe(false);
expect(
policy.evaluate({
action: 'write',
context: new User()
})
).toBe(false);
});
});

describe('when condition is predefined', () => {
it('returns true or false', () => {
class User {
constructor(public info = { name: 'Juan', age: 19 }) {}
get user() {
return this.info;
}
}

const policy = new ActionBasedPolicy({
statements: [
{
action: ['read']
},
{
action: ['write', 'update'],
condition: {
stringEquals: {
'user.name': 'Joan'
}
}
}
]
});

expect(
policy.evaluate({
action: 'write',
context: { user: { name: 'Joan', age: 17 } }
})
).toBe(true);
expect(
policy.evaluate({
action: 'write',
context: new User()
})
).toBe(false);
});
});

describe('can and cannot', () => {
it('can should return false when not found and true for when matched with allow', () => {
const policy = new ActionBasedPolicy({
Expand Down
66 changes: 47 additions & 19 deletions src/Statement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import {
EffectBlock,
ConditionBlock,
StatementInterface,
MatchConditionInterface
MatchConditionInterface,
MatchConditionResolverInterface
} from './types';
import { getValueFromPath } from './utils/getValueFromPath';
import { generateUUID } from './utils/generateUUID';
import {operators} from './conditionOperators';

abstract class Statement<T extends object> {
protected sid: string;
Expand All @@ -27,25 +29,51 @@ abstract class Statement<T extends object> {
{ context, conditionResolver }: MatchConditionInterface<T>
): boolean {
const { condition: conditions } = this;
return conditionResolver && conditions && context
? Object.keys(conditions).every((condition) =>
Object.keys(conditions[condition]).every((path) => {
const conditionValues = conditions[condition][path];
if (conditionValues instanceof Array) {
return conditionValues.some((value) =>
conditionResolver[condition](
getValueFromPath(context, path),
value
)
);
}
return conditionResolver[condition](
getValueFromPath(context, path),
conditionValues

if(conditions && context){
return Object.keys(conditions).every((condition) =>
Object.keys(conditions[condition]).every((path) => {
const conditionValues = conditions[condition][path];
if (conditionValues instanceof Array) {
return conditionValues.some((value) =>
this.evaluateCondition({
context,
conditionResolver,
condition,
path,
value
})
);
})
)
: true;
}

return this.evaluateCondition({
context,
conditionResolver,
condition,
path,
value: conditionValues
});
})
)
}

return true;
}

private evaluateCondition(
this: Statement<T>,
{ context, conditionResolver={}, path, value, condition }: MatchConditionResolverInterface<T>
): boolean {

const currentResolver= conditionResolver[condition] || operators[condition]

if(currentResolver){
return currentResolver(
getValueFromPath(context, path),
value
)
}
return false;
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/conditionOperators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {stringEquals} from './string/stringEquals';

export const operators: Record<string, unknown>={
stringEquals
};

15 changes: 15 additions & 0 deletions src/conditionOperators/string/stringEquals.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { stringEquals } from './stringEquals';

describe('stringEquals', () => {
it('returns true', () => {
expect(stringEquals('secrets', 'secrets')).toBeTruthy;
expect(stringEquals('house', 'house')).toBeTruthy;
expect(stringEquals('', '')).toBeTruthy;
});

it('returns false', () => {
expect(stringEquals('secrets', 'house')).toBeFalsy;
expect(stringEquals('house', 'secrets')).toBeFalsy;
expect(stringEquals('', '1')).toBeFalsy;
});
});
22 changes: 22 additions & 0 deletions src/conditionOperators/string/stringEquals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Exact matching, case sensitive.
*
* @since 4.3.0
* @category String
* @param {string} data The value to be compared.
* @param {string} expected The expected value.
* @returns {boolean} Returns `true` if `value` is equal to `expected value`.
* @example
* ```javascript
* stringEquals('hi', 'hi')
* // => true
*
* stringEquals('hi', 'no')
* // => false
* ```
*/
export function stringEquals(data: string, expected: string): boolean {
return (
data === expected
);
}
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ export interface MatchConditionInterface<T extends object> {
conditionResolver?: ConditionResolver;
}

export interface MatchConditionResolverInterface<T extends object> {
context: T
conditionResolver?: ConditionResolver;
path: string;
condition: string;
value: any;
}

export interface MatchActionBasedInterface<T extends object>
extends MatchConditionInterface<T> {
action: string;
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"outDir": "./dist",
"strict": true,
"noUnusedParameters": true,
"noImplicitThis": true
"noImplicitThis": true,
"moduleResolution": "node"
},
"include": ["src"],
"exclude": [
Expand Down

0 comments on commit 6fd5766

Please sign in to comment.