From 8be221da225cec43b7936de87cd3cc07e1d93d24 Mon Sep 17 00:00:00 2001 From: Emily Marigold Klassen Date: Thu, 19 Dec 2019 22:52:37 -0800 Subject: [PATCH] Add to-json-schema types (#40959) --- types/to-json-schema/index.d.ts | 223 +++++++++++++++++ types/to-json-schema/to-json-schema-tests.ts | 246 +++++++++++++++++++ types/to-json-schema/tsconfig.json | 23 ++ types/to-json-schema/tslint.json | 1 + 4 files changed, 493 insertions(+) create mode 100644 types/to-json-schema/index.d.ts create mode 100644 types/to-json-schema/to-json-schema-tests.ts create mode 100644 types/to-json-schema/tsconfig.json create mode 100644 types/to-json-schema/tslint.json diff --git a/types/to-json-schema/index.d.ts b/types/to-json-schema/index.d.ts new file mode 100644 index 0000000000..287dd74b6e --- /dev/null +++ b/types/to-json-schema/index.d.ts @@ -0,0 +1,223 @@ +// Type definitions for to-json-schema 0.2 +// Project: https://github.com/ruzicka/to-json-schema#readme +// Definitions by: Emily Marigold Klassen +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +import { JSONSchema4, JSONSchema4TypeName } from 'json-schema'; + +export = toJsonSchema; + +/** + * `to-json-schema` exports function that converts most javascript values to + * JSON schema. Such a schema can be used to further validation of similar + * objects/values + * + * @param value Any javascript value + * @param options optional options object + */ +declare function toJsonSchema( + value: any, + options?: toJsonSchema.Options +): toJsonSchema.JSONSchema3or4; + +declare namespace toJsonSchema { + interface JSONSchema3or4 { + id?: JSONSchema4['id']; + $ref?: JSONSchema4['$ref']; + $schema?: JSONSchema4['$schema']; + title?: JSONSchema4['title']; + description?: JSONSchema4['description']; + + default?: JSONSchema4['default']; + multipleOf?: JSONSchema4['multipleOf']; + /** JSON Schema 3 uses `divisibleBy` instead of `multipleOf`. */ + divisibleBy?: JSONSchema4['multipleOf']; + maximum?: JSONSchema4['maximum']; + exclusiveMaximum?: JSONSchema4['exclusiveMaximum']; + minimum?: JSONSchema4['minimum']; + exclusiveMinimum?: JSONSchema4['exclusiveMinimum']; + maxLength?: JSONSchema4['maxLength']; + minLength?: JSONSchema4['minLength']; + pattern?: JSONSchema4['pattern']; + + additionalItems?: boolean | JSONSchema3or4; + items?: JSONSchema3or4 | JSONSchema3or4[]; + + maxItems?: JSONSchema4['maxItems']; + minItems?: JSONSchema4['minItems']; + uniqueItems?: JSONSchema4['uniqueItems']; + maxProperties?: JSONSchema4['maxProperties']; + minProperties?: JSONSchema4['minProperties']; + + required?: boolean | JSONSchema4['required']; + additionalProperties?: boolean | JSONSchema3or4; + + definitions?: JSONSchema4['definitions']; + + properties?: { + [k: string]: JSONSchema3or4 + }; + + patternProperties?: { + [k: string]: JSONSchema3or4 + }; + dependencies?: { + [k: string]: JSONSchema3or4 | string | string[] + }; + + enum?: JSONSchema4['enum']; + type?: JSONSchema4['type']; + + allOf?: JSONSchema4['allOf']; + anyOf?: JSONSchema4['anyOf']; + oneOf?: JSONSchema4['oneOf']; + not?: JSONSchema4['not']; + + /** JSON Schema 3 only */ + disallow?: string | Array; + + extends?: JSONSchema3or4 | JSONSchema3or4[]; + + [k: string]: any; + + format?: string; + } + + interface Options { + /** + * specify `true` to make all properties required. + * + * @default false + * @example + * const schema = toJsonSchema(33, {required: false}); + * // { type: "integer" } + * const schema = toJsonSchema(33, {required: true}); + * // { type: "integer", "required": true } + */ + required?: boolean; + /** + * By providing `postProcessFnc`, you can modify or replace generated + * schema. This function will be called recursively for all the properties + * and sub-properties and array items from leaves to the root. If you want + * to preserve default functionality, don't forget to call defaultFunc + * which is currently responsible for setting `required` for the schema + * items if there is common option `required` set to true. + * + * @param type JSON schema type of the `value` + * @param schema Generated JSON schema + * @param value - input value + * @param defaultFunc standard function that is used to post-process + * generated schema. Takes the `type`, `schema`, + * `value` params. + */ + postProcessFnc?( + type: JSONSchema4TypeName, + schema: JSONSchema3or4, + value: any, + defaultFunc: ( + type: JSONSchema4TypeName, + schema: JSONSchema3or4, + value: any + ) => JSONSchema3or4 + ): JSONSchema3or4; + + arrays?: { + /** + * * `all` option causes parser to go through all array items, finding + * the most compatible yet most descriptive schema possible. If + * multiple types are found, the type is omitted so it can be + * validated. + * * `first` option takes only first item in the array into account. If + * performance is a concern, you may consider this option. + * * `uniform` option requires all items in array to have same structure + * (to convert to the same schema). If not, error is thrown. + * * `tuple` option generates a + * [tuple array](https://json-schema.org/understanding-json-schema/reference/array.html#tuple-validation) + * (array of objects) from arrays. + * + * @default 'all' + */ + mode?: 'all' | 'first' | 'uniform' | 'tuple'; + }; + objects?: { + /** + * By providing custom function you will be able to modify any object + * value (including nested ones) and pre-process it before it gets + * converted into schema or modify generated schema or do the schema + * conversion entirely by yourself. + * + * @param obj input object value that is supposed to be converted into + * JSON schema + * @param defaultFunc standard function that is used to generate schema + * from object. Takes just the `obj` param. + */ + preProcessFnc?( + obj: any, + defaultFunc: (obj: any) => JSONSchema3or4 + ): JSONSchema3or4; + /** + * By providing `postProcessFnc`, you can modify or replace generated + * schema. This function will be called recursively for all the + * properties and sub-properties and array items from leaves to the root + * of the `obj` object. + * + * @param schema Generated JSON schema + * @param obj input value + * @param defaultFunc standard function that is used to post-process + * generated schema. Takes the `schema`, `obj` + * params. + */ + postProcessFnc?( + schema: JSONSchema3or4, + obj: any, + defaultFnc: (schema: JSONSchema3or4, obj: any) => JSONSchema3or4 + ): JSONSchema3or4; + /** + * if set to `false`, all object schemas will include JSON schema + * property `additionalProperties: false` which makes generated schema + * to perevent any extra properties. + * + * @default true + */ + additionalProperties?: boolean; + }; + strings?: { + /** + * By providing custom function you will be able to modify any string + * value (including nested ones) and pre-process it before it gets + * converted to schema, modify generated schema or do the schema + * conversion entirely by yourself. + * + * @param value `string` to be converted into JSON schema + * @param defaultFnc default function that normally generates the + * schema. This function receives only `string` to be + * converted to JSON schema + */ + preProcessFnc?( + value: string, + defaultFnc: (value: string) => JSONSchema3or4 + ): JSONSchema3or4; + /** + * When set to true format of the strings values may be detected based + * on it's content. + * + * These JSON schema string formats can be detected: + * + * * date-time + * * date + * * time + * * utc-millisec + * * color + * * style + * * phone + * * uri + * * email + * * ip-address + * * ipv6 + * + * @default true + */ + detectFormat?: boolean; + }; + } +} diff --git a/types/to-json-schema/to-json-schema-tests.ts b/types/to-json-schema/to-json-schema-tests.ts new file mode 100644 index 0000000000..090b073529 --- /dev/null +++ b/types/to-json-schema/to-json-schema-tests.ts @@ -0,0 +1,246 @@ +import toJsonSchema = require('to-json-schema'); +{ + const objToBeConverted = { + name: 'David', + rank: 7, + born: '1990-04-05T15:09:56.704Z', + luckyNumbers: [7, 77, 5], + }; + + // $ExpectType JSONSchema3or4 + toJsonSchema(objToBeConverted); +} +{ + // $ExpectType JSONSchema3or4 + toJsonSchema(33, { required: false }); + /* + { + "type": "integer" + } + */ +} +{ + // $ExpectType JSONSchema3or4 + toJsonSchema(33, { required: true }); + /* + { + "type": "integer", + "required": true + } + */ + const options: toJsonSchema.Options = { + postProcessFnc: (type, schema, value, defaultFunc) => + type === 'integer' ? { ...schema, required: true } : defaultFunc(type, schema, value), + }; + + const instance = { + a: 1, + b: 'str', + }; + + // $ExpectType JSONSchema3or4 + toJsonSchema(instance, options); + /* + { + type: 'object', + properties: { + a: {type: 'integer', required: true}, + b: {type: 'string'}, + }, + } + */ +} +{ + const arr = [33, 44, 55]; + // $ExpectType JSONSchema3or4 + toJsonSchema(arr, { arrays: { mode: 'all' } }); + /* + { + "type": "array", + "items": { + "type": "integer" + } + } + */ +} +{ + const arr = [33, 'str', 55]; + // $ExpectType JSONSchema3or4 + toJsonSchema(arr, { arrays: { mode: 'all' } }); + /* + { + "type": "array" + } + */ +} +{ + const arr = [{ name: 'john', grades: [1, 2, 3] }, { name: 'david', grades: ['a', 'b', 'c'] }]; + // $ExpectType JSONSchema3or4 + toJsonSchema(arr, { arrays: { mode: 'all' } }); + /* + { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "grades": { + "type": "array" // due to incompatible array items' types, `items` field is omitted + } + } + } + */ +} +{ + const arr = ['str', 11, 30]; + // $ExpectType JSONSchema3or4 + toJsonSchema(arr, { arrays: { mode: 'first' } }); + /* Other than first array item is ignored + { + "type": "array", + "items": { + "type": "string" + } + } + } + */ +} +{ + const arr = ['str', 11, 30]; + // $ExpectType JSONSchema3or4 + toJsonSchema(arr, { arrays: { mode: 'uniform' } }); + /* + Above code will throw 'Error: Invalid schema, incompatible array items' + */ +} +{ + const arr = ['str', 11, 30]; + // $ExpectType JSONSchema3or4 + toJsonSchema(arr, { arrays: { mode: 'tuple' } }); + /* + { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "integer" + } + ] + } + */ +} +{ + const options: toJsonSchema.Options = { + objects: { additionalProperties: false }, + }; + const obj = { + a: { + c: 1, + d: 1, + }, + b: 'str', + }; + // $ExpectType JSONSchema3or4 + toJsonSchema(obj, options); + /* + { + type: 'object', + properties: { + a: { + type: 'object', + properties: { + c: {type: 'integer'}, + d: {type: 'integer'}, + }, + additionalProperties: false, + }, + b: {type: 'string'}, + }, + additionalProperties: false, + } + */ +} +{ + const options: toJsonSchema.Options = { + objects: { + preProcessFnc: (obj, defaultFnc) => defaultFnc({ a: obj.a, b: obj.b }), + }, + }; + const obj = { a: 1, b: 2, c: 3 }; + // $ExpectType JSONSchema3or4 + toJsonSchema(obj, options); + /* + { + "type": "object", + "properties": { + "a": { + "type": "integer" + }, + "b": { + "type": "integer", + } + } + } + */ +} +{ + const options: toJsonSchema.Options = { + objects: { + postProcessFnc: (schema, obj, defaultFnc) => ({ + ...defaultFnc(schema, obj), + required: Object.getOwnPropertyNames(obj), + }), + }, + }; + const obj = { a: 1, b: 'str' }; + // $ExpectType JSONSchema3or4 + toJsonSchema(obj, options); + /* + { + type: 'object', + properties: { + a: {type: 'integer'}, + b: {type: 'string'}, + } + required: ['a', 'b'] + } + */ +} +{ + const options: toJsonSchema.Options = { + strings: { + preProcessFnc: (value, defaultFnc) => { + const schema = defaultFnc(value); + if (value === 'date') { + schema.format = 'date'; + } + return schema; + }, + }, + }; + // $ExpectType JSONSchema3or4 + toJsonSchema('date', options); + /* + { + "type": "string", + "format": "date" + } + */ +} +{ + const obj = { + a: '2012-07-08T16:41:41.532Z', + b: '+31 42 123 4567', + c: 'http://www.google.com/', + d: 'obama@whitehouse.gov', + }; + // $ExpectType JSONSchema3or4 + toJsonSchema(obj, { strings: { detectFormat: true } }); +} diff --git a/types/to-json-schema/tsconfig.json b/types/to-json-schema/tsconfig.json new file mode 100644 index 0000000000..473526bb0e --- /dev/null +++ b/types/to-json-schema/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": [ + "es6" + ], + "noImplicitAny": true, + "noImplicitThis": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "baseUrl": "../", + "typeRoots": [ + "../" + ], + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.d.ts", + "to-json-schema-tests.ts" + ] +} diff --git a/types/to-json-schema/tslint.json b/types/to-json-schema/tslint.json new file mode 100644 index 0000000000..3db14f85ea --- /dev/null +++ b/types/to-json-schema/tslint.json @@ -0,0 +1 @@ +{ "extends": "dtslint/dt.json" }