From cde0f76a62d2efee8457281d9cf06726394ed00d Mon Sep 17 00:00:00 2001 From: Victor Tostes Date: Wed, 12 Jan 2022 01:10:18 -0300 Subject: [PATCH 1/2] adding deprecated function to BaseSchema --- src/BaseSchema.js | 15 ++++ src/BaseSchema.test.js | 165 +++++++++++++++++++++++++++++++++++++++++ src/MixedSchema.js | 1 - src/ObjectSchema.js | 2 +- src/utils.js | 3 + 5 files changed, 184 insertions(+), 2 deletions(-) diff --git a/src/BaseSchema.js b/src/BaseSchema.js index 92b08a6..1503545 100644 --- a/src/BaseSchema.js +++ b/src/BaseSchema.js @@ -4,6 +4,7 @@ const { omit, isFluentSchema, last, + isBoolean, isUniq, patchIdsWithParentId, REQUIRED, @@ -178,6 +179,20 @@ const BaseSchema = ( return setAttribute({ schema, ...options }, ['writeOnly', value, 'boolean']) }, + /** + * The value of deprecated can be left empty to indicate the property is deprecated. + * It takes an optional boolean which can be used to explicitly set deprecated true/false. + * + * {@link https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.3|reference} + * @param {Boolean} isDeprecated + * @returns {BaseSchema} + */ + deprecated: (isDeprecated) => { + if(isDeprecated && !isBoolean(isDeprecated)) throw new FluentSchemaError("'deprecated' must be a boolean value") + const value = isDeprecated !== undefined ? isDeprecated : true + return setAttribute({ schema, ...options }, ['deprecated', value, 'boolean']) + }, + /** * Required has to be chained to a property: * Examples: diff --git a/src/BaseSchema.test.js b/src/BaseSchema.test.js index d429762..f61defa 100644 --- a/src/BaseSchema.test.js +++ b/src/BaseSchema.test.js @@ -212,6 +212,171 @@ describe('BaseSchema', () => { }) }) + describe('deprecated', () => { + it('valid', () => { + expect( + BaseSchema() + .deprecated(true) + .valueOf().deprecated + ).toEqual(true) + }) + it('invalid', () => { + expect( + () => + BaseSchema() + .deprecated('somethingNotBoolean') + .valueOf().deprecated + ).toThrowError( + new S.FluentSchemaError( + "'deprecated' must be a boolean value" + ) + ) + }) + it('valid with no value', () => { + expect( + BaseSchema() + .deprecated() + .valueOf().deprecated + ).toEqual(true) + }) + it('can be set to false', () => { + expect( + BaseSchema() + .deprecated(false) + .valueOf().deprecated + ).toEqual(false) + }) + it('property', () => { + expect( + S.object() + .prop('foo', S.string()) + .prop('bar', S.string().deprecated()) + .valueOf() + ).toEqual({ + $schema: 'http://json-schema.org/draft-07/schema#', + properties: { + bar: { type: 'string', deprecated: true }, + foo: { type: 'string' } + }, + type: 'object', + }) + }); + it('object', () => { + expect( + S.object() + .prop('foo', S.string()) + .prop('bar', S + .object() + .deprecated() + .prop('raz', S.string()) + .prop('iah', S.number())) + .valueOf() + ).toEqual({ + $schema: 'http://json-schema.org/draft-07/schema#', + properties: { + foo: { type: 'string' }, + bar: { + type: 'object', + deprecated: true, + properties: { + raz: { type: 'string' }, + iah: { type: 'number' } + }, + }, + }, + type: 'object', + }) + }); + it('object property', () => { + expect( + S.object() + .prop('foo', S.string()) + .prop('bar', S + .object() + .prop('raz', S.string().deprecated()) + .prop('iah', S.number()) + ) + .valueOf() + ).toEqual({ + $schema: 'http://json-schema.org/draft-07/schema#', + properties: { + foo: { type: 'string' }, + bar: { + type: 'object', + properties: { + raz: { type: 'string', deprecated: true }, + iah: { type: 'number' } + }, + }, + }, + type: 'object', + }) + }); + it('array', () => { + expect( + S.object() + .prop('foo', S.string()) + .prop('bar', S + .array() + .deprecated() + .items(S.number()) + ) + .valueOf() + ).toEqual({ + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + foo: { type: 'string' }, + bar: { + type: 'array', + deprecated: true, + items: { type: 'number' } + } + }, + }) + }); + it('array item', () => { + expect( + S.object() + .prop('foo', S.string()) + .prop('bar', S + .array() + .items([ + S.object().prop('zoo', S.string()).prop('biz', S.string()), + S.object().deprecated().prop('zal', S.string()).prop('boz', S.string()) + ]) + ) + .valueOf() + ).toEqual({ + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + foo: { type: 'string' }, + bar: { + type: 'array', + items: [ + { + type: 'object', + properties: { + zoo: { type: 'string' }, + biz: { type: 'string' } + } + }, + { + type: 'object', + deprecated: true, + properties: { + zal: { type: 'string' }, + boz: { type: 'string' } + } + } + ] + } + }, + }) + }); + }) + describe('enum', () => { it('valid', () => { const value = ['VALUE'] diff --git a/src/MixedSchema.js b/src/MixedSchema.js index dbdc932..f95373a 100644 --- a/src/MixedSchema.js +++ b/src/MixedSchema.js @@ -1,5 +1,4 @@ 'use strict' -const { BaseSchema } = require('./BaseSchema') const { NullSchema } = require('./NullSchema') const { BooleanSchema } = require('./BooleanSchema') const { StringSchema } = require('./StringSchema') diff --git a/src/ObjectSchema.js b/src/ObjectSchema.js index 7777b1b..0529e15 100644 --- a/src/ObjectSchema.js +++ b/src/ObjectSchema.js @@ -361,7 +361,7 @@ const ObjectSchema = ({ schema = initialState, ...options } = {}) => { return ObjectSchema({ schema: { ...schema, - properties: schema.properties.filter(p => !properties.includes(p.name)), + properties: schema.properties.filter(({ name }) => !properties.includes(name)), required: schema.required.filter(p => !properties.includes(p)), }, ...options, diff --git a/src/utils.js b/src/utils.js index 344251d..1394a96 100644 --- a/src/utils.js +++ b/src/utils.js @@ -20,6 +20,8 @@ const last = array => { const isUniq = array => array.filter((v, i, a) => a.indexOf(v) === i).length === array.length; +const isBoolean = value => 'boolean' === typeof value; + const omit = (obj, props) => Object.entries(obj).reduce((memo, [key, value]) => { if (props.includes(key)) return memo @@ -218,6 +220,7 @@ module.exports = { FluentSchemaError, last, isUniq, + isBoolean, flat, toArray, omit, From 546c36968ab06d267ad2c23c326f33e8345188bf Mon Sep 17 00:00:00 2001 From: Victor Tostes Date: Tue, 18 Jan 2022 09:08:14 -0300 Subject: [PATCH 2/2] adding new methods to type definitions and validation file --- src/FluentJSONSchema.d.ts | 2 ++ src/types/index.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/FluentJSONSchema.d.ts b/src/FluentJSONSchema.d.ts index 5468698..2d5ec35 100644 --- a/src/FluentJSONSchema.d.ts +++ b/src/FluentJSONSchema.d.ts @@ -20,6 +20,7 @@ export interface BaseSchema { oneOf: (schema: Array) => T readOnly: (isReadOnly?: boolean) => T writeOnly: (isWriteOnly?: boolean) => T + deprecated: (isDeprecated?: boolean) => T isFluentSchema: boolean isFluentJSONSchema: boolean raw: (fragment: any) => T @@ -124,6 +125,7 @@ export interface ObjectSchema extends BaseSchema { propertyNames: (value: JSONSchema) => ObjectSchema extend: (schema: ObjectSchema | ExtendedSchema) => ExtendedSchema only: (properties: string[]) => ObjectSchema + without: (properties: string[]) => ObjectSchema dependentRequired: (options: DependentRequiredOptions) => ObjectSchema dependentSchemas: (options: DependentSchemaOptions) => ObjectSchema } diff --git a/src/types/index.ts b/src/types/index.ts index f5f9845..046f388 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -75,6 +75,17 @@ const userSubsetSchema = largeUserSchema.only(['username', 'password']) console.log('user subset:', JSON.stringify(userSubsetSchema.valueOf())) +const personSchema = S.object() + .prop('name', S.string()) + .prop('age', S.number()) + .prop('id', S.string().format('uuid')) + .prop('createdAt', S.string().format('time')) + .prop('updatedAt', S.string().format('time')) + +const bodySchema = personSchema.without(['createdAt', 'updatedAt']) + +console.log('person subset:', JSON.stringify(bodySchema.valueOf())) + try { S.object().prop('foo', 'boom!' as any) } catch (e) { @@ -117,3 +128,10 @@ const dependentSchemas = S.object() .valueOf() console.log('dependentRequired:\n', JSON.stringify(dependentSchemas)) + +const deprecatedSchema = S.object() + .deprecated() + .prop('foo', S.string().deprecated()) + .valueOf() + +console.log('deprecatedSchema:\n', JSON.stringify(deprecatedSchema))