Created basic shape validation functionality
This commit is contained in:
parent
36b5eadc99
commit
f0db8af726
|
@ -1,15 +0,0 @@
|
|||
export const PROP_TYPES = {
|
||||
_string: '_string',
|
||||
_number: '_number',
|
||||
_reducer: '_reducer',
|
||||
_shape: '_shape',
|
||||
_array: '_array',
|
||||
};
|
||||
|
||||
export const Types = {
|
||||
string: () => ({ type: PROP_TYPES._string, structure: PROP_TYPES._string }),
|
||||
number: () => ({ type: PROP_TYPES._number, structure: PROP_TYPES._number }),
|
||||
arrayOf: structure => () => ({ type: PROP_TYPES._array, structure }),
|
||||
reducer: structure => () => ({ type: PROP_TYPES._reducer, structure }),
|
||||
shape: structure => () => ({ type: PROP_TYPES._shape, structure}),
|
||||
};
|
|
@ -1,12 +1,12 @@
|
|||
//@flow
|
||||
import type { ReducerStructure } from './structure';
|
||||
import type { ShapeStructure } from './structure';
|
||||
|
||||
import { combineReducers } from 'redux';
|
||||
import { reduce, find } from 'lodash';
|
||||
import { createReducer } from './reducers';
|
||||
import { PROP_TYPES } from './constants';
|
||||
import { PROP_TYPES } from './structure';
|
||||
|
||||
export function buildReducers(structure: ReducerStructure) {
|
||||
export function buildReducers(structure: ShapeStructure) {
|
||||
|
||||
const tmp = combineReducers(reduce(structure, (memo, propValue, propName) => {
|
||||
const { structure } = propValue();
|
||||
|
|
|
@ -1,42 +1,19 @@
|
|||
//@flow
|
||||
import {
|
||||
PROP_TYPES,
|
||||
TYPE_DEFAULTS,
|
||||
} from './constants';
|
||||
} from './structure';
|
||||
import { reduce } from 'lodash';
|
||||
import { compose } from 'ramda';
|
||||
import { primitiveReducer } from './reducers/primitiveReducer';
|
||||
import { objectReducer } from './reducers/objectReducer';
|
||||
import { createObjectReducer } from './reducers/objectReducer';
|
||||
import { arrayReducer } from './reducers/arrayReducer';
|
||||
|
||||
const DEFAULTS_FUNCTIONS = new Map([
|
||||
[PROP_TYPES._shape, objectDefaults],
|
||||
[PROP_TYPES._array, arrayDefaults],
|
||||
[PROP_TYPES._string, primitiveDefaults],
|
||||
[PROP_TYPES._number, primitiveDefaults],
|
||||
]);
|
||||
|
||||
function objectDefaults(structure) {
|
||||
return reduce(structure, (memo, propValue, propName) => console.log(333, { propName, propValue: propValue() }) || ({
|
||||
...memo,
|
||||
[propName]: TYPE_DEFAULTS.get(propValue().type)
|
||||
}), {});
|
||||
}
|
||||
|
||||
function arrayDefaults() {
|
||||
return [];
|
||||
}
|
||||
|
||||
function primitiveDefaults(structure) {
|
||||
return TYPE_DEFAULTS.get(structure);
|
||||
}
|
||||
|
||||
function determineReducerType(reducerDescriptor) {
|
||||
const { structure } = reducerDescriptor();
|
||||
const { type } = structure();
|
||||
|
||||
let reducerFn = primitiveReducer;
|
||||
if (type === PROP_TYPES._shape) reducerFn = objectReducer;
|
||||
if (type === PROP_TYPES._shape) reducerFn = createObjectReducer;
|
||||
if (type === PROP_TYPES._array) reducerFn = arrayReducer;
|
||||
return {
|
||||
reducerFn,
|
||||
|
@ -48,10 +25,4 @@ function callReducer({ reducerFn, reducerStructureDescriptor } = {}) {
|
|||
return reducerFn(reducerStructureDescriptor);
|
||||
}
|
||||
|
||||
function determineDefaults(reducerStructureDescriptor) {
|
||||
const { type } = reducerStructureDescriptor();
|
||||
console.log(111, { type, fn: DEFAULTS_FUNCTIONS.get(type), DEFAULTS_FUNCTIONS });
|
||||
return DEFAULTS_FUNCTIONS.get(type);
|
||||
}
|
||||
|
||||
export const createReducer = compose(callReducer, determineReducerType);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//@flow
|
||||
import type { ReducerStructure } from '../structure';
|
||||
import type { ShapeStructure } from '../structure';
|
||||
|
||||
export type ArrayReducerAction = {
|
||||
type: string,
|
||||
|
@ -9,7 +9,7 @@ export type ArrayReducerAction = {
|
|||
export type ArrayReducerFactory = (structure: Object) => ArrayReducer;
|
||||
export type ArrayReducer = (state: any[], action: ArrayReducerAction) => any[];
|
||||
|
||||
export function arrayReducer<ArrayReducerFactory>(reducerStructureDescriptor: ReducerStructure) {
|
||||
export function arrayReducer<ArrayReducerFactory>(reducerStructureDescriptor: ShapeStructure) {
|
||||
return(state: any[] = [], { type, payload = []}: ArrayReducerAction = {}): any[] => {
|
||||
switch(type) {
|
||||
case 'BLARG3':
|
||||
|
|
|
@ -1,20 +1,105 @@
|
|||
//@flow
|
||||
//==============================
|
||||
// Flow imports
|
||||
//==============================
|
||||
import type { ShapeStructure, StructureType } from '../structure';
|
||||
|
||||
//==============================
|
||||
// Flow types
|
||||
//==============================
|
||||
export type ObjectReducerAction = {
|
||||
type: string,
|
||||
payload?: any,
|
||||
payload: Object,
|
||||
};
|
||||
export type ObjectReducerFactory = (structure: Object) => ObjectReducer;
|
||||
export type ObjectReducerFactory = (reducerStructure: ShapeStructure) => ObjectReducer;
|
||||
export type ObjectReducer = (state: Object, action: ObjectReducerAction) => Object;
|
||||
export type ObjectReducerBehavior = (state: Object, payload: Object, initialState: Object) => Object;
|
||||
export type ObjectReducerBehaviorsConfig = {
|
||||
[key: string]: {
|
||||
action?: (value: Object) => Object,
|
||||
reducer: ObjectReducerBehavior,
|
||||
}
|
||||
};
|
||||
export type ObjectReducerBehaviors = {
|
||||
[key: string]: ObjectReducerBehavior,
|
||||
};
|
||||
export type ObjectAction = (value: Object) => { type: string, payload: Object };
|
||||
export type ObjectActions = {
|
||||
[key: string]: ObjectAction
|
||||
};
|
||||
export type ObjectReducerOptions = {
|
||||
behaviorsConfig: ObjectReducerBehaviorsConfig,
|
||||
locationString: string,
|
||||
};
|
||||
|
||||
//==============================
|
||||
// JS imports
|
||||
//==============================
|
||||
import { reduce } from 'lodash/reduce';
|
||||
import { validateObject } from '../validatePayload';
|
||||
|
||||
export function objectReducer(): ObjectReducer {
|
||||
return (state = {}, { type, payload } = {}) => {
|
||||
switch (type) {
|
||||
case 'BLARG!':
|
||||
return { state, ...payload };
|
||||
default:
|
||||
return state;
|
||||
const DEFAULT_OBJECT_BEHAVIORS: ObjectReducerBehaviorsConfig = {
|
||||
update: {
|
||||
action(value) { return value },
|
||||
reducer(state, payload) {
|
||||
return { ...state, ...payload };
|
||||
}
|
||||
},
|
||||
reset: {
|
||||
reducer(state, payload, initialState) {
|
||||
return initialState;
|
||||
}
|
||||
},
|
||||
replace: {
|
||||
reducer(state, payload, initialState) {
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function createObjectReducer(reducerShape: StructureType, {
|
||||
behaviorsConfig = {},
|
||||
locationString
|
||||
}: ObjectReducerOptions) {
|
||||
return {
|
||||
reducer: createReducer(reducerShape, createReducerBehaviors(behaviorsConfig, locationString)),
|
||||
actions: createActions(behaviorsConfig, locationString),
|
||||
};
|
||||
}
|
||||
|
||||
function calculateDefaults(reducerStructure) {
|
||||
return reduce(reducerStructure, (memo, propValue, propName) => ({
|
||||
...memo,
|
||||
[propName]: propValue().defaultValue,
|
||||
}), {});
|
||||
}
|
||||
|
||||
function createReducer(objectStructure: StructureType, behaviors: ObjectReducerBehaviors): ObjectReducer {
|
||||
const initialState = calculateDefaults(objectStructure().structure);
|
||||
return (state = initialState, { type, payload }: ObjectReducerAction) => {
|
||||
//If the action type does not match any of the specified behaviors, just return the current state.
|
||||
if (!behaviors[type]) return state;
|
||||
//Sanitize the payload using the reducer shape, then
|
||||
//apply the sanitized payload to the state using the behavior linked to this action type.
|
||||
return behaviors[type](state, validateObject(payload), initialState);
|
||||
}
|
||||
}
|
||||
|
||||
function createActions(behaviorsConfig: ObjectReducerBehaviorsConfig, locationString: string): ObjectActions {
|
||||
//Take a reducer behavior config object, and create actions using the location string
|
||||
return reduce(behaviorsConfig, (memo, behavior, name) => ({
|
||||
...memo,
|
||||
[`${locationString}.${name}`]: (value: Object) =>
|
||||
({ type: `${locationString}.${name}`, payload: behavior.action(value) || {} })
|
||||
}), {});
|
||||
}
|
||||
|
||||
function createReducerBehaviors(behaviorsConfig: ObjectReducerBehaviorsConfig, locationString: string): ObjectReducerBehaviors {
|
||||
//Take a reducer behavior config object, and create the reducer behaviors using the location string
|
||||
return reduce(behaviorsConfig, (memo, behavior, name) => ({
|
||||
...memo,
|
||||
[`${locationString}.${name}`]: behavior,
|
||||
}), {});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//@flow
|
||||
import type { ReducerStructure } from '../structure';
|
||||
import type { ShapeStructure } from '../structure';
|
||||
|
||||
export type PrimitiveReducerAction = {
|
||||
type: string,
|
||||
|
@ -8,7 +8,7 @@ export type PrimitiveReducerAction = {
|
|||
export type PrimitiveReducerFactory = (structure: Object) => PrimitiveReducer;
|
||||
export type PrimitiveReducer = (state: any, action: PrimitiveReducerAction) => any;
|
||||
|
||||
export function primitiveReducer<ArrayReducerFactory>(reducerStructureDescriptor: ReducerStructure) {
|
||||
export function primitiveReducer<ArrayReducerFactory>(reducerStructureDescriptor: ShapeStructure) {
|
||||
return(state: any = '', { type, payload }: PrimitiveReducerAction = {}) => {
|
||||
switch(type) {
|
||||
case 'BLARG3':
|
||||
|
|
|
@ -3,21 +3,20 @@
|
|||
//==============================
|
||||
// Flow types
|
||||
//==============================
|
||||
export type ReducerStructure = {
|
||||
export type ShapeStructure = {
|
||||
[key: string]: StructureType|PrimitiveType,
|
||||
}
|
||||
export type StructureType = () => {
|
||||
type: string,
|
||||
structure: StructureType|ReducerStructure
|
||||
structure: ShapeStructure|StructureType|PrimitiveType
|
||||
};
|
||||
export type PrimitiveType = () => {
|
||||
type: string,
|
||||
structure: $Keys<typeof PROP_TYPES>,
|
||||
defaultValue: ?any,
|
||||
defaultValue?: any,
|
||||
typeofValue: string,
|
||||
};
|
||||
|
||||
export type TypesObject = {
|
||||
[key: string]: CreateStructure|CreateStringType|CreateNumberType
|
||||
[key: string]: CreateArrayType|CreateStringType|CreateNumberType|CreateObjectType;
|
||||
}
|
||||
|
||||
export type TypesObjectDefaults = {
|
||||
|
@ -27,7 +26,8 @@ export type TypesArrayDefaults = Array<mixed>|Array<TypesObjectDefaults>;
|
|||
|
||||
type CreateStringType = (defaultValue: string) => PrimitiveType;
|
||||
type CreateNumberType = (defaultValue: number) => PrimitiveType;
|
||||
type CreateStructure = (structure: ReducerStructure, defaultValue: TypesArrayDefaults|TypesObjectDefaults) => StructureType;
|
||||
type CreateArrayType = (structure: StructureType|PrimitiveType, defaultValue: TypesArrayDefaults|TypesObjectDefaults) => StructureType;
|
||||
type CreateObjectType = (structure: ShapeStructure, defaultValue: TypesArrayDefaults|TypesObjectDefaults) => StructureType;
|
||||
|
||||
//==============================
|
||||
// Structure
|
||||
|
@ -41,9 +41,26 @@ export const PROP_TYPES = {
|
|||
};
|
||||
|
||||
export const Types: TypesObject = {
|
||||
string: (defaultValue: string = '') => () => ({ type: PROP_TYPES._string, structure: PROP_TYPES._string, defaultValue }),
|
||||
number: (defaultValue: number = 0) => () => ({ type: PROP_TYPES._number, structure: PROP_TYPES._number, defaultValue }),
|
||||
arrayOf: (structure: ReducerStructure) => () => ({ type: PROP_TYPES._array, structure }),
|
||||
reducer: (structure: ReducerStructure) => () => ({ type: PROP_TYPES._reducer, structure }),
|
||||
shape: (structure: ReducerStructure) => () => ({ type: PROP_TYPES._shape, structure}),
|
||||
string: (defaultValue: string = '') => () => ({
|
||||
type: PROP_TYPES._string,
|
||||
defaultValue,
|
||||
typeofValue: 'string'
|
||||
}),
|
||||
number: (defaultValue: number = 0) => () => ({
|
||||
type: PROP_TYPES._number,
|
||||
defaultValue,
|
||||
typeofValue: 'number'
|
||||
}),
|
||||
arrayOf: (structure: StructureType|PrimitiveType) => () => ({
|
||||
type: PROP_TYPES._array,
|
||||
structure,
|
||||
}),
|
||||
reducer: (structure: ShapeStructure) => () => ({
|
||||
type: PROP_TYPES._reducer,
|
||||
structure,
|
||||
}),
|
||||
shape: (structure: ShapeStructure) => () => ({
|
||||
type: PROP_TYPES._shape,
|
||||
structure,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
//@flow
|
||||
//==============================
|
||||
// Flow imports
|
||||
//==============================
|
||||
import type { PrimitiveType, StructureType, ShapeStructure } from './structure';
|
||||
|
||||
type validationFunction = (structure: StructureType|PrimitiveType|ShapeStructure, value: any) => any;
|
||||
|
||||
//==============================
|
||||
// JS imports
|
||||
//==============================
|
||||
import { reduce } from 'lodash/reduce';
|
||||
import { isObject } from 'lodash/isObject';
|
||||
import { PROP_TYPES } from './structure';
|
||||
|
||||
export function validateObject(objectStructure: any, value: mixed): Object {
|
||||
if (!isObject(value)) {
|
||||
console.error(`The value passed to validateObject() was not an object. Value: `, value);
|
||||
return {};
|
||||
}
|
||||
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.
|
||||
//Display a console error for the developer, and skip the inclusion of this property in the payload.
|
||||
if (!valueType) {
|
||||
console.error(`The property, ${name}, was not specified in the structure
|
||||
and was stripped out of the payload. Structure:`, objectStructure);
|
||||
return memo;
|
||||
}
|
||||
|
||||
return {
|
||||
...memo,
|
||||
[name]: getTypeValidation(valueType().type)(valueType, value),
|
||||
}
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function validatePrimitive(primitive: any, value: mixed): mixed {
|
||||
//Validate primitives using the typeofValue property of the primitive type definitions.
|
||||
return typeof value === primitive().typeofValue ? value : undefined;
|
||||
}
|
||||
|
||||
export function validateArray(arrayStructure: any, value: Array<mixed>): Array<mixed> {
|
||||
//Validate arrays by performing either of the other validation types to each element of the array,
|
||||
//based on the provided reducer structure.
|
||||
const elementStructure = arrayStructure().structure;
|
||||
const elementType = arrayStructure().type;
|
||||
return value.map(v => getTypeValidation(elementType)(elementStructure, value))
|
||||
}
|
||||
|
||||
function getTypeValidation(type): validationFunction {
|
||||
const TYPE_VALIDATIONS = new Map([
|
||||
[[PROP_TYPES._string], validatePrimitive],
|
||||
[[PROP_TYPES._number], validatePrimitive],
|
||||
[[PROP_TYPES._array], validateArray],
|
||||
[[PROP_TYPES._shape], validateObject],
|
||||
]);
|
||||
const typeValidation = TYPE_VALIDATIONS.get(type);
|
||||
if (!typeValidation) {
|
||||
throw new Error(`The type ${type} does not have a corresponding
|
||||
validation function!`);
|
||||
}
|
||||
return typeValidation;
|
||||
}
|
Loading…
Reference in New Issue