From a96ea02d943df77583d58a009b98756dd62d73c5 Mon Sep 17 00:00:00 2001 From: studds Date: Fri, 6 Dec 2019 12:13:18 +1100 Subject: [PATCH 1/3] feat: add support for custom ajv keywords --- README.md | 81 +++++++++++++++++++++------------------- src/Example.validator.ts | 56 +++++++++++++++++++++++++++ src/index.ts | 9 ++++- src/parseArgs.ts | 16 ++++++++ src/printValidator.ts | 4 ++ src/template.ts | 8 ++++ 6 files changed, 134 insertions(+), 40 deletions(-) create mode 100644 src/Example.validator.ts diff --git a/README.md b/README.md index 8aca084..f26ec98 100644 --- a/README.md +++ b/README.md @@ -51,60 +51,63 @@ Note that types will be validated automatically, but you can also use annotation Usage: typescript-json-schema Options: - --help Show help [boolean] - --version Show version number [boolean] - --refs Create shared ref definitions. [boolean] [default: true] - --aliasRefs Create shared ref definitions for the type aliases. + --help Show help [boolean] + --version Show version number [boolean] + --refs Create shared ref definitions.[boolean] [default: true] + --aliasRefs Create shared ref definitions for the type aliases. [boolean] [default: false] - --topRef Create a top-level ref definition. + --topRef Create a top-level ref definition. [boolean] [default: false] - --titles Creates titles in the output schema. + --titles Creates titles in the output schema. [boolean] [default: false] - --defaultProps Create default properties definitions. + --defaultProps Create default properties definitions. [boolean] [default: true] - --noExtraProps Disable additional properties in objects by default. + --noExtraProps Disable additional properties in objects by default. [boolean] [default: false] - --propOrder Create property order definitions. + --propOrder Create property order definitions. [boolean] [default: false] - --typeOfKeyword Use typeOf keyword (https://goo.gl/DC6sni) for - functions. [boolean] [default: false] - --required Create required array for non-optional properties. + --typeOfKeyword Use typeOf keyword (https://goo.gl/DC6sni) for + functions. [boolean] [default: false] + --required Create required array for non-optional properties. [boolean] [default: true] - --strictNullChecks Make values non-nullable by default. + --strictNullChecks Make values non-nullable by default. [boolean] [default: true] - --ignoreErrors Generate even if the program has errors. + --ignoreErrors Generate even if the program has errors. [boolean] [default: false] - --validationKeywords Provide additional validation keywords to include. + --validationKeywords Provide additional validation keywords to include. [array] [default: []] - --excludePrivate Exclude private members from the schema. + --excludePrivate Exclude private members from the schema. [boolean] [default: false] - --uniqueNames Use unique names for type symbols. + --uniqueNames Use unique names for type symbols. [boolean] [default: false] - --include Further limit tsconfig to include only matching files. + --include Further limit tsconfig to include only matching files. [array] - --rejectDateType Rejects Date fields in type definitions. + --rejectDateType Rejects Date fields in type definitions. [boolean] [default: false] - --id ID of schema. [string] [default: ""] - --uniqueItems Validate `uniqueItems` keyword [boolean] [default: true] - --unicode calculate correct length of strings with unicode pairs - (true by default). Pass false to use .length of strings - that is faster, but gives "incorrect" lengths of strings - with unicode pairs - each unicode pair is counted as two - characters. [boolean] [default: true] - --nullable support keyword "nullable" from Open API 3 - specification. [boolean] [default: true] - --format formats validation mode ('fast' by default). Pass 'full' - for more correct and slow validation or false not to - validate formats at all. E.g., 25:00:00 and 2015/14/33 - will be invalid time and date in 'full' mode but it will - be valid in 'fast' mode. + --id ID of schema. [string] [default: ""] + --uniqueItems Validate `uniqueItems` keyword[boolean] [default: true] + --unicode calculate correct length of strings with unicode pairs + (true by default). Pass false to use .length of strings + that is faster, but gives "incorrect" lengths of + strings with unicode pairs - each unicode pair is + counted as two characters. [boolean] [default: true] + --nullable support keyword "nullable" from Open API 3 + specification. [boolean] [default: true] + --format formats validation mode ('fast' by default). Pass + 'full' for more correct and slow validation or false + not to validate formats at all. E.g., 25:00:00 and + 2015/14/33 will be invalid time and date in 'full' mode + but it will be valid in 'fast' mode. [choices: "fast", "full"] [default: "fast"] - --coerceTypes Change data type of data to match type keyword. e.g. - parse numbers in strings [boolean] [default: false] - --collection Process the file as a collection of types, instead of - one single type. [boolean] [default: false] - --useNamedExport Type name is a named export, rather than the default - export of the file [boolean] [default: false] + --coerceTypes Change data type of data to match type keyword. e.g. + parse numbers in strings [boolean] [default: false] + --collection Process the file as a collection of types, instead of + one single type. [boolean] [default: false] + --useNamedExport Type name is a named export, rather than the default + export of the file [boolean] [default: false] + --customKeywordFnName Name of function which configures AJV custom keywords + [string] + --customKeywordPath Path to file containing customKeywordFnName [string] -* [default: []] ``` diff --git a/src/Example.validator.ts b/src/Example.validator.ts new file mode 100644 index 0000000..7f384c5 --- /dev/null +++ b/src/Example.validator.ts @@ -0,0 +1,56 @@ +/* tslint:disable */ +// generated by typescript-json-validator +import {inspect} from 'util'; +import Ajv = require('ajv'); +import ExampleType from './Example'; + +export const ajv = new Ajv({ + allErrors: true, + coerceTypes: false, + format: 'fast', + nullable: true, + unicode: true, + uniqueItems: true, + useDefaults: true, +}); + +ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json')); + +export {ExampleType}; +export const ExampleTypeSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + defaultProperties: [], + properties: { + answer: { + default: 42, + type: 'number', + }, + email: { + type: 'string', + }, + value: { + type: 'string', + }, + }, + required: ['answer', 'value'], + type: 'object', +}; +export type ValidateFunction = ((data: unknown) => data is T) & + Pick; +export const isExampleType = ajv.compile(ExampleTypeSchema) as ValidateFunction< + ExampleType +>; +export default function validate(value: unknown): ExampleType { + if (isExampleType(value)) { + return value; + } else { + throw new Error( + ajv.errorsText( + isExampleType.errors!.filter((e: any) => e.keyword !== 'if'), + {dataVar: 'ExampleType'}, + ) + + '\n\n' + + inspect(value), + ); + } +} diff --git a/src/index.ts b/src/index.ts index 5baded0..8c5ca2d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import {writeFileSync} from 'fs'; -import {basename} from 'path'; +import {basename, relative, dirname} from 'path'; import {parseArgs} from './parseArgs'; import parse from './parse'; import { @@ -36,6 +36,13 @@ export default function run(args?: string[]) { options.useNamedExport, normalizeSchema(schema), `./${basename(fileName, /\.ts$/.test(fileName) ? '.ts' : '.tsx')}`, + options.customKeywordFnName, + options.customKeywordPath + ? relative( + dirname(outputFileName), + options.customKeywordPath, + ).replace(/.ts/, '') + : '', tsConfig, options.ajv, ); diff --git a/src/parseArgs.ts b/src/parseArgs.ts index fe40cd1..076d798 100644 --- a/src/parseArgs.ts +++ b/src/parseArgs.ts @@ -13,6 +13,8 @@ export interface Options { >; ajv: Ajv.Options; useNamedExport: boolean; + customKeywordFnName: string | undefined; + customKeywordPath: string | undefined; } export interface File { fileName: string; @@ -146,6 +148,18 @@ export function parseArgs(args?: string[]): ParsedArgs { 'useNamedExport', 'Type name is a named export, rather than the default export of the file', ) + .string('customKeywordFnName') + .default('customKeywordFnName', undefined) + .describe( + 'customKeywordFnName', + 'Name of function which configures AJV custom keywords', + ) + .string('customKeywordPath') + .default('customKeywordPath', undefined) + .describe( + 'customKeywordPath', + 'Path to file containing customKeywordFnName', + ) .parse(args); const isCollection: boolean = parsedArgs.collection; @@ -193,6 +207,8 @@ export function parseArgs(args?: string[]): ParsedArgs { useDefaults: parsedArgs.defaultProps, }, useNamedExport: parsedArgs.useNamedExport, + customKeywordFnName: parsedArgs.customKeywordFnName, + customKeywordPath: parsedArgs.customKeywordPath, }, }; } diff --git a/src/printValidator.ts b/src/printValidator.ts index adbb83d..cd814a5 100644 --- a/src/printValidator.ts +++ b/src/printValidator.ts @@ -47,6 +47,8 @@ export function printSingleTypeValidator( isNamedExport: boolean, schema: TJS.Definition, relativePath: string, + customKeywordFnName: string | undefined, + customKeywordPath: string | undefined, tsConfig: any, options: Ajv.Options = {}, ) { @@ -56,7 +58,9 @@ export function printSingleTypeValidator( t.IMPORT_INSPECT, t.IMPORT_AJV(tsConfig), t.importType(typeName, relativePath, {isNamedExport}), + t.importCustomKeyword(customKeywordFnName, customKeywordPath), t.declareAJV(options), + t.applyCustomKeyword(customKeywordFnName), t.exportNamed([typeName]), t.declareSchema(typeName + 'Schema', schema), // TODO: koa implementation diff --git a/src/template.ts b/src/template.ts index 662470d..bd5a8a7 100644 --- a/src/template.ts +++ b/src/template.ts @@ -160,3 +160,11 @@ export const VALIDATE_IMPLEMENTATION = `export function validate(typeName: strin return value as any; }; }`; + +export const importCustomKeyword = ( + fnName: string | undefined, + path: string | undefined, +) => (fnName && path ? `import {${fnName}} from '${path}';` : ''); + +export const applyCustomKeyword = (fnName: string | undefined) => + fnName ? `${fnName}(ajv);` : ''; From 197524b066cffc860ec55631fabd20ea464918fe Mon Sep 17 00:00:00 2001 From: Daniel Studds Date: Tue, 18 Feb 2020 13:44:35 +1100 Subject: [PATCH 2/3] fix: don't process customKeywordPath --- src/index.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8c5ca2d..985df78 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import {writeFileSync} from 'fs'; -import {basename, relative, dirname} from 'path'; +import {basename} from 'path'; import {parseArgs} from './parseArgs'; import parse from './parse'; import { @@ -20,11 +20,7 @@ export { export default function run(args?: string[]) { const {files, options} = parseArgs(args); const tsConfig = loadTsConfig(); - const parsed = parse( - files.map(f => f.fileName), - tsConfig, - options.schema, - ); + const parsed = parse(files.map(f => f.fileName), tsConfig, options.schema); files.forEach(({fileName, typeName}) => { const outputFileName = fileName.replace(/\.tsx?$/, '.validator.ts'); @@ -37,12 +33,7 @@ export default function run(args?: string[]) { normalizeSchema(schema), `./${basename(fileName, /\.ts$/.test(fileName) ? '.ts' : '.tsx')}`, options.customKeywordFnName, - options.customKeywordPath - ? relative( - dirname(outputFileName), - options.customKeywordPath, - ).replace(/.ts/, '') - : '', + options.customKeywordPath, tsConfig, options.ajv, ); From 84c86b708292e65f85f1870883a38a452393979d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KARASZI=20Istv=C3=A1n?= Date: Thu, 24 Sep 2020 11:57:31 +0200 Subject: [PATCH 3/3] Format index.ts --- src/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 985df78..e2b1135 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,7 +20,11 @@ export { export default function run(args?: string[]) { const {files, options} = parseArgs(args); const tsConfig = loadTsConfig(); - const parsed = parse(files.map(f => f.fileName), tsConfig, options.schema); + const parsed = parse( + files.map(f => f.fileName), + tsConfig, + options.schema, + ); files.forEach(({fileName, typeName}) => { const outputFileName = fileName.replace(/\.tsx?$/, '.validator.ts');