@ ngrx / store reducer payload valid protection

时间:2018-04-10 15:28:04

标签: angular ngrx ngrx-store ngrx-store-4.0

我经常在@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?

2 个答案:

答案 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;
  }

}