UNPKG

3.43 kBPlain TextView Raw
1import { TJS, fs, fsPath, value as valueUtil, AJV } from '../common';
2import { SchemaDefinition } from './SchemaDefinition';
3const defaultValue = valueUtil.defaultValue;
4
5/**
6 * Options for initializing a schema.
7 *
8 * For [TJS.PartialArgs] see:
9 * https://github.com/YousefED/typescript-json-schema#usage
10 *
11 * The [TJS.CompilerOptions] is actually a pass-throgh to the typescript compiler.
12 *
13 */
14export type SchemaOptions = TJS.PartialArgs &
15 TJS.CompilerOptions & {
16 files: string | string[];
17 basePath?: string;
18 };
19
20/**
21 * Generates schemas.
22 */
23export class Schema {
24 public static compile(args: SchemaOptions) {
25 return new Schema(args);
26 }
27
28 public readonly basePath: string | undefined;
29 public readonly files: string[];
30 private _program: TJS.Program;
31 private _generator: TJS.JsonSchemaGenerator;
32
33 private constructor(args: SchemaOptions) {
34 // Default values.
35 const required = defaultValue(args.required, true);
36 const strictNullChecks = defaultValue(args.strictNullChecks, true);
37 const ignoreErrors = defaultValue(args.ignoreErrors, true);
38 args = { ...args, required, strictNullChecks, ignoreErrors };
39
40 // Prepare paths.
41 this.basePath = args.basePath ? fsPath.resolve(args.basePath) : undefined;
42 const toPath = (path: string) => {
43 path = (path || '').trim();
44 if (path) {
45 path = this.basePath ? fsPath.join(this.basePath, path) : path;
46 path = fsPath.resolve(path);
47 }
48 return path;
49 };
50 this.files = Array.isArray(args.files) ? args.files : [args.files];
51 this.files = this.files.map(path => toPath(path));
52 this.files = this.files.filter(path => Boolean(path.trim()));
53
54 // Ensure the files exist.
55 if (this.files.length === 0) {
56 throw new Error(`No files were given to the schema.`);
57 }
58 this.files.forEach(path => {
59 if (!fs.pathExistsSync(path)) {
60 throw new Error(
61 `Failed to load schema. The file '${path}' does not exist.`,
62 );
63 }
64 });
65
66 // Create the compiler.
67 this._program = TJS.getProgramFromFiles(this.files, args, this.basePath);
68 const generator = TJS.buildGenerator(this._program, args);
69 if (!generator) {
70 throw new Error(`Failed to create a build-generator for the schema.`);
71 }
72 this._generator = generator;
73 }
74
75 /**
76 * Generates a JSON-schema for the specifid symbol.
77 */
78 public forType(
79 type: string | string[],
80 options: { includeReffedDefinitions?: boolean } = {},
81 ) {
82 try {
83 const g = this._generator;
84 const def = Array.isArray(type)
85 ? g.getSchemaForSymbols(type, options.includeReffedDefinitions)
86 : g.getSchemaForSymbol(type, options.includeReffedDefinitions);
87 return new SchemaDefinition({ type, def });
88 } catch (error) {
89 throw new Error(
90 `Failed to generate schema for symbol '${type}'. ${error.message}`,
91 );
92 }
93 }
94
95 /**
96 * Creates a re-usable schema validator.
97 */
98 public static async validator(schema: object | string) {
99 const ajv = AJV();
100 const def =
101 typeof schema === 'string'
102 ? await fs.readJson(fsPath.resolve(schema))
103 : schema;
104 const func = ajv.compile(def);
105 return (data?: object) => func(data);
106 }
107
108 /**
109 * Validates an object against a schema.
110 */
111
112 public static async validate(schema: object, data?: object) {
113 return (await Schema.validator(schema))(data);
114 }
115}