graphql-shield-node23 7.6.5
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/CHANGELOG.md +31 -0
- package/dist/cjs/constructors.js +134 -0
- package/dist/cjs/generator.js +205 -0
- package/dist/cjs/index.js +15 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/rules.js +402 -0
- package/dist/cjs/shield.js +52 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/utils.js +97 -0
- package/dist/cjs/validation.js +84 -0
- package/dist/esm/constructors.js +124 -0
- package/dist/esm/generator.js +201 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/rules.js +366 -0
- package/dist/esm/shield.js +45 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/utils.js +88 -0
- package/dist/esm/validation.js +79 -0
- package/dist/package.json +47 -0
- package/dist/typings/constructors.d.cts +91 -0
- package/dist/typings/constructors.d.ts +91 -0
- package/dist/typings/generator.d.cts +11 -0
- package/dist/typings/generator.d.ts +11 -0
- package/dist/typings/index.d.cts +3 -0
- package/dist/typings/index.d.ts +3 -0
- package/dist/typings/rules.d.cts +159 -0
- package/dist/typings/rules.d.ts +159 -0
- package/dist/typings/shield.d.cts +11 -0
- package/dist/typings/shield.d.ts +11 -0
- package/dist/typings/types.d.cts +64 -0
- package/dist/typings/types.d.ts +64 -0
- package/dist/typings/utils.d.cts +52 -0
- package/dist/typings/utils.d.ts +52 -0
- package/dist/typings/validation.d.cts +19 -0
- package/dist/typings/validation.d.ts +19 -0
- package/package.json +67 -0
- package/src/constructors.ts +157 -0
- package/src/generator.ts +294 -0
- package/src/index.ts +13 -0
- package/src/rules.ts +521 -0
- package/src/shield.ts +53 -0
- package/src/types.ts +94 -0
- package/src/utils.ts +101 -0
- package/src/validation.ts +90 -0
- package/tests/__snapshots__/input.test.ts.snap +7 -0
- package/tests/cache.test.ts +545 -0
- package/tests/constructors.test.ts +136 -0
- package/tests/fallback.test.ts +618 -0
- package/tests/fragments.test.ts +113 -0
- package/tests/generator.test.ts +356 -0
- package/tests/input.test.ts +63 -0
- package/tests/integration.test.ts +65 -0
- package/tests/logic.test.ts +530 -0
- package/tests/utils.test.ts +55 -0
- package/tests/validation.test.ts +139 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Rule, RuleAnd, RuleOr, RuleNot, RuleTrue, RuleFalse, InputRule, RuleChain, RuleRace, } from './rules.js';
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* @param name
|
|
5
|
+
* @param options
|
|
6
|
+
*
|
|
7
|
+
* Wraps a function into a Rule class. This way we can identify rules
|
|
8
|
+
* once we start generating middleware from our ruleTree.
|
|
9
|
+
*
|
|
10
|
+
* 1.
|
|
11
|
+
* const auth = rule()(async (parent, args, ctx, info) => {
|
|
12
|
+
* return true
|
|
13
|
+
* })
|
|
14
|
+
*
|
|
15
|
+
* 2.
|
|
16
|
+
* const auth = rule('name')(async (parent, args, ctx, info) => {
|
|
17
|
+
* return true
|
|
18
|
+
* })
|
|
19
|
+
*
|
|
20
|
+
* 3.
|
|
21
|
+
* const auth = rule({
|
|
22
|
+
* name: 'name',
|
|
23
|
+
* fragment: 'string',
|
|
24
|
+
* cache: 'cache',
|
|
25
|
+
* })(async (parent, args, ctx, info) => {
|
|
26
|
+
* return true
|
|
27
|
+
* })
|
|
28
|
+
*
|
|
29
|
+
*/
|
|
30
|
+
export const rule = (name, options) => (func) => {
|
|
31
|
+
if (typeof name === 'object') {
|
|
32
|
+
options = name;
|
|
33
|
+
name = Math.random().toString();
|
|
34
|
+
}
|
|
35
|
+
else if (typeof name === 'string') {
|
|
36
|
+
options = options || {};
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
name = Math.random().toString();
|
|
40
|
+
options = {};
|
|
41
|
+
}
|
|
42
|
+
return new Rule(name, func, {
|
|
43
|
+
fragment: options.fragment,
|
|
44
|
+
cache: options.cache,
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* Constructs a new InputRule based on the schema.
|
|
50
|
+
*
|
|
51
|
+
* @param schema
|
|
52
|
+
*/
|
|
53
|
+
export const inputRule = (name) => (schema, options) => {
|
|
54
|
+
if (typeof name === 'string') {
|
|
55
|
+
return new InputRule(name, schema, options);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
return new InputRule(Math.random().toString(), schema, options);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
*
|
|
63
|
+
* @param rules
|
|
64
|
+
*
|
|
65
|
+
* Logical operator and serves as a wrapper for and operation.
|
|
66
|
+
*
|
|
67
|
+
*/
|
|
68
|
+
export const and = (...rules) => {
|
|
69
|
+
return new RuleAnd(rules);
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param rules
|
|
74
|
+
*
|
|
75
|
+
* Logical operator and serves as a wrapper for and operation.
|
|
76
|
+
*
|
|
77
|
+
*/
|
|
78
|
+
export const chain = (...rules) => {
|
|
79
|
+
return new RuleChain(rules);
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
*
|
|
83
|
+
* @param rules
|
|
84
|
+
*
|
|
85
|
+
* Logical operator and serves as a wrapper for and operation.
|
|
86
|
+
*
|
|
87
|
+
*/
|
|
88
|
+
export const race = (...rules) => {
|
|
89
|
+
return new RuleRace(rules);
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
*
|
|
93
|
+
* @param rules
|
|
94
|
+
*
|
|
95
|
+
* Logical operator or serves as a wrapper for or operation.
|
|
96
|
+
*
|
|
97
|
+
*/
|
|
98
|
+
export const or = (...rules) => {
|
|
99
|
+
return new RuleOr(rules);
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
*
|
|
103
|
+
* @param rule
|
|
104
|
+
*
|
|
105
|
+
* Logical operator not serves as a wrapper for not operation.
|
|
106
|
+
*
|
|
107
|
+
*/
|
|
108
|
+
export const not = (rule, error) => {
|
|
109
|
+
if (typeof error === 'string')
|
|
110
|
+
return new RuleNot(rule, new Error(error));
|
|
111
|
+
return new RuleNot(rule, error);
|
|
112
|
+
};
|
|
113
|
+
/**
|
|
114
|
+
*
|
|
115
|
+
* Allow queries.
|
|
116
|
+
*
|
|
117
|
+
*/
|
|
118
|
+
export const allow = new RuleTrue();
|
|
119
|
+
/**
|
|
120
|
+
*
|
|
121
|
+
* Deny queries.
|
|
122
|
+
*
|
|
123
|
+
*/
|
|
124
|
+
export const deny = new RuleFalse();
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { isObjectType, isIntrospectionType, } from 'graphql';
|
|
2
|
+
import { isRuleFunction, isRuleFieldMap, isRule, isLogicRule, withDefault, } from './utils.js';
|
|
3
|
+
import { ValidationError } from './validation.js';
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param options
|
|
7
|
+
*
|
|
8
|
+
* Generates a middleware function from a given rule and
|
|
9
|
+
* initializes the cache object in context.
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
function generateFieldMiddlewareFromRule(rule, options) {
|
|
13
|
+
async function middleware(resolve, parent, args, ctx, info) {
|
|
14
|
+
// Cache
|
|
15
|
+
if (!ctx) {
|
|
16
|
+
ctx = {};
|
|
17
|
+
}
|
|
18
|
+
if (!ctx._shield) {
|
|
19
|
+
ctx._shield = {
|
|
20
|
+
cache: {},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// Execution
|
|
24
|
+
try {
|
|
25
|
+
const res = await rule.resolve(parent, args, ctx, info, options);
|
|
26
|
+
if (res === true) {
|
|
27
|
+
return await resolve(parent, args, ctx, info);
|
|
28
|
+
}
|
|
29
|
+
else if (res === false) {
|
|
30
|
+
if (typeof options.fallbackError === 'function') {
|
|
31
|
+
return await options.fallbackError(null, parent, args, ctx, info);
|
|
32
|
+
}
|
|
33
|
+
return options.fallbackError;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
return res;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (options.debug) {
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
else if (options.allowExternalErrors) {
|
|
44
|
+
return err;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
if (typeof options.fallbackError === 'function') {
|
|
48
|
+
return await options.fallbackError(err, parent, args, ctx, info);
|
|
49
|
+
}
|
|
50
|
+
return options.fallbackError;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (isRule(rule) && rule.extractFragment()) {
|
|
55
|
+
return {
|
|
56
|
+
fragment: rule.extractFragment(),
|
|
57
|
+
resolve: middleware,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (isLogicRule(rule)) {
|
|
61
|
+
return {
|
|
62
|
+
fragments: rule.extractFragments(),
|
|
63
|
+
resolve: middleware,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return middleware;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
*
|
|
70
|
+
* @param type
|
|
71
|
+
* @param rules
|
|
72
|
+
* @param options
|
|
73
|
+
*
|
|
74
|
+
* Generates middleware from rule for a particular type.
|
|
75
|
+
*
|
|
76
|
+
*/
|
|
77
|
+
function applyRuleToType(type, rules, options) {
|
|
78
|
+
if (isRuleFunction(rules)) {
|
|
79
|
+
/* Apply defined rule function to every field */
|
|
80
|
+
const fieldMap = type.getFields();
|
|
81
|
+
const middleware = Object.keys(fieldMap).reduce((middleware, field) => {
|
|
82
|
+
return {
|
|
83
|
+
...middleware,
|
|
84
|
+
[field]: generateFieldMiddlewareFromRule(rules, options),
|
|
85
|
+
};
|
|
86
|
+
}, {});
|
|
87
|
+
return middleware;
|
|
88
|
+
}
|
|
89
|
+
else if (isRuleFieldMap(rules)) {
|
|
90
|
+
/* Apply rules assigned to each field to each field */
|
|
91
|
+
const fieldMap = type.getFields();
|
|
92
|
+
/* Extract default type wildcard if any and remove it for validation */
|
|
93
|
+
const defaultTypeRule = rules['*'];
|
|
94
|
+
const { '*': _, ...rulesWithoutWildcard } = rules;
|
|
95
|
+
/* Validation */
|
|
96
|
+
const fieldErrors = Object.keys(rulesWithoutWildcard)
|
|
97
|
+
.filter((type) => !Object.prototype.hasOwnProperty.call(fieldMap, type))
|
|
98
|
+
.map((field) => `${type.name}.${field}`)
|
|
99
|
+
.join(', ');
|
|
100
|
+
if (fieldErrors.length > 0) {
|
|
101
|
+
throw new ValidationError(`It seems like you have applied rules to ${fieldErrors} fields but Shield cannot find them in your schema.`);
|
|
102
|
+
}
|
|
103
|
+
/* Generation */
|
|
104
|
+
const middleware = Object.keys(fieldMap).reduce((middleware, field) => ({
|
|
105
|
+
...middleware,
|
|
106
|
+
[field]: generateFieldMiddlewareFromRule(withDefault(defaultTypeRule || options.fallbackRule)(rules[field]), options),
|
|
107
|
+
}), {});
|
|
108
|
+
return middleware;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
/* Apply fallbackRule to type with no defined rule */
|
|
112
|
+
const fieldMap = type.getFields();
|
|
113
|
+
const middleware = Object.keys(fieldMap).reduce((middleware, field) => ({
|
|
114
|
+
...middleware,
|
|
115
|
+
[field]: generateFieldMiddlewareFromRule(options.fallbackRule, options),
|
|
116
|
+
}), {});
|
|
117
|
+
return middleware;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
*
|
|
122
|
+
* @param schema
|
|
123
|
+
* @param rule
|
|
124
|
+
* @param options
|
|
125
|
+
*
|
|
126
|
+
* Applies the same rule over entire schema.
|
|
127
|
+
*
|
|
128
|
+
*/
|
|
129
|
+
function applyRuleToSchema(schema, rule, options) {
|
|
130
|
+
const typeMap = schema.getTypeMap();
|
|
131
|
+
const middleware = Object.keys(typeMap)
|
|
132
|
+
.filter((type) => !isIntrospectionType(typeMap[type]))
|
|
133
|
+
.reduce((middleware, typeName) => {
|
|
134
|
+
const type = typeMap[typeName];
|
|
135
|
+
if (isObjectType(type)) {
|
|
136
|
+
return {
|
|
137
|
+
...middleware,
|
|
138
|
+
[typeName]: applyRuleToType(type, rule, options),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
return middleware;
|
|
143
|
+
}
|
|
144
|
+
}, {});
|
|
145
|
+
return middleware;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
*
|
|
149
|
+
* @param rules
|
|
150
|
+
* @param wrapper
|
|
151
|
+
*
|
|
152
|
+
* Converts rule tree to middleware.
|
|
153
|
+
*
|
|
154
|
+
*/
|
|
155
|
+
function generateMiddlewareFromSchemaAndRuleTree(schema, rules, options) {
|
|
156
|
+
if (isRuleFunction(rules)) {
|
|
157
|
+
/* Applies rule to entire schema. */
|
|
158
|
+
return applyRuleToSchema(schema, rules, options);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
/**
|
|
162
|
+
* Checks type map and field map and applies rules
|
|
163
|
+
* to particular fields.
|
|
164
|
+
*/
|
|
165
|
+
const typeMap = schema.getTypeMap();
|
|
166
|
+
/* Validation */
|
|
167
|
+
const typeErrors = Object.keys(rules)
|
|
168
|
+
.filter((type) => !Object.prototype.hasOwnProperty.call(typeMap, type))
|
|
169
|
+
.join(', ');
|
|
170
|
+
if (typeErrors.length > 0) {
|
|
171
|
+
throw new ValidationError(`It seems like you have applied rules to ${typeErrors} types but Shield cannot find them in your schema.`);
|
|
172
|
+
}
|
|
173
|
+
// Generation
|
|
174
|
+
const middleware = Object.keys(typeMap)
|
|
175
|
+
.filter((type) => !isIntrospectionType(typeMap[type]))
|
|
176
|
+
.reduce((middleware, typeName) => {
|
|
177
|
+
const type = typeMap[typeName];
|
|
178
|
+
if (isObjectType(type)) {
|
|
179
|
+
return {
|
|
180
|
+
...middleware,
|
|
181
|
+
[typeName]: applyRuleToType(type, rules[typeName], options),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
return middleware;
|
|
186
|
+
}
|
|
187
|
+
}, {});
|
|
188
|
+
return middleware;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
*
|
|
193
|
+
* @param ruleTree
|
|
194
|
+
* @param options
|
|
195
|
+
*
|
|
196
|
+
* Generates middleware from given rules.
|
|
197
|
+
*
|
|
198
|
+
*/
|
|
199
|
+
export function generateMiddlewareGeneratorFromRuleTree(ruleTree, options) {
|
|
200
|
+
return (schema) => generateMiddlewareFromSchemaAndRuleTree(schema, ruleTree, options);
|
|
201
|
+
}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import * as Yup from 'yup';
|
|
2
|
+
import { isLogicRule } from './utils.js';
|
|
3
|
+
export class Rule {
|
|
4
|
+
constructor(name, func, constructorOptions) {
|
|
5
|
+
const options = this.normalizeOptions(constructorOptions);
|
|
6
|
+
this.name = name;
|
|
7
|
+
this.func = func;
|
|
8
|
+
this.cache = options.cache;
|
|
9
|
+
this.fragment = options.fragment;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param parent
|
|
14
|
+
* @param args
|
|
15
|
+
* @param ctx
|
|
16
|
+
* @param info
|
|
17
|
+
*
|
|
18
|
+
* Resolves rule and writes to cache its result.
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
async resolve(parent, args, ctx, info, options) {
|
|
22
|
+
try {
|
|
23
|
+
/* Resolve */
|
|
24
|
+
const res = await this.executeRule(parent, args, ctx, info, options);
|
|
25
|
+
if (res instanceof Error) {
|
|
26
|
+
return res;
|
|
27
|
+
}
|
|
28
|
+
else if (typeof res === 'string') {
|
|
29
|
+
return new Error(res);
|
|
30
|
+
}
|
|
31
|
+
else if (res === true) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
if (options.debug) {
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* @param rule
|
|
50
|
+
*
|
|
51
|
+
* Compares a given rule with the current one
|
|
52
|
+
* and checks whether their functions are equal.
|
|
53
|
+
*
|
|
54
|
+
*/
|
|
55
|
+
equals(rule) {
|
|
56
|
+
return this.func === rule.func;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
*
|
|
60
|
+
* Extracts fragment from the rule.
|
|
61
|
+
*
|
|
62
|
+
*/
|
|
63
|
+
extractFragment() {
|
|
64
|
+
return this.fragment;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
*
|
|
68
|
+
* @param options
|
|
69
|
+
*
|
|
70
|
+
* Sets default values for options.
|
|
71
|
+
*
|
|
72
|
+
*/
|
|
73
|
+
normalizeOptions(options) {
|
|
74
|
+
return {
|
|
75
|
+
cache: options.cache !== undefined
|
|
76
|
+
? this.normalizeCacheOption(options.cache)
|
|
77
|
+
: 'no_cache',
|
|
78
|
+
fragment: options.fragment !== undefined ? options.fragment : undefined,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
*
|
|
83
|
+
* @param cache
|
|
84
|
+
*
|
|
85
|
+
* This ensures backward capability of shield.
|
|
86
|
+
*
|
|
87
|
+
*/
|
|
88
|
+
normalizeCacheOption(cache) {
|
|
89
|
+
switch (cache) {
|
|
90
|
+
case true: {
|
|
91
|
+
return 'strict';
|
|
92
|
+
}
|
|
93
|
+
case false: {
|
|
94
|
+
return 'no_cache';
|
|
95
|
+
}
|
|
96
|
+
default: {
|
|
97
|
+
return cache;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Executes a rule and writes to cache if needed.
|
|
103
|
+
*
|
|
104
|
+
* @param parent
|
|
105
|
+
* @param args
|
|
106
|
+
* @param ctx
|
|
107
|
+
* @param info
|
|
108
|
+
*/
|
|
109
|
+
executeRule(parent, args, ctx, info, options) {
|
|
110
|
+
switch (typeof this.cache) {
|
|
111
|
+
case 'function': {
|
|
112
|
+
/* User defined cache function. */
|
|
113
|
+
const key = `${this.name}-${this.cache(parent, args, ctx, info)}`;
|
|
114
|
+
return this.writeToCache(key)(parent, args, ctx, info);
|
|
115
|
+
}
|
|
116
|
+
case 'string': {
|
|
117
|
+
/* Standard cache option. */
|
|
118
|
+
switch (this.cache) {
|
|
119
|
+
case 'strict': {
|
|
120
|
+
const key = options.hashFunction({ parent, args });
|
|
121
|
+
return this.writeToCache(`${this.name}-${key}`)(parent, args, ctx, info);
|
|
122
|
+
}
|
|
123
|
+
case 'contextual': {
|
|
124
|
+
return this.writeToCache(this.name)(parent, args, ctx, info);
|
|
125
|
+
}
|
|
126
|
+
case 'no_cache': {
|
|
127
|
+
return this.func(parent, args, ctx, info);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/* istanbul ignore next */
|
|
132
|
+
default: {
|
|
133
|
+
throw new Error(`Unsupported cache format: ${typeof this.cache}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Writes or reads result from cache.
|
|
139
|
+
*
|
|
140
|
+
* @param key
|
|
141
|
+
*/
|
|
142
|
+
writeToCache(key) {
|
|
143
|
+
return (parent, args, ctx, info) => {
|
|
144
|
+
if (!ctx._shield.cache[key]) {
|
|
145
|
+
ctx._shield.cache[key] = this.func(parent, args, ctx, info);
|
|
146
|
+
}
|
|
147
|
+
return ctx._shield.cache[key];
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export class InputRule extends Rule {
|
|
152
|
+
constructor(name, schema, options) {
|
|
153
|
+
const validationFunction = (parent, args, ctx) => schema(Yup, ctx)
|
|
154
|
+
.validate(args, options)
|
|
155
|
+
.then(() => true)
|
|
156
|
+
.catch((err) => err);
|
|
157
|
+
super(name, validationFunction, { cache: 'strict', fragment: undefined });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export class LogicRule {
|
|
161
|
+
constructor(rules) {
|
|
162
|
+
this.rules = rules;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* By default logic rule resolves to false.
|
|
166
|
+
*/
|
|
167
|
+
async resolve(parent, args, ctx, info, options) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Evaluates all the rules.
|
|
172
|
+
*/
|
|
173
|
+
async evaluate(parent, args, ctx, info, options) {
|
|
174
|
+
const rules = this.getRules();
|
|
175
|
+
const tasks = rules.map((rule) => rule.resolve(parent, args, ctx, info, options));
|
|
176
|
+
return Promise.all(tasks);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Returns rules in a logic rule.
|
|
180
|
+
*/
|
|
181
|
+
getRules() {
|
|
182
|
+
return this.rules;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Extracts fragments from the defined rules.
|
|
186
|
+
*/
|
|
187
|
+
extractFragments() {
|
|
188
|
+
const fragments = this.rules.reduce((fragments, rule) => {
|
|
189
|
+
if (isLogicRule(rule)) {
|
|
190
|
+
return fragments.concat(...rule.extractFragments());
|
|
191
|
+
}
|
|
192
|
+
const fragment = rule.extractFragment();
|
|
193
|
+
if (fragment)
|
|
194
|
+
return fragments.concat(fragment);
|
|
195
|
+
return fragments;
|
|
196
|
+
}, []);
|
|
197
|
+
return fragments;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Extended Types
|
|
201
|
+
export class RuleOr extends LogicRule {
|
|
202
|
+
constructor(rules) {
|
|
203
|
+
super(rules);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Makes sure that at least one of them has evaluated to true.
|
|
207
|
+
*/
|
|
208
|
+
async resolve(parent, args, ctx, info, options) {
|
|
209
|
+
const result = await this.evaluate(parent, args, ctx, info, options);
|
|
210
|
+
if (result.every((res) => res !== true)) {
|
|
211
|
+
const customError = result.find((res) => res instanceof Error);
|
|
212
|
+
return customError || false;
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
export class RuleAnd extends LogicRule {
|
|
220
|
+
constructor(rules) {
|
|
221
|
+
super(rules);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Makes sure that all of them have resolved to true.
|
|
225
|
+
*/
|
|
226
|
+
async resolve(parent, args, ctx, info, options) {
|
|
227
|
+
const result = await this.evaluate(parent, args, ctx, info, options);
|
|
228
|
+
if (result.some((res) => res !== true)) {
|
|
229
|
+
const customError = result.find((res) => res instanceof Error);
|
|
230
|
+
return customError || false;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
export class RuleChain extends LogicRule {
|
|
238
|
+
constructor(rules) {
|
|
239
|
+
super(rules);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Makes sure that all of them have resolved to true.
|
|
243
|
+
*/
|
|
244
|
+
async resolve(parent, args, ctx, info, options) {
|
|
245
|
+
const result = await this.evaluate(parent, args, ctx, info, options);
|
|
246
|
+
if (result.some((res) => res !== true)) {
|
|
247
|
+
const customError = result.find((res) => res instanceof Error);
|
|
248
|
+
return customError || false;
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Evaluates all the rules.
|
|
256
|
+
*/
|
|
257
|
+
async evaluate(parent, args, ctx, info, options) {
|
|
258
|
+
const rules = this.getRules();
|
|
259
|
+
return iterate(rules);
|
|
260
|
+
async function iterate([rule, ...otherRules]) {
|
|
261
|
+
if (rule === undefined)
|
|
262
|
+
return [];
|
|
263
|
+
return rule.resolve(parent, args, ctx, info, options).then((res) => {
|
|
264
|
+
if (res !== true) {
|
|
265
|
+
return [res];
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
return iterate(otherRules).then((ress) => ress.concat(res));
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
export class RuleRace extends LogicRule {
|
|
275
|
+
constructor(rules) {
|
|
276
|
+
super(rules);
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Makes sure that at least one of them resolved to true.
|
|
280
|
+
*/
|
|
281
|
+
async resolve(parent, args, ctx, info, options) {
|
|
282
|
+
const result = await this.evaluate(parent, args, ctx, info, options);
|
|
283
|
+
if (result.some((res) => res === true)) {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
const customError = result.find((res) => res instanceof Error);
|
|
288
|
+
return customError || false;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Evaluates all the rules.
|
|
293
|
+
*/
|
|
294
|
+
async evaluate(parent, args, ctx, info, options) {
|
|
295
|
+
const rules = this.getRules();
|
|
296
|
+
return iterate(rules);
|
|
297
|
+
async function iterate([rule, ...otherRules]) {
|
|
298
|
+
if (rule === undefined)
|
|
299
|
+
return [];
|
|
300
|
+
return rule.resolve(parent, args, ctx, info, options).then((res) => {
|
|
301
|
+
if (res === true) {
|
|
302
|
+
return [res];
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
return iterate(otherRules).then((ress) => ress.concat(res));
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
export class RuleNot extends LogicRule {
|
|
312
|
+
constructor(rule, error) {
|
|
313
|
+
super([rule]);
|
|
314
|
+
this.error = error;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
*
|
|
318
|
+
* @param parent
|
|
319
|
+
* @param args
|
|
320
|
+
* @param ctx
|
|
321
|
+
* @param info
|
|
322
|
+
*
|
|
323
|
+
* Negates the result.
|
|
324
|
+
*
|
|
325
|
+
*/
|
|
326
|
+
async resolve(parent, args, ctx, info, options) {
|
|
327
|
+
const [res] = await this.evaluate(parent, args, ctx, info, options);
|
|
328
|
+
if (res instanceof Error) {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
else if (res !== true) {
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
if (this.error)
|
|
336
|
+
return this.error;
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
export class RuleTrue extends LogicRule {
|
|
342
|
+
constructor() {
|
|
343
|
+
super([]);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
*
|
|
347
|
+
* Always true.
|
|
348
|
+
*
|
|
349
|
+
*/
|
|
350
|
+
async resolve() {
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
export class RuleFalse extends LogicRule {
|
|
355
|
+
constructor() {
|
|
356
|
+
super([]);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
*
|
|
360
|
+
* Always false.
|
|
361
|
+
*
|
|
362
|
+
*/
|
|
363
|
+
async resolve() {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|