├── .gitignore ├── .prettierrc ├── .travis.yml ├── README.md ├── jest.config.js ├── license ├── package.json ├── src ├── index.ts ├── schema │ ├── constants.ts │ ├── index.ts │ ├── selectors.ts │ ├── types.ts │ └── utils.ts ├── types │ └── yup │ │ └── index.d.ts └── yup │ ├── addMethods │ ├── array.ts │ ├── index.ts │ ├── mixed.ts │ ├── number.ts │ ├── string.ts │ └── utils.ts │ ├── builder │ └── index.ts │ ├── config │ └── index.ts │ ├── index.ts │ ├── schemas │ ├── array │ │ ├── array.schema.ts │ │ └── index.ts │ ├── boolean │ │ ├── boolean.schema.ts │ │ └── index.ts │ ├── composition │ │ └── index.ts │ ├── constant.ts │ ├── enumerables.ts │ ├── index.ts │ ├── integer │ │ ├── index.ts │ │ └── integer.schema.ts │ ├── null │ │ ├── index.ts │ │ └── null.schema.ts │ ├── number │ │ ├── index.ts │ │ └── number.schema.ts │ ├── object │ │ ├── index.ts │ │ └── object.schema.ts │ ├── required.ts │ └── string │ │ ├── index.ts │ │ ├── string.constants.ts │ │ └── string.schema.ts │ ├── types.ts │ └── utils.ts ├── test └── yup │ ├── allOf.if.test.ts │ ├── array.contains.test.ts │ ├── array.items.test.ts │ ├── array.items.tuple.test.ts │ ├── array.test.ts │ ├── boolean.test.ts │ ├── composition.allof.test.ts │ ├── composition.anyof.test.ts │ ├── composition.not.test.ts │ ├── composition.oneof.test.ts │ ├── configErrors.array.test.ts │ ├── configErrors.boolean.test.ts │ ├── configErrors.number.test.ts │ ├── configErrors.object.test.ts │ ├── configErrors.string.test.ts │ ├── if.array.test.ts │ ├── if.boolean.test.ts │ ├── if.number.test.ts │ ├── if.string.test.ts │ ├── integer.test.ts │ ├── null.test.ts │ ├── number.test.ts │ ├── object.test.ts │ ├── string.format.test.ts │ ├── string.test.ts │ └── util.test.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules* 2 | coverage/* 3 | .cache/* 4 | dist/* 5 | yarn-error.log 6 | .vscode/* -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": false, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "fluid": false 11 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "12" 4 | after_success: yarn coverage 5 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | }; -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Ritchie Anesco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-schema-yup-transformer", 3 | "author": "Ritchie Anesco ", 4 | "version": "1.6.11", 5 | "description": "Transforms a draft 7 specification JSON Schema to a Yup Schema", 6 | "license": "MIT", 7 | "main": "dist/index.js", 8 | "files": [ 9 | "dist" 10 | ], 11 | "types": "dist/index.d.ts", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/ritchieanesco/json-schema-yup-transform" 15 | }, 16 | "scripts": { 17 | "build": "tsc", 18 | "test": "jest", 19 | "test:debug": "node --inspect node_modules/.bin/jest --runInBand", 20 | "coverage": "jest --coverage && cat ./coverage/lcov.info | coveralls" 21 | }, 22 | "dependencies": { 23 | "is-relative-url": "3.0.0", 24 | "lodash": "4.17.21", 25 | "stringify-object": "^3.3.0", 26 | "yup": "^0.29.1" 27 | }, 28 | "devDependencies": { 29 | "@types/is-relative-url": "^3.0.0", 30 | "@types/jest": "^25.1.4", 31 | "@types/json-schema": "^7.0.4", 32 | "@types/lodash": "^4.14.149", 33 | "@types/stringify-object": "^3.2.0", 34 | "@types/yup": "^0.26.33", 35 | "bufferutil": "^4.0.1", 36 | "canvas": "^2.5.0", 37 | "coveralls": "^3.0.11", 38 | "jest": "^25.1.0", 39 | "ts-jest": "^25.2.1", 40 | "typescript": "^3.8.3", 41 | "utf-8-validate": "^5.0.2" 42 | }, 43 | "keywords": [] 44 | } 45 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema7 } from "json-schema"; 2 | import Yup from "./yup/addMethods"; 3 | import * as yupTransformer from "./yup"; 4 | import { normalize } from "./yup/utils"; 5 | 6 | /** 7 | * Converts a valid schema to a yup schema 8 | */ 9 | const convertToYup = ( 10 | schema: JSONSchema7, 11 | config?: yupTransformer.Config 12 | ): Yup.ObjectSchema | undefined => { 13 | config && yupTransformer.setConfiguration(config); 14 | const normalizedSchema = normalize(schema); 15 | return yupTransformer.default(normalizedSchema); 16 | }; 17 | 18 | export type Config = yupTransformer.Config; 19 | export type CustomErrorMsgParam = yupTransformer.CustomErrorMsgParam 20 | export default convertToYup; 21 | -------------------------------------------------------------------------------- /src/schema/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFINITION_ROOT = "#/definitions/"; 2 | -------------------------------------------------------------------------------- /src/schema/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./selectors"; 2 | export * from "./utils"; 3 | export * from "./types"; 4 | export * from "./constants"; 5 | -------------------------------------------------------------------------------- /src/schema/selectors.ts: -------------------------------------------------------------------------------- 1 | import get from "lodash/get"; 2 | import nth from "lodash/nth"; 3 | import findKey from "lodash/findKey"; 4 | import { DEFINITION_ROOT } from "./constants"; 5 | import type { 6 | JSONSchema, 7 | JSONSchemaDefinition, 8 | JSONSchemaBasicType 9 | } from "./types"; 10 | import { CompositSchemaTypes, isSchemaObject } from "./types"; 11 | 12 | /** 13 | * Retrieve definitions property value 14 | */ 15 | 16 | export const getDefinitions = ( 17 | schema: JSONSchema 18 | ): 19 | | { 20 | [key: string]: JSONSchemaDefinition; 21 | } 22 | | undefined => schema.definitions; 23 | 24 | /** 25 | * Retrieve definition object from given reference id 26 | */ 27 | 28 | export const getDefinitionItem = ( 29 | schema: JSONSchema, 30 | ref: string 31 | ): boolean | JSONSchema | undefined => { 32 | const definitions = getDefinitions(schema); 33 | if (!definitions) { 34 | return; 35 | } 36 | const path = get$RefValue(ref); 37 | if (path.startsWith("#")) { 38 | const key = findKey( 39 | definitions, 40 | (item) => isSchemaObject(item) && item.$id === path 41 | ); 42 | return key ? get(definitions, key) : undefined; 43 | } 44 | return get(definitions, path); 45 | }; 46 | 47 | /** 48 | * Retrieve properties property value 49 | */ 50 | 51 | export const getProperties = ( 52 | schema: JSONSchema 53 | ): 54 | | { 55 | [key: string]: JSONSchemaDefinition; 56 | } 57 | | undefined => schema.properties; 58 | 59 | export const getPropertyType = (propertyItem: JSONSchema): JSONSchema["type"] => 60 | propertyItem.type; 61 | 62 | export const getCompositionType = ( 63 | propertyItem: JSONSchema 64 | ): CompositSchemaTypes | false | undefined => 65 | (propertyItem.anyOf && CompositSchemaTypes.ANYOF) || 66 | (propertyItem.allOf && CompositSchemaTypes.ALLOF) || 67 | (propertyItem.oneOf && CompositSchemaTypes.ONEOF) || 68 | (propertyItem.not && CompositSchemaTypes.NOT); 69 | 70 | /** 71 | * Retrieve required property value 72 | */ 73 | 74 | export const getRequired = (schema: JSONSchema): JSONSchema["required"] => 75 | schema.required; 76 | 77 | /** 78 | * Retrieve reference id from `$ref` attribute 79 | */ 80 | 81 | const get$RefValue = (ref: string): string => { 82 | // support for both definition key and referencing the $id directly 83 | if (ref.startsWith(DEFINITION_ROOT)) { 84 | return ref.substring(DEFINITION_ROOT.length).replace(/\//g, "."); 85 | } 86 | return ref; 87 | }; 88 | 89 | /** 90 | * Returns an item from array items tuple 91 | */ 92 | 93 | export const getItemsArrayItem = ( 94 | items: JSONSchemaBasicType[], 95 | index: number 96 | ): JSONSchemaBasicType | undefined => nth(items, index); 97 | -------------------------------------------------------------------------------- /src/schema/types.ts: -------------------------------------------------------------------------------- 1 | import isArray from "lodash/isArray"; 2 | import isPlainObject from "lodash/isPlainObject"; 3 | import has from "lodash/has"; 4 | import type { 5 | JSONSchema7, 6 | JSONSchema7Definition, 7 | JSONSchema7Type as UJSONSchema7Type, 8 | JSONSchema7TypeName as UJSONSchema7TypeName 9 | } from "json-schema"; 10 | 11 | /** 12 | * Schema Types 13 | */ 14 | 15 | export enum DataTypes { 16 | STRING = "string", 17 | NUMBER = "number", 18 | ARRAY = "array", 19 | BOOLEAN = "boolean", 20 | OBJECT = "object", 21 | NULL = "null", 22 | INTEGER = "integer" 23 | } 24 | 25 | /** 26 | * Composite schema types 27 | */ 28 | export enum CompositSchemaTypes { 29 | ALLOF = "allOf", 30 | ANYOF = "anyOf", 31 | ONEOF = "oneOf", 32 | NOT = "not" 33 | } 34 | 35 | export enum SchemaKeywords { 36 | REQUIRED = "required", 37 | ENUM = "enum", 38 | CONST = "const", 39 | FORMAT = "format", 40 | DATE_TIME_FORMAT = "dateTime", 41 | DATE_FORMAT = "date", 42 | TIME_FORMAT = "time", 43 | EMAIL_FORMAT = "email", 44 | IDN_EMAIL_FORMAT = "idnEmail", 45 | HOSTNAME_FORMAT = "hostname", 46 | IDN_HOSTNAME_FORMAT = "idnHostname", 47 | IPV4_FORMAT = "ipv4", 48 | IPV6_FORMAT = "ipv6", 49 | URI_FORMAT = "uri", 50 | URI_REFERENCE_FORMAT = "uriReference", 51 | MAXIMUM_LENGTH = "maxLength", 52 | MINIMUM_LENGTH = "minLength", 53 | PATTERN = "pattern", 54 | MAXIMUM = "maximum", 55 | MINIMUM = "minimum", 56 | EXCLUSIVE_MINIMUM = "exclusiveMinimum", 57 | EXCLUSIVE_MAXIMUM = "exclusiveMaximum", 58 | MULTIPLE_OF = "multipleOf", 59 | MINIMUM_ITEMS = "minItems", 60 | MAXIMUM_ITEMS = "maxItems", 61 | CONTAINS = "contains", 62 | TUPLE = "tuple", 63 | REGEX = "regex", 64 | UNIQUE_ITEMS = "uniqueItems" 65 | } 66 | 67 | export type JSONSchema = JSONSchema7; 68 | export type JSONSchemaDefinition = JSONSchema7Definition; 69 | export type JSONSchemaTypeName = UJSONSchema7TypeName; 70 | export type JSONSchemaType = UJSONSchema7Type; 71 | export type JSONSchemaBasicType = Omit< 72 | JSONSchemaType, 73 | "JSONSchema7Object" | "JSONSchema7Array" 74 | >; 75 | 76 | export type NodeTypes = SchemaKeywords | CompositSchemaTypes | DataTypes; 77 | 78 | export type JSONSchemaDefinitionExtended = JSONSchemaExtended | boolean; 79 | export interface JSONSchemaExtended extends JSONSchema { 80 | regex?: string; 81 | properties?: { 82 | [key: string]: JSONSchemaDefinitionExtended; 83 | }; 84 | } 85 | 86 | /** 87 | * Object type guard array items key 88 | */ 89 | 90 | export const isSchemaObject = ( 91 | items: JSONSchemaDefinition | JSONSchemaDefinition[] | undefined | unknown 92 | ): items is JSONSchema => isPlainObject(items); 93 | 94 | /** 95 | * Tuple type guard array items key 96 | */ 97 | 98 | export const isItemsArray = ( 99 | items: JSONSchemaDefinition | JSONSchemaDefinition[] | undefined 100 | ): items is JSONSchemaDefinition[] => 101 | isArray(items) && items.every((item) => has(item, "type")); 102 | 103 | export interface AnyOfSchema extends JSONSchema { 104 | anyOf: JSONSchemaDefinition[]; 105 | } 106 | 107 | export interface AllOfSchema extends JSONSchema { 108 | allOf: JSONSchemaDefinition[]; 109 | } 110 | 111 | export interface OneOfSchema extends JSONSchema { 112 | oneOf: JSONSchemaDefinition[]; 113 | } 114 | 115 | export interface NotSchema extends JSONSchema { 116 | not: JSONSchemaDefinition; 117 | } 118 | 119 | /** 120 | * String pattern key type guard 121 | */ 122 | 123 | export const hasAnyOf = (value: JSONSchema): value is AnyOfSchema => 124 | !!value.anyOf; 125 | 126 | export const hasAllOf = (value: JSONSchema): value is AllOfSchema => 127 | !!value.allOf; 128 | 129 | export const hasOneOf = (value: JSONSchema): value is OneOfSchema => 130 | !!value.oneOf; 131 | 132 | export const hasNot = (value: JSONSchema): value is NotSchema => !!value.not; 133 | -------------------------------------------------------------------------------- /src/schema/utils.ts: -------------------------------------------------------------------------------- 1 | import isArray from "lodash/isArray"; 2 | import isPlainObject from "lodash/isPlainObject"; 3 | import isNull from "lodash/isNull"; 4 | import isString from "lodash/isString"; 5 | import isNumber from "lodash/isNumber"; 6 | import isBoolean from "lodash/isBoolean"; 7 | import isInteger from "lodash/isInteger"; 8 | import { getRequired } from "./selectors"; 9 | import { DataTypes } from "./types"; 10 | import type { JSONSchema } from "./types"; 11 | 12 | /** 13 | * Returns a boolean if ID is a required field 14 | */ 15 | 16 | export const isRequiredField = (schema: JSONSchema, id: string): boolean => { 17 | const requiredList = getRequired(schema); 18 | return isArray(requiredList) && requiredList.includes(id); 19 | }; 20 | 21 | /** 22 | * Hash table to determine field values are 23 | * the expected data type. Primarily used in Yup Lazy 24 | * to ensure the field value type are supported 25 | */ 26 | 27 | export const isTypeOfValue = { 28 | [DataTypes.STRING]: isString, 29 | [DataTypes.NUMBER]: isNumber, 30 | [DataTypes.BOOLEAN]: isBoolean, 31 | [DataTypes.OBJECT]: isPlainObject, 32 | [DataTypes.NULL]: isNull, 33 | [DataTypes.ARRAY]: isArray, 34 | [DataTypes.INTEGER]: isInteger 35 | }; 36 | -------------------------------------------------------------------------------- /src/types/yup/index.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | NumberSchema, 3 | StringSchema, 4 | ArraySchema, 5 | BooleanSchema, 6 | TestOptionsMessage 7 | } from "yup"; 8 | import type { JSONSchemaDefinition, JSONSchemaType } from "../../schema/" 9 | 10 | declare module "yup" { 11 | interface NumberSchema { 12 | multipleOf(value: number, message?: string): this; 13 | } 14 | interface StringSchema { 15 | urlReference(message?: string): this; 16 | } 17 | 18 | interface ArraySchema { 19 | list(type: string, message: string): this; 20 | contains(type: string, message: string): this; 21 | tuple(items: JSONSchemaDefinition[], message: string): this; 22 | minimumItems(count: number, message: string): this; 23 | maximumItems(count: number, message: string): this; 24 | uniqueItems(enable: boolean, message?: string): this; 25 | } 26 | 27 | interface Schema { 28 | required(message: TestOptionsMessage): this; 29 | constant(value: JSONSchemaType, message?: string): this; 30 | enum(value: JSONSchemaType[], message?: string): this; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/yup/addMethods/array.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import isNumber from "lodash/isNumber"; 3 | import isString from "lodash/isString"; 4 | import isBoolean from "lodash/isBoolean"; 5 | import isPlainObject from "lodash/isPlainObject"; 6 | import isArray from "lodash/isArray"; 7 | import isInteger from "lodash/isInteger"; 8 | import type { JSONSchemaDefinitionExtended } from "../../schema"; 9 | import { DataTypes } from "../../schema"; 10 | import { validateItemsArray, isUnique } from "./utils"; 11 | 12 | /** 13 | * Validates that array length is more or equal to that 14 | * of the schema minimumItems property 15 | */ 16 | 17 | export function minimumItems( 18 | this: Yup.ArraySchema, 19 | count: number, 20 | message: string 21 | ): Yup.ArraySchema { 22 | return this.test("test-minimumItems", message, function (input: unknown[]) { 23 | const { path, createError } = this; 24 | if (input === undefined) return true; 25 | let isValid = isArray(input) && input.length >= count; 26 | return isValid || createError({ path, message }); 27 | }); 28 | } 29 | 30 | /** 31 | * Validates that array length is less or equal to that 32 | * of the schema maximumItems property 33 | */ 34 | 35 | export function maximumItems( 36 | this: Yup.ArraySchema, 37 | count: number, 38 | message: string 39 | ): Yup.ArraySchema { 40 | return this.test("test-maximumItems", message, function (input: unknown[]) { 41 | const { path, createError } = this; 42 | if (input === undefined) return true; 43 | let isValid = isArray(input) && input.length <= count; 44 | return isValid || createError({ path, message }); 45 | }); 46 | } 47 | 48 | /** 49 | * Validates the `contains` schema has one or more items in the array 50 | * equates to the data type of the schema type property 51 | */ 52 | 53 | export function contains( 54 | this: Yup.ArraySchema, 55 | value: string, 56 | message: string 57 | ): Yup.ArraySchema { 58 | return this.test("test-contains", message, function (input: unknown[]) { 59 | const { path, createError } = this; 60 | let isValid = false; 61 | if (isArray(input)) { 62 | if (value === DataTypes.NUMBER) { 63 | isValid = input.some(isNumber); 64 | } 65 | if (value === DataTypes.INTEGER) { 66 | isValid = input.some(isInteger); 67 | } 68 | if (value === DataTypes.STRING) { 69 | isValid = input.some(isString); 70 | } 71 | if (value === DataTypes.BOOLEAN) { 72 | isValid = input.some(isBoolean); 73 | } 74 | if (value === DataTypes.OBJECT) { 75 | isValid = input.some(isPlainObject); 76 | } 77 | if (value === DataTypes.ARRAY) { 78 | isValid = input.some(isArray); 79 | } 80 | } 81 | return isValid || createError({ path, message }); 82 | }); 83 | } 84 | 85 | /** 86 | * Validates the items schema property as a tuple. The array is a collection 87 | * of items where each has a different schema and the ordinal index of 88 | * each item is meaningful 89 | */ 90 | 91 | export function tuple( 92 | this: Yup.ArraySchema, 93 | items: JSONSchemaDefinitionExtended[], 94 | message: string 95 | ): Yup.ArraySchema { 96 | return this.test("test-tuple", message, function (input: JSONSchemaDefinitionExtended[]) { 97 | const { path, createError } = this; 98 | const validator = validateItemsArray(items); 99 | const isValid = input.every(validator); 100 | return isValid || createError({ path, message }); 101 | }); 102 | } 103 | 104 | /** 105 | * Validates the given array values are unique 106 | */ 107 | 108 | export function uniqueItems( 109 | this: Yup.ArraySchema, 110 | enable: boolean, 111 | message: string 112 | ): Yup.ArraySchema { 113 | return this.test("test-unique-items", message, function (input: unknown[]) { 114 | const { path, createError } = this; 115 | // method will always be valid if uniqueItems property is set to false 116 | if (!enable) return true; 117 | if (!isArray(input)) return false; 118 | // empty arrays are always considered valid 119 | if (input.length === 0) return true; 120 | return isUnique(input) || createError({ path, message }); 121 | }); 122 | } 123 | -------------------------------------------------------------------------------- /src/yup/addMethods/index.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import { 3 | minimumItems, 4 | maximumItems, 5 | contains, 6 | tuple, 7 | uniqueItems 8 | } from "./array"; 9 | import { multipleOf } from "./number"; 10 | import { urlReference } from "./string"; 11 | import { constant, enums } from "./mixed"; 12 | // Array methods 13 | 14 | Yup.addMethod>( 15 | Yup.array, 16 | "minimumItems", 17 | minimumItems 18 | ); 19 | 20 | Yup.addMethod>( 21 | Yup.array, 22 | "maximumItems", 23 | maximumItems 24 | ); 25 | 26 | Yup.addMethod>(Yup.array, "contains", contains); 27 | 28 | Yup.addMethod>(Yup.array, "tuple", tuple); 29 | 30 | Yup.addMethod>(Yup.array, "uniqueItems", uniqueItems); 31 | 32 | // Number methods 33 | 34 | Yup.addMethod(Yup.number, "multipleOf", multipleOf); 35 | 36 | // String methods 37 | 38 | Yup.addMethod(Yup.string, "urlReference", urlReference); 39 | 40 | // Mixed methods 41 | 42 | Yup.addMethod(Yup.mixed, "constant", constant); 43 | 44 | Yup.addMethod(Yup.mixed, "enum", enums); 45 | 46 | export default Yup; 47 | -------------------------------------------------------------------------------- /src/yup/addMethods/mixed.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import isEqual from "lodash/isEqual"; 3 | import type { JSONSchema } from "../../schema"; 4 | import { isValueEnum } from "./utils"; 5 | 6 | /** 7 | * Validates whether input value matches const 8 | */ 9 | 10 | export function constant( 11 | this: Yup.MixedSchema, 12 | value: JSONSchema["const"], 13 | message: string 14 | ): Yup.MixedSchema { 15 | return this.test("test-constant", message, function( 16 | input: JSONSchema["const"] 17 | ) { 18 | const { path, createError } = this; 19 | return isEqual(value, input) || createError({ path, message }); 20 | }); 21 | } 22 | 23 | /** 24 | * Validates whetherinput value is an enum 25 | */ 26 | 27 | export function enums( 28 | this: Yup.MixedSchema, 29 | value: JSONSchema["enum"], 30 | message: string 31 | ): Yup.MixedSchema { 32 | return this.test("test-enum", message, function(input: JSONSchema["enum"]) { 33 | const { path, createError } = this; 34 | return isValueEnum(value, input) || createError({ path, message }); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /src/yup/addMethods/number.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | 3 | /** 4 | * Validates a given number is a multiple of the schema multipleOf value 5 | */ 6 | 7 | export function multipleOf( 8 | this: Yup.NumberSchema, 9 | value: number, 10 | message: string 11 | ): Yup.NumberSchema { 12 | return this.test("test-multipleOf", message, function(input) { 13 | const { path, createError } = this; 14 | return input % value === 0 || createError({ path, message }); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /src/yup/addMethods/string.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import isRelativeUrl from "is-relative-url"; 3 | 4 | /** 5 | * Validates that url format is of a relative url 6 | */ 7 | 8 | export function urlReference( 9 | this: Yup.StringSchema, 10 | message: string 11 | ): Yup.StringSchema { 12 | return this.test("test-urlReference", message, function(input) { 13 | const { path, createError } = this; 14 | return isRelativeUrl(input) || createError({ path, message }); 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /src/yup/addMethods/utils.ts: -------------------------------------------------------------------------------- 1 | import isArray from "lodash/isArray"; 2 | import isString from "lodash/isString"; 3 | import isPlainObject from "lodash/isPlainObject"; 4 | import isEqual from "lodash/isEqual"; 5 | import uniq from "lodash/uniq"; 6 | import stringifyObject from "stringify-object"; 7 | import { 8 | DataTypes, 9 | isSchemaObject, 10 | getItemsArrayItem, 11 | isTypeOfValue 12 | } from "../../schema"; 13 | import type { JSONSchema, JSONSchemaBasicType } from "../../schema"; 14 | 15 | /** 16 | * Checks if input is one of enum 17 | */ 18 | export const isValueEnum = ( 19 | enums: JSONSchema["enum"], 20 | value: unknown 21 | ): boolean => isArray(enums) && enums.some((item) => isEqual(item, value)); 22 | 23 | /** 24 | * Validates the value from the schema items property. In addition, 25 | * validates const and enums for string, number and integers 26 | */ 27 | 28 | export const validateItemsArray = 29 | (items: JSONSchemaBasicType[]) => 30 | (item: JSONSchemaBasicType, index: number): boolean => { 31 | const schemaItem = getItemsArrayItem(items, index); 32 | 33 | if (!isSchemaObject(schemaItem)) return false; 34 | 35 | const { type, enum: enums, const: consts } = schemaItem; 36 | 37 | // Items do not support multiple types 38 | if (!isString(type) || !isTypeOfValue[type](item)) return false; 39 | 40 | // enums and consts are only applicable to 41 | // types, numbers and integers 42 | if ( 43 | type === DataTypes.STRING || 44 | type === DataTypes.NUMBER || 45 | type === DataTypes.INTEGER || 46 | type === DataTypes.ARRAY 47 | ) { 48 | if (enums && !isValueEnum(enums, item)) return false; 49 | if ((consts || consts === null || consts === 0) && !isEqual(item, consts)) 50 | return false; 51 | } 52 | 53 | return true; 54 | }; 55 | 56 | /** 57 | * Check if each array values is unique 58 | */ 59 | 60 | export const isUnique = (arr: unknown[]): boolean => { 61 | /** Convert array values to string in order to do value comparisons*/ 62 | const normalizedArr = arr.map((item) => { 63 | if (isArray(item) || isPlainObject(item)) 64 | return stringifyObject(item).replace(/\s/g, ""); 65 | return item; 66 | }); 67 | return uniq(normalizedArr).length === normalizedArr.length; 68 | }; 69 | -------------------------------------------------------------------------------- /src/yup/builder/index.ts: -------------------------------------------------------------------------------- 1 | import type { JSONSchema, JSONSchemaDefinition } from "../../schema"; 2 | import has from "lodash/has"; 3 | import get from "lodash/get"; 4 | import omit from "lodash/omit"; 5 | import isPlainObject from "lodash/isPlainObject"; 6 | import Yup from "../addMethods/"; 7 | import { getProperties, isSchemaObject } from "../../schema/"; 8 | import createValidationSchema from "../schemas/"; 9 | import { getObjectHead } from "../utils"; 10 | 11 | /** 12 | * Iterate through each item in properties and generate a key value pair of yup schema 13 | */ 14 | 15 | export const buildProperties = ( 16 | properties: { 17 | [key: string]: JSONSchemaDefinition; 18 | }, 19 | jsonSchema: JSONSchema 20 | ): {} | { [key: string]: Yup.Lazy | Yup.MixedSchema } => { 21 | let schema = {}; 22 | 23 | for (let [key, value] of Object.entries(properties)) { 24 | if (!isSchemaObject(value)) { 25 | continue; 26 | } 27 | const { properties, type, items } = value; 28 | 29 | // If item is object type call this function again 30 | if (type === "object" && properties) { 31 | const objSchema = build(value); 32 | if (objSchema) { 33 | const ObjectSchema = createValidationSchema([key, value], jsonSchema); 34 | schema = { ...schema, [key]: ObjectSchema.concat(objSchema) }; 35 | } 36 | } else if ( 37 | type === "array" && 38 | isSchemaObject(items) && 39 | has(items, "properties") 40 | ) { 41 | // Structured to handle nested objects in schema. First an array with all the relevant validation rules need to be applied and then the subschemas will be concatenated. 42 | const ArraySchema = createValidationSchema( 43 | [key, omit(value, "items")], 44 | jsonSchema 45 | ); 46 | schema = { 47 | ...schema, 48 | [key]: ArraySchema.concat(Yup.array(build(items))) 49 | }; 50 | } else if (type === "array" && isSchemaObject(items)) { 51 | const ArraySchema = createValidationSchema( 52 | [key, omit(value, "items")], 53 | jsonSchema 54 | ); 55 | schema = { 56 | ...schema, 57 | [key]: ArraySchema.concat( 58 | Yup.array(createValidationSchema([key, items], jsonSchema)) 59 | ) 60 | }; 61 | } else { 62 | // Check if item has a then or else schema 63 | const condition = hasIfSchema(jsonSchema, key) 64 | ? createConditionalSchema(jsonSchema) 65 | : {}; 66 | // Check if item has if schema in allOf array 67 | const conditions = hasAllOfIfSchema(jsonSchema, key) 68 | ? jsonSchema.allOf?.reduce((all, schema) => { 69 | if (typeof schema === "boolean") { 70 | return all; 71 | } 72 | return { ...all, ...createConditionalSchema(schema) }; 73 | }, []) 74 | : []; 75 | const newSchema = createValidationSchema([key, value], jsonSchema); 76 | schema = { 77 | ...schema, 78 | [key]: key in schema ? schema[key].concat(newSchema) : newSchema, 79 | ...condition, 80 | ...conditions 81 | }; 82 | } 83 | } 84 | return schema; 85 | }; 86 | 87 | /** 88 | * Determine schema has a if schema 89 | */ 90 | 91 | const hasIfSchema = (jsonSchema: JSONSchema, key: string): boolean => { 92 | const { if: ifSchema } = jsonSchema; 93 | if (!isSchemaObject(ifSchema)) return false; 94 | const { properties } = ifSchema; 95 | return isPlainObject(properties) && has(properties, key); 96 | }; 97 | 98 | /** 99 | * Determine schema has at least one if schemas inside an allOf array 100 | */ 101 | 102 | const hasAllOfIfSchema = (jsonSchema: JSONSchema, key: string): boolean => { 103 | const { allOf } = jsonSchema; 104 | 105 | if (!allOf) { 106 | return false; 107 | } 108 | 109 | return allOf.some( 110 | (schema) => typeof schema !== "boolean" && hasIfSchema(schema, key) 111 | ); 112 | }; 113 | 114 | /** 115 | * High order function that takes json schema and property item 116 | * and generates a validation schema to validate the given value 117 | */ 118 | 119 | const isValidator = 120 | ([key, value]: [string, JSONSchema], jsonSchema: JSONSchema) => 121 | (val: unknown): boolean => { 122 | const conditionalSchema = createValidationSchema([key, value], jsonSchema); 123 | const result: boolean = conditionalSchema.isValidSync(val); 124 | return result; 125 | }; 126 | 127 | /** Build `is`, `then`, `otherwise` validation schema */ 128 | 129 | const createConditionalSchema = ( 130 | jsonSchema: JSONSchema 131 | ): false | { [key: string]: Yup.MixedSchema } => { 132 | const ifSchema = get(jsonSchema, "if"); 133 | if (!isSchemaObject(ifSchema)) return false; 134 | 135 | const { properties } = ifSchema; 136 | if (!properties) return false; 137 | 138 | const ifSchemaHead = getObjectHead(properties); 139 | if (!ifSchemaHead) return false; 140 | 141 | const [ifSchemaKey, ifSchemaValue] = ifSchemaHead; 142 | if (!isSchemaObject(ifSchemaValue)) return false; 143 | 144 | const thenSchema = get(jsonSchema, "then"); 145 | 146 | if (isSchemaObject(thenSchema)) { 147 | const elseSchema = get(jsonSchema, "else"); 148 | const isValid = isValidator([ifSchemaKey, ifSchemaValue], ifSchema); 149 | return createIsThenOtherwiseSchema( 150 | [ifSchemaKey, isValid], 151 | thenSchema, 152 | elseSchema 153 | ); 154 | } 155 | 156 | return false; 157 | }; 158 | 159 | /** `createIsThenOtherwiseSchemaItem` accepts an item from the "else" and "then" schemas and returns a yup schema for each item which will be used for the then or otherwise methods in when. */ 160 | 161 | const createIsThenOtherwiseSchemaItem = ( 162 | [key, value]: [string, NonNullable], 163 | required: JSONSchema["required"] 164 | ): 165 | | { 166 | [key: string]: Yup.Lazy | Yup.MixedSchema; 167 | } 168 | | false => { 169 | const item: JSONSchema = { 170 | properties: { [key]: { ...value } } 171 | }; 172 | if (required && required.includes(key)) { 173 | item.required = [key]; 174 | } 175 | if (!item.properties) return false; 176 | const thenSchemaData = buildProperties(item.properties, item); 177 | return thenSchemaData[key]; 178 | }; 179 | 180 | /** `createIsThenOtherwiseSchema` generates a yup when schema. */ 181 | 182 | const createIsThenOtherwiseSchema = ( 183 | [ifSchemaKey, callback]: [string, (val: unknown) => boolean], 184 | thenSchema: JSONSchema, 185 | elseSchema?: JSONSchemaDefinition 186 | ): false | { [key: string]: Yup.MixedSchema } => { 187 | if (!thenSchema.properties) return false; 188 | 189 | let thenKeys = Object.keys(thenSchema.properties); 190 | // Collect all else schema keys and deduct from list when there is a matching then schema key. The remaining else keys will then be handled seperately. 191 | let elseKeys = 192 | typeof elseSchema === "object" && elseSchema.properties 193 | ? Object.keys(elseSchema.properties) 194 | : []; 195 | 196 | const schema = {}; 197 | 198 | // Iterate through then schema and check for matching else schema keys and toggle between each rule pending if condition. 199 | 200 | for (const thenKey of thenKeys) { 201 | const thenIItem = thenSchema.properties[thenKey]; 202 | if (!isSchemaObject(thenIItem)) continue; 203 | 204 | let thenSchemaItem = createIsThenOtherwiseSchemaItem( 205 | [thenKey, thenIItem], 206 | thenSchema.required 207 | ); 208 | let matchingElseSchemaItem: 209 | | { [key: string]: Yup.MixedSchema | Yup.Lazy } 210 | | false = false; 211 | 212 | if ( 213 | isSchemaObject(elseSchema) && 214 | elseSchema.properties && 215 | thenKey in elseSchema.properties 216 | ) { 217 | matchingElseSchemaItem = createIsThenOtherwiseSchemaItem( 218 | [thenKey, elseSchema.properties[thenKey] as JSONSchema], 219 | elseSchema.required 220 | ); 221 | // Remove matching else schema keys from list so remaining else schema keys can be handled separately. 222 | if (elseKeys.length) elseKeys.splice(elseKeys.indexOf(thenKey), 1); 223 | } 224 | 225 | schema[thenKey] = { 226 | is: callback, 227 | then: thenSchemaItem, 228 | ...(matchingElseSchemaItem ? { otherwise: matchingElseSchemaItem } : {}) 229 | }; 230 | } 231 | 232 | // Generate schemas for else keys that do not match the "then" schema. 233 | if (elseKeys.length) { 234 | elseKeys.forEach((k) => { 235 | if ( 236 | isSchemaObject(elseSchema) && 237 | elseSchema.properties && 238 | k in elseSchema.properties 239 | ) { 240 | const elseSchemaItem = createIsThenOtherwiseSchemaItem( 241 | [k, elseSchema.properties[k] as JSONSchema], 242 | elseSchema.required 243 | ); 244 | if (elseSchemaItem) { 245 | schema[k] = { 246 | // Hardcode false as else schema's should handle "unhappy" path. 247 | is: (schema: unknown) => callback(schema) === false, 248 | then: elseSchemaItem 249 | }; 250 | } 251 | } 252 | }); 253 | } 254 | 255 | // Generate Yup.when schemas from the schema object. 256 | const conditionalSchemas = Object.keys(schema).reduce((accum, next) => { 257 | accum[next] = Yup.mixed().when(ifSchemaKey, { ...schema[next] }); 258 | return accum; 259 | }, {}); 260 | 261 | // Create conditional schema for if schema's within else schema. 262 | let nestedConditionalSchemas = {}; 263 | if (isSchemaObject(elseSchema) && get(elseSchema, "if")) { 264 | nestedConditionalSchemas = { 265 | ...nestedConditionalSchemas, 266 | ...createConditionalSchema(elseSchema) 267 | }; 268 | } 269 | 270 | return { ...conditionalSchemas, ...nestedConditionalSchemas }; 271 | }; 272 | 273 | /** 274 | * Iterates through a valid JSON Schema and generates yup field level 275 | * and object level schema 276 | */ 277 | 278 | export const build = ( 279 | jsonSchema: JSONSchema 280 | ): Yup.ObjectSchema | undefined => { 281 | const properties = getProperties(jsonSchema); 282 | 283 | if (!properties) return properties; 284 | 285 | let Schema = buildProperties(properties, jsonSchema); 286 | return Yup.object().shape(Schema); 287 | }; 288 | 289 | export default build; 290 | -------------------------------------------------------------------------------- /src/yup/config/index.ts: -------------------------------------------------------------------------------- 1 | import get from "lodash/get"; 2 | import type { NodeTypes } from "../../schema" 3 | import type { ConfigErrors, Config, CustomErrorMsg, CustomErrorMsgParam } from "../types"; 4 | import { isConfigError } from "../types"; 5 | import { joinPath } from "../utils"; 6 | 7 | let config: Config = {}; 8 | 9 | /** Store configuration options */ 10 | 11 | export const setConfiguration = (options: Config) => { 12 | config = { ...options }; 13 | }; 14 | 15 | /** Retrieve configuration options */ 16 | 17 | export const getConfiguration = (): Config => config; 18 | 19 | /** Retrieve all errors from configuration */ 20 | 21 | export const getErrors = (): ConfigErrors | undefined => config.errors; 22 | 23 | /** Retrieve specific error from configuration */ 24 | 25 | export const getError = (path: string | false): string | CustomErrorMsg | false => { 26 | const pathArray = path && path.split("."); 27 | if (!pathArray) return false; 28 | const errors = getErrors(); 29 | return isConfigError(errors) && get(errors, pathArray); 30 | }; 31 | 32 | /** Returns 'custom' or 'default' error message */ 33 | export const getErrorMessage = (description: string | false | undefined, type: NodeTypes, params: CustomErrorMsgParam) => { 34 | let customErrorMessage = description 35 | ? getError(joinPath(description, type)) 36 | : undefined; 37 | 38 | if (typeof customErrorMessage === "undefined") customErrorMessage = getError(joinPath("defaults", type)) 39 | if (typeof customErrorMessage === "function") return customErrorMessage(params); 40 | return customErrorMessage 41 | } -------------------------------------------------------------------------------- /src/yup/index.ts: -------------------------------------------------------------------------------- 1 | import builder from "./builder"; 2 | export { setConfiguration } from "./config"; 3 | export * from "./types"; 4 | 5 | export default builder; 6 | -------------------------------------------------------------------------------- /src/yup/schemas/array/array.schema.ts: -------------------------------------------------------------------------------- 1 | import isNumber from "lodash/isNumber"; 2 | import isString from "lodash/isString"; 3 | import isArray from "lodash/isArray"; 4 | import capitalize from "lodash/capitalize"; 5 | import { DataTypes, isItemsArray, SchemaKeywords } from "../../../schema"; 6 | import type { JSONSchema } from "../../../schema" 7 | import Yup from "../../addMethods"; 8 | import { createRequiredSchema } from "../required"; 9 | import { createConstantSchema } from "../constant"; 10 | import { createEnumerableSchema } from "../enumerables"; 11 | import type { SchemaItem } from "../../types"; 12 | import { getErrorMessage } from "../../config/"; 13 | 14 | /** 15 | * Initializes a yup array schema derived from a json boolean schema 16 | */ 17 | 18 | const createArraySchema = ( 19 | [key, value]: SchemaItem, 20 | jsonSchema: JSONSchema 21 | ): Yup.ArraySchema => { 22 | const { 23 | description, 24 | default: defaults, 25 | minItems, 26 | maxItems, 27 | items, 28 | contains, 29 | uniqueItems, 30 | title 31 | } = value; 32 | 33 | const label = title || capitalize(key); 34 | 35 | const defaultMessage = 36 | getErrorMessage(description, DataTypes.ARRAY, [ key, { title }]) || 37 | `${label} is not of type array`; 38 | 39 | let Schema = Yup.array().typeError(defaultMessage); 40 | 41 | if (isArray(defaults)) { 42 | Schema = Schema.concat(Schema.default(defaults)); 43 | } 44 | 45 | /** Set required if ID is in required schema */ 46 | Schema = createRequiredSchema(Schema, jsonSchema, [key, value]); 47 | 48 | // Items key expects all values to be of same type 49 | // Contains key expects one of the values to be of a type 50 | // These rules will conflict with each other so only 51 | // allow one or the other 52 | 53 | if (contains) { 54 | const { type } = contains as JSONSchema; 55 | 56 | const message = 57 | getErrorMessage(description, SchemaKeywords.CONTAINS, [ 58 | key, 59 | { title, contains: type?.toString() } 60 | ]) || capitalize(`${key} must at least contain one item of type ${type}`); 61 | 62 | // `contains` is a custom yup method. See /yup/addons/index.ts 63 | // for implementation 64 | 65 | Schema = isString(type) 66 | ? Schema.concat(Schema.contains(type, message)) 67 | : Schema; 68 | } else { 69 | if (isItemsArray(items)) { 70 | const message = 71 | getErrorMessage(description, SchemaKeywords.TUPLE, [key, { title }]) || 72 | capitalize(`${key} must be of same type`); 73 | 74 | // `tuple` is a custom yup method. See /yup/addons/index.ts 75 | // for implementation 76 | 77 | Schema = Schema.concat(Schema.tuple(items, message)); 78 | } 79 | } 80 | 81 | if (isNumber(minItems)) { 82 | const message = 83 | getErrorMessage(description, SchemaKeywords.MINIMUM_ITEMS, [ 84 | key, 85 | { title, minItems } 86 | ]) || capitalize(`${key} requires a minimum of ${minItems} items`); 87 | 88 | // `minimumItems` is a custom yup method. See /yup/addons/index.ts 89 | // for implementation 90 | 91 | Schema = Schema.concat(Schema.minimumItems(minItems, message)); 92 | } 93 | 94 | if (isNumber(maxItems)) { 95 | const message = 96 | getErrorMessage(description, SchemaKeywords.MAXIMUM_ITEMS, [ 97 | key, 98 | { title, maxItems } 99 | ]) || capitalize(`${key} cannot exceed a maximum of ${maxItems} items`); 100 | 101 | // `maximumItems` is a custom yup method. See /yup/addons/index.ts 102 | // for implementation 103 | 104 | Schema = Schema.concat(Schema.maximumItems(maxItems, message)); 105 | } 106 | 107 | /** Determine if schema matches constant */ 108 | Schema = createConstantSchema(Schema, [key, value]); 109 | 110 | /** Determine if schema matches any enums */ 111 | Schema = createEnumerableSchema(Schema, [key, value]); 112 | 113 | if (uniqueItems) { 114 | const message = 115 | getErrorMessage(description, SchemaKeywords.UNIQUE_ITEMS, [ 116 | key, 117 | { title, uniqueItems } 118 | ]) || capitalize(`${key} values are not unique`); 119 | 120 | // `uniqueItems` is a custom yup method. See /yup/addons/index.ts 121 | // for implementation 122 | 123 | Schema = Schema.concat(Schema.uniqueItems(uniqueItems, message)); 124 | } 125 | 126 | return Schema; 127 | }; 128 | 129 | export default createArraySchema; 130 | -------------------------------------------------------------------------------- /src/yup/schemas/array/index.ts: -------------------------------------------------------------------------------- 1 | import createArraySchema from "./array.schema"; 2 | 3 | export default createArraySchema; 4 | -------------------------------------------------------------------------------- /src/yup/schemas/boolean/boolean.schema.ts: -------------------------------------------------------------------------------- 1 | import isBoolean from "lodash/isBoolean"; 2 | import capitalize from "lodash/capitalize"; 3 | import { DataTypes } from "../../../schema"; 4 | import type { JSONSchema } from "../../../schema" 5 | import Yup from "../../addMethods"; 6 | import type { SchemaItem } from "../../types"; 7 | import { getErrorMessage } from "../../config/"; 8 | import { createRequiredSchema } from "../required"; 9 | import { createConstantSchema } from "../constant"; 10 | 11 | /** 12 | * Initializes a yup boolean schema derived from a json boolean schema 13 | */ 14 | 15 | const createBooleanSchema = ( 16 | [key, value]: SchemaItem, 17 | jsonSchema: JSONSchema 18 | ): Yup.BooleanSchema => { 19 | const { 20 | description, 21 | default: defaults, 22 | title 23 | } = value; 24 | 25 | const label = title || capitalize(key); 26 | 27 | const defaultMessage = getErrorMessage(description, DataTypes.BOOLEAN, [key, { title }]) 28 | || `${label} is not of type boolean`; 29 | 30 | let Schema = Yup.boolean().typeError(defaultMessage); 31 | 32 | if (isBoolean(defaults)) { 33 | Schema = Schema.concat(Schema.default(defaults)); 34 | } 35 | 36 | /** Determine if schema matches constant */ 37 | Schema = createConstantSchema(Schema, [key, value]); 38 | 39 | /** Set required if ID is in required schema */ 40 | Schema = createRequiredSchema(Schema, jsonSchema, [key, value]); 41 | 42 | return Schema; 43 | }; 44 | 45 | export default createBooleanSchema; 46 | -------------------------------------------------------------------------------- /src/yup/schemas/boolean/index.ts: -------------------------------------------------------------------------------- 1 | import createBooleanSchema from "./boolean.schema"; 2 | 3 | export default createBooleanSchema; 4 | -------------------------------------------------------------------------------- /src/yup/schemas/composition/index.ts: -------------------------------------------------------------------------------- 1 | import { capitalize } from "lodash"; 2 | import { CompositSchemaTypes } from "../../../schema" 3 | import type { AnyOfSchema, AllOfSchema, JSONSchema, OneOfSchema , NotSchema } from "../../../schema" 4 | import createValidationSchema from ".."; 5 | import Yup from "../../addMethods"; 6 | import { getErrorMessage } from "../../config"; 7 | 8 | /** 9 | * To validate against anyOf, the given data must be valid against any (one or more) of the given subschemas. 10 | */ 11 | export const createAnyOfSchema = ( 12 | [key, value]: [string, AnyOfSchema], 13 | jsonSchema: JSONSchema 14 | ): Yup.MixedSchema => { 15 | const label = value.title || capitalize(key); 16 | const message = getErrorMessage(value.description, CompositSchemaTypes.ANYOF, [key, { title: value.title }]) || `${label} does not match alternatives`; 17 | const schemas = value.anyOf.map((val) => 18 | createValidationSchema([key, val as JSONSchema], jsonSchema) 19 | ); 20 | 21 | return Yup.mixed().test("one-of-schema", message, function (current) { 22 | return schemas.some((s) => s.isValidSync(current, this.options)); 23 | }); 24 | }; 25 | 26 | /** 27 | * To validate against allOf, the given data must be valid against all of the given subschemas. 28 | */ 29 | export const createAllOfSchema = ( 30 | [key, value]: [string, AllOfSchema], 31 | jsonSchema: JSONSchema 32 | ): Yup.MixedSchema => { 33 | const label = value.title || capitalize(key); 34 | const message = getErrorMessage(value.description, CompositSchemaTypes.ALLOF, [key, { title: value.title }]) || `${label} does not match all alternatives`; 35 | const schemas = value.allOf 36 | .filter((el) => typeof el !== "boolean" && el.type) 37 | .map((val, i) => 38 | createValidationSchema([`${key}[${i}]`, val as JSONSchema], jsonSchema) 39 | ); 40 | return Yup.mixed().test("all-of-schema", message, function (current) { 41 | return schemas.every((s) => s.isValidSync(current, this.options)); 42 | }); 43 | }; 44 | 45 | /** 46 | * To validate against oneOf, the given data must be valid against exactly one of the given subschemas. 47 | */ 48 | export const createOneOfSchema = ( 49 | [key, value]: [string, OneOfSchema], 50 | jsonSchema: JSONSchema 51 | ): Yup.MixedSchema => { 52 | const label = value.title || capitalize(key); 53 | const message = getErrorMessage(value.description, CompositSchemaTypes.ONEOF, [key, { title: value.title }]) || `${label} does not match one alternative`; 54 | const schemas = value.oneOf.map((val, i) => 55 | createValidationSchema([`${key}[${i}]`, val as JSONSchema], jsonSchema) 56 | ); 57 | 58 | return Yup.mixed().test("one-of-schema", message, function (current) { 59 | return ( 60 | schemas.filter((s) => s.isValidSync(current, this.options)).length === 1 61 | ); 62 | }); 63 | }; 64 | 65 | /** 66 | * The not keyword declares that an instance validates if it doesn’t validate against the given subschema. 67 | */ 68 | export const createNotSchema = ( 69 | [key, value]: [string, NotSchema], 70 | jsonSchema: JSONSchema 71 | ): Yup.MixedSchema => { 72 | const label = value.title || capitalize(key); 73 | const message = getErrorMessage(value.description, CompositSchemaTypes.NOT, [key, { title: value.title }]) || `${label} matches alternatives`; 74 | const schema = createValidationSchema( 75 | [key, value.not as JSONSchema], 76 | jsonSchema 77 | ); 78 | 79 | return Yup.mixed().test("not-schema", message, function (current) { 80 | return schema.isValidSync(current, this.options) === false; 81 | }); 82 | }; 83 | -------------------------------------------------------------------------------- /src/yup/schemas/constant.ts: -------------------------------------------------------------------------------- 1 | import capitalize from "lodash/capitalize"; 2 | import { SchemaKeywords } from "../../schema"; 3 | import Yup from "../addMethods"; 4 | import type { SchemaItem } from "../types"; 5 | import { getErrorMessage } from "../config"; 6 | 7 | /** 8 | * Add constant yup method when schema constant is declared 9 | */ 10 | 11 | export const createConstantSchema = >( 12 | Schema: T, 13 | [key, value]: SchemaItem 14 | ): T => { 15 | const { const: consts, description, title } = value; 16 | 17 | if (consts || consts === null || consts === 0) { 18 | const message = getErrorMessage(description, SchemaKeywords.CONST, [ key, { const: consts?.toString(), title} ]) 19 | || capitalize(`${key} does not match constant`); 20 | 21 | Schema = Schema.concat(Schema.constant(consts, message)); 22 | } 23 | 24 | return Schema; 25 | }; 26 | -------------------------------------------------------------------------------- /src/yup/schemas/enumerables.ts: -------------------------------------------------------------------------------- 1 | import isArray from "lodash/isArray"; 2 | import capitalize from "lodash/capitalize"; 3 | import { SchemaKeywords } from "../../schema"; 4 | import Yup from "../addMethods"; 5 | import type { SchemaItem } from "../types"; 6 | import { getErrorMessage } from "../config"; 7 | 8 | /** 9 | * Add enum yup method when schema enum is declared 10 | */ 11 | 12 | export const createEnumerableSchema = >( 13 | Schema: T, 14 | [key, value]: SchemaItem 15 | ): T => { 16 | const { enum: enums, description, title } = value; 17 | if (isArray(enums)) { 18 | const message = getErrorMessage(description, SchemaKeywords.ENUM, [ key, { enum: enums.join(","), title } ]) 19 | || capitalize(`${key} does not match any of the enumerables`); 20 | 21 | Schema = Schema.concat(Schema.enum(enums, message)); 22 | } 23 | 24 | return Schema; 25 | }; 26 | -------------------------------------------------------------------------------- /src/yup/schemas/index.ts: -------------------------------------------------------------------------------- 1 | import isArray from "lodash/isArray"; 2 | import isString from "lodash/isString"; 3 | import get from "lodash/get"; 4 | import has from "lodash/has"; 5 | import type { JSONSchema, JSONSchemaTypeName } from "../../schema"; 6 | import { 7 | DataTypes, 8 | getCompositionType, 9 | getPropertyType, 10 | hasAllOf, 11 | hasAnyOf, 12 | hasNot, 13 | hasOneOf, 14 | isTypeOfValue 15 | } from "../../schema"; 16 | import Yup from "../addMethods"; 17 | import type { SchemaItem } from "../types"; 18 | import createArraySchema from "./array"; 19 | import createBooleanSchema from "./boolean"; 20 | import createIntegerSchema from "./integer"; 21 | import createObjectSchema from "./object"; 22 | import createNullSchema from "./null"; 23 | import createNumberSchema from "./number"; 24 | import createStringSchema from "./string"; 25 | import { 26 | createAllOfSchema, 27 | createAnyOfSchema, 28 | createNotSchema, 29 | createOneOfSchema 30 | } from "./composition"; 31 | 32 | /** 33 | * Validates the input data type against the schema type and returns 34 | * the current type in order to generate the schema 35 | */ 36 | 37 | const getTypeOfValue = ( 38 | types: JSONSchemaTypeName[], 39 | value: unknown 40 | ): JSONSchemaTypeName => { 41 | const filteredType: JSONSchemaTypeName[] = types.filter( 42 | (item) => has(isTypeOfValue, item) && isTypeOfValue[item](value) 43 | ); 44 | const index = types.indexOf(filteredType[0]); 45 | return types[index]; 46 | }; 47 | 48 | /** 49 | * Determine which validation method to use by data type 50 | */ 51 | 52 | const getValidationSchema = ( 53 | [key, value]: SchemaItem, 54 | jsonSchema: JSONSchema 55 | ): Yup.MixedSchema => { 56 | if (hasAnyOf(value)) { 57 | return createAnyOfSchema([key, value], jsonSchema); 58 | } 59 | 60 | if (hasAllOf(value)) { 61 | return createAllOfSchema([key, value], jsonSchema); 62 | } 63 | 64 | if (hasOneOf(value)) { 65 | return createOneOfSchema([key, value], jsonSchema); 66 | } 67 | 68 | if (hasNot(value)) { 69 | return createNotSchema([key, value], jsonSchema); 70 | } 71 | 72 | const { type } = value; 73 | 74 | const schemaMap = { 75 | [DataTypes.STRING]: createStringSchema, 76 | [DataTypes.NUMBER]: createNumberSchema, 77 | [DataTypes.INTEGER]: createIntegerSchema, 78 | [DataTypes.ARRAY]: createArraySchema, 79 | [DataTypes.BOOLEAN]: createBooleanSchema, 80 | [DataTypes.NULL]: createNullSchema, 81 | [DataTypes.OBJECT]: createObjectSchema 82 | }; 83 | 84 | return schemaMap[type as JSONSchemaTypeName]([key, value], jsonSchema); 85 | }; 86 | 87 | /** 88 | * Initialises a Yup lazy instance that will determine which 89 | * schema to use based on the field value 90 | */ 91 | 92 | const getLazyValidationSchema = ( 93 | [key, value]: SchemaItem, 94 | jsonSchema: JSONSchema 95 | ): Yup.Lazy => 96 | Yup.lazy((inputValue) => { 97 | const type = get(value, "type") as JSONSchemaTypeName[]; 98 | // include a check for undefined as Formik 2.1.4 99 | // coeerces empty strings to undefined 100 | const valueType = type.includes("null") 101 | ? inputValue === "" || inputValue === undefined 102 | ? null 103 | : inputValue 104 | : inputValue; 105 | const typeOfValue = getTypeOfValue(type, valueType) || null; 106 | const newItem: SchemaItem = [key, { ...value, type: typeOfValue }]; 107 | return getValidationSchema(newItem, jsonSchema); 108 | }); 109 | 110 | /** 111 | * Generate yup validation schema from properties within 112 | * the valid schema 113 | */ 114 | 115 | const createValidationSchema = ( 116 | [key, value]: SchemaItem, 117 | jsonSchema: JSONSchema 118 | ): Yup.Lazy | Yup.MixedSchema => { 119 | const type = getPropertyType(value) || getCompositionType(value); 120 | if (isArray(type)) { 121 | return getLazyValidationSchema([key, value], jsonSchema); 122 | } 123 | if (isString(type)) { 124 | return getValidationSchema([key, value], jsonSchema); 125 | } 126 | throw new Error("Type key is missing"); 127 | }; 128 | 129 | export default createValidationSchema; 130 | -------------------------------------------------------------------------------- /src/yup/schemas/integer/index.ts: -------------------------------------------------------------------------------- 1 | import createIntegerSchema from "./integer.schema"; 2 | 3 | export default createIntegerSchema; 4 | -------------------------------------------------------------------------------- /src/yup/schemas/integer/integer.schema.ts: -------------------------------------------------------------------------------- 1 | import capitalize from "lodash/capitalize"; 2 | import { DataTypes } from "../../../schema"; 3 | import type { JSONSchema } from "../../../schema" 4 | import Yup from "../../addMethods"; 5 | import type { SchemaItem } from "../../types"; 6 | import { getErrorMessage } from "../../config/"; 7 | import { createBaseNumberSchema } from "../number"; 8 | 9 | /** 10 | * Initializes a yup integer schema derived from a json humber schema 11 | */ 12 | 13 | const createIntegerSchema = ( 14 | [key, value]: SchemaItem, 15 | jsonSchema: JSONSchema 16 | ): Yup.NumberSchema => { 17 | const { 18 | description, 19 | title 20 | } = value; 21 | 22 | const label = title || capitalize(key); 23 | const defaultMessage = getErrorMessage(description, DataTypes.INTEGER, [key, { title }]) 24 | || `${label} is not of type integer`; 25 | 26 | return createBaseNumberSchema( 27 | Yup.number().typeError(defaultMessage).integer().strict(true), 28 | [key, value], 29 | jsonSchema 30 | ); 31 | }; 32 | 33 | export default createIntegerSchema; 34 | -------------------------------------------------------------------------------- /src/yup/schemas/null/index.ts: -------------------------------------------------------------------------------- 1 | import createNullSchema from "./null.schema"; 2 | 3 | export default createNullSchema; 4 | -------------------------------------------------------------------------------- /src/yup/schemas/null/null.schema.ts: -------------------------------------------------------------------------------- 1 | import Yup from "../../addMethods"; 2 | 3 | /** 4 | * Initializes a yup null schema. Allows fields to be optional. 5 | */ 6 | 7 | const createNullSchema = (): Yup.MixedSchema => { 8 | // Mark the schema as not required. Passing undefined 9 | // as value will not fail validation. 10 | return Yup.mixed().notRequired(); 11 | }; 12 | 13 | export default createNullSchema; 14 | -------------------------------------------------------------------------------- /src/yup/schemas/number/index.ts: -------------------------------------------------------------------------------- 1 | import createNumberSchema, { createBaseNumberSchema } from "./number.schema"; 2 | 3 | export default createNumberSchema; 4 | export { createBaseNumberSchema }; 5 | -------------------------------------------------------------------------------- /src/yup/schemas/number/number.schema.ts: -------------------------------------------------------------------------------- 1 | import isNumber from "lodash/isNumber"; 2 | import capitalize from "lodash/capitalize"; 3 | import { DataTypes, SchemaKeywords } from "../../../schema"; 4 | import type { JSONSchema } from "../../../schema" 5 | import type { SchemaItem } from "../../types"; 6 | import { getErrorMessage } from "../../config"; 7 | import Yup from "../../addMethods"; 8 | import { createRequiredSchema } from "../required"; 9 | import { createConstantSchema } from "../constant"; 10 | import { createEnumerableSchema } from "../enumerables"; 11 | 12 | /** 13 | * Initializes a yup number schema derived from a json number schema 14 | */ 15 | 16 | const createNumberSchema = ( 17 | [key, value]: SchemaItem, 18 | jsonSchema: JSONSchema 19 | ): Yup.NumberSchema => { 20 | const { description, title } = value; 21 | 22 | const label = title || capitalize(key); 23 | 24 | const defaultMessage = 25 | getErrorMessage(description, DataTypes.NUMBER, [key, { title }]) || 26 | `${label} is not of type number`; 27 | 28 | return createBaseNumberSchema( 29 | Yup.number().typeError(defaultMessage), 30 | [key, value], 31 | jsonSchema 32 | ); 33 | }; 34 | /** 35 | * Generates a yup number schema instance that is used for both number and integer schema 36 | */ 37 | 38 | export const createBaseNumberSchema = ( 39 | Schema: Yup.NumberSchema, 40 | [key, value]: SchemaItem, 41 | jsonSchema: JSONSchema 42 | ): Yup.NumberSchema => { 43 | const { 44 | description, 45 | default: defaults, 46 | minimum, 47 | maximum, 48 | exclusiveMaximum, 49 | exclusiveMinimum, 50 | multipleOf, 51 | title 52 | } = value; 53 | 54 | const label = title || capitalize(key); 55 | 56 | const isMinNumber = isNumber(minimum); 57 | const isMaxNumber = isNumber(maximum); 58 | const isExclusiveMaxNumber = isNumber(exclusiveMaximum); 59 | const isExclusiveMinNumber = isNumber(exclusiveMinimum); 60 | 61 | if (isNumber(defaults)) { 62 | Schema = Schema.concat(Schema.default(defaults)); 63 | } 64 | 65 | if (isExclusiveMinNumber && isMinNumber) { 66 | throw new Error( 67 | "Minimum and exclusive minimum keys can not be used together" 68 | ); 69 | } 70 | 71 | if (isExclusiveMaxNumber && isMaxNumber) { 72 | throw new Error( 73 | "Maximum and exclusive maximum keys can not be used together" 74 | ); 75 | } 76 | 77 | // Minimum value is inclusive 78 | if (isMinNumber) { 79 | const message = 80 | getErrorMessage(description, SchemaKeywords.MINIMUM, [ 81 | key, 82 | { title, minimum } 83 | ]) || capitalize(`${label} requires a minimum value of ${minimum}`); 84 | 85 | Schema = Schema.concat(Schema.min(minimum as number, message)); 86 | } 87 | 88 | if (isExclusiveMinNumber) { 89 | const message = 90 | getErrorMessage(description, SchemaKeywords.EXCLUSIVE_MINIMUM, [ 91 | key, 92 | { title, exclusiveMinimum } 93 | ]) || 94 | capitalize( 95 | `${label} requires a exclusive minimum value of ${exclusiveMinimum}` 96 | ); 97 | 98 | Schema = Schema.concat( 99 | Schema.moreThan((exclusiveMinimum as number), message) 100 | ); 101 | } 102 | 103 | // Maximum value is inclusive 104 | if (isMaxNumber) { 105 | const message = 106 | getErrorMessage(description, SchemaKeywords.MAXIMUM, [ 107 | key, 108 | { title, maximum } 109 | ]) || capitalize(`${label} cannot exceed a maximum value of ${maximum}`); 110 | 111 | Schema = Schema.concat(Schema.max(maximum as number, message)); 112 | } 113 | 114 | if (isExclusiveMaxNumber) { 115 | const message = 116 | getErrorMessage(description, SchemaKeywords.EXCLUSIVE_MAXIMUM, [ 117 | key, 118 | { title, exclusiveMaximum } 119 | ]) || 120 | capitalize( 121 | `${label} cannot exceed a exclusive maximum value of ${exclusiveMaximum}` 122 | ); 123 | 124 | Schema = Schema.concat( 125 | Schema.lessThan((exclusiveMaximum as number), message) 126 | ); 127 | } 128 | 129 | if (multipleOf) { 130 | const message = 131 | getErrorMessage(description, SchemaKeywords.MULTIPLE_OF, [ 132 | key, 133 | { title, multipleOf } 134 | ]) || capitalize(`${label} requires a multiple of ${multipleOf}`); 135 | 136 | // `multipleOf` is a custom yup method. See /yup/addons/index.ts 137 | // for implementation 138 | Schema = Schema.concat(Schema.multipleOf(multipleOf, message)); 139 | } 140 | 141 | /** Determine if schema matches constant */ 142 | Schema = createConstantSchema(Schema, [key, value]); 143 | 144 | /** Determine if schema matches any enums */ 145 | Schema = createEnumerableSchema(Schema, [key, value]); 146 | 147 | /** Set required if ID is in required schema */ 148 | Schema = createRequiredSchema(Schema, jsonSchema, [key, value]); 149 | 150 | return Schema; 151 | }; 152 | 153 | export default createNumberSchema; 154 | -------------------------------------------------------------------------------- /src/yup/schemas/object/index.ts: -------------------------------------------------------------------------------- 1 | import createObjectSchema from "./object.schema"; 2 | 3 | export default createObjectSchema; 4 | -------------------------------------------------------------------------------- /src/yup/schemas/object/object.schema.ts: -------------------------------------------------------------------------------- 1 | import capitalize from "lodash/capitalize"; 2 | import { DataTypes } from "../../../schema"; 3 | import type { JSONSchema } from "../../../schema" 4 | import type { SchemaItem } from "../../types"; 5 | import Yup from "../../addMethods"; 6 | import { createRequiredSchema } from "../required"; 7 | import { getErrorMessage } from "../../config/"; 8 | import { buildProperties } from "../../builder"; 9 | 10 | /** 11 | * Initializes a yup object schema derived from a json object schema 12 | */ 13 | 14 | const createObjectSchema = ( 15 | [key, value]: SchemaItem, 16 | jsonSchema: JSONSchema): Yup.ObjectSchema => { 17 | const { 18 | description, 19 | title 20 | } = value; 21 | 22 | const label = title || capitalize(key); 23 | 24 | const defaultMessage = getErrorMessage(description, DataTypes.OBJECT, [key, { title }]) 25 | || capitalize(`${label} is not of type object`); 26 | 27 | // Seperate compositional schemas from standard schemas. 28 | // Standard schemas return Object schemas, compositional schemas return mixed or lazy schemas. 29 | // Lazy schemas can not be concatenated which will throw an error when traversing a nested object. 30 | const schm = JSON.stringify(jsonSchema.properties) 31 | const isComposition = schm.indexOf("anyOf") > -1 || schm.indexOf("oneOf") > -1 32 | 33 | let Schema: Yup.ObjectSchema 34 | if (isComposition) { 35 | let shape = value.properties && buildProperties(value.properties, jsonSchema); 36 | (value.required ?? []).forEach(requiredField => { 37 | if (shape !== undefined) { 38 | shape[requiredField] = createRequiredSchema(shape[requiredField], value, [requiredField, value]) 39 | } 40 | }); 41 | Schema = Yup.object(shape).typeError(defaultMessage); 42 | } else { 43 | Schema = Yup.object().typeError(defaultMessage); 44 | } 45 | 46 | /** Set required if ID is in required schema */ 47 | Schema = createRequiredSchema(Schema, jsonSchema, [key, value]); 48 | 49 | return Schema; 50 | }; 51 | 52 | export default createObjectSchema; 53 | -------------------------------------------------------------------------------- /src/yup/schemas/required.ts: -------------------------------------------------------------------------------- 1 | import capitalize from "lodash/capitalize"; 2 | import type { JSONSchema } from "../../schema"; 3 | import { isRequiredField, SchemaKeywords } from "../../schema"; 4 | import Yup from "../addMethods"; 5 | import type { SchemaItem } from "../types"; 6 | import { getErrorMessage } from "../config"; 7 | 8 | /** 9 | * Add required schema should subschema is required 10 | */ 11 | 12 | export const createRequiredSchema = >( 13 | Schema: T, 14 | jsonSchema: JSONSchema, 15 | [key, value]: SchemaItem 16 | ): T => { 17 | if (!isRequiredField(jsonSchema, key)) return Schema; 18 | 19 | const { description, title } = value; 20 | const label = title || capitalize(key); 21 | const required = jsonSchema.type === "object" ? jsonSchema.required : value.required 22 | const message = getErrorMessage(description, SchemaKeywords.REQUIRED, [ key, { title, required: required?.join(",") } ]) 23 | || `${label} is required`; 24 | 25 | return Schema.concat(Schema.required(message)); 26 | }; 27 | -------------------------------------------------------------------------------- /src/yup/schemas/string/index.ts: -------------------------------------------------------------------------------- 1 | import createStringSchema from "./string.schema"; 2 | export * from "./string.constants"; 3 | 4 | export default createStringSchema; 5 | -------------------------------------------------------------------------------- /src/yup/schemas/string/string.constants.ts: -------------------------------------------------------------------------------- 1 | /** Regex to test for international email regex. source: https://awik.io/international-email-address-validation-javascript/ */ 2 | 3 | export const INTERNATIONAL_EMAIL_REGEX = /^(?!\.)((?!.*\.{2})[a-zA-Z0-9\u0080-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u0300-\u036F\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F\u0D80-\u0DFF\u0E00-\u0E7F\u0E80-\u0EFF\u0F00-\u0FFF\u1000-\u109F\u10A0-\u10FF\u1100-\u11FF\u1200-\u137F\u1380-\u139F\u13A0-\u13FF\u1400-\u167F\u1680-\u169F\u16A0-\u16FF\u1700-\u171F\u1720-\u173F\u1740-\u175F\u1760-\u177F\u1780-\u17FF\u1800-\u18AF\u1900-\u194F\u1950-\u197F\u1980-\u19DF\u19E0-\u19FF\u1A00-\u1A1F\u1B00-\u1B7F\u1D00-\u1D7F\u1D80-\u1DBF\u1DC0-\u1DFF\u1E00-\u1EFF\u1F00-\u1FFFu20D0-\u20FF\u2100-\u214F\u2C00-\u2C5F\u2C60-\u2C7F\u2C80-\u2CFF\u2D00-\u2D2F\u2D30-\u2D7F\u2D80-\u2DDF\u2F00-\u2FDF\u2FF0-\u2FFF\u3040-\u309F\u30A0-\u30FF\u3100-\u312F\u3130-\u318F\u3190-\u319F\u31C0-\u31EF\u31F0-\u31FF\u3200-\u32FF\u3300-\u33FF\u3400-\u4DBF\u4DC0-\u4DFF\u4E00-\u9FFF\uA000-\uA48F\uA490-\uA4CF\uA700-\uA71F\uA800-\uA82F\uA840-\uA87F\uAC00-\uD7AF\uF900-\uFAFF\\.!#$%&'*+-/=?^_`{|}~\-\d]+)@(?!\.)([a-zA-Z0-9\u0080-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u0300-\u036F\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F\u0D80-\u0DFF\u0E00-\u0E7F\u0E80-\u0EFF\u0F00-\u0FFF\u1000-\u109F\u10A0-\u10FF\u1100-\u11FF\u1200-\u137F\u1380-\u139F\u13A0-\u13FF\u1400-\u167F\u1680-\u169F\u16A0-\u16FF\u1700-\u171F\u1720-\u173F\u1740-\u175F\u1760-\u177F\u1780-\u17FF\u1800-\u18AF\u1900-\u194F\u1950-\u197F\u1980-\u19DF\u19E0-\u19FF\u1A00-\u1A1F\u1B00-\u1B7F\u1D00-\u1D7F\u1D80-\u1DBF\u1DC0-\u1DFF\u1E00-\u1EFF\u1F00-\u1FFF\u20D0-\u20FF\u2100-\u214F\u2C00-\u2C5F\u2C60-\u2C7F\u2C80-\u2CFF\u2D00-\u2D2F\u2D30-\u2D7F\u2D80-\u2DDF\u2F00-\u2FDF\u2FF0-\u2FFF\u3040-\u309F\u30A0-\u30FF\u3100-\u312F\u3130-\u318F\u3190-\u319F\u31C0-\u31EF\u31F0-\u31FF\u3200-\u32FF\u3300-\u33FF\u3400-\u4DBF\u4DC0-\u4DFF\u4E00-\u9FFF\uA000-\uA48F\uA490-\uA4CF\uA700-\uA71F\uA800-\uA82F\uA840-\uA87F\uAC00-\uD7AF\uF900-\uFAFF\-\\.\d]+)((\.([a-zA-Z\u0080-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u0300-\u036F\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F\u0D80-\u0DFF\u0E00-\u0E7F\u0E80-\u0EFF\u0F00-\u0FFF\u1000-\u109F\u10A0-\u10FF\u1100-\u11FF\u1200-\u137F\u1380-\u139F\u13A0-\u13FF\u1400-\u167F\u1680-\u169F\u16A0-\u16FF\u1700-\u171F\u1720-\u173F\u1740-\u175F\u1760-\u177F\u1780-\u17FF\u1800-\u18AF\u1900-\u194F\u1950-\u197F\u1980-\u19DF\u19E0-\u19FF\u1A00-\u1A1F\u1B00-\u1B7F\u1D00-\u1D7F\u1D80-\u1DBF\u1DC0-\u1DFF\u1E00-\u1EFF\u1F00-\u1FFF\u20D0-\u20FF\u2100-\u214F\u2C00-\u2C5F\u2C60-\u2C7F\u2C80-\u2CFF\u2D00-\u2D2F\u2D30-\u2D7F\u2D80-\u2DDF\u2F00-\u2FDF\u2FF0-\u2FFF\u3040-\u309F\u30A0-\u30FF\u3100-\u312F\u3130-\u318F\u3190-\u319F\u31C0-\u31EF\u31F0-\u31FF\u3200-\u32FF\u3300-\u33FF\u3400-\u4DBF\u4DC0-\u4DFF\u4E00-\u9FFF\uA000-\uA48F\uA490-\uA4CF\uA700-\uA71F\uA800-\uA82F\uA840-\uA87F\uAC00-\uD7AF\uF900-\uFAFF]){2,63})+)$/i; 4 | 5 | /** 6 | * RegExp to test a string for a ISO 8601 Date spec 7 | * YYYY-MM-DDThh:mmTZD 8 | * YYYY-MM-DDThh:mm:ssTZD 9 | * YYYY-MM-DDThh:mm:ss.sTZD 10 | */ 11 | 12 | export const ISO_8601_DATE_TIME_REGEX = /^(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:Z|[+-][01]\d:[0-5]\d)$/; 13 | 14 | /** 15 | * RegExp to test a string for a ISO 8601 Time spec 16 | * hh:mmTZD 17 | * hh:mm:ssTZD 18 | * hh:mm:ss.sTZD 19 | */ 20 | 21 | export const ISO_8601_TIME_REGEX = /^([01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:Z|[+-][01]\d:[0-5]\d)$/i; 22 | 23 | /** 24 | * Date Regex for the following format 25 | * YYYY-MM-DD 26 | */ 27 | export const DATE_REGEX = /^(?:[1-9]\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)$/i; 28 | 29 | /** 30 | * Hostname Regex. source: https://www.regextester.com/23 31 | * Validates all host names including international hostname. 32 | */ 33 | 34 | export const HOSTNAME_REGEX = /^(((?!-))(xn--|_{1,1})?[a-z0-9-]{0,61}[a-z0-9]{1,1}\.)*(xn--)?([a-z0-9][a-z0-9\\-]{0,60}|[a-z0-9-]{1,30}\.[a-z]{2,})$/; 35 | 36 | /** 37 | * International hostname Regex. source: https://regexr.com/3abjr 38 | * Validates: 39 | * xn-fsqu00a.xn-0zwm56d 40 | * xn--stackoverflow.com 41 | */ 42 | 43 | export const INTERNATIONAL_HOSTNAME_REGEX = /^^xn--?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9\\-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/; 44 | 45 | /** 46 | * IPV4 Regex. source: https://www.oreilly.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html 47 | * Validates: 48 | * 8.8.8.8 49 | * 192.14.23.4 50 | * 1.0.0.255 51 | */ 52 | 53 | export const IPV4_REGEX = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/; 54 | 55 | /** 56 | * IPV6 Regex. source: https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch08s17.html 57 | * Validates: 58 | * 1762:0:0:0:0:B03:1:AF18 59 | * 21DA:D3:0:2F3B:2AA:FF:FE28:9C5A 60 | * FE80:0000:0000:0000:0202:B3FF:FE1E:8329 61 | */ 62 | 63 | export const IPV6_REGEX = /^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$/; 64 | -------------------------------------------------------------------------------- /src/yup/schemas/string/string.schema.ts: -------------------------------------------------------------------------------- 1 | import isNumber from "lodash/isNumber"; 2 | import capitalize from "lodash/capitalize"; 3 | import type { JSONSchemaExtended } from "../../../schema"; 4 | import { DataTypes, SchemaKeywords } from "../../../schema"; 5 | import Yup from "../../addMethods"; 6 | import { getErrorMessage } from "../../config/"; 7 | import { createRequiredSchema } from "../required"; 8 | import { createConstantSchema } from "../constant"; 9 | import { createEnumerableSchema } from "../enumerables"; 10 | import { 11 | INTERNATIONAL_EMAIL_REGEX, 12 | ISO_8601_DATE_TIME_REGEX, 13 | ISO_8601_TIME_REGEX, 14 | DATE_REGEX, 15 | HOSTNAME_REGEX, 16 | INTERNATIONAL_HOSTNAME_REGEX, 17 | IPV4_REGEX, 18 | IPV6_REGEX 19 | } from "./string.constants"; 20 | 21 | /** 22 | * Initializes a yup string schema derived from a json string schema 23 | */ 24 | 25 | const createStringSchema = ( 26 | [key, value]: [string, JSONSchemaExtended], 27 | jsonSchema: JSONSchemaExtended 28 | ): Yup.StringSchema => { 29 | const { 30 | description, 31 | default: defaults, 32 | minLength, 33 | maxLength, 34 | pattern, 35 | format, 36 | regex, 37 | title 38 | } = value; 39 | 40 | const label = title || capitalize(key); 41 | 42 | const defaultMessage = 43 | getErrorMessage(description, DataTypes.STRING, [key, { title }]) || 44 | capitalize(`${label} is not of type string`); 45 | 46 | let Schema = Yup.string().typeError(defaultMessage); 47 | 48 | if (defaults) { 49 | Schema = Schema.concat(Schema.default(defaults)); 50 | } 51 | 52 | /** Set required if ID is in required schema */ 53 | Schema = createRequiredSchema(Schema, jsonSchema, [key, value]); 54 | 55 | /** Determine if schema matches constant */ 56 | Schema = createConstantSchema(Schema, [key, value]); 57 | 58 | /** Determine if schema matches any enums */ 59 | Schema = createEnumerableSchema(Schema, [key, value]); 60 | 61 | if (isNumber(minLength)) { 62 | const message = 63 | getErrorMessage(description, SchemaKeywords.MINIMUM_LENGTH, [ 64 | key, 65 | { title, minLength } 66 | ]) || `${label} requires a minimum of ${minLength} characters`; 67 | 68 | Schema = Schema.concat(Schema.min(minLength, message)); 69 | } 70 | 71 | if (isNumber(maxLength)) { 72 | const message = 73 | getErrorMessage(description, SchemaKeywords.MAXIMUM_LENGTH, [ 74 | key, 75 | { title, maxLength } 76 | ]) || `${label} cannot exceed a maximum of ${maxLength} characters`; 77 | 78 | Schema = Schema.concat(Schema.max(maxLength, message)); 79 | } 80 | 81 | if (pattern) { 82 | const reg = new RegExp(pattern); 83 | const message = 84 | getErrorMessage(description, SchemaKeywords.PATTERN, [ 85 | key, 86 | { title, pattern: reg.toString() } 87 | ]) || `${label} is an incorrect format`; 88 | 89 | Schema = Schema.concat(Schema.matches(reg, message)); 90 | } 91 | 92 | if (regex) { 93 | const reg = new RegExp(regex); 94 | const message = 95 | getErrorMessage(description, SchemaKeywords.REGEX, [ 96 | key, 97 | { title, regex: reg.toString() } 98 | ]) || `${label} is an incorrect format`; 99 | 100 | Schema = Schema.concat(Schema.matches(reg, message)); 101 | } 102 | 103 | if (format) { 104 | Schema = stringSchemaFormat([key, value], Schema); 105 | } 106 | return Schema; 107 | }; 108 | 109 | export const stringSchemaFormat = ( 110 | [key, value]: [string, JSONSchemaExtended], 111 | Schema: Yup.StringSchema 112 | ) => { 113 | const { format, description, title } = value; 114 | 115 | const label = title || capitalize(key); 116 | 117 | if (format === "date-time") { 118 | const message = 119 | getErrorMessage(description, SchemaKeywords.FORMAT, [ 120 | key, 121 | { title, format } 122 | ]) || `${label} is an invalid date and time format`; 123 | Schema = Schema.concat(Schema.matches(ISO_8601_DATE_TIME_REGEX, message)); 124 | } 125 | 126 | if (format === "time") { 127 | const message = 128 | getErrorMessage(description, SchemaKeywords.FORMAT, [ 129 | key, 130 | { title, format } 131 | ]) || `${label} is an invalid time format`; 132 | Schema = Schema.concat(Schema.matches(ISO_8601_TIME_REGEX, message)); 133 | } 134 | 135 | if (format === "date") { 136 | const message = 137 | getErrorMessage(description, SchemaKeywords.FORMAT, [ 138 | key, 139 | { title, format } 140 | ]) || `${label} is an invalid date format`; 141 | Schema = Schema.concat(Schema.matches(DATE_REGEX, message)); 142 | } 143 | 144 | // email 145 | 146 | if (format === "email") { 147 | const message = 148 | getErrorMessage(description, SchemaKeywords.FORMAT, [ 149 | key, 150 | { title, format } 151 | ]) || `${label} is an invalid email format`; 152 | Schema = Schema.concat(Schema.email(message)); 153 | } 154 | 155 | // international email format 156 | 157 | if (format === "idn-email") { 158 | const message = 159 | getErrorMessage(description, SchemaKeywords.FORMAT, [ 160 | key, 161 | { title, format } 162 | ]) || `${label} is an invalid international email format`; 163 | Schema = Schema.concat(Schema.matches(INTERNATIONAL_EMAIL_REGEX, message)); 164 | } 165 | 166 | // hostnames 167 | 168 | if (format === "hostname") { 169 | const message = 170 | getErrorMessage(description, SchemaKeywords.FORMAT, [ 171 | key, 172 | { title, format } 173 | ]) || `${label} is an invalid hostname format`; 174 | Schema = Schema.concat(Schema.matches(HOSTNAME_REGEX, message)); 175 | } 176 | 177 | if (format === "idn-hostname") { 178 | const message = 179 | getErrorMessage(description, SchemaKeywords.FORMAT, [ 180 | key, 181 | { title, format } 182 | ]) || `${label} is an invalid international hostname format`; 183 | Schema = Schema.concat( 184 | Schema.matches(INTERNATIONAL_HOSTNAME_REGEX, message) 185 | ); 186 | } 187 | 188 | // ip addresses 189 | 190 | if (format === "ipv4") { 191 | const message = 192 | getErrorMessage(description, SchemaKeywords.FORMAT, [ 193 | key, 194 | { title, format } 195 | ]) || `${label} is an invalid ipv4 format`; 196 | Schema = Schema.concat(Schema.matches(IPV4_REGEX, message)); 197 | } 198 | 199 | if (format === "ipv6") { 200 | const message = 201 | getErrorMessage(description, SchemaKeywords.FORMAT, [ 202 | key, 203 | { title, format } 204 | ]) || `${label} is an invalid ipv6 format`; 205 | Schema = Schema.concat(Schema.matches(IPV6_REGEX, message)); 206 | } 207 | 208 | // resource identifiers 209 | 210 | if (format === "uri") { 211 | const message = 212 | getErrorMessage(description, SchemaKeywords.FORMAT, [ 213 | key, 214 | { title, format } 215 | ]) || `${label} is an invalid URI format`; 216 | Schema = Schema.concat(Schema.url(message)); 217 | } 218 | 219 | if (format === "uri-reference") { 220 | const message = 221 | getErrorMessage(description, SchemaKeywords.FORMAT, [ 222 | key, 223 | { title, format } 224 | ]) || `${label} is an invalid URI reference format`; 225 | 226 | // `urlReference` is a custom yup method. See /yup/addons/index.ts 227 | // for implementation 228 | 229 | Schema = Schema.concat(Schema.urlReference(message)); 230 | } 231 | 232 | if (format === "iri") { 233 | console.warn("iri format is not supported"); 234 | } 235 | 236 | if (format === "iri-reference") { 237 | console.warn("iri-reference format is not supported"); 238 | } 239 | 240 | return Schema; 241 | }; 242 | 243 | export default createStringSchema; 244 | -------------------------------------------------------------------------------- /src/yup/types.ts: -------------------------------------------------------------------------------- 1 | import type { JSONSchema, JSONSchemaType, NodeTypes } from "../schema" 2 | import isPlainObject from "lodash/isPlainObject"; 3 | 4 | export const isConfigError = ( 5 | errors: undefined | ConfigErrors 6 | ): errors is ConfigErrors => isPlainObject(errors); 7 | 8 | export type SchemaItem = [string, JSONSchema]; 9 | 10 | /* Configuration type to handle error messaging */ 11 | 12 | export type ConfigErrorTypes = { 13 | [key in NodeTypes]?: string | CustomErrorMsg; 14 | }; 15 | 16 | // Custom error messaging callback 17 | 18 | export type CustomErrorMsgParam = [ string, Record ] 19 | 20 | export type CustomErrorMsg = (param: CustomErrorMsgParam) => string 21 | 22 | export interface ConfigErrors { 23 | [key: string]: ConfigErrors | ConfigErrorTypes; 24 | } 25 | export interface Config { 26 | errors?: ConfigErrors; 27 | } 28 | -------------------------------------------------------------------------------- /src/yup/utils.ts: -------------------------------------------------------------------------------- 1 | import get from "lodash/get"; 2 | import head from "lodash/head"; 3 | import isPlainObject from "lodash/isPlainObject"; 4 | import isEmpty from "lodash/isEmpty"; 5 | import isArray from "lodash/isArray"; 6 | import transform from "lodash/transform"; 7 | import flow from "lodash/flow"; 8 | import has from "lodash/has"; 9 | import { getDefinitionItem } from "../schema"; 10 | import type { JSONSchema } from "../schema"; 11 | 12 | /** 13 | * Concatenates the schema field path and schema key in order to retrieve error message 14 | * from configuration 15 | */ 16 | 17 | export const joinPath = ( 18 | description: string | undefined, 19 | schemaKey: string 20 | ): string | false => (description ? `${description}.${schemaKey}` : false); 21 | 22 | /** Retrieves the first item in an object */ 23 | 24 | export const getObjectHead = (obj: T): false | [string, T[keyof T]] => { 25 | if (!isPlainObject(obj) || isEmpty(obj)) return false; 26 | /** Get all keys from obj */ 27 | const arr = Object.keys(obj); 28 | /** Grab the first key */ 29 | const key = head(arr) as string; 30 | /** Grab the first item */ 31 | const value = get(obj, key); 32 | return [key, value]; 33 | }; 34 | 35 | /** Recursively removes any empty objects */ 36 | 37 | export const removeEmptyObjects = (schema: JSONSchema) => { 38 | const cleaner = (result: JSONSchema, value: any, key: string) => { 39 | const isCollection = isPlainObject(value); 40 | const cleaned = isCollection ? cleanObject(value) : value; 41 | if (isCollection && isEmpty(cleaned)) return; 42 | isArray(result) ? result.push(cleaned) : (result[key] = cleaned); 43 | }; 44 | const cleanObject = (schema: JSONSchema) => transform(schema, cleaner); 45 | return isPlainObject(schema) ? cleanObject(schema) : schema; 46 | }; 47 | 48 | /** Replace all $ref instances with their definition */ 49 | 50 | export const transformRefs = (schema: JSONSchema): JSONSchema => { 51 | const replaceRefs = (result: JSONSchema, value: any, key: string) => { 52 | const hasRef = get(value, "$ref"); 53 | const replaced = hasRef 54 | ? getDefinitionItem(schema, get(value, "$ref")) 55 | : isPlainObject(value) || isArray(value) 56 | ? replaceAllRefs(value) 57 | : value; 58 | result[key] = replaced; 59 | }; 60 | const replaceAllRefs = (schema: JSONSchema): JSONSchema => 61 | transform(schema, replaceRefs); 62 | return isPlainObject(schema) ? replaceAllRefs(schema) : schema; 63 | }; 64 | 65 | /** 66 | * Add type property to all if schema's using the id of that schema 67 | * to lookup the type in the properties schema 68 | * */ 69 | 70 | export const applyIfTypes = (schema: JSONSchema): JSONSchema => { 71 | const addType = (schema: JSONSchema): JSONSchema => 72 | transform(schema, (result: JSONSchema, value: any, key: string) => { 73 | if (key === "if" && !isEmpty(value)) { 74 | const properties = get(schema, "properties"); 75 | const ifProperties = get(value, "properties"); 76 | const ifSchema = ifProperties && getObjectHead(ifProperties); 77 | if (ifSchema) { 78 | const [ifSchemaKey, ifSchemaValue] = ifSchema; 79 | const type = 80 | ifSchemaKey && 81 | !has(ifProperties, [ifSchemaKey, "type"]) && 82 | has(properties, ifSchemaKey) && 83 | get(properties, [ifSchemaKey, "type"]); 84 | value = type 85 | ? { 86 | ...value, 87 | properties: { 88 | ...ifProperties, 89 | [ifSchemaKey]: { ...ifSchemaValue, type } 90 | } 91 | } 92 | : value; 93 | } 94 | } 95 | result[key] = isPlainObject(value) ? addType(value) : value; 96 | }); 97 | return isPlainObject(schema) ? addType(schema) : schema; 98 | }; 99 | 100 | /** 101 | * Iterate through schema and adds description property with the associated node path 102 | * This will remove any existing description values! 103 | */ 104 | 105 | export const applyPaths = (schema: JSONSchema): JSONSchema => { 106 | const invalidKeys = [ 107 | "properties", 108 | "then", 109 | "if", 110 | "definitions", 111 | "else", 112 | "items" 113 | ]; 114 | const addPath = (schema: JSONSchema, path: string = ""): JSONSchema => 115 | transform(schema, (result: JSONSchema, value: any, key: string) => { 116 | /** Target field node only */ 117 | const isField = has(value, "type") && !has(value, "properties"); 118 | if (isField) { 119 | value = { 120 | ...value, 121 | description: `${path}${key}` 122 | }; 123 | } 124 | /** Capture path of the field id only */ 125 | const id = invalidKeys.includes(key) ? "" : `${key}.`; 126 | result[key] = isPlainObject(value) 127 | ? addPath(value, `${path}${id}`) 128 | : value; 129 | }); 130 | return isPlainObject(schema) ? addPath(schema) : schema; 131 | }; 132 | 133 | /** 134 | * Normalizes schema to the required shape. Removes empty objects, 135 | * replaces $ref values with the related definition and adds 136 | * missing type properties to if schemas 137 | */ 138 | 139 | export const normalize = (schema: JSONSchema): JSONSchema => { 140 | const normalizer = flow([ 141 | removeEmptyObjects, 142 | transformRefs, 143 | applyPaths, // this needs to be before if injection or it will apply the path there 144 | applyIfTypes 145 | ]); 146 | return normalizer(schema); 147 | }; 148 | -------------------------------------------------------------------------------- /test/yup/allOf.if.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | // Note: Unit tests cover the core functionality. Formats have been excluded 6 | // as all those validators use the pattern method 7 | 8 | describe("convertToYup() string conditions", () => { 9 | it("should validate single if statement in allOf", () => { 10 | const schema: JSONSchema = { 11 | type: "object", 12 | $schema: "http://json-schema.org/draft-07/schema#", 13 | $id: "test", 14 | title: "Test", 15 | properties: { 16 | country: { 17 | type: "string", 18 | enum: ["Australia", "Canada"] 19 | } 20 | }, 21 | required: ["country"], 22 | allOf: [ 23 | { 24 | if: { 25 | properties: { country: { type: "string", const: "Australia" } } 26 | }, 27 | then: { 28 | properties: { 29 | postal_code: { type: "string", pattern: "[0-9]{5}(-[0-9]{4})?" } 30 | }, 31 | required: ["postal_code"] 32 | } 33 | } 34 | ] 35 | }; 36 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 37 | 38 | let isValid = yupschema.isValidSync({ 39 | country: "Australia" 40 | }); 41 | expect(isValid).toBeFalsy(); 42 | }); 43 | it("should ignore non-relevant if statements in allOf", () => { 44 | const schema: JSONSchema = { 45 | type: "object", 46 | $schema: "http://json-schema.org/draft-07/schema#", 47 | $id: "test", 48 | title: "Test", 49 | properties: { 50 | country: { 51 | type: "string", 52 | enum: ["Australia", "Canada"] 53 | } 54 | }, 55 | required: ["country"], 56 | allOf: [ 57 | { 58 | if: { 59 | properties: { country: { type: "string", const: "Canada" } } 60 | }, 61 | then: { 62 | properties: { 63 | postal_code: { type: "string", pattern: "[0-9]{5}(-[0-9]{4})?" } 64 | }, 65 | required: ["postal_code"] 66 | } 67 | } 68 | ] 69 | }; 70 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 71 | 72 | let isValid = yupschema.isValidSync({ 73 | country: "Australia" 74 | }); 75 | expect(isValid).toBeTruthy(); 76 | }); 77 | it("should validate multiple if statement in allOf", () => { 78 | const schema: JSONSchema = { 79 | type: "object", 80 | $schema: "http://json-schema.org/draft-07/schema#", 81 | $id: "test", 82 | title: "Test", 83 | properties: { 84 | country: { 85 | type: "string", 86 | enum: ["Australia", "Canada"] 87 | }, 88 | isMinor: { 89 | type: "boolean" 90 | } 91 | }, 92 | required: ["country", "isMinor"], 93 | allOf: [ 94 | { 95 | if: { 96 | properties: { isMinor: { type: "boolean", const: true } } 97 | }, 98 | then: { 99 | properties: { hasParentConsent: { type: "boolean" } }, 100 | required: ["hasParentConsent"] 101 | } 102 | }, 103 | { 104 | if: { 105 | properties: { country: { type: "string", const: "Australia" } } 106 | }, 107 | then: { 108 | properties: { 109 | postal_code: { type: "string", pattern: "[0-9]{5}(-[0-9]{4})?" } 110 | }, 111 | required: ["postal_code"] 112 | } 113 | } 114 | ] 115 | }; 116 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 117 | 118 | const notValid1 = yupschema.isValidSync({ 119 | country: "Canada", 120 | isMinor: true 121 | }); 122 | expect(notValid1).toBeFalsy(); 123 | 124 | const notValid2 = yupschema.isValidSync({ 125 | country: "Australia", 126 | isMinor: false 127 | }); 128 | expect(notValid2).toBeFalsy(); 129 | 130 | const notValid3 = yupschema.isValidSync({ 131 | country: "Australia", 132 | isMinor: true 133 | }); 134 | expect(notValid3).toBeFalsy(); 135 | 136 | const notValid4 = yupschema.isValidSync({ 137 | country: "Australia", 138 | isMinor: true, 139 | postal_code: "12345" 140 | }); 141 | expect(notValid4).toBeFalsy(); 142 | 143 | const notValid5 = yupschema.isValidSync({ 144 | country: "Australia", 145 | isMinor: true, 146 | hasParentConsent: true 147 | }); 148 | expect(notValid5).toBeFalsy(); 149 | 150 | const isValid1 = yupschema.isValidSync({ 151 | country: "Australia", 152 | isMinor: false, 153 | postal_code: "12345" 154 | }); 155 | expect(isValid1).toBeTruthy(); 156 | 157 | const isValid2 = yupschema.isValidSync({ 158 | country: "Australia", 159 | isMinor: true, 160 | postal_code: "12345", 161 | hasParentConsent: true 162 | }); 163 | expect(isValid2).toBeTruthy(); 164 | }); 165 | it("should validate deep schema", () => { 166 | const schema: JSONSchema = { 167 | type: "object", 168 | $schema: "http://json-schema.org/draft-07/schema#", 169 | $id: "test", 170 | title: "Test", 171 | definitions: { 172 | location: { 173 | $id: "/definitions/location", 174 | $schema: "https://json-schema.org/draft/2020-12/schema", 175 | type: "object", 176 | properties: { 177 | country: { 178 | type: "string", 179 | enum: ["Australia", "Canada"] 180 | } 181 | }, 182 | required: ["country"], 183 | allOf: [ 184 | { 185 | if: { 186 | properties: { country: { type: "string", const: "Australia" } } 187 | }, 188 | then: { 189 | properties: { 190 | postal_code: { 191 | type: "string", 192 | pattern: "[0-9]{5}(-[0-9]{4})?" 193 | } 194 | }, 195 | required: ["postal_code"] 196 | } 197 | } 198 | ] 199 | } 200 | }, 201 | properties: { 202 | location: { 203 | description: "", 204 | $ref: "#/definitions/location" 205 | } 206 | }, 207 | required: ["location"] 208 | }; 209 | 210 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 211 | 212 | const isValid = yupschema.isValidSync({ 213 | location: { 214 | country: "Australia", 215 | isMinor: true, 216 | postal_code: "12345", 217 | hasParentConsent: true 218 | } 219 | }); 220 | expect(isValid).toBeTruthy(); 221 | 222 | const isInvalid = yupschema.isValidSync({ 223 | location: { 224 | country: "Australia" 225 | } 226 | }); 227 | expect(isInvalid).toBeFalsy(); 228 | }); 229 | }); 230 | -------------------------------------------------------------------------------- /test/yup/array.contains.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() array contains", () => { 6 | it("should validate strings", () => { 7 | const schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | things: { 14 | type: "array", 15 | contains: { 16 | type: "string" 17 | } 18 | } 19 | } 20 | }; 21 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 22 | let valid; 23 | 24 | valid = yupschema.isValidSync({ 25 | things: ["a", 1, {}] 26 | }); 27 | expect(valid).toBeTruthy(); 28 | 29 | valid = yupschema.isValidSync({ 30 | things: ["1"] 31 | }); 32 | expect(valid).toBeTruthy(); 33 | 34 | yupschema.isValidSync({ 35 | things: [] 36 | }); 37 | expect(valid).toBeTruthy(); 38 | 39 | valid = yupschema.isValidSync({ 40 | things: [1, null] 41 | }); 42 | expect(valid).toBeFalsy(); 43 | 44 | valid = yupschema.isValidSync({ 45 | things: [[], false] 46 | }); 47 | expect(valid).toBeFalsy(); 48 | 49 | valid = yupschema.isValidSync({ 50 | things: [{}, 1] 51 | }); 52 | expect(valid).toBeFalsy(); 53 | 54 | try { 55 | valid = yupschema.validateSync({ things: [{}, 1] }); 56 | } catch (e) { 57 | valid = e.errors[0]; 58 | } 59 | expect(valid).toBe("Things must at least contain one item of type string"); 60 | }); 61 | 62 | it("should validate numbers", () => { 63 | const schema: JSONSchema = { 64 | type: "object", 65 | $schema: "http://json-schema.org/draft-07/schema#", 66 | $id: "test", 67 | title: "Test", 68 | properties: { 69 | things: { 70 | type: "array", 71 | contains: { 72 | type: "number" 73 | } 74 | } 75 | } 76 | }; 77 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 78 | let valid; 79 | 80 | valid = yupschema.isValidSync({ 81 | things: [1] 82 | }); 83 | expect(valid).toBeTruthy(); 84 | 85 | yupschema.isValidSync({ 86 | things: [] 87 | }); 88 | expect(valid).toBeTruthy(); 89 | 90 | valid = yupschema.isValidSync({ 91 | things: [2, null] 92 | }); 93 | expect(valid).toBeTruthy(); 94 | 95 | valid = yupschema.isValidSync({ 96 | things: [null, false] 97 | }); 98 | expect(valid).toBeFalsy(); 99 | 100 | try { 101 | valid = yupschema.validateSync({ things: [null, false] }); 102 | } catch (e) { 103 | valid = e.errors[0]; 104 | } 105 | expect(valid).toBe("Things must at least contain one item of type number"); 106 | }); 107 | 108 | it("should validate integers", () => { 109 | const schema: JSONSchema = { 110 | type: "object", 111 | $schema: "http://json-schema.org/draft-07/schema#", 112 | $id: "test", 113 | title: "Test", 114 | properties: { 115 | things: { 116 | type: "array", 117 | contains: { 118 | type: "integer" 119 | } 120 | } 121 | } 122 | }; 123 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 124 | let valid; 125 | 126 | valid = yupschema.isValidSync({ 127 | things: [1] 128 | }); 129 | expect(valid).toBeTruthy(); 130 | 131 | yupschema.isValidSync({ 132 | things: [] 133 | }); 134 | expect(valid).toBeTruthy(); 135 | 136 | valid = yupschema.isValidSync({ 137 | things: [2, 2.36, 50.0] 138 | }); 139 | expect(valid).toBeTruthy(); 140 | 141 | valid = yupschema.isValidSync({ 142 | things: [null, false] 143 | }); 144 | expect(valid).toBeFalsy(); 145 | 146 | valid = yupschema.isValidSync({ 147 | things: [3.56, "a"] 148 | }); 149 | expect(valid).toBeFalsy(); 150 | 151 | try { 152 | valid = yupschema.validateSync({ things: [3.56, "a"] }); 153 | } catch (e) { 154 | valid = e.errors[0]; 155 | } 156 | expect(valid).toBe("Things must at least contain one item of type integer"); 157 | }); 158 | 159 | it("should validate booleans", () => { 160 | const schema: JSONSchema = { 161 | type: "object", 162 | $schema: "http://json-schema.org/draft-07/schema#", 163 | $id: "test", 164 | title: "Test", 165 | properties: { 166 | things: { 167 | type: "array", 168 | contains: { 169 | type: "boolean" 170 | } 171 | } 172 | } 173 | }; 174 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 175 | let valid; 176 | 177 | valid = yupschema.isValidSync({ 178 | things: [true] 179 | }); 180 | expect(valid).toBeTruthy(); 181 | 182 | yupschema.isValidSync({ 183 | things: [] 184 | }); 185 | expect(valid).toBeTruthy(); 186 | 187 | valid = yupschema.isValidSync({ 188 | things: ["A", null] 189 | }); 190 | expect(valid).toBeFalsy(); 191 | 192 | valid = yupschema.isValidSync({ 193 | things: [[], 1] 194 | }); 195 | expect(valid).toBeFalsy(); 196 | 197 | try { 198 | valid = yupschema.validateSync({ things: [[], 1] }); 199 | } catch (e) { 200 | valid = e.errors[0]; 201 | } 202 | expect(valid).toBe("Things must at least contain one item of type boolean"); 203 | }); 204 | 205 | it("should validate objects", () => { 206 | const schema: JSONSchema = { 207 | type: "object", 208 | $schema: "http://json-schema.org/draft-07/schema#", 209 | $id: "test", 210 | title: "Test", 211 | properties: { 212 | things: { 213 | type: "array", 214 | contains: { 215 | type: "object" 216 | } 217 | } 218 | } 219 | }; 220 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 221 | let valid; 222 | 223 | valid = yupschema.isValidSync({ 224 | things: [{}] 225 | }); 226 | expect(valid).toBeTruthy(); 227 | 228 | yupschema.isValidSync({ 229 | things: [] 230 | }); 231 | expect(valid).toBeTruthy(); 232 | 233 | valid = yupschema.isValidSync({ 234 | things: [{ s: "1" }, null] 235 | }); 236 | expect(valid).toBeTruthy(); 237 | 238 | valid = yupschema.isValidSync({ 239 | things: ["a", 1] 240 | }); 241 | expect(valid).toBeFalsy(); 242 | 243 | try { 244 | valid = yupschema.validateSync({ things: ["a", 1] }); 245 | } catch (e) { 246 | valid = e.errors[0]; 247 | } 248 | expect(valid).toBe("Things must at least contain one item of type object"); 249 | }); 250 | 251 | it("should validate array", () => { 252 | const schema: JSONSchema = { 253 | type: "object", 254 | $schema: "http://json-schema.org/draft-07/schema#", 255 | $id: "test", 256 | title: "Test", 257 | properties: { 258 | things: { 259 | type: "array", 260 | contains: { 261 | type: "array" 262 | } 263 | } 264 | } 265 | }; 266 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 267 | let valid; 268 | 269 | valid = yupschema.isValidSync({ 270 | things: [[]] 271 | }); 272 | expect(valid).toBeTruthy(); 273 | 274 | yupschema.isValidSync({ 275 | things: [] 276 | }); 277 | expect(valid).toBeTruthy(); 278 | 279 | valid = yupschema.isValidSync({ 280 | things: [["a"], null] 281 | }); 282 | expect(valid).toBeTruthy(); 283 | 284 | valid = yupschema.isValidSync({ 285 | things: ["a", 1] 286 | }); 287 | expect(valid).toBeFalsy(); 288 | 289 | try { 290 | valid = yupschema.validateSync({ things: ["a", 1] }); 291 | } catch (e) { 292 | valid = e.errors[0]; 293 | } 294 | expect(valid).toBe("Things must at least contain one item of type array"); 295 | }); 296 | }); 297 | -------------------------------------------------------------------------------- /test/yup/array.items.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import isEqual from "lodash/isEqual"; 3 | import type { JSONSchema } from "../../src/schema" 4 | import convertToYup from "../../src"; 5 | 6 | describe("convertToYup() array items", () => { 7 | it("should validate definitions", () => { 8 | const schema: JSONSchema = { 9 | type: "object", 10 | $schema: "http://json-schema.org/draft-07/schema#", 11 | $id: "test", 12 | title: "Test", 13 | definitions: { 14 | country: { 15 | type: "object", 16 | properties: { 17 | country: { 18 | type: "string", 19 | minLength: 1, 20 | maxLength: 30, 21 | description: "The country of the resident" 22 | }, 23 | hasID: { 24 | type: "string", 25 | minLength: 1, 26 | maxLength: 8 27 | } 28 | }, 29 | required: ["country", "hasID"] 30 | } 31 | }, 32 | properties: { 33 | countries: { 34 | type: "array", 35 | items: { 36 | $ref: "#/definitions/country" 37 | } 38 | } 39 | } 40 | }; 41 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 42 | let valid = yupschema.isValidSync({ 43 | countries: [ 44 | { 45 | country: "Singapore", 46 | hasID: "true" 47 | } 48 | ] 49 | }); 50 | expect(valid).toBeTruthy(); 51 | }); 52 | 53 | it("should validate strings", () => { 54 | const schema: JSONSchema = { 55 | type: "object", 56 | $schema: "http://json-schema.org/draft-07/schema#", 57 | $id: "test", 58 | title: "Test", 59 | properties: { 60 | things: { 61 | type: "array", 62 | items: { 63 | type: "string" 64 | } 65 | } 66 | } 67 | }; 68 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 69 | let valid; 70 | 71 | valid = yupschema.isValidSync({ 72 | things: ["a"] 73 | }); 74 | expect(valid).toBeTruthy(); 75 | 76 | valid = yupschema.isValidSync({ 77 | things: ["1"] 78 | }); 79 | expect(valid).toBeTruthy(); 80 | 81 | yupschema.isValidSync({ 82 | things: [] 83 | }); 84 | expect(valid).toBeTruthy(); 85 | 86 | valid = yupschema.isValidSync({ 87 | things: ["a", null] 88 | }); 89 | expect(valid).toBeFalsy(); 90 | }); 91 | 92 | it("should validate unique strings", () => { 93 | const schema: JSONSchema = { 94 | type: "object", 95 | $schema: "http://json-schema.org/draft-07/schema#", 96 | $id: "test", 97 | title: "Test", 98 | properties: { 99 | things: { 100 | type: "array", 101 | uniqueItems: true, 102 | items: { 103 | type: "string" 104 | } 105 | } 106 | } 107 | }; 108 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 109 | let valid; 110 | 111 | valid = yupschema.isValidSync({ 112 | things: ["a", "b"] 113 | }); 114 | expect(valid).toBeTruthy(); 115 | 116 | valid = yupschema.isValidSync({ 117 | things: ["a", "a"] 118 | }); 119 | expect(valid).toBeFalsy(); 120 | 121 | valid = yupschema.isValidSync({ 122 | things: [] 123 | }); 124 | expect(valid).toBeTruthy(); 125 | }); 126 | 127 | it("should validate numbers", () => { 128 | const schema: JSONSchema = { 129 | type: "object", 130 | $schema: "http://json-schema.org/draft-07/schema#", 131 | $id: "test", 132 | title: "Test", 133 | properties: { 134 | things: { 135 | type: "array", 136 | items: { 137 | type: "number" 138 | } 139 | } 140 | } 141 | }; 142 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 143 | let valid; 144 | 145 | valid = yupschema.isValidSync({ 146 | things: [1] 147 | }); 148 | expect(valid).toBeTruthy(); 149 | 150 | yupschema.isValidSync({ 151 | things: [] 152 | }); 153 | expect(valid).toBeTruthy(); 154 | 155 | valid = yupschema.isValidSync({ 156 | things: [2, null] 157 | }); 158 | expect(valid).toBeFalsy(); 159 | 160 | valid = yupschema.isValidSync({ 161 | things: [3, false] 162 | }); 163 | expect(valid).toBeFalsy(); 164 | }); 165 | 166 | it("should validate integers", () => { 167 | const schema: JSONSchema = { 168 | type: "object", 169 | $schema: "http://json-schema.org/draft-07/schema#", 170 | $id: "test", 171 | title: "Test", 172 | properties: { 173 | things: { 174 | type: "array", 175 | items: { 176 | type: "integer" 177 | } 178 | } 179 | } 180 | }; 181 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 182 | let valid; 183 | 184 | valid = yupschema.isValidSync({ 185 | things: [1] 186 | }); 187 | expect(valid).toBeTruthy(); 188 | 189 | yupschema.isValidSync({ 190 | things: [] 191 | }); 192 | expect(valid).toBeTruthy(); 193 | 194 | valid = yupschema.isValidSync({ 195 | things: [2, null] 196 | }); 197 | expect(valid).toBeFalsy(); 198 | 199 | valid = yupschema.isValidSync({ 200 | things: [3, false] 201 | }); 202 | expect(valid).toBeFalsy(); 203 | 204 | valid = yupschema.isValidSync({ 205 | things: [3.56, 1] 206 | }); 207 | expect(valid).toBeFalsy(); 208 | }); 209 | 210 | it("should validate booleans", () => { 211 | const schema: JSONSchema = { 212 | type: "object", 213 | $schema: "http://json-schema.org/draft-07/schema#", 214 | $id: "test", 215 | title: "Test", 216 | properties: { 217 | things: { 218 | type: "array", 219 | items: { 220 | type: "boolean" 221 | } 222 | } 223 | } 224 | }; 225 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 226 | let valid; 227 | 228 | valid = yupschema.isValidSync({ 229 | things: [true] 230 | }); 231 | expect(valid).toBeTruthy(); 232 | 233 | yupschema.isValidSync({ 234 | things: [] 235 | }); 236 | expect(valid).toBeTruthy(); 237 | 238 | valid = yupschema.isValidSync({ 239 | things: [false, null] 240 | }); 241 | expect(valid).toBeFalsy(); 242 | }); 243 | 244 | it("should validate objects", () => { 245 | const schema: JSONSchema = { 246 | type: "object", 247 | $schema: "http://json-schema.org/draft-07/schema#", 248 | $id: "test", 249 | title: "Test", 250 | properties: { 251 | things: { 252 | type: "array", 253 | items: { 254 | type: "object" 255 | } 256 | } 257 | } 258 | }; 259 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 260 | let valid; 261 | 262 | valid = yupschema.isValidSync({ 263 | things: [{}] 264 | }); 265 | expect(valid).toBeTruthy(); 266 | 267 | yupschema.isValidSync({ 268 | things: [] 269 | }); 270 | expect(valid).toBeTruthy(); 271 | 272 | valid = yupschema.isValidSync({ 273 | things: [{ s: "1" }, null] 274 | }); 275 | expect(valid).toBeFalsy(); 276 | 277 | valid = yupschema.isValidSync({ 278 | things: [{ s: "1" }, 1] 279 | }); 280 | expect(valid).toBeFalsy(); 281 | }); 282 | 283 | it("should validate array", () => { 284 | const schema: JSONSchema = { 285 | type: "object", 286 | $schema: "http://json-schema.org/draft-07/schema#", 287 | $id: "test", 288 | title: "Test", 289 | properties: { 290 | things: { 291 | type: "array", 292 | items: { 293 | type: "array" 294 | } 295 | } 296 | } 297 | }; 298 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 299 | let valid; 300 | 301 | valid = yupschema.isValidSync({ 302 | things: [[]] 303 | }); 304 | expect(valid).toBeTruthy(); 305 | 306 | yupschema.isValidSync({ 307 | things: [] 308 | }); 309 | expect(valid).toBeTruthy(); 310 | 311 | valid = yupschema.isValidSync({ 312 | things: [["a"], null] 313 | }); 314 | expect(valid).toBeFalsy(); 315 | 316 | valid = yupschema.isValidSync({ 317 | things: [[{ s: "1" }], 1] 318 | }); 319 | expect(valid).toBeFalsy(); 320 | }); 321 | it("should set default value", () => { 322 | const defaultValue = ["a"]; 323 | const schema: JSONSchema = { 324 | type: "object", 325 | $schema: "http://json-schema.org/draft-07/schema#", 326 | $id: "test", 327 | title: "Test", 328 | properties: { 329 | list: { 330 | type: "array", 331 | default: defaultValue 332 | } 333 | }, 334 | required: ["list"] 335 | }; 336 | 337 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 338 | let isValid = yupschema 339 | .test("is-default", "${path} is default value", (value) => 340 | isEqual(value.list, defaultValue) 341 | ) 342 | .isValidSync({}); 343 | 344 | expect(isValid).toBeTruthy(); 345 | }); 346 | }); 347 | -------------------------------------------------------------------------------- /test/yup/array.items.tuple.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() array items tuple", () => { 6 | it("should validate data types", () => { 7 | let schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | things: { 14 | type: "array", 15 | items: [ 16 | { type: "string" }, 17 | { type: "number" }, 18 | { type: "boolean" }, 19 | { type: "object" }, 20 | { type: "array" }, 21 | { type: "null" }, 22 | { type: "integer" } 23 | ] 24 | } 25 | } 26 | }; 27 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 28 | let valid; 29 | 30 | valid = yupschema.isValidSync({ 31 | things: ["test", 1.5, true, { a: "1" }, ["test"], null, 5] 32 | }); 33 | expect(valid).toBeTruthy(); 34 | 35 | valid = yupschema.isValidSync({ 36 | things: [null, 1.5, true, { a: "1" }, ["test"], null, 5] 37 | }); 38 | expect(valid).toBeFalsy(); 39 | 40 | valid = yupschema.isValidSync({ 41 | things: ["test", "1.5", true, { a: "1" }, ["test"], null, 5] 42 | }); 43 | expect(valid).toBeFalsy(); 44 | 45 | valid = yupschema.isValidSync({ 46 | things: ["test", 1.5, 1, { a: "1" }, ["test"], null, 5] 47 | }); 48 | expect(valid).toBeFalsy(); 49 | 50 | valid = yupschema.isValidSync({ 51 | things: ["test", 1.5, true, null, ["test"], null, 5] 52 | }); 53 | expect(valid).toBeFalsy(); 54 | 55 | valid = yupschema.isValidSync({ 56 | things: ["test", 1.5, true, { a: "1" }, "test", null, 5] 57 | }); 58 | expect(valid).toBeFalsy(); 59 | 60 | valid = yupschema.isValidSync({ 61 | things: ["test", 1.5, true, { a: "1" }, ["test"], "a", 5] 62 | }); 63 | expect(valid).toBeFalsy(); 64 | 65 | valid = yupschema.isValidSync({ 66 | things: ["test", 1.5, true, { a: "1" }, ["test"], null, 5.5] 67 | }); 68 | expect(valid).toBeFalsy(); 69 | }); 70 | 71 | it("should validate string const", () => { 72 | const schema: JSONSchema = { 73 | type: "object", 74 | $schema: "http://json-schema.org/draft-07/schema#", 75 | $id: "test", 76 | title: "Test", 77 | properties: { 78 | things: { 79 | type: "array", 80 | items: [ 81 | { 82 | type: "string", 83 | const: "test" 84 | }, 85 | { type: "number" }, 86 | { type: "boolean" } 87 | ] 88 | } 89 | } 90 | }; 91 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 92 | let valid; 93 | 94 | valid = yupschema.isValidSync({ 95 | things: ["test", 1, true] 96 | }); 97 | expect(valid).toBeTruthy(); 98 | 99 | valid = yupschema.isValidSync({ 100 | things: ["null", 1, true] 101 | }); 102 | expect(valid).toBeFalsy(); 103 | }); 104 | 105 | it("should validate number const", () => { 106 | const schema: JSONSchema = { 107 | type: "object", 108 | $schema: "http://json-schema.org/draft-07/schema#", 109 | $id: "test", 110 | title: "Test", 111 | properties: { 112 | things: { 113 | type: "array", 114 | items: [ 115 | { 116 | type: "number", 117 | const: 0 118 | }, 119 | { type: "number" }, 120 | { type: "boolean" } 121 | ] 122 | } 123 | } 124 | }; 125 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 126 | let valid; 127 | 128 | valid = yupschema.isValidSync({ 129 | things: [0, 1, true] 130 | }); 131 | expect(valid).toBeTruthy(); 132 | 133 | valid = yupschema.isValidSync({ 134 | things: [1, 1, true] 135 | }); 136 | expect(valid).toBeFalsy(); 137 | }); 138 | 139 | it("should validate integer const", () => { 140 | const schema: JSONSchema = { 141 | type: "object", 142 | $schema: "http://json-schema.org/draft-07/schema#", 143 | $id: "test", 144 | title: "Test", 145 | properties: { 146 | things: { 147 | type: "array", 148 | items: [ 149 | { 150 | type: "integer", 151 | const: 8 152 | }, 153 | { type: "number" }, 154 | { type: "boolean" } 155 | ] 156 | } 157 | } 158 | }; 159 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 160 | let valid; 161 | 162 | valid = yupschema.isValidSync({ 163 | things: [8, 1, true] 164 | }); 165 | expect(valid).toBeTruthy(); 166 | 167 | valid = yupschema.isValidSync({ 168 | things: [8.1, 1, true] 169 | }); 170 | expect(valid).toBeFalsy(); 171 | }); 172 | 173 | it("should NOT validate object const", () => { 174 | const schema: JSONSchema = { 175 | type: "object", 176 | $schema: "http://json-schema.org/draft-07/schema#", 177 | $id: "test", 178 | title: "Test", 179 | properties: { 180 | things: { 181 | type: "array", 182 | items: [ 183 | { 184 | type: "object", 185 | const: "test" 186 | }, 187 | { type: "number" }, 188 | { type: "boolean" } 189 | ] 190 | } 191 | } 192 | }; 193 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 194 | let valid; 195 | 196 | valid = yupschema.isValidSync({ 197 | things: [{ a: "whwhwh" }, 1, true] 198 | }); 199 | expect(valid).toBeTruthy(); 200 | 201 | valid = yupschema.isValidSync({ 202 | things: ["null", 1, true] 203 | }); 204 | expect(valid).toBeFalsy(); 205 | }); 206 | 207 | it("should validate array const", () => { 208 | const schema: JSONSchema = { 209 | type: "object", 210 | $schema: "http://json-schema.org/draft-07/schema#", 211 | $id: "test", 212 | title: "Test", 213 | properties: { 214 | things: { 215 | type: "array", 216 | items: [ 217 | { 218 | type: "array", 219 | const: ["test"] 220 | }, 221 | { type: "number" }, 222 | { type: "boolean" } 223 | ] 224 | } 225 | } 226 | }; 227 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 228 | let valid; 229 | 230 | valid = yupschema.isValidSync({ 231 | things: [["test"], 1, true] 232 | }); 233 | expect(valid).toBeTruthy(); 234 | 235 | valid = yupschema.isValidSync({ 236 | things: ["null", 1, true] 237 | }); 238 | expect(valid).toBeFalsy(); 239 | }); 240 | 241 | it("should validate string enum", () => { 242 | const schema: JSONSchema = { 243 | type: "object", 244 | $schema: "http://json-schema.org/draft-07/schema#", 245 | $id: "test", 246 | title: "Test", 247 | properties: { 248 | things: { 249 | type: "array", 250 | items: [ 251 | { 252 | type: "string", 253 | enum: ["test", "testB"] 254 | }, 255 | { type: "number" }, 256 | { type: "boolean" } 257 | ] 258 | } 259 | } 260 | }; 261 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 262 | let valid; 263 | 264 | valid = yupschema.isValidSync({ 265 | things: ["test", 1, true] 266 | }); 267 | expect(valid).toBeTruthy(); 268 | 269 | valid = yupschema.isValidSync({ 270 | things: ["testB", 1, true] 271 | }); 272 | expect(valid).toBeTruthy(); 273 | 274 | valid = yupschema.isValidSync({ 275 | things: ["null", 1, true] 276 | }); 277 | expect(valid).toBeFalsy(); 278 | }); 279 | 280 | it("should validate number enum", () => { 281 | const schema: JSONSchema = { 282 | type: "object", 283 | $schema: "http://json-schema.org/draft-07/schema#", 284 | $id: "test", 285 | title: "Test", 286 | properties: { 287 | things: { 288 | type: "array", 289 | items: [ 290 | { 291 | type: "number", 292 | enum: [1.5, 2] 293 | }, 294 | { type: "number" }, 295 | { type: "boolean" } 296 | ] 297 | } 298 | } 299 | }; 300 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 301 | let valid; 302 | 303 | valid = yupschema.isValidSync({ 304 | things: [1.5, 1, true] 305 | }); 306 | expect(valid).toBeTruthy(); 307 | 308 | valid = yupschema.isValidSync({ 309 | things: [2, 1, true] 310 | }); 311 | expect(valid).toBeTruthy(); 312 | 313 | valid = yupschema.isValidSync({ 314 | things: [3, 1, true] 315 | }); 316 | expect(valid).toBeFalsy(); 317 | }); 318 | 319 | it("should validate integer enum", () => { 320 | const schema: JSONSchema = { 321 | type: "object", 322 | $schema: "http://json-schema.org/draft-07/schema#", 323 | $id: "test", 324 | title: "Test", 325 | properties: { 326 | things: { 327 | type: "array", 328 | items: [ 329 | { 330 | type: "integer", 331 | enum: [1, 2] 332 | }, 333 | { type: "number" }, 334 | { type: "boolean" } 335 | ] 336 | } 337 | } 338 | }; 339 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 340 | let valid; 341 | 342 | valid = yupschema.isValidSync({ 343 | things: [1, 1, true] 344 | }); 345 | expect(valid).toBeTruthy(); 346 | 347 | valid = yupschema.isValidSync({ 348 | things: [2, 1, true] 349 | }); 350 | expect(valid).toBeTruthy(); 351 | 352 | valid = yupschema.isValidSync({ 353 | things: [3, 1, true] 354 | }); 355 | expect(valid).toBeFalsy(); 356 | }); 357 | 358 | it("should validate array enum", () => { 359 | const schema: JSONSchema = { 360 | type: "object", 361 | $schema: "http://json-schema.org/draft-07/schema#", 362 | $id: "test", 363 | title: "Test", 364 | properties: { 365 | things: { 366 | type: "array", 367 | items: [ 368 | { 369 | type: "array", 370 | enum: [ 371 | ["a", "b"], 372 | ["c", "d"] 373 | ] 374 | }, 375 | { type: "number" }, 376 | { type: "boolean" } 377 | ] 378 | } 379 | } 380 | }; 381 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 382 | let valid; 383 | 384 | valid = yupschema.isValidSync({ 385 | things: [["a", "b"], 1, true] 386 | }); 387 | expect(valid).toBeTruthy(); 388 | 389 | valid = yupschema.isValidSync({ 390 | things: [["c", "d"], 1, true] 391 | }); 392 | expect(valid).toBeTruthy(); 393 | 394 | valid = yupschema.isValidSync({ 395 | things: [["e", "f"], 1, true] 396 | }); 397 | expect(valid).toBeFalsy(); 398 | }); 399 | 400 | it("should not validate multiple types", () => { 401 | const schema: JSONSchema = { 402 | type: "object", 403 | $schema: "http://json-schema.org/draft-07/schema#", 404 | $id: "test", 405 | title: "Test", 406 | properties: { 407 | things: { 408 | type: "array", 409 | items: [ 410 | { 411 | type: ["string", "null"] 412 | } 413 | ] 414 | } 415 | } 416 | }; 417 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 418 | let valid; 419 | 420 | valid = yupschema.isValidSync({ 421 | things: ["a"] 422 | }); 423 | expect(valid).toBeFalsy(); 424 | 425 | valid = yupschema.isValidSync({ 426 | things: [null] 427 | }); 428 | expect(valid).toBeFalsy(); 429 | }); 430 | }); 431 | -------------------------------------------------------------------------------- /test/yup/array.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() array", () => { 6 | it("should validate array type", () => { 7 | const schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | items: { 14 | type: "array" 15 | } 16 | } 17 | }; 18 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 19 | let isValid = yupschema.isValidSync({ 20 | items: [] 21 | }); 22 | expect(isValid).toBeTruthy(); 23 | 24 | isValid = yupschema.isValidSync({ 25 | items: [1, 2] 26 | }); 27 | expect(isValid).toBeTruthy(); 28 | 29 | isValid = yupschema.isValidSync({ 30 | items: ["a", "b"] 31 | }); 32 | expect(isValid).toBeTruthy(); 33 | 34 | isValid = yupschema.isValidSync({ 35 | items: { a: "a" } 36 | }); 37 | expect(isValid).toBeFalsy(); 38 | 39 | isValid = yupschema.isValidSync({ 40 | items: "test123" 41 | }); 42 | expect(isValid).toBeFalsy(); 43 | }); 44 | 45 | it("should validate multiple types", () => { 46 | const schema: JSONSchema = { 47 | type: "object", 48 | $schema: "http://json-schema.org/draft-07/schema#", 49 | $id: "test", 50 | title: "Test", 51 | properties: { 52 | items: { 53 | type: ["array", "null"] 54 | } 55 | } 56 | }; 57 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 58 | 59 | let isValid = yupschema.isValidSync({ 60 | items: ["a", "b"] 61 | }); 62 | expect(isValid).toBeTruthy(); 63 | 64 | isValid = yupschema.isValidSync({ 65 | items: null 66 | }); 67 | expect(isValid).toBeTruthy(); 68 | }); 69 | 70 | it("should validate required", () => { 71 | const schema: JSONSchema = { 72 | type: "object", 73 | $schema: "http://json-schema.org/draft-07/schema#", 74 | $id: "test", 75 | title: "Test", 76 | properties: { 77 | items: { 78 | type: "array" 79 | } 80 | }, 81 | required: ["items"] 82 | }; 83 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 84 | let valid; 85 | 86 | valid = yupschema.isValidSync({ 87 | items: ["a"] 88 | }); 89 | expect(valid).toBeTruthy(); 90 | 91 | valid = yupschema.isValidSync({}); 92 | expect(valid).toBeFalsy(); 93 | try { 94 | valid = yupschema.validateSync({}); 95 | } catch (e) { 96 | valid = e.errors[0]; 97 | } 98 | expect(valid).toBe("Items is required"); 99 | }); 100 | 101 | it("should validate minItems", () => { 102 | const schema: JSONSchema = { 103 | type: "object", 104 | $schema: "http://json-schema.org/draft-07/schema#", 105 | $id: "test", 106 | title: "Test", 107 | properties: { 108 | items: { 109 | type: "array", 110 | minItems: 3 111 | } 112 | } 113 | }; 114 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 115 | let valid; 116 | 117 | valid = yupschema.isValidSync({}); 118 | expect(valid).toBeTruthy(); 119 | 120 | valid = yupschema.isValidSync({ 121 | items: ["a", "b", "c"] 122 | }); 123 | expect(valid).toBeTruthy(); 124 | 125 | valid = yupschema.isValidSync({ 126 | items: ["a", "b"] 127 | }); 128 | expect(valid).toBeFalsy(); 129 | 130 | try { 131 | valid = yupschema.validateSync({ items: ["a", "b"] }); 132 | } catch (e) { 133 | valid = e.errors[0]; 134 | } 135 | expect(valid).toBe("Items requires a minimum of 3 items"); 136 | }); 137 | 138 | it("should validate maxItems", () => { 139 | const schema: JSONSchema = { 140 | type: "object", 141 | $schema: "http://json-schema.org/draft-07/schema#", 142 | $id: "test", 143 | title: "Test", 144 | properties: { 145 | items: { 146 | type: "array", 147 | maxItems: 6 148 | } 149 | } 150 | }; 151 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 152 | let valid; 153 | 154 | valid = yupschema.isValidSync({}); 155 | expect(valid).toBeTruthy(); 156 | 157 | valid = yupschema.isValidSync({ 158 | items: ["a", "b", "c"] 159 | }); 160 | expect(valid).toBeTruthy(); 161 | 162 | valid = yupschema.isValidSync({ 163 | items: ["a", "b", "c", "d", "e", "f", "g"] 164 | }); 165 | expect(valid).toBeFalsy(); 166 | 167 | try { 168 | valid = yupschema.validateSync({ 169 | items: ["a", "b", "c", "d", "e", "f", "g"] 170 | }); 171 | } catch (e) { 172 | valid = e.errors[0]; 173 | } 174 | expect(valid).toBe("Items cannot exceed a maximum of 6 items"); 175 | }); 176 | 177 | it("should validate constant", () => { 178 | const schema: JSONSchema = { 179 | type: "object", 180 | $schema: "http://json-schema.org/draft-07/schema#", 181 | $id: "test", 182 | title: "Test", 183 | properties: { 184 | list: { 185 | type: "array", 186 | const: ["a", "b"] 187 | } 188 | } 189 | }; 190 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 191 | 192 | let isValid = yupschema.isValidSync({ 193 | list: ["a", "b"] 194 | }); 195 | expect(isValid).toBeTruthy(); 196 | 197 | isValid = yupschema.isValidSync({ 198 | list: ["a"] 199 | }); 200 | expect(isValid).toBeFalsy(); 201 | 202 | let errorMessage; 203 | try { 204 | errorMessage = yupschema.validateSync({ list: ["b"] }); 205 | } catch (e) { 206 | errorMessage = e.errors[0]; 207 | } 208 | expect(errorMessage).toBe("List does not match constant"); 209 | }); 210 | 211 | it("should validate enum", () => { 212 | const schema: JSONSchema = { 213 | type: "object", 214 | $schema: "http://json-schema.org/draft-07/schema#", 215 | $id: "test", 216 | title: "Test", 217 | properties: { 218 | list: { 219 | type: "array", 220 | enum: [ 221 | ["a", "b"], 222 | ["c", "d"] 223 | ] 224 | } 225 | } 226 | }; 227 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 228 | 229 | let isValid = yupschema.isValidSync({ 230 | list: ["a", "b"] 231 | }); 232 | expect(isValid).toBeTruthy(); 233 | 234 | isValid = yupschema.isValidSync({ 235 | list: ["a"] 236 | }); 237 | expect(isValid).toBeFalsy(); 238 | 239 | let errorMessage; 240 | try { 241 | errorMessage = yupschema.validateSync({ list: ["b"] }); 242 | } catch (e) { 243 | errorMessage = e.errors[0]; 244 | } 245 | expect(errorMessage).toBe("List does not match any of the enumerables"); 246 | }); 247 | 248 | it("should validate unique items", () => { 249 | const schema: JSONSchema = { 250 | type: "object", 251 | $schema: "http://json-schema.org/draft-07/schema#", 252 | $id: "test", 253 | title: "Test", 254 | properties: { 255 | items: { 256 | type: "array", 257 | uniqueItems: true 258 | } 259 | } 260 | }; 261 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 262 | let valid; 263 | 264 | valid = yupschema.isValidSync({ 265 | items: ["a", "b", "c"] 266 | }); 267 | expect(valid).toBeTruthy(); 268 | 269 | valid = yupschema.isValidSync({ 270 | items: [{ a: 1 }, { b: 2 }, { c: 3 }] 271 | }); 272 | expect(valid).toBeTruthy(); 273 | 274 | valid = yupschema.isValidSync({ 275 | items: [["a"], ["b"], ["c"]] 276 | }); 277 | expect(valid).toBeTruthy(); 278 | 279 | valid = yupschema.isValidSync({ 280 | items: [] 281 | }); 282 | expect(valid).toBeTruthy(); 283 | 284 | valid = yupschema.isValidSync({ 285 | items: [{ a: 1 }, { b: 2 }, { b: 2 }] 286 | }); 287 | expect(valid).toBeFalsy(); 288 | 289 | valid = yupschema.isValidSync({ 290 | items: ["a", "b", "b"] 291 | }); 292 | expect(valid).toBeFalsy(); 293 | 294 | valid = yupschema.isValidSync({ 295 | items: [1, 2, 2] 296 | }); 297 | expect(valid).toBeFalsy(); 298 | 299 | valid = yupschema.isValidSync({ 300 | items: [["a"], ["b"], ["b"]] 301 | }); 302 | expect(valid).toBeFalsy(); 303 | 304 | try { 305 | valid = yupschema.validateSync({ items: ["b", "b"] }); 306 | } catch (e) { 307 | valid = e.errors[0]; 308 | } 309 | expect(valid).toBe("Items values are not unique"); 310 | }); 311 | 312 | it("should not validate unique items", () => { 313 | const schema: JSONSchema = { 314 | type: "object", 315 | $schema: "http://json-schema.org/draft-07/schema#", 316 | $id: "test", 317 | title: "Test", 318 | properties: { 319 | items: { 320 | type: "array", 321 | uniqueItems: false 322 | } 323 | } 324 | }; 325 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 326 | let valid; 327 | 328 | valid = yupschema.isValidSync({ 329 | items: ["a", "b", "c"] 330 | }); 331 | expect(valid).toBeTruthy(); 332 | 333 | valid = yupschema.isValidSync({ 334 | items: [] 335 | }); 336 | expect(valid).toBeTruthy(); 337 | 338 | valid = yupschema.isValidSync({ 339 | items: ["a", "b", "b"] 340 | }); 341 | expect(valid).toBeTruthy(); 342 | 343 | valid = yupschema.isValidSync({ 344 | items: [1, 2, 2] 345 | }); 346 | expect(valid).toBeTruthy(); 347 | }); 348 | 349 | it("should use title as label in error message", () => { 350 | const fieldTitle = "Item Types"; 351 | const schema: JSONSchema = { 352 | type: "object", 353 | $schema: "http://json-schema.org/draft-07/schema#", 354 | $id: "test", 355 | title: "Test", 356 | properties: { 357 | item: { 358 | type: "array", 359 | title: fieldTitle 360 | } 361 | } 362 | }; 363 | 364 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 365 | let errorMessage; 366 | try { 367 | errorMessage = yupschema.validateSync({ item: "test" }); 368 | } catch (e) { 369 | errorMessage = e.errors[0]; 370 | } 371 | expect(errorMessage).toBe(`${fieldTitle} is not of type array`); 372 | }); 373 | }); 374 | -------------------------------------------------------------------------------- /test/yup/boolean.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() boolean", () => { 6 | it("should validate boolean type", () => { 7 | const schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | enable: { 14 | type: "boolean" 15 | } 16 | } 17 | }; 18 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 19 | 20 | let isValid = yupschema.isValidSync({ 21 | enable: true 22 | }); 23 | expect(isValid).toBeTruthy(); 24 | 25 | isValid = yupschema.isValidSync({ 26 | enable: false 27 | }); 28 | expect(isValid).toBeTruthy(); 29 | 30 | isValid = yupschema.isValidSync({ 31 | enable: null 32 | }); 33 | expect(isValid).toBeFalsy(); 34 | 35 | isValid = yupschema.isValidSync({ 36 | enable: {} 37 | }); 38 | expect(isValid).toBeFalsy(); 39 | }); 40 | 41 | it("should validate multiple types", () => { 42 | const schema: JSONSchema = { 43 | type: "object", 44 | $schema: "http://json-schema.org/draft-07/schema#", 45 | $id: "test", 46 | title: "Test", 47 | properties: { 48 | terms: { 49 | type: ["boolean", "null"] 50 | } 51 | } 52 | }; 53 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 54 | 55 | let isValid = yupschema.isValidSync({ 56 | terms: true 57 | }); 58 | expect(isValid).toBeTruthy(); 59 | 60 | isValid = yupschema.isValidSync({ 61 | terms: null 62 | }); 63 | expect(isValid).toBeTruthy(); 64 | }); 65 | 66 | it("should validate required", () => { 67 | const schema: JSONSchema = { 68 | type: "object", 69 | $schema: "http://json-schema.org/draft-07/schema#", 70 | $id: "test", 71 | title: "Test", 72 | properties: { 73 | enable: { 74 | type: "boolean" 75 | } 76 | }, 77 | required: ["enable"] 78 | }; 79 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 80 | let valid; 81 | 82 | valid = yupschema.isValidSync({ 83 | enable: true 84 | }); 85 | expect(valid).toBeTruthy(); 86 | 87 | valid = yupschema.isValidSync({}); 88 | expect(valid).toBeFalsy(); 89 | try { 90 | valid = yupschema.validateSync({}); 91 | } catch (e) { 92 | valid = e.errors[0]; 93 | } 94 | expect(valid).toBe("Enable is required"); 95 | }); 96 | 97 | it("should set default value", () => { 98 | const defaultValue = true; 99 | const schema: JSONSchema = { 100 | type: "object", 101 | $schema: "http://json-schema.org/draft-07/schema#", 102 | $id: "test", 103 | title: "Test", 104 | properties: { 105 | consent: { 106 | type: "boolean", 107 | default: defaultValue 108 | } 109 | }, 110 | required: ["consent"] 111 | }; 112 | 113 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 114 | let isValid = yupschema 115 | .test( 116 | "is-default", 117 | "${path} is default value", 118 | (value) => value.consent === defaultValue 119 | ) 120 | .isValidSync({}); 121 | 122 | expect(isValid).toBeTruthy(); 123 | }); 124 | 125 | it("should validate constant", () => { 126 | const schema: JSONSchema = { 127 | type: "object", 128 | $schema: "http://json-schema.org/draft-07/schema#", 129 | $id: "test", 130 | title: "Test", 131 | properties: { 132 | isActive: { 133 | type: "boolean", 134 | const: true 135 | } 136 | } 137 | }; 138 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 139 | 140 | let isValid = yupschema.isValidSync({ 141 | isActive: true 142 | }); 143 | expect(isValid).toBeTruthy(); 144 | 145 | isValid = yupschema.isValidSync({ 146 | isActive: false 147 | }); 148 | expect(isValid).toBeFalsy(); 149 | 150 | let errorMessage; 151 | try { 152 | errorMessage = yupschema.validateSync({ isActive: false }); 153 | } catch (e) { 154 | errorMessage = e.errors[0]; 155 | } 156 | expect(errorMessage).toBe("Isactive does not match constant"); 157 | }); 158 | 159 | it("should use title as label in error message", () => { 160 | const fieldTitle = "Is Active"; 161 | const schema: JSONSchema = { 162 | type: "object", 163 | $schema: "http://json-schema.org/draft-07/schema#", 164 | $id: "test", 165 | title: "Test", 166 | properties: { 167 | isActive: { 168 | type: "boolean", 169 | title: fieldTitle 170 | } 171 | } 172 | }; 173 | 174 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 175 | let errorMessage; 176 | try { 177 | errorMessage = yupschema.validateSync({ isActive: "test" }); 178 | } catch (e) { 179 | errorMessage = e.errors[0]; 180 | } 181 | expect(errorMessage).toBe(`${fieldTitle} is not of type boolean`); 182 | }); 183 | }); 184 | -------------------------------------------------------------------------------- /test/yup/composition.allof.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() allOf", () => { 6 | it("should validate data types", () => { 7 | let schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | things: { 14 | allOf: [ 15 | { type: "string", minLength: 4 }, 16 | { type: "string", maxLength: 6 } 17 | ] 18 | } 19 | } 20 | }; 21 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 22 | let valid; 23 | 24 | valid = yupschema.isValidSync({ 25 | things: "12345" 26 | }); 27 | expect(valid).toBeTruthy(); 28 | 29 | valid = yupschema.isValidSync({ 30 | things: "1234567" 31 | }); 32 | expect(valid).toBeFalsy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/yup/composition.anyof.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() anyOf", () => { 6 | it("should validate data types", () => { 7 | let schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | things: { 14 | anyOf: [ 15 | { type: "string", minLength: 6 }, 16 | { type: "string", const: "test" } 17 | ] 18 | } 19 | }, 20 | required: ["things"] 21 | }; 22 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 23 | let valid; 24 | 25 | valid = yupschema.isValidSync({ 26 | things: "test" 27 | }); 28 | expect(valid).toBeTruthy(); 29 | 30 | valid = yupschema.isValidSync({ 31 | things: "allowed" 32 | }); 33 | expect(valid).toBeTruthy(); 34 | 35 | valid = yupschema.isValidSync({ 36 | things: "fail" 37 | }); 38 | expect(valid).toBeFalsy(); 39 | 40 | valid = yupschema.isValidSync({}); 41 | expect(valid).toBeFalsy(); 42 | }); 43 | 44 | it("should validate fields using definition", () => { 45 | let schema: JSONSchema = { 46 | type: "object", 47 | $schema: "http://json-schema.org/draft-07/schema#", 48 | $id: "test", 49 | title: "Test", 50 | definitions: { 51 | person: { 52 | type: "object", 53 | properties: { 54 | personName: { type: "string" } 55 | }, 56 | required: ["personName"] 57 | }, 58 | company: { 59 | type: "object", 60 | properties: { 61 | companyName: { type: "string" } 62 | }, 63 | required: ["companyName"] 64 | } 65 | }, 66 | properties: { 67 | entity: { 68 | anyOf: [ 69 | { $ref: "#/definitions/person" }, 70 | { $ref: "#/definitions/company" } 71 | ] 72 | } 73 | }, 74 | required: ["entity"] 75 | }; 76 | 77 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 78 | let valid; 79 | 80 | valid = yupschema.isValidSync({ 81 | entity: { 82 | personName: "jane doe" 83 | } 84 | }); 85 | expect(valid).toBeTruthy(); 86 | 87 | valid = yupschema.isValidSync({ 88 | entity: { 89 | companyName: "things incorporated" 90 | } 91 | }); 92 | expect(valid).toBeTruthy(); 93 | 94 | valid = yupschema.isValidSync({ 95 | entity: { 96 | things: "lol" 97 | } 98 | }); 99 | expect(valid).toBeFalsy(); 100 | 101 | valid = yupschema.isValidSync({ 102 | entity: undefined 103 | }); 104 | expect(valid).toBeFalsy(); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /test/yup/composition.not.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema"; 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() not", () => { 6 | it("should validate data types", () => { 7 | let schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | things: { 14 | not: { type: "string", minLength: 6 } 15 | } 16 | } 17 | }; 18 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 19 | let valid; 20 | 21 | valid = yupschema.isValidSync({ 22 | things: "1234" 23 | }); 24 | expect(valid).toBeTruthy(); 25 | 26 | valid = yupschema.isValidSync({ 27 | things: "123456" 28 | }); 29 | expect(valid).toBeFalsy(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/yup/composition.oneof.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() oneOf", () => { 6 | it("should validate data types", () => { 7 | let schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | things: { 14 | oneOf: [ 15 | { type: "string", minLength: 6 }, 16 | { type: "string", minLength: 3 } 17 | ] 18 | } 19 | } 20 | }; 21 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 22 | let valid; 23 | 24 | valid = yupschema.isValidSync({ 25 | things: "1234" 26 | }); 27 | expect(valid).toBeTruthy(); 28 | 29 | valid = yupschema.isValidSync({ 30 | things: "123456" 31 | }); 32 | expect(valid).toBeFalsy(); 33 | }); 34 | 35 | it("should validate fields using definition", () => { 36 | let schema: JSONSchema = { 37 | type: "object", 38 | $schema: "http://json-schema.org/draft-07/schema#", 39 | $id: "test", 40 | title: "Test", 41 | definitions: { 42 | person: { 43 | $id: "#person", 44 | type: "object", 45 | properties: { 46 | personName: { type: "string" } 47 | }, 48 | required: ["personName"] 49 | }, 50 | company: { 51 | $id: "#company", 52 | type: "object", 53 | properties: { 54 | companyName: { type: "string" } 55 | }, 56 | required: ["companyName"] 57 | } 58 | }, 59 | properties: { 60 | entity: { 61 | oneOf: [{ $ref: "#person" }, { $ref: "#company" }] 62 | } 63 | }, 64 | required: ["entity"] 65 | }; 66 | 67 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 68 | let valid; 69 | 70 | valid = yupschema.isValidSync({ 71 | entity: { 72 | personName: "jane doe" 73 | } 74 | }); 75 | expect(valid).toBeTruthy(); 76 | 77 | valid = yupschema.isValidSync({ 78 | entity: { 79 | companyName: "things incorporated" 80 | } 81 | }); 82 | expect(valid).toBeTruthy(); 83 | 84 | valid = yupschema.isValidSync({ 85 | entity: 123 86 | }); 87 | 88 | expect(valid).toBeFalsy(); 89 | }); 90 | }); 91 | -------------------------------------------------------------------------------- /test/yup/configErrors.boolean.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import type { Config } from "../../src"; 4 | import convertToYup from "../../src"; 5 | 6 | describe("convertToYup() boolean configuration errors", () => { 7 | it("should show configuration error for incorrect data type", () => { 8 | const schema: JSONSchema = { 9 | type: "object", 10 | $schema: "http://json-schema.org/draft-07/schema#", 11 | $id: "test", 12 | title: "Test", 13 | properties: { 14 | termsConditions: { 15 | type: "boolean" 16 | } 17 | } 18 | }; 19 | const config = { 20 | errors: { 21 | defaults: { 22 | boolean: "Default boolean message" 23 | } 24 | } 25 | }; 26 | const yupschema = convertToYup(schema, config) as Yup.ObjectSchema; 27 | let errorMessage; 28 | try { 29 | errorMessage = yupschema.validateSync({ termsConditions: "ABC" }); 30 | } catch (e) { 31 | errorMessage = e.errors[0]; 32 | } 33 | expect(errorMessage).toBe("Default boolean message"); 34 | }); 35 | 36 | it("should show configuration CUSTOM error for incorrect data type", () => { 37 | const schema: JSONSchema = { 38 | type: "object", 39 | $schema: "http://json-schema.org/draft-07/schema#", 40 | $id: "test", 41 | title: "Test", 42 | properties: { 43 | termsConditions: { 44 | type: "boolean" 45 | } 46 | } 47 | }; 48 | const config: Config = { 49 | errors: { 50 | defaults: { 51 | boolean: ([key]) => `${key} field is invalid` 52 | } 53 | } 54 | }; 55 | const yupschema = convertToYup(schema, config) as Yup.ObjectSchema; 56 | let errorMessage; 57 | try { 58 | errorMessage = yupschema.validateSync({ termsConditions: "ABC" }); 59 | } catch (e) { 60 | errorMessage = e.errors[0]; 61 | } 62 | expect(errorMessage).toBe("termsConditions field is invalid"); 63 | }); 64 | 65 | it("should show configuration error for required", () => { 66 | const schema: JSONSchema = { 67 | type: "object", 68 | $schema: "http://json-schema.org/draft-07/schema#", 69 | $id: "test", 70 | title: "Test", 71 | properties: { 72 | termsConditions: { 73 | type: "boolean" 74 | } 75 | }, 76 | required: ["termsConditions"] 77 | }; 78 | const config = { 79 | errors: { 80 | termsConditions: { 81 | required: "Terms and conditions (boolean) is required" 82 | } 83 | } 84 | }; 85 | const yupschema = convertToYup(schema, config) as Yup.ObjectSchema; 86 | let errorMessage; 87 | try { 88 | errorMessage = yupschema.validateSync({}); 89 | } catch (e) { 90 | errorMessage = e.errors[0]; 91 | } 92 | expect(errorMessage).toBe(config.errors.termsConditions.required); 93 | }); 94 | 95 | it("should show configuration CUSTOM error for required", () => { 96 | const schema: JSONSchema = { 97 | type: "object", 98 | $schema: "http://json-schema.org/draft-07/schema#", 99 | $id: "test", 100 | title: "Test", 101 | properties: { 102 | termsConditions: { 103 | type: "boolean" 104 | } 105 | }, 106 | required: ["termsConditions"] 107 | }; 108 | const config: Config = { 109 | errors: { 110 | termsConditions: { 111 | required: ([key, { required }]) => 112 | `${key} field is invalid. It is listed as a required field: ${required}` 113 | } 114 | } 115 | }; 116 | const yupschema = convertToYup(schema, config) as Yup.ObjectSchema; 117 | let errorMessage; 118 | try { 119 | errorMessage = yupschema.validateSync({}); 120 | } catch (e) { 121 | errorMessage = e.errors[0]; 122 | } 123 | expect(errorMessage).toBe( 124 | "termsConditions field is invalid. It is listed as a required field: termsConditions" 125 | ); 126 | }); 127 | 128 | it("should show configuration error when value does not match constant", () => { 129 | const schema: JSONSchema = { 130 | type: "object", 131 | $schema: "http://json-schema.org/draft-07/schema#", 132 | $id: "test", 133 | title: "Test", 134 | properties: { 135 | isActive: { 136 | type: "boolean", 137 | const: true 138 | } 139 | } 140 | }; 141 | const config = { 142 | errors: { 143 | isActive: { 144 | const: "Incorrect constant" 145 | } 146 | } 147 | }; 148 | const yupschema = convertToYup(schema, config) as Yup.ObjectSchema; 149 | let errorMessage; 150 | try { 151 | errorMessage = yupschema.validateSync({ isActive: false }); 152 | } catch (e) { 153 | errorMessage = e.errors[0]; 154 | } 155 | expect(errorMessage).toBe("Incorrect constant"); 156 | }); 157 | 158 | it("should show configuration CUSTOM error for constant", () => { 159 | const schema: JSONSchema = { 160 | type: "object", 161 | $schema: "http://json-schema.org/draft-07/schema#", 162 | $id: "test", 163 | title: "Test", 164 | properties: { 165 | isActive: { 166 | type: "boolean", 167 | const: true 168 | } 169 | } 170 | }; 171 | const config: Config = { 172 | errors: { 173 | isActive: { 174 | const: ([key, { const: consts }]) => 175 | `${key} is invalid. Needs to match the constant ${consts}` 176 | } 177 | } 178 | }; 179 | const yupschema = convertToYup(schema, config) as Yup.ObjectSchema; 180 | let errorMessage; 181 | try { 182 | errorMessage = yupschema.validateSync({ isActive: false }); 183 | } catch (e) { 184 | errorMessage = e.errors[0]; 185 | } 186 | expect(errorMessage).toBe( 187 | "isActive is invalid. Needs to match the constant true" 188 | ); 189 | }); 190 | }); 191 | -------------------------------------------------------------------------------- /test/yup/configErrors.object.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import type { Config } from "../../src"; 4 | import convertToYup from "../../src"; 5 | 6 | describe("convertToYup() object configuration errors", () => { 7 | it("should show configuration error for incorrect data type", () => { 8 | const schema: JSONSchema = { 9 | type: "object", 10 | $schema: "http://json-schema.org/draft-07/schema#", 11 | $id: "test", 12 | title: "Test", 13 | properties: { 14 | address: { 15 | type: "object" 16 | } 17 | } 18 | }; 19 | const config: Config = { 20 | errors: { 21 | defaults: { 22 | object: "Default object message" 23 | } 24 | } 25 | }; 26 | const yupschema = convertToYup(schema, config) as Yup.ObjectSchema; 27 | let errorMessage; 28 | try { 29 | errorMessage = yupschema.validateSync({ address: "ABC" }); 30 | } catch (e) { 31 | errorMessage = e.errors[0]; 32 | } 33 | expect(errorMessage).toBe("Default object message"); 34 | }); 35 | 36 | it("should show configuration CUSTOM error for incorrect data type", () => { 37 | const schema: JSONSchema = { 38 | type: "object", 39 | $schema: "http://json-schema.org/draft-07/schema#", 40 | $id: "test", 41 | title: "Test", 42 | properties: { 43 | address: { 44 | type: "object" 45 | } 46 | } 47 | }; 48 | const config: Config = { 49 | errors: { 50 | defaults: { 51 | object: ([key]) => `${key} field has custom error message` 52 | } 53 | } 54 | }; 55 | const yupschema = convertToYup(schema, config) as Yup.ObjectSchema; 56 | let errorMessage; 57 | try { 58 | errorMessage = yupschema.validateSync({ address: "ABC" }); 59 | } catch (e) { 60 | errorMessage = e.errors[0]; 61 | } 62 | expect(errorMessage).toBe("address field has custom error message"); 63 | }); 64 | 65 | it("should show configuration error for required", () => { 66 | const schema: JSONSchema = { 67 | type: "object", 68 | $schema: "http://json-schema.org/draft-07/schema#", 69 | $id: "test", 70 | title: "Test", 71 | properties: { 72 | address: { 73 | type: "object" 74 | } 75 | }, 76 | required: ["address"] 77 | }; 78 | const config: Config = { 79 | errors: { 80 | address: { 81 | required: "Address is required" 82 | } 83 | } 84 | }; 85 | const yupschema = convertToYup(schema, config) as Yup.ObjectSchema; 86 | let errorMessage; 87 | try { 88 | errorMessage = yupschema.validateSync({}); 89 | } catch (e) { 90 | errorMessage = e.errors[0]; 91 | } 92 | expect(errorMessage).toBe("Address is required"); 93 | }); 94 | 95 | it("should show configuration CUSTOM error for required", () => { 96 | const schema: JSONSchema = { 97 | type: "object", 98 | $schema: "http://json-schema.org/draft-07/schema#", 99 | $id: "test", 100 | title: "Test", 101 | properties: { 102 | address: { 103 | type: "object" 104 | } 105 | }, 106 | required: ["address"] 107 | }; 108 | const config: Config = { 109 | errors: { 110 | address: { 111 | required: ([key, { required }]) => 112 | `${key} field is in required fields. i.e. ${required}` 113 | } 114 | } 115 | }; 116 | const yupschema = convertToYup(schema, config) as Yup.ObjectSchema; 117 | let errorMessage; 118 | try { 119 | errorMessage = yupschema.validateSync({}); 120 | } catch (e) { 121 | errorMessage = e.errors[0]; 122 | } 123 | expect(errorMessage).toBe( 124 | "address field is in required fields. i.e. address" 125 | ); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /test/yup/if.array.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() array conditions", () => { 6 | it("should validate all fields with exception to conditional fields", () => { 7 | const schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | list: { 14 | type: "array" 15 | } 16 | }, 17 | required: ["list"], 18 | if: { 19 | properties: { list: { minItems: 3, type: "array" } } 20 | }, 21 | then: { 22 | properties: { 23 | otherList: { type: "array" } 24 | } 25 | } 26 | }; 27 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 28 | 29 | let isValid = yupschema.isValidSync({ 30 | list: ["a", "b", "c"] 31 | }); 32 | expect(isValid).toBeTruthy(); 33 | }); 34 | 35 | it("should validate conditional when dependency matches minimum items", () => { 36 | const schema: JSONSchema = { 37 | type: "object", 38 | $schema: "http://json-schema.org/draft-07/schema#", 39 | $id: "test", 40 | title: "Test", 41 | properties: { 42 | list: { 43 | type: "array" 44 | } 45 | }, 46 | required: ["list"], 47 | if: { 48 | properties: { list: { minItems: 3, type: "array" } } 49 | }, 50 | then: { 51 | properties: { 52 | otherList: { type: "array" } 53 | } 54 | } 55 | }; 56 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 57 | 58 | let isValid = yupschema.isValidSync({ 59 | list: ["a", "b", "c"], 60 | otherList: ["d"] 61 | }); 62 | expect(isValid).toBeTruthy(); 63 | 64 | yupschema.isValidSync({ 65 | list: ["a", "b"] 66 | }); 67 | expect(isValid).toBeTruthy(); 68 | 69 | isValid = yupschema.isValidSync({ 70 | list: ["a", "b", "c"], 71 | otherList: "A" 72 | }); 73 | expect(isValid).toBeFalsy(); 74 | 75 | isValid = yupschema.isValidSync({ 76 | list: ["a", "b"], 77 | otherList: "A" 78 | }); 79 | expect(isValid).toBeTruthy(); 80 | }); 81 | 82 | it("should validate conditional when dependency matches maximum items", () => { 83 | const schema: JSONSchema = { 84 | type: "object", 85 | $schema: "http://json-schema.org/draft-07/schema#", 86 | $id: "test", 87 | title: "Test", 88 | properties: { 89 | list: { 90 | type: "array" 91 | } 92 | }, 93 | required: ["list"], 94 | if: { 95 | properties: { list: { maxItems: 3, type: "array" } } 96 | }, 97 | then: { 98 | properties: { 99 | otherList: { type: "array" } 100 | } 101 | } 102 | }; 103 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 104 | 105 | let isValid = yupschema.isValidSync({ 106 | list: ["a", "b", "c"], 107 | otherList: ["d"] 108 | }); 109 | expect(isValid).toBeTruthy(); 110 | 111 | isValid = yupschema.isValidSync({ 112 | list: ["a", "b", "c", "d"] 113 | }); 114 | expect(isValid).toBeTruthy(); 115 | 116 | isValid = yupschema.isValidSync({ 117 | list: ["a", "b", "c"], 118 | otherList: "A" 119 | }); 120 | expect(isValid).toBeFalsy(); 121 | 122 | isValid = yupschema.isValidSync({ 123 | list: ["a", "b", "c", "d", "e"], 124 | otherList: "A" 125 | }); 126 | expect(isValid).toBeTruthy(); 127 | }); 128 | 129 | it("should validate an item of objects", () => { 130 | const schema: JSONSchema = { 131 | $schema: "http://json-schema.org/draft-07/schema#", 132 | $id: "crs", 133 | description: "CRS", 134 | type: "object", 135 | properties: { 136 | isTaxResidentOnly: { 137 | type: "string" 138 | } 139 | }, 140 | required: ["isTaxResidentOnly"], 141 | if: { 142 | properties: { 143 | isTaxResidentOnly: { 144 | type: "string", 145 | const: "false" 146 | } 147 | } 148 | }, 149 | then: { 150 | properties: { 151 | countries: { 152 | type: "array", 153 | items: { 154 | type: "object", 155 | properties: { 156 | country: { 157 | type: "string", 158 | description: "The country of the resident" 159 | }, 160 | hasID: { 161 | type: "string" 162 | } 163 | }, 164 | required: ["country", "hasID"] 165 | }, 166 | minItems: 1, 167 | maxItems: 5 168 | } 169 | }, 170 | required: ["countries"] 171 | } 172 | }; 173 | 174 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 175 | let isValid = yupschema.isValidSync({ 176 | isTaxResidentOnly: "true", 177 | countries: [ 178 | { 179 | country: "Singapore", 180 | idReason: "", 181 | idNoExplanation: "" 182 | } 183 | ] 184 | }); 185 | expect(isValid).toBeTruthy(); 186 | 187 | isValid = yupschema.isValidSync({ 188 | isTaxResidentOnly: "false", 189 | countries: [ 190 | { 191 | country: "Singapore", 192 | idReason: "", 193 | idNoExplanation: "", 194 | hasID: "asdasdasd" 195 | } 196 | ] 197 | }); 198 | expect(isValid).toBeTruthy(); 199 | 200 | isValid = yupschema.isValidSync({ 201 | isTaxResidentOnly: "false", 202 | countries: [ 203 | { 204 | country: "Singapore", 205 | idReason: "", 206 | idNoExplanation: "" 207 | } 208 | ] 209 | }); 210 | expect(isValid).toBeFalsy(); 211 | }); 212 | 213 | it("should validate $ref items", () => { 214 | const schema: JSONSchema = { 215 | $schema: "http://json-schema.org/draft-07/schema#", 216 | $id: "crs", 217 | description: "CRS", 218 | type: "object", 219 | definitions: { 220 | country: { 221 | type: "object", 222 | properties: { 223 | country: { 224 | type: "string" 225 | }, 226 | hasID: { 227 | type: "string" 228 | } 229 | }, 230 | required: ["country", "hasID"] 231 | } 232 | }, 233 | properties: { 234 | isTaxResidentOnly: { 235 | type: "string" 236 | } 237 | }, 238 | required: ["isTaxResidentOnly"], 239 | if: { 240 | properties: { 241 | isTaxResidentOnly: { 242 | type: "string", 243 | const: "false" 244 | } 245 | } 246 | }, 247 | then: { 248 | properties: { 249 | countries: { 250 | type: "array", 251 | items: { 252 | $ref: "#/definitions/country" 253 | }, 254 | minItems: 1, 255 | maxItems: 5 256 | } 257 | }, 258 | required: ["countries"] 259 | } 260 | }; 261 | 262 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 263 | let isValid = yupschema.isValidSync({ 264 | isTaxResidentOnly: "true", 265 | countries: [ 266 | { 267 | country: "Singapore" 268 | } 269 | ] 270 | }); 271 | 272 | expect(isValid).toBeTruthy(); 273 | 274 | isValid = yupschema.isValidSync({ 275 | isTaxResidentOnly: "true" 276 | }); 277 | expect(isValid).toBeTruthy(); 278 | 279 | isValid = yupschema.isValidSync({ 280 | isTaxResidentOnly: "false", 281 | countries: [ 282 | { 283 | country: "Singapore", 284 | hasID: "TEST" 285 | } 286 | ] 287 | }); 288 | expect(isValid).toBeTruthy(); 289 | 290 | isValid = yupschema.isValidSync({ 291 | isTaxResidentOnly: "false", 292 | countries: [ 293 | { 294 | country: "Singapore", 295 | hasID: "TEST" 296 | }, 297 | { 298 | country: "Singapore", 299 | hasID: "TEST" 300 | }, 301 | { 302 | country: "Singapore", 303 | hasID: "TEST" 304 | }, 305 | { 306 | country: "Singapore", 307 | hasID: "TEST" 308 | }, 309 | { 310 | country: "Singapore", 311 | hasID: "TEST" 312 | }, 313 | { 314 | country: "Singapore", 315 | hasID: "TEST" 316 | } 317 | ] 318 | }); 319 | expect(isValid).toBeFalsy(); 320 | 321 | isValid = yupschema.isValidSync({ 322 | isTaxResidentOnly: "false", 323 | countries: [] 324 | }); 325 | expect(isValid).toBeFalsy(); 326 | 327 | isValid = yupschema.isValidSync({ 328 | isTaxResidentOnly: "false", 329 | countries: [ 330 | { 331 | country: "Singapore" 332 | } 333 | ] 334 | }); 335 | expect(isValid).toBeFalsy(); 336 | }); 337 | 338 | it("should validate nested conditions", () => { 339 | const schema: JSONSchema = { 340 | $schema: "http://json-schema.org/draft-07/schema#", 341 | $id: "crs", 342 | description: "CRS", 343 | type: "object", 344 | definitions: { 345 | country: { 346 | type: "object", 347 | properties: { 348 | country: { 349 | type: "string", 350 | minLength: 1, 351 | maxLength: 30 352 | }, 353 | hasID: { 354 | type: "string", 355 | minLength: 1, 356 | maxLength: 8 357 | } 358 | }, 359 | required: ["country", "hasID"], 360 | if: { 361 | properties: { 362 | hasID: { 363 | const: "true" 364 | } 365 | } 366 | }, 367 | then: { 368 | properties: { 369 | id: { 370 | type: "string", 371 | minLength: 1, 372 | maxLength: 8 373 | } 374 | }, 375 | required: ["id"] 376 | }, 377 | else: { 378 | properties: { 379 | idReason: { 380 | type: "string", 381 | minLength: 1, 382 | maxLength: 50 383 | } 384 | }, 385 | required: ["idReason"], 386 | if: { 387 | properties: { 388 | idReason: { 389 | const: "UNOBTAINABLE" 390 | } 391 | } 392 | }, 393 | then: { 394 | properties: { 395 | idNoExplanation: { 396 | type: "string", 397 | minLength: 1, 398 | maxLength: 8 399 | } 400 | }, 401 | required: ["idNoExplanation"] 402 | } 403 | } 404 | } 405 | }, 406 | properties: { 407 | isTaxResidentOnly: { 408 | type: "string" 409 | } 410 | }, 411 | required: ["isTaxResidentOnly"], 412 | if: { 413 | properties: { 414 | isTaxResidentOnly: { 415 | type: "string", 416 | const: "false" 417 | } 418 | } 419 | }, 420 | then: { 421 | properties: { 422 | countries: { 423 | type: "array", 424 | items: { 425 | $ref: "#/definitions/country" 426 | }, 427 | minItems: 1, 428 | maxItems: 5 429 | } 430 | }, 431 | required: ["countries"] 432 | } 433 | }; 434 | 435 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 436 | let isValid = yupschema.isValidSync({ 437 | isTaxResidentOnly: "false", 438 | countries: [ 439 | { 440 | country: "Singapore", 441 | hasID: "true", 442 | id: "TEST" 443 | } 444 | ] 445 | }); 446 | 447 | expect(isValid).toBeTruthy(); 448 | 449 | isValid = yupschema.isValidSync({ 450 | isTaxResidentOnly: "false", 451 | countries: [ 452 | { 453 | country: "Singapore", 454 | hasID: "false", 455 | idReason: "TEST" 456 | } 457 | ] 458 | }); 459 | 460 | expect(isValid).toBeTruthy(); 461 | 462 | isValid = yupschema.isValidSync({ 463 | isTaxResidentOnly: "false", 464 | countries: [ 465 | { 466 | country: "Singapore", 467 | hasID: "false" 468 | } 469 | ] 470 | }); 471 | 472 | expect(isValid).toBeFalsy(); 473 | 474 | isValid = yupschema.isValidSync({ 475 | isTaxResidentOnly: "false", 476 | countries: [ 477 | { 478 | country: "Singapore", 479 | hasID: "false", 480 | idReason: "UNOBTAINABLE" 481 | } 482 | ] 483 | }); 484 | 485 | expect(isValid).toBeFalsy(); 486 | 487 | isValid = yupschema.isValidSync({ 488 | isTaxResidentOnly: "false", 489 | countries: [ 490 | { 491 | country: "Singapore", 492 | hasID: "false", 493 | idReason: "UNOBTAINABLE", 494 | idNoExplanation: "TEST" 495 | } 496 | ] 497 | }); 498 | 499 | expect(isValid).toBeTruthy(); 500 | }); 501 | 502 | it("should validate multiple types", () => { 503 | const schema: JSONSchema = { 504 | type: "object", 505 | $schema: "http://json-schema.org/draft-07/schema#", 506 | $id: "test", 507 | title: "Test", 508 | properties: { 509 | list: { 510 | type: ["array", "null"] 511 | } 512 | } 513 | }; 514 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 515 | 516 | let isValid = yupschema.isValidSync({ 517 | list: ["a"] 518 | }); 519 | expect(isValid).toBeTruthy(); 520 | 521 | isValid = yupschema.isValidSync({ 522 | list: null 523 | }); 524 | expect(isValid).toBeTruthy(); 525 | 526 | isValid = yupschema.isValidSync({ 527 | list: "" 528 | }); 529 | expect(isValid).toBeTruthy(); 530 | }); 531 | }); 532 | -------------------------------------------------------------------------------- /test/yup/if.boolean.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() boolean conditions", () => { 6 | it("should validate all fields with exception to conditional fields", () => { 7 | const schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | consent: { 14 | type: "boolean" 15 | } 16 | }, 17 | required: ["consent"], 18 | if: { 19 | properties: { consent: { type: "boolean", const: true } } 20 | }, 21 | then: { 22 | properties: { 23 | phone: { type: "number" } 24 | } 25 | } 26 | }; 27 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 28 | 29 | let isValid = yupschema.isValidSync({ 30 | consent: false 31 | }); 32 | expect(isValid).toBeTruthy(); 33 | 34 | isValid = yupschema.isValidSync({ 35 | consent: true 36 | }); 37 | expect(isValid).toBeTruthy(); 38 | }); 39 | 40 | it("should validate conditional when dependency matches constant", () => { 41 | let schema: JSONSchema = { 42 | type: "object", 43 | $schema: "http://json-schema.org/draft-07/schema#", 44 | $id: "test", 45 | title: "Test", 46 | properties: { 47 | consent: { 48 | type: "boolean" 49 | } 50 | }, 51 | required: ["consent"], 52 | if: { 53 | properties: { consent: { type: "boolean", const: true } } 54 | }, 55 | then: { 56 | properties: { 57 | phone: { type: "number" } 58 | } 59 | } 60 | }; 61 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 62 | 63 | let isValid = yupschema.isValidSync({ 64 | consent: false 65 | }); 66 | expect(isValid).toBeTruthy(); 67 | 68 | isValid = yupschema.isValidSync({ 69 | consent: true, 70 | phone: 12345 71 | }); 72 | expect(isValid).toBeTruthy(); 73 | 74 | isValid = yupschema.isValidSync({ 75 | consent: true, 76 | phone: "ABC" 77 | }); 78 | expect(isValid).toBeFalsy(); 79 | 80 | schema = { 81 | type: "object", 82 | $schema: "http://json-schema.org/draft-07/schema#", 83 | $id: "test", 84 | title: "Test", 85 | properties: { 86 | consent: { 87 | type: "boolean" 88 | } 89 | }, 90 | required: ["consent"], 91 | if: { 92 | properties: { consent: { type: "boolean", const: true } } 93 | }, 94 | then: { 95 | properties: { 96 | confirm: { type: "boolean" } 97 | } 98 | } 99 | }; 100 | yupschema = convertToYup(schema) as Yup.ObjectSchema; 101 | 102 | isValid = yupschema.isValidSync({ 103 | consent: true, 104 | confirm: true 105 | }); 106 | expect(isValid).toBeTruthy(); 107 | 108 | isValid = yupschema.isValidSync({ 109 | consent: true, 110 | confirm: "ABC" 111 | }); 112 | expect(isValid).toBeFalsy(); 113 | }); 114 | 115 | it("should validate required conditional", () => { 116 | const schema: JSONSchema = { 117 | type: "object", 118 | $schema: "http://json-schema.org/draft-07/schema#", 119 | $id: "test", 120 | title: "Test", 121 | properties: { 122 | consent: { 123 | type: "boolean" 124 | } 125 | }, 126 | required: ["consent"], 127 | if: { 128 | properties: { consent: { type: "boolean", const: true } } 129 | }, 130 | then: { 131 | properties: { 132 | phone: { type: "number" } 133 | }, 134 | required: ["phone"] 135 | } 136 | }; 137 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 138 | 139 | let isValid = yupschema.isValidSync({ 140 | consent: true, 141 | phone: 12345 142 | }); 143 | expect(isValid).toBeTruthy(); 144 | 145 | isValid = yupschema.isValidSync({ 146 | consent: true 147 | }); 148 | expect(isValid).toBeFalsy(); 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /test/yup/if.number.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() number conditions", () => { 6 | it("should validate all fields with exception to conditional fields", () => { 7 | const schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | postcode: { 14 | type: "number", 15 | enum: [3000, 4000] 16 | } 17 | }, 18 | required: ["postcode"], 19 | if: { 20 | properties: { postcode: { type: "number", const: 3000 } } 21 | }, 22 | then: { 23 | properties: { 24 | phone: { type: "number" } 25 | } 26 | } 27 | }; 28 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 29 | 30 | let isValid = yupschema.isValidSync({ 31 | postcode: 3000 32 | }); 33 | expect(isValid).toBeTruthy(); 34 | 35 | isValid = yupschema.isValidSync({ 36 | postcode: 4000 37 | }); 38 | expect(isValid).toBeTruthy(); 39 | }); 40 | 41 | it("should validate conditional when dependency matches constant", () => { 42 | const schema: JSONSchema = { 43 | type: "object", 44 | $schema: "http://json-schema.org/draft-07/schema#", 45 | $id: "test", 46 | title: "Test", 47 | properties: { 48 | postcode: { 49 | type: "number", 50 | enum: [3000, 4000] 51 | } 52 | }, 53 | required: ["postcode"], 54 | if: { 55 | properties: { postcode: { type: "number", const: 3000 } } 56 | }, 57 | then: { 58 | properties: { 59 | productId: { type: "number" } 60 | } 61 | } 62 | }; 63 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 64 | 65 | let isValid = yupschema.isValidSync({ 66 | postcode: 3000, 67 | productId: 123145 68 | }); 69 | expect(isValid).toBeTruthy(); 70 | 71 | isValid = yupschema.isValidSync({ 72 | postcode: 3000, 73 | productId: "AA" 74 | }); 75 | expect(isValid).toBeFalsy(); 76 | }); 77 | 78 | it("should validate conditional when dependency matches enum", () => { 79 | const schema: JSONSchema = { 80 | type: "object", 81 | $schema: "http://json-schema.org/draft-07/schema#", 82 | $id: "test", 83 | title: "Test", 84 | properties: { 85 | postcode: { 86 | type: "number", 87 | enum: [3000, 4000, 2000] 88 | } 89 | }, 90 | required: ["postcode"], 91 | if: { 92 | properties: { postcode: { type: "number", enum: [3000, 2000] } } 93 | }, 94 | then: { 95 | properties: { 96 | productId: { type: "number" } 97 | } 98 | } 99 | }; 100 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 101 | 102 | let isValid = yupschema.isValidSync({ 103 | postcode: 3000, 104 | productId: 123145 105 | }); 106 | expect(isValid).toBeTruthy(); 107 | 108 | isValid = yupschema.isValidSync({ 109 | postcode: 2000, 110 | productId: 123145 111 | }); 112 | expect(isValid).toBeTruthy(); 113 | 114 | isValid = yupschema.isValidSync({ 115 | postcode: 2000 116 | }); 117 | expect(isValid).toBeTruthy(); 118 | 119 | isValid = yupschema.isValidSync({ 120 | postcode: 3000, 121 | productId: "AA" 122 | }); 123 | expect(isValid).toBeFalsy(); 124 | 125 | isValid = yupschema.isValidSync({ 126 | postcode: 2000, 127 | productId: "AA" 128 | }); 129 | expect(isValid).toBeFalsy(); 130 | }); 131 | 132 | it("should validate conditional when dependency matches minimum number", () => { 133 | const schema: JSONSchema = { 134 | type: "object", 135 | $schema: "http://json-schema.org/draft-07/schema#", 136 | $id: "test", 137 | title: "Test", 138 | properties: { 139 | products: { 140 | type: "number" 141 | } 142 | }, 143 | required: ["products"], 144 | if: { 145 | properties: { products: { type: "number", minimum: 100 } } 146 | }, 147 | then: { 148 | properties: { 149 | productId: { type: "number", pattern: "^[0-9]*$" } 150 | } 151 | } 152 | }; 153 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 154 | 155 | let isValid = yupschema.isValidSync({ 156 | products: 101, 157 | productId: 123145 158 | }); 159 | expect(isValid).toBeTruthy(); 160 | 161 | isValid = yupschema.isValidSync({ 162 | products: 101, 163 | productId: "AA" 164 | }); 165 | expect(isValid).toBeFalsy(); 166 | 167 | isValid = yupschema.isValidSync({ 168 | products: 99 169 | }); 170 | expect(isValid).toBeTruthy(); 171 | }); 172 | 173 | it("should validate conditional when dependency matches maximum number", () => { 174 | const schema: JSONSchema = { 175 | type: "object", 176 | $schema: "http://json-schema.org/draft-07/schema#", 177 | $id: "test", 178 | title: "Test", 179 | properties: { 180 | products: { 181 | type: "number" 182 | } 183 | }, 184 | required: ["products"], 185 | if: { 186 | properties: { products: { type: "number", maximum: 100 } } 187 | }, 188 | then: { 189 | properties: { 190 | productId: { type: "number" } 191 | } 192 | } 193 | }; 194 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 195 | 196 | let isValid = yupschema.isValidSync({ 197 | products: 99, 198 | productId: 123145 199 | }); 200 | expect(isValid).toBeTruthy(); 201 | 202 | isValid = yupschema.isValidSync({ 203 | products: 101, 204 | productId: "AA" 205 | }); 206 | expect(isValid).toBeTruthy(); 207 | 208 | isValid = yupschema.isValidSync({ 209 | products: 99 210 | }); 211 | expect(isValid).toBeTruthy(); 212 | }); 213 | 214 | it("should validate conditional when dependency matches a multiple of", () => { 215 | const schema: JSONSchema = { 216 | type: "object", 217 | $schema: "http://json-schema.org/draft-07/schema#", 218 | $id: "test", 219 | title: "Test", 220 | properties: { 221 | products: { 222 | type: "number" 223 | } 224 | }, 225 | required: ["products"], 226 | if: { 227 | properties: { products: { type: "number", multipleOf: 100 } } 228 | }, 229 | then: { 230 | properties: { 231 | productId: { type: "number" } 232 | } 233 | } 234 | }; 235 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 236 | 237 | let isValid = yupschema.isValidSync({ 238 | products: 100, 239 | productId: 123145 240 | }); 241 | expect(isValid).toBeTruthy(); 242 | 243 | isValid = yupschema.isValidSync({ 244 | products: 500, 245 | productId: "AA" 246 | }); 247 | expect(isValid).toBeFalsy(); 248 | 249 | isValid = yupschema.isValidSync({ 250 | products: 200 251 | }); 252 | expect(isValid).toBeTruthy(); 253 | }); 254 | 255 | it("should validate required conditionals", () => { 256 | const schema: JSONSchema = { 257 | type: "object", 258 | $schema: "http://json-schema.org/draft-07/schema#", 259 | $id: "test", 260 | title: "Test", 261 | properties: { 262 | products: { 263 | type: "number" 264 | } 265 | }, 266 | required: ["products"], 267 | if: { 268 | properties: { products: { type: "number", const: 100 } } 269 | }, 270 | then: { 271 | properties: { 272 | productId: { type: "number" } 273 | }, 274 | required: ["productId"] 275 | } 276 | }; 277 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 278 | 279 | let isValid = yupschema.isValidSync({ 280 | products: 100, 281 | productId: 123145 282 | }); 283 | expect(isValid).toBeTruthy(); 284 | 285 | isValid = yupschema.isValidSync({ 286 | products: 100 287 | }); 288 | expect(isValid).toBeFalsy(); 289 | }); 290 | 291 | it("should validate conditional constant when dependency matches constant", () => { 292 | const schema: JSONSchema = { 293 | type: "object", 294 | $schema: "http://json-schema.org/draft-07/schema#", 295 | $id: "test", 296 | title: "Test", 297 | properties: { 298 | postcode: { 299 | type: "number", 300 | enum: [3000, 4000] 301 | } 302 | }, 303 | required: ["postcode"], 304 | if: { 305 | properties: { postcode: { type: "number", const: 3000 } } 306 | }, 307 | then: { 308 | properties: { 309 | productId: { type: "number", const: 123 } 310 | } 311 | } 312 | }; 313 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 314 | 315 | let isValid = yupschema.isValidSync({ 316 | postcode: 3000, 317 | productId: 123 318 | }); 319 | expect(isValid).toBeTruthy(); 320 | 321 | isValid = yupschema.isValidSync({ 322 | postcode: 3000, 323 | productId: 456 324 | }); 325 | expect(isValid).toBeFalsy(); 326 | }); 327 | 328 | it("should validate conditional enum when dependency matches constant", () => { 329 | const schema: JSONSchema = { 330 | type: "object", 331 | $schema: "http://json-schema.org/draft-07/schema#", 332 | $id: "test", 333 | title: "Test", 334 | properties: { 335 | postcode: { 336 | type: "number", 337 | enum: [3000, 4000] 338 | } 339 | }, 340 | required: ["postcode"], 341 | if: { 342 | properties: { postcode: { type: "number", const: 3000 } } 343 | }, 344 | then: { 345 | properties: { 346 | productId: { type: "number", enum: [123, 456] } 347 | } 348 | } 349 | }; 350 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 351 | 352 | let isValid = yupschema.isValidSync({ 353 | postcode: 3000, 354 | productId: 123 355 | }); 356 | expect(isValid).toBeTruthy(); 357 | 358 | isValid = yupschema.isValidSync({ 359 | postcode: 3000, 360 | productId: 456 361 | }); 362 | expect(isValid).toBeTruthy(); 363 | 364 | isValid = yupschema.isValidSync({ 365 | postcode: 3000, 366 | productId: 789 367 | }); 368 | expect(isValid).toBeFalsy(); 369 | }); 370 | 371 | it("should validate conditional minimum number when dependency matches constant", () => { 372 | const schema: JSONSchema = { 373 | type: "object", 374 | $schema: "http://json-schema.org/draft-07/schema#", 375 | $id: "test", 376 | title: "Test", 377 | properties: { 378 | postcode: { 379 | type: "number", 380 | enum: [3000, 4000] 381 | } 382 | }, 383 | required: ["postcode"], 384 | if: { 385 | properties: { postcode: { type: "number", const: 3000 } } 386 | }, 387 | then: { 388 | properties: { 389 | productId: { type: "number", minimum: 100 } 390 | } 391 | } 392 | }; 393 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 394 | 395 | let isValid = yupschema.isValidSync({ 396 | postcode: 3000, 397 | productId: 101 398 | }); 399 | expect(isValid).toBeTruthy(); 400 | 401 | isValid = yupschema.isValidSync({ 402 | postcode: 3000, 403 | productId: 99 404 | }); 405 | expect(isValid).toBeFalsy(); 406 | 407 | isValid = yupschema.isValidSync({ 408 | postcode: 4000 409 | }); 410 | expect(isValid).toBeTruthy(); 411 | }); 412 | 413 | it("should validate conditional maximum number when dependency matches constant", () => { 414 | const schema: JSONSchema = { 415 | type: "object", 416 | $schema: "http://json-schema.org/draft-07/schema#", 417 | $id: "test", 418 | title: "Test", 419 | properties: { 420 | postcode: { 421 | type: "number", 422 | enum: [3000, 4000] 423 | } 424 | }, 425 | required: ["postcode"], 426 | if: { 427 | properties: { postcode: { type: "number", const: 3000 } } 428 | }, 429 | then: { 430 | properties: { 431 | productId: { type: "number", maximum: 100 } 432 | } 433 | } 434 | }; 435 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 436 | 437 | let isValid = yupschema.isValidSync({ 438 | postcode: 3000, 439 | productId: 99 440 | }); 441 | expect(isValid).toBeTruthy(); 442 | 443 | isValid = yupschema.isValidSync({ 444 | postcode: 3000, 445 | productId: 101 446 | }); 447 | expect(isValid).toBeFalsy(); 448 | 449 | isValid = yupschema.isValidSync({ 450 | postcode: 4000 451 | }); 452 | expect(isValid).toBeTruthy(); 453 | }); 454 | 455 | it("should validate conditional multiple of number when dependency matches constant", () => { 456 | const schema: JSONSchema = { 457 | type: "object", 458 | $schema: "http://json-schema.org/draft-07/schema#", 459 | $id: "test", 460 | title: "Test", 461 | properties: { 462 | postcode: { 463 | type: "number", 464 | enum: [3000, 4000] 465 | } 466 | }, 467 | required: ["postcode"], 468 | if: { 469 | properties: { postcode: { type: "number", const: 3000 } } 470 | }, 471 | then: { 472 | properties: { 473 | productId: { type: "number", multipleOf: 100 } 474 | } 475 | } 476 | }; 477 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 478 | 479 | let isValid = yupschema.isValidSync({ 480 | postcode: 3000, 481 | productId: 500 482 | }); 483 | expect(isValid).toBeTruthy(); 484 | 485 | isValid = yupschema.isValidSync({ 486 | postcode: 3000, 487 | productId: 101 488 | }); 489 | expect(isValid).toBeFalsy(); 490 | 491 | isValid = yupschema.isValidSync({ 492 | postcode: 4000 493 | }); 494 | expect(isValid).toBeTruthy(); 495 | }); 496 | 497 | it("should validate other conditional", () => { 498 | const schema: JSONSchema = { 499 | type: "object", 500 | $schema: "http://json-schema.org/draft-07/schema#", 501 | $id: "test", 502 | title: "Test", 503 | properties: { 504 | postcode: { 505 | type: "number", 506 | enum: [3000, 4000] 507 | } 508 | }, 509 | required: ["postcode"], 510 | if: { 511 | properties: { postcode: { type: "number", const: 3000 } } 512 | }, 513 | then: { 514 | properties: { 515 | productId: { type: "number", multipleOf: 11 } 516 | } 517 | }, 518 | else: { 519 | properties: { 520 | productId: { type: "number", multipleOf: 3 } 521 | } 522 | } 523 | }; 524 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 525 | let isValid = yupschema.isValidSync({ 526 | postcode: 4000, 527 | productId: 5 528 | }); 529 | expect(isValid).toBeFalsy(); 530 | }); 531 | }); 532 | -------------------------------------------------------------------------------- /test/yup/integer.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() integer", () => { 6 | it("should validate integer type", () => { 7 | const schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | phone: { 14 | type: "integer" 15 | } 16 | } 17 | }; 18 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 19 | 20 | let isValid = yupschema.isValidSync({ 21 | phone: 123 22 | }); 23 | expect(isValid).toBeTruthy(); 24 | 25 | isValid = yupschema.isValidSync({ 26 | phone: "123" 27 | }); 28 | expect(isValid).toBeFalsy(); 29 | 30 | isValid = yupschema.isValidSync({ 31 | phone: 123.16 32 | }); 33 | expect(isValid).toBeFalsy(); 34 | }); 35 | 36 | it("should use title as label in error message", () => { 37 | const fieldTitle = "Phone Number"; 38 | const schema: JSONSchema = { 39 | type: "object", 40 | $schema: "http://json-schema.org/draft-07/schema#", 41 | $id: "test", 42 | title: "Test", 43 | properties: { 44 | phone: { 45 | type: "integer", 46 | title: fieldTitle 47 | } 48 | } 49 | }; 50 | 51 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 52 | let errorMessage; 53 | try { 54 | errorMessage = yupschema.validateSync({ phone: "phone" }); 55 | } catch (e) { 56 | errorMessage = e.errors[0]; 57 | } 58 | expect(errorMessage).toBe(`${fieldTitle} is not of type integer`); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/yup/null.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() null", () => { 6 | it("should allow null values", () => { 7 | const schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | name: { 14 | type: "null" 15 | } 16 | } 17 | }; 18 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 19 | let isValid = yupschema.isValidSync({ 20 | name: null 21 | }); 22 | expect(isValid).toBeTruthy(); 23 | 24 | isValid = yupschema.isValidSync({ 25 | name: "" 26 | }); 27 | expect(isValid).toBeTruthy(); 28 | 29 | isValid = yupschema.isValidSync({ 30 | name: 0 31 | }); 32 | expect(isValid).toBeTruthy(); 33 | 34 | isValid = yupschema.isValidSync({ 35 | name: false 36 | }); 37 | expect(isValid).toBeTruthy(); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/yup/number.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() number", () => { 6 | it("should validate number type", () => { 7 | const schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | name: { 14 | type: "number" 15 | } 16 | } 17 | }; 18 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 19 | let isValid = yupschema.isValidSync({ 20 | name: 123 21 | }); 22 | expect(isValid).toBeTruthy(); 23 | isValid = yupschema.isValidSync({ 24 | name: "abv" 25 | }); 26 | expect(isValid).toBeFalsy(); 27 | }); 28 | 29 | it("should validate multiple types", () => { 30 | const schema: JSONSchema = { 31 | type: "object", 32 | $schema: "http://json-schema.org/draft-07/schema#", 33 | $id: "test", 34 | title: "Test", 35 | properties: { 36 | name: { 37 | type: ["number", "null"] 38 | } 39 | } 40 | }; 41 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 42 | 43 | let isValid = yupschema.isValidSync({ 44 | name: 123 45 | }); 46 | expect(isValid).toBeTruthy(); 47 | 48 | isValid = yupschema.isValidSync({ 49 | name: null 50 | }); 51 | expect(isValid).toBeTruthy(); 52 | 53 | isValid = yupschema.isValidSync({ 54 | name: "" 55 | }); 56 | expect(isValid).toBeTruthy(); 57 | }); 58 | 59 | it("should validate required", () => { 60 | const schema: JSONSchema = { 61 | type: "object", 62 | $schema: "http://json-schema.org/draft-07/schema#", 63 | $id: "test", 64 | title: "Test", 65 | properties: { 66 | years: { 67 | type: "number" 68 | } 69 | }, 70 | required: ["years"] 71 | }; 72 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 73 | let valid; 74 | 75 | valid = yupschema.isValidSync({ 76 | years: 5 77 | }); 78 | expect(valid).toBeTruthy(); 79 | 80 | valid = yupschema.isValidSync({}); 81 | expect(valid).toBeFalsy(); 82 | try { 83 | valid = yupschema.validateSync({}); 84 | } catch (e) { 85 | valid = e.errors[0]; 86 | } 87 | expect(valid).toBe("Years is required"); 88 | }); 89 | 90 | it("should validate minimum", () => { 91 | const schema: JSONSchema = { 92 | type: "object", 93 | $schema: "http://json-schema.org/draft-07/schema#", 94 | $id: "test", 95 | title: "Test", 96 | properties: { 97 | years: { 98 | type: "number", 99 | minimum: 5 100 | } 101 | } 102 | }; 103 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 104 | let valid; 105 | 106 | valid = yupschema.isValidSync({ 107 | years: 6 108 | }); 109 | expect(valid).toBeTruthy(); 110 | 111 | // testing rule is inclusive 112 | valid = yupschema.isValidSync({ years: 5 }); 113 | expect(valid).toBeTruthy(); 114 | 115 | try { 116 | valid = yupschema.validateSync({ years: 4 }); 117 | } catch (e) { 118 | valid = e.errors[0]; 119 | } 120 | expect(valid).toBe("Years requires a minimum value of 5"); 121 | }); 122 | 123 | it("should validate exclusive minimum", () => { 124 | const schema: JSONSchema = { 125 | type: "object", 126 | $schema: "http://json-schema.org/draft-07/schema#", 127 | $id: "test", 128 | title: "Test", 129 | properties: { 130 | years: { 131 | type: "number", 132 | exclusiveMinimum: 5 133 | } 134 | } 135 | }; 136 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 137 | let valid; 138 | 139 | valid = yupschema.isValidSync({ 140 | years: 6 141 | }); 142 | expect(valid).toBeTruthy(); 143 | 144 | // testing rule is exclusive 145 | valid = yupschema.isValidSync({ years: 5 }); 146 | expect(valid).toBeFalsy(); 147 | 148 | try { 149 | valid = yupschema.validateSync({ years: 4 }); 150 | } catch (e) { 151 | valid = e.errors[0]; 152 | } 153 | expect(valid).toBe("Years requires a exclusive minimum value of 5"); 154 | }); 155 | 156 | it("should validate maximum", () => { 157 | const schema: JSONSchema = { 158 | type: "object", 159 | $schema: "http://json-schema.org/draft-07/schema#", 160 | $id: "test", 161 | title: "Test", 162 | properties: { 163 | years: { 164 | type: "number", 165 | maximum: 5 166 | } 167 | } 168 | }; 169 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 170 | let valid; 171 | 172 | // testing rule is inclusive 173 | valid = yupschema.isValidSync({ 174 | years: 5 175 | }); 176 | expect(valid).toBeTruthy(); 177 | 178 | valid = yupschema.isValidSync({ years: 7 }); 179 | expect(valid).toBeFalsy(); 180 | 181 | try { 182 | valid = yupschema.validateSync({ years: 7 }); 183 | } catch (e) { 184 | valid = e.errors[0]; 185 | } 186 | expect(valid).toBe("Years cannot exceed a maximum value of 5"); 187 | }); 188 | 189 | it("should validate exclusive maximum", () => { 190 | const schema: JSONSchema = { 191 | type: "object", 192 | $schema: "http://json-schema.org/draft-07/schema#", 193 | $id: "test", 194 | title: "Test", 195 | properties: { 196 | years: { 197 | type: "number", 198 | exclusiveMaximum: 5 199 | } 200 | } 201 | }; 202 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 203 | let valid; 204 | 205 | valid = yupschema.isValidSync({ 206 | years: 4 207 | }); 208 | expect(valid).toBeTruthy(); 209 | 210 | // testing rule is exclusive 211 | valid = yupschema.isValidSync({ years: 5 }); 212 | expect(valid).toBeFalsy(); 213 | 214 | try { 215 | valid = yupschema.validateSync({ years: 7 }); 216 | } catch (e) { 217 | valid = e.errors[0]; 218 | } 219 | expect(valid).toBe("Years cannot exceed a exclusive maximum value of 5"); 220 | }); 221 | 222 | it("should validate minimum and maximum", () => { 223 | const schema: JSONSchema = { 224 | type: "object", 225 | $schema: "http://json-schema.org/draft-07/schema#", 226 | $id: "test", 227 | title: "Test", 228 | properties: { 229 | years: { 230 | type: "number", 231 | minimum: 5, 232 | maximum: 10 233 | } 234 | } 235 | }; 236 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 237 | let valid; 238 | 239 | valid = yupschema.isValidSync({ 240 | years: 6 241 | }); 242 | expect(valid).toBeTruthy(); 243 | 244 | valid = yupschema.isValidSync({ years: 11 }); 245 | expect(valid).toBeFalsy(); 246 | }); 247 | 248 | it("should validate exclusive minimum and exclusive maximum", () => { 249 | const schema: JSONSchema = { 250 | type: "object", 251 | $schema: "http://json-schema.org/draft-07/schema#", 252 | $id: "test", 253 | title: "Test", 254 | properties: { 255 | years: { 256 | type: "number", 257 | exclusiveMinimum: 5, 258 | exclusiveMaximum: 10 259 | } 260 | } 261 | }; 262 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 263 | let valid; 264 | 265 | valid = yupschema.isValidSync({ 266 | years: 5 267 | }); 268 | expect(valid).toBeFalsy(); 269 | 270 | valid = yupschema.isValidSync({ years: 10 }); 271 | expect(valid).toBeFalsy(); 272 | 273 | valid = yupschema.isValidSync({ 274 | years: 7 275 | }); 276 | expect(valid).toBeTruthy(); 277 | 278 | valid = yupschema.isValidSync({ years: 8 }); 279 | expect(valid).toBeTruthy(); 280 | }); 281 | 282 | it("should throw error when maximum and exclusive maximum are used together", () => { 283 | const schema: JSONSchema = { 284 | type: "object", 285 | $schema: "http://json-schema.org/draft-07/schema#", 286 | $id: "test", 287 | title: "Test", 288 | properties: { 289 | years: { 290 | type: "number", 291 | exclusiveMaximum: 5, 292 | maximum: 5 293 | } 294 | } 295 | }; 296 | expect(() => { 297 | convertToYup(schema); 298 | }).toThrowError( 299 | "Maximum and exclusive maximum keys can not be used together" 300 | ); 301 | }); 302 | 303 | it("should throw error when minimum and exclusive minimum are used together", () => { 304 | const schema: JSONSchema = { 305 | type: "object", 306 | $schema: "http://json-schema.org/draft-07/schema#", 307 | $id: "test", 308 | title: "Test", 309 | properties: { 310 | years: { 311 | type: "number", 312 | exclusiveMinimum: 5, 313 | minimum: 5 314 | } 315 | } 316 | }; 317 | expect(() => { 318 | convertToYup(schema); 319 | }).toThrowError( 320 | "Minimum and exclusive minimum keys can not be used together" 321 | ); 322 | }); 323 | 324 | it("should validate multiple of", () => { 325 | const schema: JSONSchema = { 326 | type: "object", 327 | $schema: "http://json-schema.org/draft-07/schema#", 328 | $id: "test", 329 | title: "Test", 330 | properties: { 331 | years: { 332 | type: "number", 333 | multipleOf: 5 334 | } 335 | } 336 | }; 337 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 338 | let valid; 339 | 340 | // testing rule is inclusive 341 | valid = yupschema.isValidSync({ 342 | years: 5 343 | }); 344 | expect(valid).toBeTruthy(); 345 | 346 | valid = yupschema.isValidSync({ years: 7 }); 347 | expect(valid).toBeFalsy(); 348 | 349 | try { 350 | valid = yupschema.validateSync({ years: 7 }); 351 | } catch (e) { 352 | valid = e.errors[0]; 353 | } 354 | expect(valid).toBe("Years requires a multiple of 5"); 355 | }); 356 | 357 | it("should validate constant", () => { 358 | const schema: JSONSchema = { 359 | type: "object", 360 | $schema: "http://json-schema.org/draft-07/schema#", 361 | $id: "test", 362 | title: "Test", 363 | properties: { 364 | years: { 365 | type: "number", 366 | const: 2 367 | } 368 | } 369 | }; 370 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 371 | let valid; 372 | 373 | valid = yupschema.isValidSync({ 374 | years: 2 375 | }); 376 | expect(valid).toBeTruthy(); 377 | 378 | valid = yupschema.isValidSync({ years: 3 }); 379 | expect(valid).toBeFalsy(); 380 | try { 381 | valid = yupschema.validateSync({ years: 3 }); 382 | } catch (e) { 383 | valid = e.errors[0]; 384 | } 385 | expect(valid).toBe("Years does not match constant"); 386 | }); 387 | 388 | it("should validate falsy constant", () => { 389 | const schema: JSONSchema = { 390 | type: "object", 391 | $schema: "http://json-schema.org/draft-07/schema#", 392 | $id: "test", 393 | title: "Test", 394 | properties: { 395 | years: { 396 | type: "number", 397 | const: 0 398 | } 399 | } 400 | }; 401 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 402 | let valid = yupschema.isValidSync({ 403 | years: 0 404 | }); 405 | expect(valid).toBeTruthy(); 406 | }); 407 | 408 | it("should validate enum", () => { 409 | const schema: JSONSchema = { 410 | type: "object", 411 | $schema: "http://json-schema.org/draft-07/schema#", 412 | $id: "test", 413 | title: "Test", 414 | properties: { 415 | years: { 416 | type: "number", 417 | enum: [2, 3] 418 | } 419 | } 420 | }; 421 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 422 | let valid; 423 | 424 | valid = yupschema.isValidSync({ 425 | years: 2 426 | }); 427 | expect(valid).toBeTruthy(); 428 | 429 | valid = yupschema.isValidSync({ 430 | years: 3 431 | }); 432 | expect(valid).toBeTruthy(); 433 | 434 | valid = yupschema.isValidSync({ years: 4 }); 435 | expect(valid).toBeFalsy(); 436 | try { 437 | valid = yupschema.validateSync({ years: 4 }); 438 | } catch (e) { 439 | valid = e.errors[0]; 440 | } 441 | expect(valid).toBe("Years does not match any of the enumerables"); 442 | }); 443 | 444 | it("should set default value", () => { 445 | const defaultValue = 7; 446 | const schema: JSONSchema = { 447 | type: "object", 448 | $schema: "http://json-schema.org/draft-07/schema#", 449 | $id: "test", 450 | title: "Test", 451 | properties: { 452 | age: { 453 | type: "number", 454 | default: defaultValue 455 | } 456 | }, 457 | required: ["age"] 458 | }; 459 | 460 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 461 | let isValid = yupschema 462 | .test( 463 | "is-default", 464 | "${path} is default value", 465 | (value) => value.age === defaultValue 466 | ) 467 | .isValidSync({}); 468 | expect(isValid).toBeTruthy(); 469 | }); 470 | 471 | it("should use title as label in error message", () => { 472 | const fieldTitle = "My Age"; 473 | const schema: JSONSchema = { 474 | type: "object", 475 | $schema: "http://json-schema.org/draft-07/schema#", 476 | $id: "test", 477 | title: "Test", 478 | properties: { 479 | age: { 480 | type: "number", 481 | title: fieldTitle 482 | } 483 | } 484 | }; 485 | 486 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 487 | let errorMessage; 488 | try { 489 | errorMessage = yupschema.validateSync({ age: "Forty" }); 490 | } catch (e) { 491 | errorMessage = e.errors[0]; 492 | } 493 | expect(errorMessage).toBe(`${fieldTitle} is not of type number`); 494 | }); 495 | }); 496 | -------------------------------------------------------------------------------- /test/yup/object.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchema } from "../../src/schema" 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() object", () => { 6 | it("should validate object type", () => { 7 | const schema: JSONSchema = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | items: { 14 | type: "object" 15 | } 16 | } 17 | }; 18 | 19 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 20 | let isValid = yupschema.isValidSync({ 21 | items: {} 22 | }); 23 | expect(isValid).toBeTruthy(); 24 | 25 | isValid = yupschema.isValidSync({ 26 | items: { a: "a" } 27 | }); 28 | expect(isValid).toBeTruthy(); 29 | 30 | isValid = yupschema.isValidSync({ 31 | items: "test123" 32 | }); 33 | expect(isValid).toBeFalsy(); 34 | }); 35 | it("should validate required", () => { 36 | const schema: JSONSchema = { 37 | type: "object", 38 | $schema: "http://json-schema.org/draft-07/schema#", 39 | $id: "test", 40 | title: "Test", 41 | properties: { 42 | items: { 43 | type: "object" 44 | } 45 | }, 46 | required: ["items"] 47 | }; 48 | 49 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 50 | let isValid = yupschema.isValidSync({}); 51 | expect(isValid).toBeFalsy(); 52 | }); 53 | 54 | it("should validate nested object type", () => { 55 | let schema: JSONSchema = { 56 | type: "object", 57 | $schema: "http://json-schema.org/draft-07/schema#", 58 | $id: "test", 59 | title: "Test", 60 | properties: { 61 | address: { 62 | type: "object", 63 | properties: { 64 | state: { type: "string" }, 65 | postcode: { type: "string" } 66 | } 67 | } 68 | } 69 | }; 70 | 71 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 72 | 73 | let isValid = yupschema.isValidSync({ 74 | address: { 75 | state: "VIC", 76 | postcode: "3030" 77 | } 78 | }); 79 | expect(isValid).toBeTruthy(); 80 | 81 | isValid = yupschema.isValidSync({ 82 | address: { 83 | state: "VIC", 84 | postcode: null 85 | } 86 | }); 87 | expect(isValid).toBeFalsy(); 88 | 89 | schema = { 90 | type: "object", 91 | $schema: "http://json-schema.org/draft-07/schema#", 92 | $id: "test", 93 | title: "Test", 94 | properties: { 95 | address: { 96 | type: "object", 97 | properties: { 98 | mailingAddress: { 99 | type: "object", 100 | properties: { 101 | state: { type: "string" }, 102 | postcode: { type: "string" } 103 | } 104 | } 105 | } 106 | } 107 | } 108 | }; 109 | 110 | yupschema = convertToYup(schema) as Yup.ObjectSchema; 111 | 112 | isValid = yupschema.isValidSync({ 113 | address: { 114 | mailingAddress: { 115 | state: "VIC", 116 | postcode: "3030" 117 | } 118 | } 119 | }); 120 | expect(isValid).toBeTruthy(); 121 | 122 | isValid = yupschema.isValidSync({ 123 | address: { 124 | mailingAddress: { 125 | state: "VIC", 126 | postcode: null 127 | } 128 | } 129 | }); 130 | expect(isValid).toBeFalsy(); 131 | }); 132 | 133 | it("should validate multiple types", () => { 134 | const schema: JSONSchema = { 135 | type: "object", 136 | $schema: "http://json-schema.org/draft-07/schema#", 137 | $id: "test", 138 | title: "Test", 139 | properties: { 140 | address: { 141 | type: ["object", "null"], 142 | properties: { 143 | state: { type: "string" } 144 | } 145 | } 146 | } 147 | }; 148 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 149 | 150 | let isValid = yupschema.isValidSync({ 151 | address: { 152 | state: "VIC" 153 | } 154 | }); 155 | expect(isValid).toBeTruthy(); 156 | 157 | isValid = yupschema.isValidSync({ 158 | address: null 159 | }); 160 | expect(isValid).toBeTruthy(); 161 | }); 162 | 163 | it("should validate fields from definitions", () => { 164 | let schema: JSONSchema = { 165 | type: "object", 166 | $schema: "http://json-schema.org/draft-07/schema#", 167 | $id: "test", 168 | title: "Test", 169 | definitions: { 170 | address: { 171 | type: "object", 172 | properties: { 173 | street_address: { type: "string" }, 174 | city: { type: "string" }, 175 | state: { type: "string" } 176 | }, 177 | required: ["street_address", "city", "state"] 178 | } 179 | }, 180 | properties: { 181 | mailingAddress: { 182 | $ref: "#/definitions/address" 183 | } 184 | } 185 | }; 186 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 187 | 188 | let isValid = yupschema.isValidSync({ 189 | mailingAddress: { 190 | street_address: "test", 191 | city: "Melbourne", 192 | state: "VIC" 193 | } 194 | }); 195 | expect(isValid).toBeTruthy(); 196 | 197 | isValid = yupschema.isValidSync({ 198 | mailingAddress: { 199 | street_address: "test", 200 | city: "Melbourne", 201 | state: null 202 | } 203 | }); 204 | expect(isValid).toBeFalsy(); 205 | }); 206 | 207 | it("should validate fields from definitions", () => { 208 | let schema: JSONSchema = { 209 | type: "object", 210 | $schema: "http://json-schema.org/draft-07/schema#", 211 | $id: "test", 212 | title: "Test", 213 | definitions: undefined, 214 | properties: { 215 | mailingAddress: { 216 | $ref: "#/definitions/address" 217 | } 218 | } 219 | }; 220 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 221 | 222 | let isValid = yupschema.isValidSync({ 223 | mailingAddress: { 224 | street_address: "test", 225 | city: "Melbourne", 226 | state: "VIC" 227 | } 228 | }); 229 | expect(isValid).toBeTruthy(); 230 | }); 231 | 232 | it("should validate fields using definition id", () => { 233 | let schema: JSONSchema = { 234 | type: "object", 235 | $schema: "http://json-schema.org/draft-07/schema#", 236 | $id: "test", 237 | title: "Test", 238 | definitions: { 239 | address: { 240 | $id: "#address", 241 | type: "object", 242 | properties: { 243 | street_address: { type: "string" }, 244 | city: { type: "string" }, 245 | state: { type: "string" } 246 | }, 247 | required: ["street_address", "city", "state"] 248 | } 249 | }, 250 | properties: { 251 | mailingAddress: { 252 | $ref: "#address" 253 | } 254 | } 255 | }; 256 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 257 | 258 | let isValid = yupschema.isValidSync({ 259 | mailingAddress: { 260 | street_address: "test", 261 | city: "Melbourne", 262 | state: "VIC" 263 | } 264 | }); 265 | expect(isValid).toBeTruthy(); 266 | 267 | isValid = yupschema.isValidSync({ 268 | mailingAddress: { 269 | street_address: "test", 270 | city: "Melbourne", 271 | state: null 272 | } 273 | }); 274 | expect(isValid).toBeFalsy(); 275 | }); 276 | 277 | it("should bypass field if not an object", () => { 278 | const schema: JSONSchema = { 279 | type: "object", 280 | $schema: "http://json-schema.org/draft-07/schema#", 281 | $id: "test", 282 | title: "Test", 283 | properties: { 284 | name: { 285 | type: "string" 286 | }, 287 | test: true 288 | } 289 | }; 290 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 291 | 292 | let isValid = yupschema.isValidSync({ 293 | name: "test" 294 | }); 295 | expect(isValid).toBeTruthy(); 296 | 297 | isValid = yupschema.isValidSync({ 298 | name: null 299 | }); 300 | expect(isValid).toBeFalsy(); 301 | }); 302 | 303 | it("should validate multiple types", () => { 304 | const schema: JSONSchema = { 305 | type: "object", 306 | $schema: "http://json-schema.org/draft-07/schema#", 307 | $id: "test", 308 | title: "Test", 309 | properties: { 310 | address: { 311 | type: ["object", "null"] 312 | } 313 | } 314 | }; 315 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 316 | 317 | let isValid = yupschema.isValidSync({ 318 | address: {} 319 | }); 320 | expect(isValid).toBeTruthy(); 321 | 322 | isValid = yupschema.isValidSync({ 323 | address: null 324 | }); 325 | expect(isValid).toBeTruthy(); 326 | 327 | isValid = yupschema.isValidSync({ 328 | address: "" 329 | }); 330 | expect(isValid).toBeTruthy(); 331 | }); 332 | 333 | it("should throw error when type key is missing", () => { 334 | const schema: JSONSchema = { 335 | type: "object", 336 | $schema: "http://json-schema.org/draft-07/schema#", 337 | $id: "test", 338 | title: "Test", 339 | properties: { 340 | address: { 341 | const: "test" 342 | } 343 | } 344 | }; 345 | expect(() => { 346 | convertToYup(schema) as Yup.ObjectSchema; 347 | }).toThrowError("Type key is missing"); 348 | }); 349 | 350 | it("should return undefined when properties does not exist", () => { 351 | const schema: JSONSchema = { 352 | type: "object", 353 | $schema: "http://json-schema.org/draft-07/schema#", 354 | $id: "test", 355 | title: "Test" 356 | }; 357 | const yupschema = convertToYup(schema); 358 | expect(yupschema).toBeUndefined(); 359 | }); 360 | }); 361 | -------------------------------------------------------------------------------- /test/yup/string.test.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from "yup"; 2 | import type { JSONSchemaExtended } from "../../src/schema"; 3 | import convertToYup from "../../src"; 4 | 5 | describe("convertToYup() string", () => { 6 | it("should validate string type", () => { 7 | const schema: JSONSchemaExtended = { 8 | type: "object", 9 | $schema: "http://json-schema.org/draft-07/schema#", 10 | $id: "test", 11 | title: "Test", 12 | properties: { 13 | name: { 14 | type: "string" 15 | } 16 | } 17 | }; 18 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 19 | 20 | let isValid = yupschema.isValidSync({ 21 | name: "test" 22 | }); 23 | expect(isValid).toBeTruthy(); 24 | 25 | isValid = yupschema.isValidSync({ 26 | name: null 27 | }); 28 | expect(isValid).toBeFalsy(); 29 | }); 30 | 31 | it("should validate multiple types", () => { 32 | const schema: JSONSchemaExtended = { 33 | type: "object", 34 | $schema: "http://json-schema.org/draft-07/schema#", 35 | $id: "test", 36 | title: "Test", 37 | properties: { 38 | name: { 39 | type: ["string", "null"] 40 | } 41 | } 42 | }; 43 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 44 | 45 | let isValid = yupschema.isValidSync({ 46 | name: "test" 47 | }); 48 | expect(isValid).toBeTruthy(); 49 | 50 | isValid = yupschema.isValidSync({ 51 | name: null 52 | }); 53 | expect(isValid).toBeTruthy(); 54 | 55 | isValid = yupschema.isValidSync({ 56 | name: "" 57 | }); 58 | expect(isValid).toBeTruthy(); 59 | }); 60 | 61 | it("should throw error if value type is not one of multiple types", () => { 62 | const schema: JSONSchemaExtended = { 63 | type: "object", 64 | $schema: "http://json-schema.org/draft-07/schema#", 65 | $id: "test", 66 | title: "Test", 67 | properties: { 68 | name: { 69 | type: ["string", "null"] 70 | } 71 | } 72 | }; 73 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 74 | expect(() => { 75 | yupschema.isValidSync({ 76 | name: [] 77 | }); 78 | }).toBeTruthy(); 79 | }); 80 | 81 | it("should validate required", () => { 82 | const schema: JSONSchemaExtended = { 83 | type: "object", 84 | $schema: "http://json-schema.org/draft-07/schema#", 85 | $id: "test", 86 | title: "Test", 87 | properties: { 88 | name: { 89 | type: "string" 90 | } 91 | }, 92 | required: ["name"] 93 | }; 94 | 95 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 96 | let valid = yupschema.isValidSync({ 97 | name: "test" 98 | }); 99 | expect(valid).toBeTruthy(); 100 | 101 | valid = yupschema.isValidSync({}); 102 | expect(valid).toBeFalsy(); 103 | 104 | let errorMessage; 105 | try { 106 | errorMessage = yupschema.validateSync({}); 107 | } catch (e) { 108 | errorMessage = e.errors[0]; 109 | } 110 | expect(errorMessage).toBe("Name is required"); 111 | }); 112 | 113 | it("should validate minimum character length", () => { 114 | const schema: JSONSchemaExtended = { 115 | type: "object", 116 | $schema: "http://json-schema.org/draft-07/schema#", 117 | $id: "test", 118 | title: "Test", 119 | properties: { 120 | name: { 121 | type: "string", 122 | minLength: 6 123 | } 124 | } 125 | }; 126 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 127 | let valid = yupschema.isValidSync({ 128 | name: "abcdef" 129 | }); 130 | expect(valid).toBeTruthy(); 131 | 132 | valid = yupschema.isValidSync({ name: "abcd" }); 133 | expect(valid).toBeFalsy(); 134 | 135 | valid = yupschema.isValidSync({ name: null }); 136 | expect(valid).toBeFalsy(); 137 | 138 | let errorMessage; 139 | try { 140 | errorMessage = yupschema.validateSync({ name: "abcd" }); 141 | } catch (e) { 142 | errorMessage = e.errors[0]; 143 | } 144 | expect(errorMessage).toBe("Name requires a minimum of 6 characters"); 145 | }); 146 | 147 | it("should validate maximum character length", () => { 148 | const schema: JSONSchemaExtended = { 149 | type: "object", 150 | $schema: "http://json-schema.org/draft-07/schema#", 151 | $id: "test", 152 | title: "Test", 153 | properties: { 154 | name: { 155 | type: "string", 156 | maxLength: 6 157 | } 158 | } 159 | }; 160 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 161 | let valid = yupschema.isValidSync({ 162 | name: "abcdef" 163 | }); 164 | expect(valid).toBeTruthy(); 165 | 166 | valid = yupschema.isValidSync({ name: "abcdefgh" }); 167 | expect(valid).toBeFalsy(); 168 | 169 | valid = yupschema.isValidSync({ name: null }); 170 | expect(valid).toBeFalsy(); 171 | 172 | let errorMessage; 173 | try { 174 | errorMessage = yupschema.validateSync({ name: "abcdefgh" }); 175 | } catch (e) { 176 | errorMessage = e.errors[0]; 177 | } 178 | expect(errorMessage).toBe("Name cannot exceed a maximum of 6 characters"); 179 | }); 180 | 181 | it("should validate pattern", () => { 182 | const schema: JSONSchemaExtended = { 183 | type: "object", 184 | $schema: "http://json-schema.org/draft-07/schema#", 185 | $id: "test", 186 | title: "Test", 187 | properties: { 188 | name: { 189 | type: "string", 190 | pattern: "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$" 191 | } 192 | } 193 | }; 194 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 195 | let valid = yupschema.isValidSync({ 196 | name: "555-1212" 197 | }); 198 | expect(valid).toBeTruthy(); 199 | 200 | valid = yupschema.isValidSync({ 201 | name: "(888)555-1212" 202 | }); 203 | expect(valid).toBeTruthy(); 204 | 205 | valid = yupschema.isValidSync({ name: "(888)555-1212 ext. 532" }); 206 | expect(valid).toBeFalsy(); 207 | 208 | valid = yupschema.isValidSync({ name: null }); 209 | expect(valid).toBeFalsy(); 210 | 211 | let errorMessage; 212 | try { 213 | errorMessage = yupschema.validateSync({ name: "(800)FLOWERS" }); 214 | } catch (e) { 215 | errorMessage = e.errors[0]; 216 | } 217 | expect(errorMessage).toBe("Name is an incorrect format"); 218 | }); 219 | 220 | it("should validate constant", () => { 221 | const schema: JSONSchemaExtended = { 222 | type: "object", 223 | $schema: "http://json-schema.org/draft-07/schema#", 224 | $id: "test", 225 | title: "Test", 226 | properties: { 227 | name: { 228 | type: "string", 229 | const: "test" 230 | } 231 | } 232 | }; 233 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 234 | 235 | let isValid = yupschema.isValidSync({ 236 | name: "test" 237 | }); 238 | expect(isValid).toBeTruthy(); 239 | 240 | isValid = yupschema.isValidSync({ 241 | name: "blah" 242 | }); 243 | expect(isValid).toBeFalsy(); 244 | 245 | let errorMessage; 246 | try { 247 | errorMessage = yupschema.validateSync({ name: "(800)FLOWERS" }); 248 | } catch (e) { 249 | errorMessage = e.errors[0]; 250 | } 251 | expect(errorMessage).toBe("Name does not match constant"); 252 | }); 253 | 254 | it("should validate enum", () => { 255 | const schema: JSONSchemaExtended = { 256 | type: "object", 257 | $schema: "http://json-schema.org/draft-07/schema#", 258 | $id: "test", 259 | title: "Test", 260 | properties: { 261 | name: { 262 | type: "string", 263 | enum: ["test", "other"] 264 | } 265 | } 266 | }; 267 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 268 | 269 | let isValid = yupschema.isValidSync({ 270 | name: "test" 271 | }); 272 | expect(isValid).toBeTruthy(); 273 | 274 | isValid = yupschema.isValidSync({ 275 | name: "other" 276 | }); 277 | expect(isValid).toBeTruthy(); 278 | 279 | isValid = yupschema.isValidSync({ 280 | name: "blah" 281 | }); 282 | expect(isValid).toBeFalsy(); 283 | 284 | let errorMessage; 285 | try { 286 | errorMessage = yupschema.validateSync({ name: "(800)FLOWERS" }); 287 | } catch (e) { 288 | errorMessage = e.errors[0]; 289 | } 290 | expect(errorMessage).toBe("Name does not match any of the enumerables"); 291 | }); 292 | 293 | it("should set default value", () => { 294 | const defaultValue = "Roger"; 295 | const schema: JSONSchemaExtended = { 296 | type: "object", 297 | $schema: "http://json-schema.org/draft-07/schema#", 298 | $id: "test", 299 | title: "Test", 300 | properties: { 301 | name: { 302 | type: "string", 303 | default: defaultValue 304 | } 305 | }, 306 | required: ["name"] 307 | }; 308 | 309 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 310 | let isValid = yupschema 311 | .test( 312 | "is-default", 313 | "${path} is default value", 314 | (value) => value.name === defaultValue 315 | ) 316 | .isValidSync({}); 317 | 318 | expect(isValid).toBeTruthy(); 319 | }); 320 | 321 | it("should not validate empty value if field has multiple types", () => { 322 | const schema: JSONSchemaExtended = { 323 | type: "object", 324 | $schema: "http://json-schema.org/draft-07/schema#", 325 | $id: "test", 326 | title: "Test", 327 | properties: { 328 | name: { 329 | type: ["string", "null"], 330 | pattern: "[\\-0-9A-Za-z]" 331 | } 332 | } 333 | }; 334 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 335 | 336 | let isValid = yupschema.isValidSync({ 337 | name: "" 338 | }); 339 | expect(isValid).toBeTruthy(); 340 | 341 | isValid = yupschema.isValidSync({ 342 | name: null 343 | }); 344 | expect(isValid).toBeTruthy(); 345 | }); 346 | 347 | it("should validate regex", () => { 348 | const schema: JSONSchemaExtended = { 349 | type: "object", 350 | $schema: "http://json-schema.org/draft-07/schema#", 351 | $id: "test", 352 | title: "Test", 353 | properties: { 354 | name: { 355 | type: "string", 356 | regex: "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$" 357 | } 358 | } 359 | }; 360 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 361 | let valid = yupschema.isValidSync({ 362 | name: "555-1212" 363 | }); 364 | expect(valid).toBeTruthy(); 365 | 366 | valid = yupschema.isValidSync({ 367 | name: "(888)555-1212" 368 | }); 369 | expect(valid).toBeTruthy(); 370 | 371 | valid = yupschema.isValidSync({ name: "(888)555-1212 ext. 532" }); 372 | expect(valid).toBeFalsy(); 373 | 374 | valid = yupschema.isValidSync({ name: null }); 375 | expect(valid).toBeFalsy(); 376 | 377 | let errorMessage; 378 | try { 379 | errorMessage = yupschema.validateSync({ name: "(800)FLOWERS" }); 380 | } catch (e) { 381 | errorMessage = e.errors[0]; 382 | } 383 | expect(errorMessage).toBe("Name is an incorrect format"); 384 | }); 385 | 386 | it("should use title as label in error message", () => { 387 | const fieldTitle = "First Name"; 388 | const schema: JSONSchemaExtended = { 389 | type: "object", 390 | $schema: "http://json-schema.org/draft-07/schema#", 391 | $id: "test", 392 | title: "Test", 393 | properties: { 394 | name: { 395 | type: "string", 396 | title: fieldTitle 397 | } 398 | }, 399 | required: ["name"] 400 | }; 401 | 402 | let yupschema = convertToYup(schema) as Yup.ObjectSchema; 403 | let errorMessage; 404 | try { 405 | errorMessage = yupschema.validateSync({ name: "" }); 406 | } catch (e) { 407 | errorMessage = e.errors[0]; 408 | } 409 | expect(errorMessage).toBe(`${fieldTitle} is required`); 410 | }); 411 | 412 | 413 | it("should validate multiple types in a nested object", () => { 414 | const schema: JSONSchemaExtended = { 415 | type: "object", 416 | $schema: "http://json-schema.org/draft-07/schema#", 417 | $id: "test", 418 | title: "Test", 419 | properties: { 420 | address: { 421 | type: "object", 422 | properties: { 423 | name: { 424 | type: ["string", "null"] 425 | } 426 | } 427 | } 428 | } 429 | }; 430 | const yupschema = convertToYup(schema) as Yup.ObjectSchema; 431 | 432 | expect(() => { 433 | yupschema.isValidSync({ 434 | address: { 435 | name: "" 436 | } 437 | }); 438 | }).toBeTruthy(); 439 | 440 | expect(() => { 441 | yupschema.isValidSync({ 442 | address: { 443 | name: null 444 | } 445 | }); 446 | }).toBeTruthy(); 447 | 448 | }); 449 | 450 | }); 451 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "module": "commonjs", 5 | "target": "ES6", 6 | "jsx": "preserve", 7 | "esModuleInterop": true, 8 | "sourceMap": true, 9 | "allowJs": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noImplicitAny": true, 14 | "strictNullChecks": true, 15 | "suppressImplicitAnyIndexErrors": true, 16 | "noUnusedLocals": true, 17 | "declaration": true, 18 | "allowSyntheticDefaultImports": true, 19 | "experimentalDecorators": true, 20 | "emitDecoratorMetadata": true, 21 | "skipLibCheck": true, 22 | "resolveJsonModule": true, 23 | "isolatedModules": true, 24 | "noEmit": false, 25 | "lib": ["DOM", "ES6"], 26 | "rootDir": "src", 27 | "outDir": "dist", 28 | "moduleResolution": "node", 29 | "typeRoots": ["node_modules/@types", "types"] 30 | }, 31 | "include": ["src"] 32 | } 33 | --------------------------------------------------------------------------------