我经常在@ngrx/store
中使用我的缩减器来设置状态值,有时来自http请求
我看到的所有示例和教程都只是将有效负载设置为状态,或者通过Object.assign
或传播将有效负载设置为状态中的密钥。
问题是,如何验证有效负载是否有效?我有很多情况,有人通过this.store.dispatch()
提供了有效的有效载荷。它将是错误的格式,或者会丢失一个密钥等,这会带来无声的错误,因为商店的reducer
会搞砸州。对于http requests
来说尤其如此。
这导致我的团队项目完全 LITTERED ,无处不在,因为我们不能再相信商店了。
人们如何验证或保护他们的商店免受不良数据的侵害?
我从阅读中得到了一些想法:
How can I check that two objects have the same set of property names?
答案 0 :(得分:1)
所以人们会向商店发送无效的行动数据吗?
依靠Typescript来验证动作有效负载可能还不够。如果您需要更多支持,请尝试使用tassign(https://www.npmjs.com/package/tassign)vs Object.assign
之类的内容。
如果仍然不够,您可以尝试在http请求完成之后以及发送之前验证效果中的数据。
答案 1 :(得分:1)
我使用辅助服务ObjectShapeComparer
来确保我的外部配置文件在将其发布到商店之前具有正确的形状(即没有重命名或删除属性)。
它侧重于类型,结构和缺少的属性而不是值(因此,为避免对存储读取进行空值检查,您需要更多功能)。我想最好的是它管理错误并将它们发布到商店。
以下是它通过的测试列表。
it('should compare a template object to another object and return a list of
it('should ignore functions', () => {
it('should flag missing properties', () => {
it('should flag differently named properties', () => {
it('should flag all differences', () => {
it('should ignore extra properties on actual', () => {
it('should ignore value differences', () => {
it('should flag type differences (number vs string)', () => {
it('should flag type differences (object vs string)', () => {
it('should flag type differences (object vs array)', () => {
it('should ignore type when template uses undefined value', () => {
it('should ignore property order when no diffs', () => {
it('should ignore property order when diffs', () => {
it('should flag diffs on nested objects', () => {
it('should full qualify nested paths', () => {
it('should flag structure diffs when levels differ', () => {
it('should flag type diffs when levels differ', () => {
it('should flag array diffs', () => {
it('should ignore array ordering', () => {
it('should flag array diffs regardless of array ordering', () => {
it('should flag nested array diffs', () => {
<强>调用强> 这是一个使用它的函数(注意我使用的是angular-redux而不是ngrx)。这是处理http请求的中间件例程的一部分。
我想在ngrx中你可以将它合并到一个效果中。您需要将操作类型映射到模板。
checkTemplate(data): boolean {
const results = this.objectShapeComparer.compare(this.configTemplate, data);
if (results.length === 0) {
return true;
}
results.forEach(result => {
this.ngRedux.dispatch({
type: ConfigActions.INITIALIZE_CONFIG_TEMPLATE_ERROR,
data: result,
});
});
return false;
}
这是一个示例模板
this.pageConfigTemplate = {
pageTitle: '', // type should be string
pageDescription: '',
listTitle: '',
listWidth: 0, // type should be number
badgeUnits: '',
resultsZoom: ''
};
this.configTemplate = {
baseDataUrl: '',
page1Config: {
filePrefixes: [], // type should be array
numDataPointsForSparkline: 0,
numInitialFilesToDisplay: 0,
page: this.pageConfigTemplate // type should be object
},
...
<强> ObjectShapeComparer.ts 强>
import { Injectable } from '@angular/core';
@Injectable()
export class ObjectShapeComparer {
compare(expected, actual): string[] {
return this.compareObjectShape(expected, actual);
}
private compareObjectShape(expected, actual, path = ''): string[] {
let diffs = [];
for (const key in expected) {
// Ignore function properties
if (!expected.hasOwnProperty(key) || typeof expected[key] === 'function') {
continue;
}
const fullPath = path + (path === '' ? '' : '.') + key;
// Property exists?
if (!actual.hasOwnProperty(key)) {
diffs.push(`Missing property ${fullPath}`);
continue; // no need to check further when actual is missing
}
// Template value = undefined, means no type checking, no nested objects
const expectedValue = expected[key];
if (expectedValue === undefined) {
continue;
}
// Types match?
const expectedType = this.getType(expectedValue);
const actualValue = actual[key];
const actualType = this.getType(actualValue);
if (expectedType !== actualType) {
diffs.push(`Types differ for property ${fullPath} (${expectedType} vs ${actualType})`);
}
// Recurse nested objects and arrays
diffs = diffs.concat(this.recurse(expectedValue, actualValue, fullPath));
}
return diffs;
}
private recurse(expectedValue, actualValue, path): string[] {
let diffs = [];
const expectedType = this.getType(expectedValue);
if (expectedType === 'array') {
diffs = diffs.concat(this.compareArrays(expectedValue, actualValue, path));
}
if (expectedType === 'object') {
diffs = diffs.concat(this.compareObjectShape(expectedValue, actualValue, path));
}
return diffs;
}
private compareArrays(expectedArray, actualArray, path): string[] {
let diffs = [];
if (expectedArray.length === 0 || this.arrayIsPrimitive(expectedArray)) {
return diffs;
}
// Look for expected element anywhere in the actuals array
const actualKeys = actualArray.map(element => this.getKeys(element).join(','));
for (let index = 0; index < expectedArray.length; index++) {
const fullPath = path + '.' + index;
const expectedElement = expectedArray[index];
const actualElement = this.actualMatchingExpected(expectedElement, actualArray);
if (!actualElement) {
diffs.push(`Missing array element ${fullPath} (keys: ${this.getKeys(expectedElement).join(',')})`);
continue;
}
diffs = diffs.concat(this.recurse(expectedElement, actualElement, fullPath));
};
return diffs;
}
private getKeys(obj): any[] {
return typeof obj === 'object' ?
Object.keys(obj)
.filter(key => obj.hasOwnProperty(key)) // ignore function properties
.sort()
: [];
}
private getType(el: any): string {
return Array.isArray(el) ? 'array' : typeof el;
}
private arrayIsPrimitive(array): boolean {
const arrayType = this.getType(array[0]);
return arrayType !== 'object' && arrayType !== 'array';
}
private actualMatchingExpected(expected, actuals): any {
const expectedKeys = this.getKeys(expected).join(',');
const actualKeys = actuals.map(element => this.getKeys(element).join(','));
const match = actualKeys.indexOf(expectedKeys);
// tslint:disable-next-line:no-bitwise
return (~match) ? actuals[match] : null;
}
}