@via-profit/ability 3.3.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -11
- package/dist/core/AbilityCondition.d.ts +4 -2
- package/dist/core/AbilityError.d.ts +2 -2
- package/dist/core/AbilityRule.d.ts +6 -1
- package/dist/index.js +248 -185
- package/dist/parsers/dsl/AbilityDSLParser.d.ts +0 -1
- package/dist/parsers/dsl/AbilityDSLToken.d.ts +3 -1
- package/package.json +1 -1
- package/dist/core/AbilityParser.d.ts +0 -61
package/README.md
CHANGED
|
@@ -526,17 +526,6 @@ order.total
|
|
|
526
526
|
| **contains** | `includes`, `has` | `tags contains 'vip'` | Array contains the element | array |
|
|
527
527
|
| **not contains** | `not includes`, `not has` | `tags not contains 'vip'` | Array does not contain the element | array |
|
|
528
528
|
|
|
529
|
-
**String Operators**
|
|
530
|
-
|
|
531
|
-
| DSL Operator | Synonyms | Example | Description | Types |
|
|
532
|
-
|--------------|----------|---------|-------------|-------|
|
|
533
|
-
| **starts with** | `begins with` | `email starts with 'admin@'` | String starts with | string |
|
|
534
|
-
| **not starts with** | — | `email not starts with 'test'` | String does not start with | string |
|
|
535
|
-
| **ends with** | — | `email ends with '.ru'` | String ends with | string |
|
|
536
|
-
| **not ends with** | — | `email not ends with '.com'` | String does not end with | string |
|
|
537
|
-
| **includes** | `contains substring` | `name includes 'lex'` | String contains substring | string |
|
|
538
|
-
| **not includes** | — | `name not includes 'test'` | String does not contain substring | string |
|
|
539
|
-
|
|
540
529
|
**Boolean Operators**
|
|
541
530
|
|
|
542
531
|
| DSL Operator | Synonyms | Example | Description | Types |
|
|
@@ -552,6 +541,34 @@ order.total
|
|
|
552
541
|
| **length greater than** | `len >` | `tags length greater than 2` | Length greater than | array, string |
|
|
553
542
|
| **length less than** | `len <` | `tags length less than 5` | Length less than | array, string |
|
|
554
543
|
|
|
544
|
+
Here is the English version, keeping the structure and tone consistent with your documentation style.
|
|
545
|
+
|
|
546
|
+
**Special Operators**
|
|
547
|
+
|
|
548
|
+
| DSL Operator | Synonyms | Example | Description | Types |
|
|
549
|
+
|--------------|----------|---------|-------------|--------|
|
|
550
|
+
| **always** | — | `always` | The condition is always true. Used for global allow rules or simplifying logic. | special operator |
|
|
551
|
+
| **never** | — | `never` | The condition is always false. Used for global deny rules or disabling a rule. | special operator |
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
**always**
|
|
555
|
+
An operator that always returns `true`.
|
|
556
|
+
Used for:
|
|
557
|
+
|
|
558
|
+
- global allow (`permit permission.* if all: always`)
|
|
559
|
+
- testing
|
|
560
|
+
- disabling complex conditions
|
|
561
|
+
- creating fallback rules
|
|
562
|
+
|
|
563
|
+
**never**
|
|
564
|
+
An operator that always returns `false`.
|
|
565
|
+
Used for:
|
|
566
|
+
|
|
567
|
+
- global deny (`deny permission.* if all: never`)
|
|
568
|
+
- temporarily disabling a rule
|
|
569
|
+
- explicit unconditional rejection
|
|
570
|
+
|
|
571
|
+
|
|
555
572
|
#### Value
|
|
556
573
|
|
|
557
574
|
Supported values:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import AbilityCode from './AbilityCode';
|
|
2
|
-
export type AbilityConditionCodeType = '=' | '<>' | '>' | '<' | '>=' | '<=' | 'in' | 'not in' | 'contains' | 'not contains' | 'length greater than' | 'length less than' | 'length equals';
|
|
3
|
-
export type AbilityConditionLiteralType = 'equals' | 'not_equals' | 'contains' | '
|
|
2
|
+
export type AbilityConditionCodeType = '=' | '<>' | '>' | '<' | '>=' | '<=' | 'in' | 'not in' | 'contains' | 'not contains' | 'length greater than' | 'length less than' | 'length equals' | 'always' | 'never';
|
|
3
|
+
export type AbilityConditionLiteralType = 'equals' | 'not_equals' | 'contains' | 'not_contains' | 'in' | 'not_in' | 'greater_than' | 'less_than' | 'less_or_equal' | 'greater_or_equal' | 'length_greater_than' | 'length_less_than' | 'length_equals' | 'always' | 'never';
|
|
4
4
|
export declare class AbilityCondition extends AbilityCode<AbilityConditionCodeType> {
|
|
5
5
|
static equals: AbilityCondition;
|
|
6
6
|
static not_equals: AbilityCondition;
|
|
@@ -15,6 +15,8 @@ export declare class AbilityCondition extends AbilityCode<AbilityConditionCodeTy
|
|
|
15
15
|
static length_greater_than: AbilityCondition;
|
|
16
16
|
static length_less_than: AbilityCondition;
|
|
17
17
|
static length_equals: AbilityCondition;
|
|
18
|
+
static always: AbilityCondition;
|
|
19
|
+
static never: AbilityCondition;
|
|
18
20
|
static fromLiteral(literal: AbilityConditionLiteralType): AbilityCondition;
|
|
19
21
|
get literal(): AbilityConditionLiteralType;
|
|
20
22
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare class AbilityError extends Error {
|
|
2
|
-
constructor(message: string);
|
|
2
|
+
constructor(message: string, options?: ErrorOptions);
|
|
3
3
|
}
|
|
4
4
|
export declare class AbilityParserError extends Error {
|
|
5
|
-
constructor(message: string);
|
|
5
|
+
constructor(message: string, options?: ErrorOptions);
|
|
6
6
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import AbilityMatch from './AbilityMatch';
|
|
2
|
-
import AbilityCondition,
|
|
2
|
+
import { AbilityCondition, AbilityConditionCodeType } from './AbilityCondition';
|
|
3
3
|
export type AbilityRuleConfig = {
|
|
4
4
|
readonly id?: string | null;
|
|
5
5
|
readonly name?: string | null;
|
|
@@ -42,6 +42,11 @@ export declare class AbilityRule<Resources extends object = object, Environment
|
|
|
42
42
|
* @param params
|
|
43
43
|
*/
|
|
44
44
|
constructor(params: AbilityRuleConstructorProps);
|
|
45
|
+
private isPrimitive;
|
|
46
|
+
private isNumber;
|
|
47
|
+
private isString;
|
|
48
|
+
private valueLen;
|
|
49
|
+
private operatorHandlers;
|
|
45
50
|
/**
|
|
46
51
|
* Check if the rule is matched
|
|
47
52
|
* @param resource - The resource to check
|
package/dist/index.js
CHANGED
|
@@ -22,69 +22,116 @@ class AbilityCompare extends AbilityCode {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
class AbilityError extends Error {
|
|
25
|
-
constructor(message) {
|
|
26
|
-
super(message);
|
|
25
|
+
constructor(message, options) {
|
|
26
|
+
super(message, options);
|
|
27
|
+
this.name = 'AbilityError';
|
|
28
|
+
if (Error.captureStackTrace) {
|
|
29
|
+
Error.captureStackTrace(this, this.constructor);
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
class AbilityParserError extends Error {
|
|
30
|
-
constructor(message) {
|
|
31
|
-
super(message);
|
|
34
|
+
constructor(message, options) {
|
|
35
|
+
super(message, options);
|
|
36
|
+
this.name = 'AbilityParserError';
|
|
37
|
+
if (Error.captureStackTrace) {
|
|
38
|
+
Error.captureStackTrace(this, this.constructor);
|
|
39
|
+
}
|
|
32
40
|
}
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
class AbilityCondition extends AbilityCode {
|
|
36
|
-
static equals
|
|
37
|
-
static not_equals
|
|
38
|
-
static greater_than
|
|
39
|
-
static less_than
|
|
40
|
-
static less_or_equal
|
|
41
|
-
static greater_or_equal
|
|
42
|
-
static in
|
|
43
|
-
static not_in
|
|
44
|
-
static contains
|
|
45
|
-
static not_contains
|
|
46
|
-
static length_greater_than
|
|
47
|
-
static length_less_than
|
|
48
|
-
static length_equals
|
|
44
|
+
static equals;
|
|
45
|
+
static not_equals;
|
|
46
|
+
static greater_than;
|
|
47
|
+
static less_than;
|
|
48
|
+
static less_or_equal;
|
|
49
|
+
static greater_or_equal;
|
|
50
|
+
static in;
|
|
51
|
+
static not_in;
|
|
52
|
+
static contains;
|
|
53
|
+
static not_contains;
|
|
54
|
+
static length_greater_than;
|
|
55
|
+
static length_less_than;
|
|
56
|
+
static length_equals;
|
|
57
|
+
static always;
|
|
58
|
+
static never;
|
|
59
|
+
static {
|
|
60
|
+
this.equals = new AbilityCondition('=');
|
|
61
|
+
this.not_equals = new AbilityCondition('<>');
|
|
62
|
+
this.greater_than = new AbilityCondition('>');
|
|
63
|
+
this.less_than = new AbilityCondition('<');
|
|
64
|
+
this.less_or_equal = new AbilityCondition('<=');
|
|
65
|
+
this.greater_or_equal = new AbilityCondition('>=');
|
|
66
|
+
this.in = new AbilityCondition('in');
|
|
67
|
+
this.not_in = new AbilityCondition('not in');
|
|
68
|
+
this.contains = new AbilityCondition('contains');
|
|
69
|
+
this.not_contains = new AbilityCondition('not contains');
|
|
70
|
+
this.length_greater_than = new AbilityCondition('length greater than');
|
|
71
|
+
this.length_less_than = new AbilityCondition('length less than');
|
|
72
|
+
this.length_equals = new AbilityCondition('length equals');
|
|
73
|
+
this.always = new AbilityCondition('always');
|
|
74
|
+
this.never = new AbilityCondition('never');
|
|
75
|
+
}
|
|
49
76
|
static fromLiteral(literal) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return this.not_in;
|
|
71
|
-
case 'length_greater_than':
|
|
72
|
-
return this.length_greater_than;
|
|
73
|
-
case 'length_equals':
|
|
74
|
-
return this.length_equals;
|
|
75
|
-
default:
|
|
76
|
-
throw new AbilityParserError(`Literal ${literal} does not found in AbilityCondition class`);
|
|
77
|
+
const map = {
|
|
78
|
+
equals: this.equals,
|
|
79
|
+
not_equals: this.not_equals,
|
|
80
|
+
greater_than: this.greater_than,
|
|
81
|
+
less_than: this.less_than,
|
|
82
|
+
less_or_equal: this.less_or_equal,
|
|
83
|
+
greater_or_equal: this.greater_or_equal,
|
|
84
|
+
in: this.in,
|
|
85
|
+
not_in: this.not_in,
|
|
86
|
+
contains: this.contains,
|
|
87
|
+
not_contains: this.not_contains,
|
|
88
|
+
length_greater_than: this.length_greater_than,
|
|
89
|
+
length_equals: this.length_equals,
|
|
90
|
+
always: this.always,
|
|
91
|
+
never: this.never,
|
|
92
|
+
length_less_than: this.length_less_than,
|
|
93
|
+
};
|
|
94
|
+
const condition = map[literal];
|
|
95
|
+
if (!condition) {
|
|
96
|
+
throw new AbilityParserError(`Literal "${literal}" does not found in AbilityCondition class`);
|
|
77
97
|
}
|
|
98
|
+
return condition;
|
|
78
99
|
}
|
|
79
100
|
get literal() {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
101
|
+
switch (this.code) {
|
|
102
|
+
case '=':
|
|
103
|
+
return 'equals';
|
|
104
|
+
case '<>':
|
|
105
|
+
return 'not_equals';
|
|
106
|
+
case '>':
|
|
107
|
+
return 'greater_than';
|
|
108
|
+
case '<':
|
|
109
|
+
return 'less_than';
|
|
110
|
+
case '>=':
|
|
111
|
+
return 'greater_or_equal';
|
|
112
|
+
case '<=':
|
|
113
|
+
return 'less_or_equal';
|
|
114
|
+
case 'in':
|
|
115
|
+
return 'in';
|
|
116
|
+
case 'not in':
|
|
117
|
+
return 'not_in';
|
|
118
|
+
case 'contains':
|
|
119
|
+
return 'contains';
|
|
120
|
+
case 'not contains':
|
|
121
|
+
return 'not_contains';
|
|
122
|
+
case 'length greater than':
|
|
123
|
+
return 'length_greater_than';
|
|
124
|
+
case 'length less than':
|
|
125
|
+
return 'length_less_than';
|
|
126
|
+
case 'length equals':
|
|
127
|
+
return 'length_equals';
|
|
128
|
+
case 'always':
|
|
129
|
+
return 'always';
|
|
130
|
+
case 'never':
|
|
131
|
+
return 'never';
|
|
132
|
+
default:
|
|
133
|
+
throw new Error(`Unknown condition code: ${this.code}`);
|
|
86
134
|
}
|
|
87
|
-
return literal;
|
|
88
135
|
}
|
|
89
136
|
}
|
|
90
137
|
|
|
@@ -618,145 +665,122 @@ class AbilityRule {
|
|
|
618
665
|
this.resource = resource;
|
|
619
666
|
this.condition = condition;
|
|
620
667
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
if (AbilityCondition.less_than.isEqual(this.condition)) {
|
|
640
|
-
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
641
|
-
is = subjectValue < resourceValue;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
// less or equal
|
|
645
|
-
if (AbilityCondition.less_or_equal.isEqual(this.condition)) {
|
|
646
|
-
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
647
|
-
is = subjectValue <= resourceValue;
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
// more than
|
|
651
|
-
if (AbilityCondition.greater_than.isEqual(this.condition)) {
|
|
652
|
-
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
653
|
-
is = subjectValue > resourceValue;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
// more or equal
|
|
657
|
-
if (AbilityCondition.greater_or_equal.isEqual(this.condition)) {
|
|
658
|
-
if (typeof subjectValue === 'number' && typeof resourceValue === 'number') {
|
|
659
|
-
is = subjectValue >= resourceValue;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
// in
|
|
663
|
-
if (AbilityCondition.in.isEqual(this.condition)) {
|
|
664
|
-
// value in array
|
|
665
|
-
if (isValue(subjectValue) && Array.isArray(resourceValue)) {
|
|
666
|
-
is = resourceValue.includes(subjectValue);
|
|
667
|
-
}
|
|
668
|
-
// array intersects array
|
|
669
|
-
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
670
|
-
is = subjectValue.some(v => resourceValue.includes(v));
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
// not in
|
|
674
|
-
if (AbilityCondition.not_in.isEqual(this.condition)) {
|
|
675
|
-
if (isValue(subjectValue) && Array.isArray(resourceValue)) {
|
|
676
|
-
is = !resourceValue.includes(subjectValue);
|
|
677
|
-
}
|
|
678
|
-
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
679
|
-
is = !subjectValue.some(v => resourceValue.includes(v));
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
// contains
|
|
683
|
-
if (AbilityCondition.contains.isEqual(this.condition)) {
|
|
684
|
-
// array contains value
|
|
685
|
-
if (Array.isArray(subjectValue) && isValue(resourceValue)) {
|
|
686
|
-
is = subjectValue.includes(resourceValue);
|
|
687
|
-
}
|
|
688
|
-
// array intersects array
|
|
689
|
-
else if (Array.isArray(subjectValue) && Array.isArray(resourceValue)) {
|
|
690
|
-
is = subjectValue.some(v => resourceValue.includes(v));
|
|
668
|
+
isPrimitive(v) {
|
|
669
|
+
return typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || v === null;
|
|
670
|
+
}
|
|
671
|
+
isNumber(v) {
|
|
672
|
+
return typeof v === 'number';
|
|
673
|
+
}
|
|
674
|
+
isString(v) {
|
|
675
|
+
return typeof v === 'string';
|
|
676
|
+
}
|
|
677
|
+
valueLen = (v) => this.isString(v) || Array.isArray(v) ? v.length : null;
|
|
678
|
+
operatorHandlers = {
|
|
679
|
+
[AbilityCondition.always.literal]: () => true,
|
|
680
|
+
[AbilityCondition.never.literal]: () => false,
|
|
681
|
+
[AbilityCondition.equals.literal]: (a, b) => a === b,
|
|
682
|
+
[AbilityCondition.not_equals.literal]: (a, b) => a !== b,
|
|
683
|
+
[AbilityCondition.contains.literal]: (a, b) => {
|
|
684
|
+
if (Array.isArray(a) && this.isPrimitive(b)) {
|
|
685
|
+
return a.includes(b);
|
|
691
686
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
if (AbilityCondition.not_contains.isEqual(this.condition)) {
|
|
695
|
-
if (Array.isArray(subjectValue) && isValue(resourceValue)) {
|
|
696
|
-
is = !subjectValue.includes(resourceValue);
|
|
687
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
688
|
+
return a.some(v => b.includes(v));
|
|
697
689
|
}
|
|
698
|
-
|
|
699
|
-
|
|
690
|
+
return false;
|
|
691
|
+
},
|
|
692
|
+
[AbilityCondition.not_contains.literal]: (a, b) => {
|
|
693
|
+
if (Array.isArray(a) && this.isPrimitive(b)) {
|
|
694
|
+
return !a.includes(b);
|
|
700
695
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
if (AbilityCondition.length_equals.isEqual(this.condition)) {
|
|
704
|
-
// foo.bar == n
|
|
705
|
-
if (isValue(subjectValue) && typeof resourceValue === 'number') {
|
|
706
|
-
is = String(subjectValue).length === resourceValue;
|
|
696
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
697
|
+
return !a.some(v => b.includes(v));
|
|
707
698
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
699
|
+
return false;
|
|
700
|
+
},
|
|
701
|
+
[AbilityCondition.in.literal]: (a, b) => {
|
|
702
|
+
if (this.isPrimitive(a) && Array.isArray(b)) {
|
|
703
|
+
return b.includes(a);
|
|
711
704
|
}
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
is = subjectValue.length === resourceValue.length;
|
|
705
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
706
|
+
return a.some(v => b.includes(v));
|
|
715
707
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
708
|
+
return false;
|
|
709
|
+
},
|
|
710
|
+
[AbilityCondition.not_in.literal]: (a, b) => {
|
|
711
|
+
if (this.isPrimitive(a) && Array.isArray(b)) {
|
|
712
|
+
return !b.includes(a);
|
|
719
713
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
if (AbilityCondition.length_greater_than.isEqual(this.condition)) {
|
|
723
|
-
// foo.bar > n
|
|
724
|
-
if (isValue(subjectValue) && typeof resourceValue === 'number') {
|
|
725
|
-
is = String(subjectValue).length > resourceValue;
|
|
714
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
715
|
+
return !a.some(v => b.includes(v));
|
|
726
716
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
717
|
+
return false;
|
|
718
|
+
},
|
|
719
|
+
[AbilityCondition.greater_than.literal]: (a, b) => {
|
|
720
|
+
return this.isNumber(a) && this.isNumber(b) ? a > b : false;
|
|
721
|
+
},
|
|
722
|
+
[AbilityCondition.less_than.literal]: (a, b) => {
|
|
723
|
+
return this.isNumber(a) && this.isNumber(b) ? a < b : false;
|
|
724
|
+
},
|
|
725
|
+
[AbilityCondition.greater_or_equal.literal]: (a, b) => {
|
|
726
|
+
return this.isNumber(a) && this.isNumber(b) ? a >= b : false;
|
|
727
|
+
},
|
|
728
|
+
[AbilityCondition.less_or_equal.literal]: (a, b) => {
|
|
729
|
+
return this.isNumber(a) && this.isNumber(b) ? a <= b : false;
|
|
730
|
+
},
|
|
731
|
+
[AbilityCondition.length_greater_than.literal]: (a, b) => {
|
|
732
|
+
const alen = this.valueLen(a);
|
|
733
|
+
if (alen === null) {
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
if (this.isNumber(b)) {
|
|
737
|
+
return alen > b;
|
|
738
|
+
}
|
|
739
|
+
const bLen = this.valueLen(b);
|
|
740
|
+
if (bLen !== null) {
|
|
741
|
+
return alen > bLen;
|
|
730
742
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
743
|
+
return false;
|
|
744
|
+
},
|
|
745
|
+
[AbilityCondition.length_less_than.literal]: (a, b) => {
|
|
746
|
+
const alen = this.valueLen(a);
|
|
747
|
+
if (alen === null) {
|
|
748
|
+
return false;
|
|
734
749
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
is = subjectValue.length > resourceValue.length;
|
|
750
|
+
if (this.isNumber(b)) {
|
|
751
|
+
return alen < b;
|
|
738
752
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
// foo.bar < n
|
|
743
|
-
if (isValue(subjectValue) && typeof resourceValue === 'number') {
|
|
744
|
-
is = String(subjectValue).length < resourceValue;
|
|
753
|
+
const bLen = this.valueLen(b);
|
|
754
|
+
if (bLen !== null) {
|
|
755
|
+
return alen < bLen;
|
|
745
756
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
757
|
+
return false;
|
|
758
|
+
},
|
|
759
|
+
[AbilityCondition.length_equals.literal]: (a, b) => {
|
|
760
|
+
const alen = this.valueLen(a);
|
|
761
|
+
if (alen === null) {
|
|
762
|
+
return false;
|
|
749
763
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
is = subjectValue.length < resourceValue.length;
|
|
764
|
+
if (this.isNumber(b)) {
|
|
765
|
+
return alen === b;
|
|
753
766
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
767
|
+
const bLen = this.valueLen(b);
|
|
768
|
+
if (bLen !== null) {
|
|
769
|
+
return alen === bLen;
|
|
757
770
|
}
|
|
758
|
-
|
|
759
|
-
|
|
771
|
+
return false;
|
|
772
|
+
},
|
|
773
|
+
};
|
|
774
|
+
/**
|
|
775
|
+
* Check if the rule is matched
|
|
776
|
+
* @param resource - The resource to check
|
|
777
|
+
* @param environment
|
|
778
|
+
*/
|
|
779
|
+
async check(resource, environment) {
|
|
780
|
+
const [subjectValue, resourceValue] = this.extractValues(resource, environment);
|
|
781
|
+
const handler = this.operatorHandlers[this.condition.literal];
|
|
782
|
+
const result = handler(subjectValue, resourceValue);
|
|
783
|
+
this.state = result ? AbilityMatch.match : AbilityMatch.mismatch;
|
|
760
784
|
return this.state;
|
|
761
785
|
}
|
|
762
786
|
/**
|
|
@@ -1221,6 +1245,8 @@ class AbilityDSLToken extends AbilityCode {
|
|
|
1221
1245
|
static LEN_LT = 'LEN_LT';
|
|
1222
1246
|
static LEN_EQ = 'LEN_EQ';
|
|
1223
1247
|
static NOT_EQ = 'NOT_EQ';
|
|
1248
|
+
static ALWAYS = 'ALWAYS';
|
|
1249
|
+
static NEVER = 'NEVER';
|
|
1224
1250
|
static STRING = 'STRING';
|
|
1225
1251
|
static NUMBER = 'NUMBER';
|
|
1226
1252
|
static BOOLEAN = 'BOOLEAN';
|
|
@@ -1263,6 +1289,8 @@ class AbilityDSLLexer {
|
|
|
1263
1289
|
'is',
|
|
1264
1290
|
'or',
|
|
1265
1291
|
'than',
|
|
1292
|
+
'always',
|
|
1293
|
+
'never',
|
|
1266
1294
|
]);
|
|
1267
1295
|
constructor(input) {
|
|
1268
1296
|
this.input = input;
|
|
@@ -1408,6 +1436,12 @@ class AbilityDSLLexer {
|
|
|
1408
1436
|
}
|
|
1409
1437
|
}
|
|
1410
1438
|
const word = this.input.slice(start, this.pos);
|
|
1439
|
+
if (word === 'always') {
|
|
1440
|
+
return new AbilityDSLToken(AbilityDSLToken.ALWAYS, word, startLine, startColumn);
|
|
1441
|
+
}
|
|
1442
|
+
if (word === 'never') {
|
|
1443
|
+
return new AbilityDSLToken(AbilityDSLToken.NEVER, word, startLine, startColumn);
|
|
1444
|
+
}
|
|
1411
1445
|
// Если есть точка — это путь (identifier или permission)
|
|
1412
1446
|
if (word.includes('.')) {
|
|
1413
1447
|
const last = this.tokens[this.tokens.length - 1];
|
|
@@ -1670,7 +1704,9 @@ class AbilityDSLParser {
|
|
|
1670
1704
|
if (this.isStartOfGroup() || this.isStartOfPolicy()) {
|
|
1671
1705
|
break;
|
|
1672
1706
|
}
|
|
1673
|
-
if (this.check(AbilityDSLToken.IDENTIFIER)
|
|
1707
|
+
if (this.check(AbilityDSLToken.IDENTIFIER) ||
|
|
1708
|
+
this.check(AbilityDSLToken.ALWAYS) ||
|
|
1709
|
+
this.check(AbilityDSLToken.NEVER)) {
|
|
1674
1710
|
group.addRule(this.parseRule());
|
|
1675
1711
|
}
|
|
1676
1712
|
else {
|
|
@@ -1687,7 +1723,7 @@ class AbilityDSLParser {
|
|
|
1687
1723
|
parseGroup() {
|
|
1688
1724
|
this.consumeLeadingComments();
|
|
1689
1725
|
const meta = this.takeAnnotations();
|
|
1690
|
-
const compareToken = this.consumeOneOf([AbilityDSLToken.ALL, AbilityDSLToken.ANY], 'Expected "all" or "any"');
|
|
1726
|
+
const compareToken = this.consumeOneOf([AbilityDSLToken.ALL, AbilityDSLToken.ANY, AbilityDSLToken.ALWAYS, AbilityDSLToken.NEVER], 'Expected "all" or "any" or "always" or "never"');
|
|
1691
1727
|
const compareMethod = compareToken.code === AbilityDSLToken.ALL ? AbilityCompare.and : AbilityCompare.or;
|
|
1692
1728
|
if (this.check(AbilityDSLToken.OF)) {
|
|
1693
1729
|
this.advance();
|
|
@@ -1717,11 +1753,26 @@ class AbilityDSLParser {
|
|
|
1717
1753
|
parseRule() {
|
|
1718
1754
|
this.consumeLeadingComments();
|
|
1719
1755
|
const meta = this.takeAnnotations();
|
|
1720
|
-
if (
|
|
1721
|
-
|
|
1756
|
+
// if (this.check(AbilityDSLToken.ALWAYS) || this.check(AbilityDSLToken.NEVER)) {
|
|
1757
|
+
// // Checking that there are no extra tokens after the value
|
|
1758
|
+
// // (skip comments)
|
|
1759
|
+
// this.consumeLeadingComments();
|
|
1760
|
+
// const specOperator = this.consume();
|
|
1761
|
+
// // return new AbilityRule({
|
|
1762
|
+
// // subject: '',
|
|
1763
|
+
// // resource,
|
|
1764
|
+
// // condition,
|
|
1765
|
+
// // name: meta.name,
|
|
1766
|
+
// // });
|
|
1767
|
+
// }
|
|
1768
|
+
const isNeverAlways = this.check(AbilityDSLToken.ALWAYS) || this.check(AbilityDSLToken.NEVER);
|
|
1769
|
+
if (!isNeverAlways && !this.check(AbilityDSLToken.IDENTIFIER)) {
|
|
1770
|
+
this.syntaxError(`Expected identifier, but got ${this.peek().code}`, this.peek());
|
|
1722
1771
|
}
|
|
1723
1772
|
// Subject (e.g., "user.roles")
|
|
1724
|
-
const subject =
|
|
1773
|
+
const subject = isNeverAlways
|
|
1774
|
+
? ''
|
|
1775
|
+
: this.consume(AbilityDSLToken.IDENTIFIER, 'Expected field').value;
|
|
1725
1776
|
// Operator (e.g., "contains", "equals", "is not null")
|
|
1726
1777
|
const { condition, operator } = this.parseConditionOperator();
|
|
1727
1778
|
let resource;
|
|
@@ -1729,7 +1780,9 @@ class AbilityDSLParser {
|
|
|
1729
1780
|
// Special operators that don't consume a value token.
|
|
1730
1781
|
if (operator === AbilityDSLToken.EQ_NULL ||
|
|
1731
1782
|
operator === AbilityDSLToken.NOT_EQ_NULL ||
|
|
1732
|
-
operator === AbilityDSLToken.NULL
|
|
1783
|
+
operator === AbilityDSLToken.NULL ||
|
|
1784
|
+
operator === AbilityDSLToken.ALWAYS ||
|
|
1785
|
+
operator === AbilityDSLToken.NEVER) {
|
|
1733
1786
|
resource = null;
|
|
1734
1787
|
}
|
|
1735
1788
|
else {
|
|
@@ -1761,6 +1814,16 @@ class AbilityDSLParser {
|
|
|
1761
1814
|
*/
|
|
1762
1815
|
parseConditionOperator() {
|
|
1763
1816
|
const savedPos = this.pos;
|
|
1817
|
+
// "always"
|
|
1818
|
+
if (this.matchWord('always')) {
|
|
1819
|
+
return { condition: AbilityCondition.always, operator: AbilityDSLToken.ALWAYS };
|
|
1820
|
+
}
|
|
1821
|
+
this.pos = savedPos;
|
|
1822
|
+
// "never"
|
|
1823
|
+
if (this.matchWord('never')) {
|
|
1824
|
+
return { condition: AbilityCondition.never, operator: AbilityDSLToken.NEVER };
|
|
1825
|
+
}
|
|
1826
|
+
this.pos = savedPos;
|
|
1764
1827
|
// "length equals"
|
|
1765
1828
|
if (this.matchWord('length') && this.matchWord('equals')) {
|
|
1766
1829
|
return { condition: AbilityCondition.length_equals, operator: AbilityDSLToken.LEN_EQ };
|
|
@@ -1956,7 +2019,10 @@ class AbilityDSLParser {
|
|
|
1956
2019
|
return false;
|
|
1957
2020
|
}
|
|
1958
2021
|
const token = this.peek();
|
|
1959
|
-
if ((token.code === AbilityDSLToken.KEYWORD ||
|
|
2022
|
+
if ((token.code === AbilityDSLToken.KEYWORD ||
|
|
2023
|
+
token.code === AbilityDSLToken.IDENTIFIER ||
|
|
2024
|
+
token.code === AbilityDSLToken.ALWAYS ||
|
|
2025
|
+
token.code === AbilityDSLToken.NEVER) &&
|
|
1960
2026
|
token.value === word) {
|
|
1961
2027
|
this.advance();
|
|
1962
2028
|
return true;
|
|
@@ -2101,9 +2167,6 @@ class AbilityDSLParser {
|
|
|
2101
2167
|
}
|
|
2102
2168
|
throw new AbilityDSLSyntaxError(token.line, token.column, context + '\n', finalDetails);
|
|
2103
2169
|
}
|
|
2104
|
-
getLine(lineNumber) {
|
|
2105
|
-
return this.dsl.split(/\r?\n/)[lineNumber - 1] ?? '';
|
|
2106
|
-
}
|
|
2107
2170
|
suggest(actual, expectedTypes) {
|
|
2108
2171
|
const candidates = [];
|
|
2109
2172
|
for (const type of expectedTypes) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import AbilityCode from '../../core/AbilityCode';
|
|
2
|
-
export type TokenType = 'EFFECT' | 'IF' | 'PERMISSION' | 'IDENTIFIER' | 'COLON' | 'COMMA' | 'DOT' | 'LBRACKET' | 'RBRACKET' | 'ALL' | 'ANY' | 'OF' | 'EOF' | 'COMMENT' | 'EQ' | 'CONTAINS' | 'IN' | 'NOT_IN' | 'NOT_CONTAINS' | 'GT' | 'GTE' | 'LT' | 'LTE' | 'NULL' | 'EQ_NULL' | 'NOT_EQ_NULL' | 'NOT_EQ' | 'LEN_GT' | 'LEN_LT' | 'LEN_EQ' | 'STRING' | 'NUMBER' | 'BOOLEAN' | 'SYMBOL' | 'KEYWORD' | 'UNKNOWN';
|
|
2
|
+
export type TokenType = 'EFFECT' | 'IF' | 'PERMISSION' | 'IDENTIFIER' | 'COLON' | 'COMMA' | 'DOT' | 'LBRACKET' | 'RBRACKET' | 'ALL' | 'ANY' | 'OF' | 'EOF' | 'COMMENT' | 'EQ' | 'CONTAINS' | 'IN' | 'NOT_IN' | 'NOT_CONTAINS' | 'GT' | 'GTE' | 'LT' | 'LTE' | 'NULL' | 'EQ_NULL' | 'NOT_EQ_NULL' | 'NOT_EQ' | 'LEN_GT' | 'LEN_LT' | 'LEN_EQ' | 'ALWAYS' | 'NEVER' | 'STRING' | 'NUMBER' | 'BOOLEAN' | 'SYMBOL' | 'KEYWORD' | 'UNKNOWN';
|
|
3
3
|
/**
|
|
4
4
|
* Represents a single token produced by the Ability DSL lexer.
|
|
5
5
|
* Each token carries a type (e.g., EFFECT, IDENTIFIER, STRING) and its raw string value.
|
|
@@ -47,6 +47,8 @@ export declare class AbilityDSLToken<Code extends TokenType = TokenType> extends
|
|
|
47
47
|
static readonly LEN_LT: TokenType;
|
|
48
48
|
static readonly LEN_EQ: TokenType;
|
|
49
49
|
static readonly NOT_EQ: TokenType;
|
|
50
|
+
static readonly ALWAYS: TokenType;
|
|
51
|
+
static readonly NEVER: TokenType;
|
|
50
52
|
static readonly STRING: TokenType;
|
|
51
53
|
static readonly NUMBER: TokenType;
|
|
52
54
|
static readonly BOOLEAN: TokenType;
|
package/package.json
CHANGED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import AbilityPolicy from './AbilityPolicy';
|
|
2
|
-
export type Primitive = string | number | boolean | null | undefined;
|
|
3
|
-
export type NestedDict<T = Primitive> = {
|
|
4
|
-
[key: string]: NestedDict<T> | T;
|
|
5
|
-
};
|
|
6
|
-
export type ResourceObject = Record<string, unknown>;
|
|
7
|
-
export type ResourcesMap = Record<string, ResourceObject>;
|
|
8
|
-
export declare class AbilityParser {
|
|
9
|
-
/**
|
|
10
|
-
* Sets a value in a nested object structure based on a dot/bracket notation path.
|
|
11
|
-
* @param object - The target object to modify.
|
|
12
|
-
* @param path - The path to the property in dot/bracket notation.
|
|
13
|
-
* @param value - The value to set at the specified path.
|
|
14
|
-
*/
|
|
15
|
-
static setValueDotValue<T extends Primitive>(object: NestedDict<T>, path: string, value: T): void;
|
|
16
|
-
/**
|
|
17
|
-
* Generates TypeScript type definitions based on the provided policies.
|
|
18
|
-
* @param policies - An array of AbilityPolicy instances.
|
|
19
|
-
* @returns A generated type definitions.
|
|
20
|
-
*/
|
|
21
|
-
static generateTypeDefs(policies: readonly AbilityPolicy[]): string;
|
|
22
|
-
/**
|
|
23
|
-
* Determines TypeScript type based on the rule
|
|
24
|
-
* @param rule - The rule to analyze
|
|
25
|
-
* @returns TypeScript type as string
|
|
26
|
-
*/
|
|
27
|
-
private static determineTypeFromRule;
|
|
28
|
-
/**
|
|
29
|
-
* Gets TypeScript type for array values
|
|
30
|
-
* @param resource - The resource value to analyze
|
|
31
|
-
* @returns TypeScript array type as string
|
|
32
|
-
*/
|
|
33
|
-
private static getArrayType;
|
|
34
|
-
/**
|
|
35
|
-
* Gets primitive TypeScript type for a value
|
|
36
|
-
* @param value - The value to analyze
|
|
37
|
-
* @returns TypeScript primitive type as string
|
|
38
|
-
*/
|
|
39
|
-
private static getPrimitiveType;
|
|
40
|
-
/**
|
|
41
|
-
* Builds nested structure from flat paths
|
|
42
|
-
* Example: 'user.profile.name' -> { user: { profile: { name: 'string' } } }
|
|
43
|
-
* @param flatStructure - Flat structure with dot notation paths
|
|
44
|
-
* @returns Nested object structure
|
|
45
|
-
*/
|
|
46
|
-
private static buildNestedStructure;
|
|
47
|
-
/**
|
|
48
|
-
* Formats type structure into a string
|
|
49
|
-
* @param structure - Nested type structure
|
|
50
|
-
* @returns Formatted TypeScript type definition string
|
|
51
|
-
*/
|
|
52
|
-
private static formatTypeDefinitions;
|
|
53
|
-
/**
|
|
54
|
-
* Recursively formats nested object
|
|
55
|
-
* @param obj - Object to format
|
|
56
|
-
* @param indent - Current indentation level
|
|
57
|
-
* @returns Formatted string
|
|
58
|
-
*/
|
|
59
|
-
private static formatNestedObject;
|
|
60
|
-
}
|
|
61
|
-
export default AbilityParser;
|