Skip to content
This repository has been archived by the owner on Oct 31, 2024. It is now read-only.

add description and required control #32

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Disclamer

this repository is based on https://github.com/aspecto-io/genson-js
contains some minor tweaks that allow use of `description` tag and make `required` collection more flexible

# genson-js
![Build](https://github.com/aspecto-io/genson-js/workflows/Build/badge.svg) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![TypeScript](https://badgen.net/npm/types/env-var)](http://www.typescriptlang.org/) [![NPM version](https://img.shields.io/npm/v/genson-js.svg)](https://www.npmjs.com/package/genson-js)

Expand Down
69 changes: 50 additions & 19 deletions src/schema-builder.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,83 @@
import { ValueType, Schema, SchemaGenOptions } from './types';

function createSchemaFor(value: any, options?: SchemaGenOptions): Schema {
function createSchemaFor(value: any, options?: SchemaGenOptions, description? : string, enums? : string[]): Schema {
let additions = {};
if(enums){
additions["enum"] = enums;
}
if (description) {
additions["description"] = description;
}
switch (typeof value) {
case 'number':
if (Number.isInteger(value)) {
return { type: ValueType.Integer };
return { type: ValueType.Integer, ...additions };
}
return { type: ValueType.Number };
return { type: ValueType.Number, ...additions };
case 'boolean':
return { type: ValueType.Boolean };
return { type: ValueType.Boolean, ...additions };
case 'string':
return { type: ValueType.String };
return { type: ValueType.String, ...additions };
case 'object':
if (value === null) {
return { type: ValueType.Null };
return { type: ValueType.Null, ...additions };
}
if (Array.isArray(value)) {
return createSchemaForArray(value, options);
return createSchemaForArray(value, options, description);
}
return createSchemaForObject(value, options);
return createSchemaForObject(value, options, description);
}
}

function createSchemaForArray(arr: Array<any>, options?: SchemaGenOptions): Schema {
function createSchemaForArray(arr: Array<any>, options?: SchemaGenOptions, description? : string): Schema {
if (arr.length === 0) {
return { type: ValueType.Array };
return description ? { type: ValueType.Array, description } : { type: ValueType.Array };
}
const elementSchemas = arr.map((value) => createSchemaFor(value, options));
const items = combineSchemas(elementSchemas);
return { type: ValueType.Array, items };
return description ? { type: ValueType.Array, items, description } : { type: ValueType.Array, items };
}

function createSchemaForObject(obj: Object, options?: SchemaGenOptions): Schema {
function createSchemaForObject(obj: Object, options?: SchemaGenOptions, description? : string): Schema {
const keys = Object.keys(obj);
const nonFunctionKeys = keys.filter((key) => typeof obj[key] !== 'function');
if (keys.length === 0) {
return {

return description ? {
type: ValueType.Object,
};
description
} : { type: ValueType.Object };
}
const properties = Object.entries(obj).reduce((props, [key, val]) => {
props[key] = createSchemaFor(val, options);
let description : string | undefined = undefined;
let enums : string[] | undefined = undefined;

if(obj["addDescriptionOfProperty"]){
description = obj["addDescriptionOfProperty"](key);
}

if(obj["addEnumForProperty"]){
enums = obj["addEnumForProperty"](key);
}

if(typeof val !== 'function'){
props[key] = createSchemaFor(val, options, description, enums);
}
return props;
}, {});

const schema: Schema = { type: ValueType.Object, properties };
const schema: Schema = description ? { type: ValueType.Object, properties, description } : { type: ValueType.Object, properties };

let requiredKeys = nonFunctionKeys;

if (!options?.noRequired) {
schema.required = keys;
if(obj["excludeFromRequired"]){
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

special, reserved function. Use to specify which properties should be excluded from the required of specific object schema. terurns array of names of properties that should not be included in required array

const excluded : string[] = obj["excludeFromRequired"]();
requiredKeys = requiredKeys.filter(x => !excluded.includes(x))
}
schema.required = requiredKeys;
}

return schema;
}

Expand Down Expand Up @@ -231,9 +262,9 @@ function isContainerSchema(schema: Schema): boolean {
// FACADE

export function createSchema(value: any, options?: SchemaGenOptions): Schema {
JSON.stringify(value);//just to catch circular dependencies
if (typeof value === 'undefined') value = null;
const clone = JSON.parse(JSON.stringify(value));
return createSchemaFor(clone, options);
return createSchemaFor(value, options);
}

export function mergeSchemas(schemas: Schema[], options?: SchemaGenOptions): Schema {
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type Schema = {
properties?: Record<string, Schema>;
required?: string[];
anyOf?: Array<Schema>;
description?: string;
};

export type SchemaGenOptions = {
Expand Down
1 change: 1 addition & 0 deletions tests/__snapshots__/schema-builder.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Object {
"lvl1PropObj2": Object {
"properties": Object {
"lvl2PropArr1": Object {
"description": "some description",
"items": Object {
"type": "integer",
},
Expand Down
30 changes: 30 additions & 0 deletions tests/schema-builder.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { type } from 'os';
import { createSchema, mergeSchemas, ValueType, extendSchema, createCompoundSchema } from '../src';
import { pp } from './test-utils';

Expand Down Expand Up @@ -180,13 +181,40 @@ describe('SchemaBuilder', () => {
});

describe('all cases combined', () => {
it('should exclude from required all properties that are returned by excludeFromRequired function', () => {
type TestType = {
notRequired?: string;
required: string;
excludeFromRequired? : () => string[]
};

const value: TestType = {
notRequired: 'sometimes undefined',
required: 'never undefined',
excludeFromRequired: () =>['notRequired']
}

const schema = createSchema(value);
expect(schema).toEqual({
type: 'object',
properties: {
notRequired: { type: 'string' },
required: { type: 'string' },
},
required: ['required'],
});
});

it('should generate valid schemas for complex objects', () => {
const schema = createSchema([
{
lvl1PropNum: 1,
lvl1PropStr: 'second',
addEnumForProperty: (propName: string) => propName === 'lvl1PropStr'? ['first', 'second', 'third'] : undefined,
addDescriptionOfProperty: (propName: string) => propName === 'lvl1PropStr'? 'enum prop' : undefined,
lvl1PropObj1: { lvl2PropArr: [1, 2] },
lvl1PropObj2: {
addDescriptionOfProperty: (propName: string) => propName === 'lvl2PropArr1'? 'some description' : undefined,
lvl2PropNum1: 5,
lvl2PropArr1: [5],
six: null,
Expand Down Expand Up @@ -273,6 +301,8 @@ describe('SchemaBuilder', () => {
},
});
});


});

describe('prototype methods', () => {
Expand Down