├── .babelrc ├── .flowconfig ├── .gitignore ├── .npmignore ├── README.md ├── package.json ├── src ├── index.js ├── parse.js ├── types.js └── validate.js └── test ├── build.js ├── example.js └── example.js.flow /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*build/.* 3 | .*node_modules/babel.* 4 | .*node_modules/y18n.* 5 | 6 | [include] 7 | 8 | [libs] 9 | 10 | [options] 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/ 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | node_modules 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | runtime-types 2 | ============= 3 | 4 | Use flow type information at runtime. Automatically generate validation code, ORM schemas, etc from the type definition. 5 | 6 | Installation 7 | ------------ 8 | 9 | npm install --save runtime-types 10 | 11 | Runtime Example 12 | --------------- 13 | 14 | If this file is in ./example-types.js 15 | 16 | ```js 17 | // @flow 18 | export type PhoneNumber = string; 19 | 20 | export type User = { 21 | username: string; 22 | age: number; 23 | phone: PhoneNumber; 24 | created: ?Date; 25 | } 26 | ``` 27 | 28 | You can import the type information as follows: 29 | 30 | ```js 31 | var types = require('runtime-types') 32 | var path = require('path') 33 | 34 | // read the file into a runtime type description 35 | var MyTypes = types.readFile(path.join(__dirname, '../test/example-types.js')) 36 | 37 | // MyTypes is now equal to: 38 | { 39 | PhoneNumber: { name: 'string' }, 40 | 41 | User: { 42 | name: 'Object', 43 | properties: [ 44 | { key: 'username', type: { name: 'string' } }, 45 | { key: 'age', type: { name: 'number' } }, 46 | { key: 'phone', type: { name: 'PhoneNumber' } }, 47 | { key: 'created', type: { name: 'Date', nullable: true } } 48 | ] 49 | } 50 | } 51 | ``` 52 | 53 | Validation Example 54 | ------------------ 55 | 56 | You can use the object provided by `readFile` to create validators for your types 57 | 58 | ```js 59 | var types = require('runtime-types') 60 | var validate = require('runtime-types').validate 61 | 62 | var MyTypes = types.readFile(path.join(__dirname, '../test/example-types.js')) 63 | 64 | var VALIDATORS = { 65 | PhoneNumber: validate.validateRegex(/^\d{10}$/), 66 | } 67 | 68 | var validators = validate.createAll(VALIDATORS, MyTypes) 69 | ``` 70 | 71 | Then you can check various objects to make sure they match `User` at runtime. 72 | 73 | ```js 74 | var errs = validators.User({ 75 | username: "bobby", 76 | age: 23, 77 | phone: "8014114399", 78 | created: null 79 | }) 80 | 81 | // ==> [] 82 | ``` 83 | 84 | Checks if fields are set 85 | 86 | ```js 87 | var errs = validators.User({ 88 | age: 23, 89 | phone: "8014114399" 90 | }) 91 | 92 | // ==> [ { key: 'username', value: undefined, error: 'missing' } ] 93 | // no error for created because it is nullable 94 | ``` 95 | 96 | Checks correct typeof for `string`, `number` and `boolean` 97 | 98 | ```js 99 | var errs = validators.User({ 100 | username: "bobby", 101 | age: "not an age", 102 | phone: "8014114399", 103 | }) 104 | 105 | // ==> [ { key: 'age', value: 'not an age', error: 'expected typeof number' } ] 106 | ``` 107 | 108 | Checks instances for `Date` 109 | 110 | ```js 111 | var errs = validators.User({ 112 | username: "bobby", 113 | age: 23, 114 | phone: "8014114399", 115 | created: 1432757991843 // was supposed to be date, not a timestamp 116 | }) 117 | 118 | // [ { key: 'created', 119 | // value: 1432757991843, 120 | // error: 'expected instance of function Date() { [native code] }' } ] 121 | ``` 122 | 123 | Provided Validators: regex 124 | 125 | ```js 126 | var VALIDATORS:ValidatorMap = { 127 | PhoneNumber: validate.validateRegex(/^\d{10}$/), 128 | } 129 | 130 | var validators = validate.createAll(VALIDATORS, MyTypes) 131 | 132 | var errs = validators.User({ 133 | username: "bobby", 134 | age: 23, 135 | phone: "801-443-8899", // should be 10 digits without hyphens 136 | }) 137 | 138 | // [ { key: 'phone', 139 | // value: '801-411-4399', 140 | // error: 'did not match /^\\d{10}$/' }, ] 141 | ``` 142 | 143 | Custom Validators: anything 144 | 145 | ```js 146 | var VALIDATORS:ValidatorMap = { 147 | PhoneNumber: function(value) { 148 | if (value.length == 10) { 149 | return true 150 | } 151 | else { 152 | return "wrong length!" 153 | } 154 | } 155 | } 156 | ``` 157 | 158 | It does not try to guess validators for your type aliases. If you forget to provide one it will throw an error when you generate the validators 159 | 160 | ```js 161 | var VALIDATORS:ValidatorMap = {} 162 | 163 | var validators = validate.createAll(VALIDATORS, MyTypes) 164 | 165 | // Error: Could not find validator for type: PhoneNumber 166 | ``` 167 | 168 | Mapping to ORM Schemas 169 | ---------------------- 170 | 171 | Coming soon. Will be similar to implementation of `validate.js` 172 | 173 | 174 | API: runtime-types 175 | ------------------ 176 | 177 | readFile. See [example](#runtime-example) 178 | 179 | ```js 180 | // read a file synchronously and return a type definition for each type alias found 181 | // keys are the name of the alias 182 | // values are the type description 183 | // you should run this when your program starts 184 | 185 | readFile(filepath:string):ObjectMap; 186 | ``` 187 | 188 | Property and Type 189 | 190 | ```js 191 | type Property = { 192 | key: string; 193 | type: Type; 194 | optional?: boolean; 195 | } 196 | 197 | type Type = { 198 | name: string; // number, string, boolean, Post, User, Array 199 | 200 | literal?: string; // for string literals 201 | 202 | nullable?: boolean; 203 | 204 | // only filled for object types 205 | properties?: Array; 206 | 207 | // only filled for generics, like Array 208 | params?: Array; 209 | } 210 | 211 | export type ObjectMap = {[key: string]: T} 212 | ``` 213 | 214 | API: validate 215 | ------------- 216 | 217 | See the [example](#validation-example) 218 | 219 | This library returns `ValidateObject` functions: they accept an object and return an array of errors 220 | 221 | ```js 222 | type ValidationError = string; 223 | 224 | type KeyedError = { 225 | key: string; 226 | value: string; 227 | error: ValidationError; 228 | } 229 | 230 | type ValidateObject = (value:Object) => Array 231 | ``` 232 | 233 | Create a single validate function 234 | 235 | ```js 236 | create(map:ValidatorMap, type:Type):ValidateObject; 237 | ``` 238 | 239 | Create a map of validation functions, with keys equal to the name of the types 240 | 241 | ```js 242 | createAll(map:ValidatorMap, types:ObjectMap):ObjectMap; 243 | ``` 244 | 245 | Validators are the functions that you use as building blocks. They return either `true` or an error message 246 | 247 | ```js 248 | type Validator = (value:T) => ValidationResult 249 | 250 | // use === true to test 251 | type ValidationResult = boolean | ValidationError; 252 | ``` 253 | 254 | Provided Validators: 255 | 256 | ```js 257 | validateExists():Validator; 258 | 259 | validateTypeOf(type:string):Validator; 260 | 261 | validateInstanceOf(type:any):Validator; 262 | 263 | validateRegex(regex:RegExp):Validator; 264 | ``` 265 | 266 | 267 | The ValidationMap connects types to validators 268 | 269 | ```js 270 | type ValidatorMap = {[key:string]:Validator} 271 | 272 | // the default validation map, override by passing to `create` 273 | 274 | var VALIDATORS_BY_TYPE:ValidatorMap = { 275 | "string" : validateTypeOf("string"), 276 | "number" : validateTypeOf("number"), 277 | "boolean" : validateTypeOf("boolean"), 278 | "Date" : validateInstanceOf(Date), 279 | "Object" : validateExists(), 280 | } 281 | ``` 282 | 283 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "runtime-types", 3 | "version": "1.0.8", 4 | "description": "Use flow type information at runtime. Useful for validations, mapping to an ORM, and more. ", 5 | "keywords": [ 6 | "orm", 7 | "flow", 8 | "type", 9 | "static types", 10 | "runtime", 11 | "validate", 12 | "validation", 13 | "object", 14 | "valid", 15 | "typecheck" 16 | ], 17 | "main": "build/index.js", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/seanhess/runtime-types.git" 21 | }, 22 | "homepage": "https://github.com/seanhess/runtime-types", 23 | "scripts": { 24 | "prepublish": "npm run build", 25 | "test": "babel-node test/example.js", 26 | "build": "node_modules/.bin/babel --no-comments -d build/ src/" 27 | }, 28 | "author": { 29 | "name": "Sean Hess", 30 | "url": "http://seanhess.github.io" 31 | }, 32 | "license": "MIT", 33 | "dependencies": { 34 | "esprima-fb": "15001.1.0-dev-harmony-fb", 35 | "lodash": "3.9.3" 36 | }, 37 | "devDependencies": { 38 | "babel-cli": "^6.6.5", 39 | "babel-preset-es2015": "^6.6.0", 40 | "babel-preset-react": "^6.5.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import {readFile} from './parse' 3 | import type {Type, Property} from './types' 4 | import * as validate from './validate' 5 | 6 | export {readFile, validate} 7 | export type {Type, Property} 8 | -------------------------------------------------------------------------------- /src/parse.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | // https://github.com/estree/estree/blob/master/spec.md 3 | 4 | var esprima = require('esprima-fb') 5 | var path = require('path') 6 | var lodash = require('lodash') 7 | var {assign, curry} = lodash 8 | var fs = require('fs') 9 | 10 | import type {Type, Property, ObjectMap} from './types' 11 | 12 | 13 | ////////////////////////////////////////////////////////////// 14 | // fileTypes 15 | 16 | 17 | // read a file synchronously and return a type definition for each type alias found 18 | // keys are the name of the alias 19 | // values are the type description 20 | // you should run this when your program starts 21 | 22 | export function readFile(filepath:string):ObjectMap { 23 | return findTypes(parseFile(filepath)) 24 | } 25 | 26 | function parseFile(filepath:string):Tree { 27 | var data = fs.readFileSync(filepath).toString() 28 | // Strip 'declare export' statements from Flow 0.19, which aren't supported by esprima. 29 | // They're not useful to us anyway. 30 | data = data.replace(/declare export .*?(?:\n|$)/ig, '') 31 | return esprima.parse(data.toString(), {tolerant:true}) 32 | } 33 | 34 | function findTypes(tree:Tree):ObjectMap { 35 | //console.log("DATA", tree.body) 36 | var aliases:Array = tree.body.map(function(s:$Subtype) { 37 | 38 | if (s.type == "ExportDeclaration") { 39 | var ex:ExportDeclaration = (s : any) 40 | s = ex.declaration 41 | } 42 | 43 | if (s.type == "TypeAlias") { 44 | return s 45 | } 46 | }) 47 | 48 | return aliases.reduce(function(values, alias:?TypeAlias) { 49 | if (alias) { 50 | values[alias.id.name] = toType(alias.right) 51 | } 52 | return values 53 | }, {}) 54 | } 55 | 56 | function toProperty(prop:TypeProperty):Property { 57 | var p:any = { 58 | key: prop.key.name, 59 | type: toType(prop.value), 60 | } 61 | 62 | if (prop.optional) { 63 | p.optional = true 64 | } 65 | 66 | return p 67 | } 68 | 69 | function toType(anno:TypeAnnotation):Type { 70 | 71 | if (anno.type === Syntax.ObjectTypeAnnotation) { 72 | return objectType((anno : any)) 73 | } 74 | 75 | else if (anno.type === Syntax.GenericTypeAnnotation) { 76 | return genericType((anno : any)) 77 | } 78 | 79 | else if (anno.type === Syntax.NullableTypeAnnotation) { 80 | return nullableType((anno : any)) 81 | } 82 | 83 | else if (anno.type === Syntax.StringLiteralTypeAnnotation) { 84 | return literalType((anno : any)) 85 | } 86 | 87 | else if (anno.type === Syntax.UnionTypeAnnotation) { 88 | return unionType((anno : any)) 89 | } 90 | 91 | else { 92 | return valueType(anno) 93 | } 94 | } 95 | 96 | //GenericTypeAnnotation 97 | function genericType(anno:GenericTypeAnnotation):Type { 98 | var type = (emptyType(anno.id.name) : any) 99 | 100 | if (anno.typeParameters) { 101 | type.params = anno.typeParameters.params.map(toType) 102 | } 103 | 104 | return type 105 | } 106 | 107 | function objectType(anno:ObjectTypeAnnotation):Type { 108 | var type = (emptyType('Object') : any) 109 | type.properties = anno.properties.map(toProperty) 110 | return type 111 | } 112 | 113 | function nullableType(anno:WrapperTypeAnnotation):Type { 114 | var type = toType(anno.typeAnnotation) 115 | type.nullable = true 116 | return type 117 | } 118 | 119 | function literalType(anno:StringLiteralTypeAnnotation):Type { 120 | var type = valueType(anno) 121 | type.literal = anno.value 122 | return type 123 | } 124 | 125 | function unionType(anno:UnionTypeAnnotation):Type { 126 | var type = (emptyType('Union') : any) 127 | type.types = anno.types.map(toType) 128 | return type 129 | } 130 | 131 | //VoidTypeAnnotation 132 | //StringTypeAnnotation 133 | //BooleanTypeAnnotation 134 | //NumberTypeAnnotation 135 | //FunctionTypeAnnotation 136 | //StringLiteralTypeAnnotation 137 | //AnyTypeAnnotation 138 | //UnionTypeAnnotation 139 | 140 | // UNSUPPORTED 141 | //ArrayTypeAnnotation (it uses GenericTypeAnnotation) 142 | //IntersectionTypeAnnotation 143 | //TupleTypeAnnotation 144 | //TypeAnnotation 145 | //TypeofTypeAnnotation 146 | 147 | function valueType(anno:TypeAnnotation):Type { 148 | var type = emptyType(shortName(anno)) 149 | return (type : any) 150 | } 151 | 152 | function emptyType(name:string):Type { 153 | return { 154 | name: name, 155 | } 156 | } 157 | 158 | function shortName(anno:TypeAnnotation):string { 159 | 160 | if (anno.type === Syntax.StringTypeAnnotation) { 161 | return 'string' 162 | } 163 | 164 | else if (anno.type === Syntax.NumberTypeAnnotation) { 165 | return 'number' 166 | } 167 | 168 | else if (anno.type === Syntax.BooleanTypeAnnotation) { 169 | return 'boolean' 170 | } 171 | 172 | else if (anno.type === Syntax.AnyTypeAnnotation) { 173 | return 'any' 174 | } 175 | 176 | return anno.type.replace('TypeAnnotation', '') 177 | } 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | ////////////////////////////////////////////////////////////// 192 | // Type description of what esprima returns 193 | ////////////////////////////////////////////////////////////// 194 | 195 | type Tree = { 196 | type: string; 197 | body: Array; 198 | } 199 | 200 | type AnySyntax = TypeAlias | ExportDeclaration; 201 | 202 | type ExportDeclaration = { 203 | type: string; 204 | declaration: AnySyntax; 205 | } 206 | 207 | type TypeAlias = { 208 | type: string; 209 | id: Identifier; 210 | typeParameters: ?TypeParameters; 211 | right: TypeAnnotation; 212 | } 213 | 214 | type TypeProperty = { 215 | type: string; // ObjectTypeProperty 216 | key: Identifier; 217 | value: TypeAnnotation; 218 | optional: boolean; 219 | // static: any; 220 | } 221 | 222 | 223 | type TypeParameters = { 224 | type: 'TypeParameterInstantiation'; 225 | params: Array; 226 | } 227 | 228 | type Identifier = { 229 | type: 'Identifier'; 230 | name: string; 231 | typeAnnotation: any; // undefined 232 | optional: any; // undefined 233 | } 234 | 235 | // ------------------------------------------------- 236 | // annotations 237 | 238 | // use an intersection type so I don't have to cast later 239 | type TypeAnnotation = ObjectTypeAnnotation | ValueTypeAnnotation | GenericTypeAnnotation | WrapperTypeAnnotation | StringLiteralTypeAnnotation | UnionTypeAnnotation; 240 | 241 | type ValueTypeAnnotation = { 242 | type: string; // StringTypeAnnotation, NumberTypeAnnotation 243 | } 244 | 245 | type StringLiteralTypeAnnotation = { 246 | type: "StringLiteralTypeAnnotation"; 247 | value: string; 248 | raw: string; 249 | } 250 | 251 | type WrapperTypeAnnotation = { 252 | type: string; 253 | typeAnnotation: TypeAnnotation; 254 | } 255 | 256 | type ObjectTypeAnnotation = { 257 | type: "ObjectTypeAnnotation"; 258 | properties: Array; 259 | indexers?: Array; 260 | callProperties?: Array; 261 | } 262 | 263 | // Array uses this 264 | type GenericTypeAnnotation = { 265 | type: string; // "GenericTypeAnnotation"; 266 | id: Identifier; 267 | typeParameters: ?TypeParameters; 268 | } 269 | 270 | type UnionTypeAnnotation = { 271 | type: "UnionTypeAnnotation"; 272 | types: TypeAnnotation[]; 273 | } 274 | 275 | ////////////////////////////////////////////////////////////////// 276 | 277 | type SyntaxTokens = { 278 | AnyTypeAnnotation: string; 279 | ArrayTypeAnnotation: string; 280 | BooleanTypeAnnotation: string; 281 | FunctionTypeAnnotation: string; 282 | GenericTypeAnnotation: string; 283 | IntersectionTypeAnnotation: string; 284 | NullableTypeAnnotation: string; 285 | NumberTypeAnnotation: string; 286 | ObjectTypeAnnotation: string; 287 | StringLiteralTypeAnnotation: string; 288 | StringTypeAnnotation: string; 289 | TupleTypeAnnotation: string; 290 | TypeAnnotation: string; 291 | TypeofTypeAnnotation: string; 292 | UnionTypeAnnotation: string; 293 | VoidTypeAnnotation: string; 294 | } 295 | 296 | var Syntax:SyntaxTokens = esprima.Syntax 297 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type ObjectMap = {[key: string]: T} 4 | 5 | export type Property = { 6 | key: string; 7 | type: Type; 8 | optional?: boolean; 9 | } 10 | 11 | export type Type = { 12 | name: string; // number, string, boolean, Post, User, Array 13 | 14 | literal?: string; // for string literals 15 | 16 | nullable?: boolean; 17 | 18 | // only filled for object types 19 | properties?: Array; 20 | 21 | // only filled for generics, like Array 22 | params?: Array; 23 | 24 | // only filled for union types 25 | types?: Array; 26 | } 27 | -------------------------------------------------------------------------------- /src/validate.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | // TODO FEATURE support nested objects 4 | 5 | import type {Type, Property, ObjectMap} from './types' 6 | import {flatten, extend, constant, find} from 'lodash' 7 | 8 | export type Validator = (value:T) => ValidationResult 9 | export type ValidationError = string; 10 | // either true, or a string with the error 11 | // use === true to test 12 | export type ValidationResult = boolean | ValidationError; 13 | 14 | export type ValidatorMap = {[key:string]:Validator} 15 | 16 | export type ValidateObject = (value:Object) => Array 17 | 18 | export type KeyedError = { 19 | key: string; 20 | value: string; 21 | error: ValidationError; 22 | } 23 | 24 | type KeyedValidator = [string, Validator]; 25 | 26 | 27 | // ------------------------------------------------------------- 28 | 29 | // create a single validator: a function to call with your object 30 | // it will return an array of errors 31 | 32 | export function create(map:ValidatorMap, type:Type):ValidateObject { 33 | var vs = typeValidators(map, type) 34 | return function(obj) { 35 | return validateAll(vs, obj) 36 | } 37 | } 38 | 39 | export function createAll(map:ValidatorMap, types:ObjectMap):ObjectMap { 40 | var vs = {} 41 | for (var name in types) { 42 | vs[name] = create(map, types[name]) 43 | } 44 | return vs 45 | } 46 | 47 | 48 | // --------------------------------------------------------------- 49 | 50 | var VALIDATORS_BY_TYPE:ValidatorMap = { 51 | "string" : validateTypeOf("string"), 52 | "number" : validateTypeOf("number"), 53 | "boolean" : validateTypeOf("boolean"), 54 | "Date" : validateInstanceOf(Date), 55 | "Object" : validateExists(), 56 | } 57 | 58 | function validateAll(vs:Array, obj:Object):Array { 59 | var maybeErrs:Array = vs.map(function(kv) { 60 | return validate(kv, obj) 61 | }) 62 | 63 | var errs:any = maybeErrs.filter(function(err:?KeyedError):boolean { 64 | return (err !== undefined) 65 | }) 66 | 67 | return errs 68 | } 69 | 70 | function validate([key, validator]:KeyedValidator, obj:Object):?KeyedError { 71 | // this runs the validator 72 | var result = validator(obj[key]) 73 | if (!valid(result)) { 74 | return {key: key, value: obj[key], error: (result : any)} 75 | } 76 | } 77 | 78 | function valid(result:ValidationResult):boolean { 79 | return result === true 80 | } 81 | 82 | ////////////////////////////////////////////////////////////////////////// 83 | 84 | // turns a property into a validator 85 | // ignore optional, it doesn't work right 86 | function propToValidator(map:ValidatorMap, prop:Property):KeyedValidator { 87 | return typeToValidator(map, prop.key, prop.type) 88 | } 89 | 90 | // just do required for now? 91 | // you want to allow them to override the mapping 92 | // especially for their custom types! 93 | function typeToValidator(map:ValidatorMap, key:string, type:Type):KeyedValidator { 94 | 95 | // TODO nested objects 96 | //if (type.properties) { 97 | 98 | ////console.log("TWE", type.properties) 99 | //var all = objToValidators(map, type.properties) 100 | 101 | //function allThemBones(value) { 102 | //var errs = validateAll(all, value) 103 | 104 | //if (errs.length) { 105 | //return "error there was some stuff: " + errs.length 106 | //} 107 | 108 | //return true 109 | //} 110 | 111 | //return [key, allThemBones] 112 | //} 113 | 114 | // now run the type-based validator 115 | var validator = map[type.name] 116 | 117 | if (!validator) { 118 | throw new Error("Could not find validator for type: " + type.name) 119 | } 120 | 121 | function isValid(value) { 122 | if (!exists(value)) { 123 | // if the property doesn't exist, and it's not a nullable property 124 | // otherwise just do the second one 125 | if (type.nullable) { 126 | return true 127 | } 128 | 129 | else { 130 | return "missing" 131 | } 132 | } 133 | 134 | else { 135 | return validator(value) 136 | } 137 | } 138 | 139 | return [key, isValid] 140 | } 141 | 142 | function typeValidators(map:ValidatorMap, type:Type):Array { 143 | var fullMap:ValidatorMap = extend(map, VALIDATORS_BY_TYPE) 144 | if (type.properties) { 145 | return objToValidators(fullMap, type.properties) 146 | } 147 | 148 | else { 149 | return [typeToValidator(map, "", type)] 150 | } 151 | } 152 | 153 | function objToValidators(map:ValidatorMap, props:Array):Array { 154 | return props.map(function(prop) { 155 | return propToValidator(map, prop) 156 | }) 157 | } 158 | 159 | ////////////////////////////////////////////////////////////// 160 | // Validators 161 | ////////////////////////////////////////////////////////////// 162 | 163 | export function validateExists():Validator { 164 | return function(val) { 165 | return exists(val) || "missing" 166 | } 167 | } 168 | 169 | export function validateTypeOf(type:string):Validator { 170 | return function(val) { 171 | return (typeof val === type) || "expected typeof " + type 172 | } 173 | } 174 | 175 | export function validateInstanceOf(func:Function):Validator { 176 | return function(val) { 177 | return (val instanceof func) || "expected instance of " + func.name 178 | } 179 | } 180 | 181 | export function validateRegex(regex:RegExp):Validator { 182 | return function(val) { 183 | return (regex.test(val)) || "did not match " + regex.toString() 184 | } 185 | } 186 | 187 | function exists(value):boolean { 188 | return !(value === undefined || value === null) 189 | } 190 | 191 | -------------------------------------------------------------------------------- /test/build.js: -------------------------------------------------------------------------------- 1 | // test the build process 2 | var path = require('path') 3 | var types = require('../build') 4 | var validate = require('../build/validate') 5 | 6 | var VALIDATORS = { 7 | GUID: validateGUID(), 8 | PhoneNumber: validate.validateRegex(/^\d{9}$/), 9 | ID: validate.validateTypeOf('number') 10 | } 11 | 12 | function validateGUID() { 13 | return function(guid) { 14 | return true 15 | } 16 | } 17 | 18 | function validatePhoneNumber() { 19 | return function(phone) { 20 | if (phone.length != 9) { 21 | return "invalid phone number: " + phone 22 | } 23 | return true 24 | } 25 | } 26 | 27 | var sample = { 28 | global_member_offer_uuid: "asdf", 29 | account_number: "12345678a", 30 | global_offer_id: 1234, 31 | awarded_global_location_id: 2134, 32 | earned_date: new Date(), 33 | start_date: null, 34 | expire_date: new Date(), 35 | } 36 | 37 | 38 | 39 | // ------------------------ 40 | 41 | // find all the types in a given file 42 | var Types = types.readFile(path.join(__dirname, './example-types.js')) 43 | 44 | // create validators for all the types 45 | var validators = validate.createAll(VALIDATORS, Types) 46 | var errs = validators.MemberOffer(sample) 47 | console.log("ERRS", errs) 48 | -------------------------------------------------------------------------------- /test/example.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | var path = require('path') 3 | var validate = require('../src/validate') 4 | import * as types from '../src' 5 | var {create, createAll, validateRegex, validateTypeOf} = types.validate 6 | import type {ValidatorMap, Validator} from '../src/validate'; 7 | import type {PhoneNumber, User} from './example' 8 | 9 | var VALIDATORS:ValidatorMap = { 10 | PhoneNumber: validateRegex(/^\d{10}$/), 11 | } 12 | 13 | function validateAnyGUID():Validator { 14 | return function(guid) { 15 | return true 16 | } 17 | } 18 | 19 | function validatePhoneNumber():Validator { 20 | return function(phone:PhoneNumber) { 21 | if (phone.length != 9) { 22 | return "invalid phone number: " + phone 23 | } 24 | return true 25 | } 26 | } 27 | 28 | var sample:User = { 29 | username: "bobby", 30 | age: 23, 31 | phone: "8014114399", 32 | created: null 33 | } 34 | 35 | // ------------------------------------- 36 | 37 | var MyTypes = types.readFile(path.join(__dirname, 'example.js.flow')) 38 | console.log(MyTypes.User.properties) 39 | 40 | // ------------------------------------ 41 | 42 | //var validateGUID = validate.create({}, MyTypes.GUID) 43 | 44 | //console.log("TEST") 45 | //var errs = validateGUID(1235) // guids should be strings! 46 | //console.log(errs) 47 | 48 | // ------------------------ 49 | 50 | // find all the types in a given file 51 | // should I use the working directory or .. 52 | //console.log(MyTypes.MemberOffer.properties) 53 | 54 | // create validators for all the types 55 | console.log("") 56 | console.log("VALIDATE") 57 | console.log("") 58 | var validators = createAll(VALIDATORS, MyTypes) 59 | 60 | console.log(validators.User({ 61 | age: 23, 62 | phone: "8014114399" 63 | })) 64 | 65 | console.log(validators.User({ 66 | username: "bobby", 67 | age: "hello", 68 | phone: "8014114399", 69 | })) 70 | 71 | 72 | console.log(validators.User({ 73 | username: "bobby", 74 | age: 23, 75 | phone: "801-411-4399", 76 | created: 1432757991843 // was supposed to be date, not a timestamp 77 | })) 78 | 79 | // -------------------------------------------- 80 | 81 | console.log(validators.Kiosk({ 82 | mac_address: "asdf", 83 | global_location_id: 1, 84 | settings: { 85 | test: 1234, 86 | }, 87 | stuffs: {message: "HI"} 88 | })) 89 | 90 | export var aNum = 3 91 | export var aFunc = function() {} 92 | -------------------------------------------------------------------------------- /test/example.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | export type PhoneNumber = string 3 | 4 | export type User = { 5 | username: string; 6 | age: number; 7 | phone: PhoneNumber; 8 | created: ?Date; 9 | } 10 | 11 | export type Kiosk = { 12 | mac_address: string; 13 | global_location_id: number; 14 | settings: {[key:string] : string}; 15 | stuffs: { 16 | message: string; 17 | } 18 | } 19 | 20 | // Flow 0.19 syntax, should be automatically stripped from file as not to 21 | // confuse esprima. 22 | declare export var aNum: number 23 | declare export var aFunc: Function 24 | --------------------------------------------------------------------------------