-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature(#285): Basic Implementation of Vue Props Validators
- Loading branch information
1 parent
68c1312
commit 41daabf
Showing
18 changed files
with
1,672 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# `@3yourmind/vue-props-validators` | ||
|
||
> Generate Properly Validated Vue Props | ||
## Usage | ||
|
||
```typescript | ||
import { vuePropsValidators } from '@3yourmind/vue-props-validators' | ||
|
||
export default defineComponent { | ||
props: vuePropsValidators({ | ||
example: { | ||
nullable: true, | ||
required: true, | ||
type: 'enum', | ||
} | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module.exports = { | ||
presets: [ | ||
['@babel/preset-env', { targets: { node: 'current' } }], | ||
'@babel/preset-typescript', | ||
], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
{ | ||
"bugs": { | ||
"url": "https://github.com/3YOURMIND/kotti/issues" | ||
}, | ||
"dependencies": {}, | ||
"description": "Vue Props Validators", | ||
"directories": { | ||
"dist": "dist" | ||
}, | ||
"homepage": "https://github.com/3YOURMIND/kotti/tree/master/packages/vue-props-validators", | ||
"keywords": [ | ||
"vue", | ||
"props", | ||
"validators", | ||
"validator", | ||
"vuejs" | ||
], | ||
"license": "MIT", | ||
"main": "dist/index.js", | ||
"name": "@3yourmind/vue-props-validators", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/3YOURMIND/kotti.git" | ||
}, | ||
"scripts": { | ||
"build": "rm -rf dist && tsc", | ||
"test": "jest", | ||
"test:watch": "jest --watch" | ||
}, | ||
"version": "1.0.0" | ||
} |
7 changes: 7 additions & 0 deletions
7
packages/vue-props-validators/source/common/base-validator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { Option } from '../modules' | ||
|
||
export const baseValidator = ( | ||
option: Option, | ||
validator: (value: unknown) => boolean, | ||
) => (value: unknown) => | ||
option.nullable && value === null ? true : validator(value) |
9 changes: 9 additions & 0 deletions
9
packages/vue-props-validators/source/common/resolve-default.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { REQUIRED } from '../constants' | ||
import { Option } from '../modules' | ||
|
||
export const resolveDefault = ( | ||
option: Option, | ||
): Option['default'] extends typeof REQUIRED | ||
? { required: true } | ||
: { default: Option['default'] } => | ||
option.default === REQUIRED ? { required: true } : { default: option.default } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const REQUIRED = Symbol('REQUIRED') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { resolveDefault } from './common/resolve-default' | ||
import { REQUIRED } from './constants' | ||
import { createEnum } from './modules/enum' | ||
import { Options, ExtendsOne, Result } from './types' | ||
import { isNumber } from './utilities' | ||
|
||
export const vuePropsValidators = <PROPS extends Options>( | ||
props: PROPS, | ||
): { | ||
[KEY in keyof PROPS]: ExtendsOne< | ||
PROPS[KEY], | ||
'enum', | ||
Result<PROPS[KEY], StringConstructor>, | ||
ExtendsOne< | ||
PROPS[KEY], | ||
'float', | ||
Result<PROPS[KEY], NumberConstructor>, | ||
ExtendsOne< | ||
PROPS[KEY], | ||
'integer', | ||
Result<PROPS[KEY], NumberConstructor>, | ||
never | ||
> | ||
> | ||
> | ||
} => | ||
Object.fromEntries( | ||
Object.entries(props).map(([prop, option]) => { | ||
switch (option.type) { | ||
case 'enum': | ||
return [prop, createEnum(option)] | ||
|
||
case 'float': | ||
return [ | ||
prop, | ||
{ | ||
...resolveDefault(option), | ||
type: Number, | ||
validator: (value: unknown) => isNumber(value), | ||
}, | ||
] | ||
|
||
case 'integer': | ||
return [ | ||
prop, | ||
{ | ||
...resolveDefault(option), | ||
type: Number, | ||
validator: (value: unknown) => Number.isSafeInteger(value), | ||
}, | ||
] | ||
} | ||
|
||
throw new Error('invalid') | ||
}), | ||
) | ||
|
||
const X = vuePropsValidators({ | ||
example: { | ||
default: REQUIRED, | ||
nullable: true, | ||
type: 'enum', | ||
}, | ||
example2: { | ||
default: REQUIRED, | ||
nullable: true, | ||
type: 'integer', | ||
}, | ||
example3: { | ||
// eslint-disable-next-line no-magic-numbers | ||
default: () => 123, | ||
nullable: true, | ||
type: 'integer', | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { vuePropsValidators } from '..' | ||
import { REQUIRED } from '../constants' | ||
|
||
const BASE_ENUM = { | ||
default: REQUIRED as typeof REQUIRED, | ||
nullable: false, | ||
type: 'enum' as const, | ||
options: [], | ||
} | ||
|
||
test('enum has correct type', () => | ||
expect( | ||
vuePropsValidators({ | ||
example: BASE_ENUM, | ||
}), | ||
).toMatchObject({ example: { type: String } })) | ||
|
||
test('enum validator works', () => { | ||
const { validator } = vuePropsValidators({ | ||
example: { ...BASE_ENUM, options: ['test', 'example'] }, | ||
}).example | ||
|
||
expect(validator('test')).toBeTruthy() | ||
expect(validator('example')).toBeTruthy() | ||
expect(validator('anything')).toBeFalsy() | ||
}) | ||
|
||
test('enum (nullable: false)', () => | ||
expect( | ||
vuePropsValidators({ | ||
example: { ...BASE_ENUM, nullable: false }, | ||
}).example.validator(null), | ||
).toBeFalsy()) | ||
|
||
test('enum (nullable: true)', () => | ||
expect( | ||
vuePropsValidators({ | ||
example: { ...BASE_ENUM, nullable: true }, | ||
}).example.validator(null), | ||
).toBeTruthy()) | ||
|
||
test('enum (default)', () => | ||
expect( | ||
vuePropsValidators({ | ||
example: { ...BASE_ENUM, default: () => null }, | ||
}).example.default(), | ||
).toBe(null)) | ||
|
||
test('enum (required)', () => | ||
expect( | ||
vuePropsValidators({ | ||
example: { ...BASE_ENUM, default: REQUIRED }, | ||
}), | ||
).toMatchObject({ | ||
example: { | ||
required: true, | ||
}, | ||
})) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { baseValidator } from '../common/base-validator' | ||
import { resolveDefault } from '../common/resolve-default' | ||
import { Result } from '../types' | ||
|
||
import { TypeBase } from '.' | ||
|
||
export type TypeEnum = TypeBase & { | ||
options: string[] | ||
type: 'enum' | ||
} | ||
|
||
export const createEnum = <OPTION extends TypeEnum>( | ||
option: OPTION, | ||
): Result<OPTION, StringConstructor> => ({ | ||
...resolveDefault(option), | ||
type: String, | ||
validator: baseValidator( | ||
option, | ||
(value: unknown) => | ||
typeof value === 'string' && option.options.includes(value), | ||
), | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { TypeBase } from '.' | ||
|
||
export type TypeFloat = TypeBase & { | ||
type: 'float' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { REQUIRED } from '../constants' | ||
|
||
import { TypeEnum } from './enum' | ||
import { TypeFloat } from './float' | ||
import { TypeInteger } from './integer' | ||
import { TypeString } from './string' | ||
|
||
export type TypeBase = { | ||
default: typeof REQUIRED | (() => unknown) | ||
nullable: boolean | ||
} | ||
|
||
export type Option = TypeEnum | TypeFloat | TypeInteger | TypeString |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { TypeBase } from '.' | ||
|
||
export type TypeInteger = TypeBase & { | ||
type: 'integer' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { TypeBase } from '.' | ||
|
||
export type TypeString = TypeBase & { | ||
type: 'string' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { REQUIRED } from './constants' | ||
import { Option } from './modules' | ||
|
||
type Id<T extends object> = { [KEY in keyof T]: T[KEY] } | ||
|
||
export type Options = Record<string, Option> | ||
|
||
export type ExtendsOne< | ||
INPUT extends Option, | ||
IF extends Option['type'], | ||
RESULT, | ||
ELSE | ||
> = INPUT['type'] extends IF ? RESULT : ELSE | ||
|
||
export type Result< | ||
INPUT extends Option, | ||
TYPE extends typeof String | typeof Number | ||
> = Id< | ||
{ | ||
type: TYPE | ||
validator: (value: unknown) => boolean | ||
} & (INPUT['default'] extends typeof REQUIRED | ||
? { required: true } | ||
: { default: INPUT['default'] }) | ||
> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/** | ||
* We don't need the full package, as we don’t consider strings to be valid numbers | ||
* @see {@link https://github.com/jonschlinkert/is-number/blob/98e8ff1da1a89f93d1397a24d7413ed15421c139/index.js#L11-L13} | ||
*/ | ||
export const isNumber = (value: unknown): boolean => | ||
// eslint-disable-next-line sonarjs/no-identical-expressions | ||
typeof value === 'number' ? value - value === 0 : false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"compilerOptions": { | ||
"allowJs": true, | ||
"allowSyntheticDefaultImports": true, | ||
"declaration": true, | ||
"declarationDir": "dist", | ||
"esModuleInterop": true, | ||
"experimentalDecorators": true, | ||
"lib": ["ESNext", "ES2019"], | ||
"module": "CommonJS", | ||
"moduleResolution": "node", | ||
"noImplicitAny": true, | ||
"outDir": "dist", | ||
"resolveJsonModule": true, | ||
"rootDir": "source", | ||
"skipLibCheck": true, | ||
"sourceMap": true, | ||
"strict": true, | ||
"target": "es5" | ||
}, | ||
"exclude": ["node_modules", "dist"], | ||
"include": ["source/*.ts", "source/**/*.ts"] | ||
} |
Oops, something went wrong.