Add wildcardKey type
Being able to type a known structure ahead of time is super useful. Sometimes, however, this isn't possible. What if your application has an entirely dynamic portion, where you use data from the server to build, day, a form? In this situation it'd be great to still be able to type. This commit introduces the basic implementation of the 'wildcardKey' type, for use in defining an object property. At the moment, every object can have one wildcardKey which specifies a specific type (including any()) which the reducer will happily accept, if an unknown property has it. The aim is to introduce regex to this type, and the ability to use multiple instances of the type, in order to specifically craft dynamic reducer structures (e.g. all properties with _date in them will be assigned to the Type.any(), but all others must be Type.string()).
This commit is contained in:
parent
be42ab5958
commit
bfa06ac17f
|
@ -51,7 +51,7 @@ describe('reducers', () => {
|
|||
|
||||
describe('determineReducerType', () => {
|
||||
it('should return the correct creator function for the default mapping', () => {
|
||||
forEach(omit(Types, 'reducer'), structureType => {
|
||||
forEach(omit(Types, 'reducer', 'wildcardKey'), structureType => {
|
||||
const returnVal = determineReducerType(Types.reducer(structureType()), {
|
||||
name: 'toast',
|
||||
locationString: 'toasty',
|
||||
|
|
|
@ -3,7 +3,9 @@ import {
|
|||
validatePrimitive,
|
||||
validateShape,
|
||||
validateArray,
|
||||
getTypeValidation
|
||||
getTypeValidation,
|
||||
hasWildcardKey,
|
||||
getValueType,
|
||||
} from '../validatePayload';
|
||||
|
||||
describe('Validation functionality', () => {
|
||||
|
@ -91,7 +93,38 @@ describe('Validation functionality', () => {
|
|||
test1: {},
|
||||
test2: 'bar',
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
const testObjectStructure4 = Types.shape({
|
||||
test2: Types.string(),
|
||||
[Types.wildcardKey()]: Types.string(),
|
||||
});
|
||||
const testObjectStructure5 = Types.shape({
|
||||
test2: Types.string(),
|
||||
[Types.wildcardKey()]: Types.any(),
|
||||
});
|
||||
it('Should, if a key is not specified, see if the key matches the wildcard type, and apply if true', () => {
|
||||
expect(validateShape(testObjectStructure4, { test1: 'foo', test2: 'bar' })).toEqual({
|
||||
test1: 'foo',
|
||||
test2: 'bar',
|
||||
});
|
||||
|
||||
expect(validateShape(testObjectStructure5, { test1: 0, test2: 'bar' })).toEqual({
|
||||
test1: 0,
|
||||
test2: 'bar',
|
||||
});
|
||||
});
|
||||
|
||||
const testObjectStructure6 = Types.shape({
|
||||
test2: Types.string(),
|
||||
[Types.wildcardKey()]: Types.string(),
|
||||
});
|
||||
it('Should, if a key is not specified, and does not match the wildcardKey, strip it out', () => {
|
||||
expect(validateShape(testObjectStructure6, { test1: 0, test2: 'bar' })).toEqual({
|
||||
test2: 'bar',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Non covered types', () => {
|
||||
|
@ -100,4 +133,50 @@ describe('Validation functionality', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Has wildcard value', () => {
|
||||
const testObjectStructure = Types.shape({
|
||||
test1: Types.string(),
|
||||
test2: Types.number(),
|
||||
[Types.wildcardKey()]: Types.any(),
|
||||
});
|
||||
|
||||
const testObjectStructure2 = Types.shape({
|
||||
test1: Types.string(),
|
||||
test2: Types.number(),
|
||||
});
|
||||
|
||||
it('should return true if the objectStructure passed in has a wildcard key', () => {
|
||||
expect(hasWildcardKey(testObjectStructure)).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if no wildcard key passed in', () => {
|
||||
expect(hasWildcardKey(testObjectStructure2)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GetValueType', () => {
|
||||
const testObjectStructure = Types.shape({
|
||||
test1: Types.string(),
|
||||
test2: Types.number(),
|
||||
[Types.wildcardKey()]: Types.number(),
|
||||
});
|
||||
|
||||
const testObjectStructure2 = Types.shape({
|
||||
test1: Types.string(),
|
||||
test2: Types.number(),
|
||||
});
|
||||
|
||||
it('should return the correct type for a key that is present, if no wildcard present', () => {
|
||||
expect(getValueType(testObjectStructure, 'test1', false)().type).toEqual(Types.string()().type);
|
||||
});
|
||||
|
||||
it('should return the wildcard value if key not present and wildcard is', () => {
|
||||
expect(getValueType(testObjectStructure, 'test3', true)().type).toEqual(Types.number()().type);
|
||||
});
|
||||
|
||||
it('should return undefined if no wildcard or matching key', () => {
|
||||
expect(getValueType(testObjectStructure, 'test3', false)).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -47,7 +47,7 @@ import { updateAtIndex, removeAtIndex } from '../utils/arrayUtils';
|
|||
import { PROP_TYPES } from '../structure';
|
||||
|
||||
|
||||
function checkIndex(index: ?number, payload: any = '', behaviorName: string = ''): boolean {
|
||||
function checkIndex(index: ?number, payload: any = '', behaviorName: string): boolean {
|
||||
if (!isNumber(index) || index === -1) {
|
||||
console.warn(`Index not passed to ${behaviorName} for payload ${payload}.`);
|
||||
return false;
|
||||
|
|
|
@ -43,6 +43,7 @@ export const PROP_TYPES = {
|
|||
_shape: '_shape',
|
||||
_array: '_array',
|
||||
_any: '_any',
|
||||
_wildcardKey: '_wildcardKey',
|
||||
};
|
||||
|
||||
//The types objects are used in order to build up the structure of a store chunk, and provide/accept
|
||||
|
@ -81,4 +82,5 @@ export const Types: TypesObject = {
|
|||
type: PROP_TYPES._shape,
|
||||
structure,
|
||||
}),
|
||||
wildcardKey: () => PROP_TYPES._wildcardKey,
|
||||
};
|
||||
|
|
|
@ -12,6 +12,18 @@ type validationFunction = (structure: StructureType | PrimitiveType | ShapeStruc
|
|||
import reduce from 'lodash/reduce';
|
||||
import isObject from 'lodash/isObject';
|
||||
import { PROP_TYPES } from './structure';
|
||||
const find = require('lodash/fp/find').convert({ cap: false });
|
||||
|
||||
|
||||
export const hasWildcardKey = (objectStructure: any) =>
|
||||
!!find((prop, key) => key === PROP_TYPES._wildcardKey)(objectStructure().structure);
|
||||
|
||||
|
||||
export const getValueType = (objectStructure: any, key: string, wildcardKeyPresent: boolean) =>
|
||||
wildcardKeyPresent
|
||||
? objectStructure().structure[key] || objectStructure().structure[PROP_TYPES._wildcardKey]
|
||||
: objectStructure().structure[key];
|
||||
|
||||
|
||||
export function validateShape(objectStructure: any, value: mixed): Object {
|
||||
if (!isObject(value)) {
|
||||
|
@ -19,20 +31,23 @@ export function validateShape(objectStructure: any, value: mixed): Object {
|
|||
return {};
|
||||
}
|
||||
|
||||
const wildcardKeyPresent = hasWildcardKey(objectStructure);
|
||||
|
||||
return reduce(value, (memo, value, name) => {
|
||||
const valueType = objectStructure().structure[name];
|
||||
//If the value type does not exist in the reducer structure, we don't want to include it in the payload.
|
||||
const valueType = getValueType(objectStructure, name, wildcardKeyPresent);
|
||||
//If the value type does not exist in the reducer structure, and there's no wildcard key, then
|
||||
//we don't want to include it in the payload.
|
||||
//Display a console error for the developer, and skip the inclusion of this property in the payload.
|
||||
if (!valueType) {
|
||||
console.warn(`The property, ${name}, was not specified in the structure` +
|
||||
' and was stripped out of the payload. Structure: ', objectStructure().structure);
|
||||
` and was stripped out of the payload. Structure: ${ objectStructure().structure }`);
|
||||
return memo;
|
||||
}
|
||||
|
||||
const validatedValue = getTypeValidation(valueType().type)(valueType, value);
|
||||
if (validatedValue === undefined) {
|
||||
console.warn(`The property, ${name}, was populated with a type ${ typeof value } which does not` +
|
||||
' match that specified in the reducer configuration. It has been stripped from' +
|
||||
` match that specified in the reducer configuration ${ wildcardKeyPresent ? ', nor did it match a wildcardKey': ''}. It has been stripped from` +
|
||||
' the payload');
|
||||
return memo;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue