epiphone / class-validator-jsonschema Goto Github PK
View Code? Open in Web Editor NEWConvert class-validator-decorated classes into JSON schema
License: MIT License
Convert class-validator-decorated classes into JSON schema
License: MIT License
Hey @epiphone,
Thanks for this package, and for routing-controllers-openapi - it's going to save a huge amount of man-hours on the various TS based API's I'm working on at the moment.
Because I'm using Class Transformer as well as Routing Controllers (with TypeORM and class validator), the models that are being used for the schemas are showing the model property names - however the I need it to show the transformed names.
export class MyCoolModel
{
@IsString()
@Expose({name: `what_a_cool_field`})
@Column({unique: true, type: `text`, nullable: false})
public abstract whatACoolField: string;
}
And obviously in the schema it's showing:
"schemas": {
"MyCoolModel": {
"properties": {
"whatACoolField": {
"type": "string",
"minLength": 1
},
... etc ...
However I'm wanting it to show the "what_a_cool_field" name.
Are there any plans to roll class-transformer support into this library?
Have you got any advice on how to approach this? (I was thinking of having to parsing the class-validator metadata (easy) and swap out the field names from the class-transformer metadata (which I'm having issues with).
Thanks again.
This line is
return { ...schema, enum: [meta.constraints[0]] }
but should be
return { ...schema, const: meta.constraints[0] }
Similar for line 51.
I have the latest version of the package installed with the correct peer dependecy as listed in the package.json but i get this error when I run tsc
node_modules/class-validator-jsonschema/build/options.d.ts:1:13 - error TS1005: '=' expected.
1 import type { MetadataStorage as ClassTransformerMetadataStorage } from 'class-transformer/types/MetadataStorage';
~
node_modules/class-validator-jsonschema/build/options.d.ts:1:73 - error TS1005: ';' expected.
1 import type { MetadataStorage as ClassTransformerMetadataStorage } from 'class-transformer/types/MetadataStorage';
Hey Aleksi,
Another one issue which I spotted is that empty required fields in schema produce not valid output required: []
Error messages from different validators are:
Schema error at components.schemas['Test'].required
should NOT have less than 1 items
limit: 1
https://mermade.org.uk/openapi-converter
status: false
message: 'expected Array [] not to be empty (false negative fail)'
context: '#/components/schemas/Test'
Can it be fixed as well?
Hi,
I am using fastify, i tried the following:
I was able to load the schemas in definitions but now paths object changed.
If I try this then I get paths but definitions are gone.
I wonder what is missing here @epiphone
Hello.
I've found some serious problem with @Exclude
decorator processing on filtering metadata with isExcluded
when I trying to overload some properties on classes inheritance.
Trying to explain on code sample:
// create some basic class
class UserProfile {
@IsString()
name: string;
@IsString()
surname: string;
@IsDate()
birthDate: Date;
}
// declare class with overloaded `birthDate`
class UserProfileSkipDate extends UserProfile {
@Exclude()
@IsOptional()
birthDate: Date;
}
But after creating JSONSchema for UserProfileSkipDate
I see that data:
{
"properties":{
"name":{
"type":"string"
},
"surname":{
"type":"string"
},
"birthDate":{
"oneOf":[
{
"format":"date",
"type":"string"
},
{
"format":"date-time",
"type":"string"
}
]
}
},
"type":"object",
"required":[
"name",
"surname",
"birthDate"
]
}
You may see, that the resulting JSON still contains the birthDate
attributes into properties
and required
.
That happens because filter for metas
value into function validationMetadataArrayToSchemas
detect IsExclude
on the top of list and correctly ignores birthDate
meta the first iteration, but on the next steps still keeps metadata from getInheritedMetadatas
list and in fact restores the default metadata for overloaded property.
I've prepared the PR to fix this bug. Hope you'll approve that.
Imagine classes like these
@JSONSchema()
class RichTextSchema extends BaseContentSchema {
static collectionName: SchemaCollectionName = {
plural: "RichTexts",
singular: "RichText",
};
@IsOptional()
@IsString()
text: string;
}
@JSONSchema()
class TextImageSchema extends RichTextSchema {
@IsOptional()
@IsString()
image: string;
}
By the way "metas" are currently built
const metas = ownMetas
.concat(getInheritedMetadatas(target, metadatas))
.filter(
(propMeta) =>
!(
isExcluded(propMeta, options) ||
isExcluded({ ...propMeta, target }, options)
)
)
Property "image" of sub class TextImageSchema will be output before "text" of parent class RichTextSchema.
I suggest there should be at least an option to be able to output inherited metas before own metas, like
const allMetas = options.inheritedPropertiesFirst ?
getInheritedMetadatas(target, metadatas).concat(ownMetas) :
ownMetas.concat(getInheritedMetadatas(target, metadatas))
const metas = allMetas
.filter(
(propMeta) =>
!(
isExcluded(propMeta, options) ||
isExcluded({ ...propMeta, target }, options)
)
)
In our use case it makes more sense, because we achieve the same "ordering" of properties which helps visualizing them in documentation and react-json-schema-forms
The latest version of class transformer v0.3.2
breaks the use of @validatenested
and @type
, pay attention to the devices
property
Code example:
Withe the current code:
@JSONSchema({
description: 'User payload',
})
export class AccountResponse extends BaseModel {
@Expose()
@IsOptional()
@IsNumber()
@JSONSchema({ description: 'Account unique identifier', example: '1234' })
guid: number
@Expose()
@IsOptional()
@IsString()
@JSONSchema({ description: 'Account name', example: 'katana' })
account: string;
@Expose()
@IsOptional()
@IsString()
@JSONSchema({ description: 'Account email', example: '[email protected]' })
email: string
@Expose()
@IsOptional()
@JSONSchema({ description: 'Current device'})
@Type(() => DeviceResponse)
@ValidateNested()
current_device: DeviceResponse
@Expose()
@IsOptional()
@JSONSchema({ description: 'Account devices' })
@ValidateNested({each: true})
@Type(() => DeviceResponse)
devices: DeviceResponse[]
}
export class DeviceResponse {
@JSONSchema({ description: 'Device unique id', example: '64ae4f16-f7ec-4a44-97bd-efa2dbc63c27' })
@Expose()
@IsUUID()
uuid: string
@JSONSchema({ description: 'Device metadata', example: '{model: xioami mi 8}' })
@Expose()
@IsJSON()
info: object
@JSONSchema({ description: 'If is the master device', example: true })
@IsOptional()
@IsBoolean()
@Expose()
is_master: boolean
}
Output:
AccountResponse:
properties:
guid:
type: number
description: Account unique identifier
example: '1234'
account:
type: string
description: Account name
example: katana
email:
type: string
description: Account email
example: [email protected]
current_device:
$ref: '#/components/schemas/DeviceResponse'
description: Current device
devices:
items:
$ref: '#/components/schemas/Array'
type: array
description: Account devices
With v0.3.1
is working fine with the same input code
Output:
AccountResponse:
properties:
guid:
type: number
description: Account unique identifier
example: '1234'
account:
type: string
description: Account name
example: katana
email:
type: string
description: Account email
example: [email protected]
current_device:
$ref: '#/components/schemas/DeviceResponse'
description: Current device
devices:
items:
$ref: '#/components/schemas/DeviceResponse'
type: array
description: Account devices
class-validator v0.12.0
has been releases as of 18-04-2020. class-validator-jsonschema
is not compatible because of breaking changes, see changelog.
To be specific:
import { ValidationMetadata } from 'class-validator/metadata/ValidationMetadata';
needs to be replaced by
import { ValidationMetadata } from 'class-validator/types/metadata/ValidationMetadata'
Hello
First of all I would like to thank you for this wonderful module. I am very new to class-validator module itself and just started using it in one of our projects and really liked it. I was able to add this module to generate schema from our nestjs project however some of the models we have contain some extra properties that's been used only internally and I am wondering is there anyway to exclude those properties from being added to the schema. E.g if any property is decorated with @exclude , can be hidden from the schema file.
Thank you.
When using the @IsDateString()
decorator I would get the error:
err: "Validation error: data.startTime should match pattern \"d{4}-[01]d-[0-3]dT[0-2]d:[0-5]d:[0-5]d.d+Z?\""
When passing a date value of:
"2021-05-01T12:00:00.000Z"
Due to the regular expression expecting the character 'd' and not a digit.
Hi, thanks a lot for this great package !
I've got one issue with arrays: I always get type $ref: "#/definitions/Array"
. I can reproduce with the following test (just add it and run jest ./__tests__/arrays.test.ts
):
import 'reflect-metadata';
import {Type} from 'class-transformer';
import {getFromContainer, IsArray, MetadataStorage, ValidateNested, ValidationTypes} from 'class-validator';
import {validationMetadatasToSchemas} from '../src';
import {defaultConverters} from '../src/defaultConverters';
describe('arrays', () => {
class Child {
name: string;
}
class Parent {
@ValidateNested({each: true})
@IsArray()
@Type(() => Child)
children: Child[];
@ValidateNested()
@Type(() => Child)
child: Child;
}
it('should define $ref to Child', () => {
console.log(Parent, Child);
const metadatas = (getFromContainer(MetadataStorage) as any).validationMetadatas;
const schemas = validationMetadatasToSchemas(metadatas, {
additionalConverters: {
[ValidationTypes.IS_ARRAY]: (meta, _options) => {
console.log(meta);
return defaultConverters[ValidationTypes.IS_ARRAY];
},
}
});
console.log(JSON.stringify(schemas, null, 4));
});
});
Which prints:
{
"Parent": {
"properties": {
"children": {
"items": {
"$ref": "#/definitions/Array"
},
"type": "array"
},
"child": {
"$ref": "#/definitions/Child"
}
},
"type": "object",
"required": [
"children",
"child"
]
}
}
Any clue ?
import { getFromContainer, IsOptional, IsString, MaxLength, MetadataStorage } from 'class-validator'
import { validationMetadatasToSchemas } from 'class-validator-jsonschema'
class BlogPost {
@IsString() id: string
@IsOptional()
@maxlength(20, { each: true })
tags: string[]
}
{
"name": "openapi3x1",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"start": "node ./dist/server.js"
},
"author": "zoro",
"license": "ISC",
"dependencies": {
"body-parser": "^1.19.0",
"class-validator-jsonschema": "^1.3.0",
"config": "^3.1.0",
"express": "^4.17.1",
"lodash": "^4.17.11",
"routing-controllers-openapi": "^1.7.0"
},
"devDependencies": {
"@types/body-parser": "^1.17.0",
"@types/config": "0.0.34",
"@types/express": "^4.17.0",
"@types/lodash": "^4.14.135",
"@types/node": "^12.0.12"
}
}
Hi there! I'm using class-validator-jsonschema to create multiple .schema.json
files for creating schemas and then other libraries to dereference the schemas and build markdown documentation. The dereferencing only works with a bit of a hack, though: With the default refPointerPrefix
of #/definitions/
the schemas cannot be dereferenced, as class-validator-jsonschema doesn't really put them in #/definitions/
. Instead I now create the schemas as individual files, use refPointerPrefix
as ./
and manually add .schema.json
as a suffic to all #ref
s so the dereferencer can find the files.
It would be great to either have a refPointerSuffix
option to add .schema.json
to all #ref
s, or some guidance on what the preferred way is to link the schemas together and dereference them correctly.
I'm not sure whether this is part of as conditional decorator limitations in the readme. In any case...
The @IsOptional
decorator should be adding anyOf: [{type: someType}, {type: 'null'}]
as well as removing the property from the required
array. It doesn't seem to be doing the former.
I note that internally, class validator uses conditionalValidation
for the IsOptional
decorator. Am I correct that the limitation is that this doesn't work when there are multiple decorators for one field? For example:
@IsOptional()
@IsNumber()
thing: number
@IsOptional()
@IsSting()
otherThing: string
The functionality of IsOptional
depends on the other decorators.
Steps to reproduce:
Observed error:
node_modules/class-validator-jsonschema/build/options.d.ts:1:73 - error TS2307: Cannot find module 'class-transformer/types/MetadataStorage' or its corresponding type declarations.
1 import type { MetadataStorage as ClassTransformerMetadataStorage } from 'class-transformer/types/MetadataStorage';
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
[9:13:38 AM] Found 1 error. Watching for file changes.
Is there a way to convert JSON back to a class? (I want to store the validation schema in a database, and then use it).
I had a look through the source code, and unless I'm missing something, this doesn't appear to be possible?
Hey Aleksi,
Thank you for your quick update. But seems the recent update of dependency now cause issue with type of generated schemas which is not assignable to additionalProperties: Partial<OpenAPIObject>
in routing-controllers-openapi
.
const metadatas = (getFromContainer(MetadataStorage) as any).validationMetadatas;
const schemas = validationMetadatasToSchemas(metadatas, {
refPointerPrefix: '#/components/schemas/',
});
// Parse routing-controllers classes into OpenAPI spec:
const storage = getMetadataArgsStorage();
const spec = routingControllersToSpec(storage, routingControllersOptions, {
components: { schemas }
});
The code above now generates outputs with error:
error TS2345: Argument of type '{ components: { schemas: { [key: string]: SchemaObject; }; }; }' is not assignable to parameter of type 'Partial'.
Types of property 'components' are incompatible.
Type '{ schemas: { [key: string]: SchemaObject; }; }' is not assignable to type 'ComponentsObject'.
Types of property 'schemas' are incompatible.
Type '{ [key: string]: SchemaObject; }' is not assignable to type '{ [schema: string]:SchemaObject; }'.
Could you check please?
There appears to be a regression bug. The instructions provided in the readme work on 3.1.1 but do not work on version 5.0.0
// eslint-disable-next-line max-classes-per-file
import { Type } from 'class-transformer';
import { IsString, ValidateNested } from 'class-validator';
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
const { defaultMetadataStorage } = require('class-transformer/cjs/storage');
class BlogPost {
@IsString()
public words: string;
}
class User {
@ValidateNested({ each: true })
@Type(() => BlogPost)
public blogPosts: BlogPost[];
}
const schemas = validationMetadatasToSchemas({
classTransformerMetadataStorage: defaultMetadataStorage,
});
TypeError: Cannot read properties of undefined (reading 'map')
17 | }
18 |
> 19 | const schemas = validationMetadatasToSchemas({
| ^
20 | classTransformerMetadataStorage: defaultMetadataStorage,
21 | });
22 |
at populateMetadatasWithConstraints (../../common/temp/node_modules/.pnpm/[email protected]_e6kgdsnyya5caxg3ysdyxrqm7a/node_modules/class-validator-jsonschema/src/index.ts:146:20)
at getMetadatasFromStorage (../../common/temp/node_modules/.pnpm/[email protected]_e6kgdsnyya5caxg3ysdyxrqm7a/node_modules/class-validator-jsonschema/src/index.ts:137:23)
at validationMetadatasToSchemas (../../common/temp/node_modules/.pnpm/[email protected]_e6kgdsnyya5caxg3ysdyxrqm7a/node_modules/class-validator-jsonschema/src/index.ts:29:21)
at Object.<anonymous> (src/__tests__/te.test.ts:19:45)
When using version 5.0.0 the only way to call validationMetadatasToSchemas appears to be without any "()" or "({}".
I use latest class-validator 0.11.0, and it log warning during yarn install for class-validator-jsonschema. I think it should upgrade to class-validator version 0.11.0 in class-validator-jsonschema.
yarn install v1.19.1
[1/4] 🔍 Resolving packages...
[2/4] 🚚 Fetching packages...
[3/4] 🔗 Linking dependencies...
warning " > [email protected]" has incorrect peer dependency "class-validator@^0.9.1".
Hello, I'm trying to implement the @jsonschema decorator to add some special properties and for some unknown reason this doesn't seems to work. Nothing is being added to my schemas.
@ApiModelProperty({required: true, example: '#fff'})
@IsNotEmpty()
@IsHexColor()
@JSONSchema({
isColorPicker: true,
})
readonly textColor: string;
@ApiModelProperty({required: true, type: [CreateBannerTextDto]})
@ArrayNotEmpty()
@Type(() => CreateBannerTextDto)
@JSONSchema({
isWysiwyg: true,
})
readonly content: CreateBannerTextDto[];
Validation through groups is a feature which I always use to define rules among different api. This because Create and Update body could have different constraint, this implies a different generated schema. I see in limitation section this feature is not available. Is this feature in the roardmap of the project? When it will be released?
Thank you so much :)
I have 2 projects sharing some entities, and I'm trying to find a way to keep the validation consistent through all my application, and if possible, reuse part of the code instead of copying/pasting it.
// This class describes the database table
class UserDB {
id: string; // generated by DB when a new record is added
email: string; // NOT NULL
age?: number;
firstName?: string;
lastName?: string;
}
// This class describes all fields validations
class User {
@IsUUID("4") @JSONSchema({ description: `User's ID` })
id: string;
@IsEmail() @JSONSchema({ description: `User's email` })
email: string;
@IsOption() @IsInt() @IsPositive() @Min(18) @JSONSchema({ description: `User's age` })
age?: number;
@IsOption() @MinLength(3) @JSONSchema({ description: `User's first name` })
firstName?: string;
@IsOption() @MinLength(5) @JSONSchema({ description: `User's last name` })
lastName?: string;
}
class CreateUserRequest {
@IsEmail() @JSONSchema({ description: `User's email` })
email: string;
@IsOption() @IsInt() @IsPositive() @Min(18) @JSONSchema({ description: `User's age` })
age?: number;
@IsOption() @MinLength(3) @JSONSchema({ description: `User's first name` })
firstName?: string;
@IsOption() @MinLength(5) @JSONSchema({ description: `User's last name` })
lastName?: string;
}
class CreateUserResponse extends User { }
class ChangeEmailRequest {
@IsEmail() @JSONSchema({ description: `New email address` })
email: string;
}
class ChangeEmailResponse extends User { }
As we can see, we keep copying/pasting all the validations and descriptions from class to class, so I'm trying to find a better way to reuse the code so that it is also easier to maintain.
I create a common class containing the "base" properties.
class UserCommon {
@IsEmail() @JSONSchema({ description: `User's email` })
email: string;
@IsOption() @IsInt() @IsPositive() @Min(18) @JSONSchema({ description: `User's age` })
age?: number;
@IsOption() @MinLength(3) @JSONSchema({ description: `User's first name` })
firstName?: string;
@IsOption() @MinLength(5) @JSONSchema({ description: `User's last name` })
lastName?: string;
}
class User extends UserCommon {
@IsUUID("4") @JSONSchema({ description: `User's ID` })
id: string;
}
And then try to reuse the "base" class whenever possible
class CreateUserRequest extends UserCommon {}
class CreateUserResponse extends User {}
class ChangeEmailRequest {
@IsEmail() @JSONSchema({ description: `User's email` })
email: string;
}
class ChangeEmailResponse extends User {}
Create a base class describing all the fields with their validations.
class User {
@IsUUID("4") @JSONSchema({ description: `User's ID` })
id: string;
@IsEmail() @JSONSchema({ description: `User's email` })
email: string;
@IsOption() @IsInt() @IsPositive() @Min(18) @JSONSchema({ description: `User's age` })
age?: number;
@IsOption() @MinLength(3) @JSONSchema({ description: `User's first name` })
firstName?: string;
@IsOption() @MinLength(5) @JSONSchema({ description: `User's last name` })
lastName?: string;
}
And then extend it excluding or exposing fields
class CreateUserRequest extends User {
@Exclude()
id: string;
}
class CreateUserResponse extends User {}
class ChangeEmailRequest {
@Expose()
email: string;
}
class ChangeEmailResponse extends User {}
Solution 1
can be already implemented, even tho it will be hard to isolate the "common" properties when the app starts becoming big. i.e. if I introduce an UpdateUser API, probably I want to keep the email out of it, so I have to remove the email from the UserCommon
class.
Solution 2
would be really flexible but I guess it is not supported currently by this library, right? Any chance to get this implemented?
Do you have any feedback? or any smarter way to achieve this result?
The new version breaks validation in routing-controllers.
The validation is ignored the moment I use the "validationMetadatasToSchemas" function.
I rolled back to the previous version and it works fine.
Hello,
In your exemple you give:
import { IsOptional, IsString, MaxLength } from 'class-validator'
import { validationMetadatasToSchemas } from 'class-validator-jsonschema'
class BlogPost {
@IsString() id: string
@IsOptional()
@MaxLength(20, { each: true })
tags: string[]
}
const schemas = validationMetadatasToSchemas()
console.log(schemas)
Unfortunatly I have too much classes using class-validator and I only want some. Is there a way to only get for given ones ?
Something like this:
import { IsOptional, IsString, MaxLength } from 'class-validator'
import { validationMetadatasToSchemas } from 'class-validator-jsonschema'
class BlogPost {
@IsString() id: string
@IsOptional()
@MaxLength(20, { each: true })
tags: string[]
}
const schema = validationClassToSchema(BlogPost) // or validationClassToSchemas([BlogPost])
console.log(schema)
Tried to create my own MetadataStorage
with only the classes I want to be in but I don't find any exemples on how to achieve that. Did you have ?
Actually I do:
const schemasToGet = [BlogPost.name];
const configSchema = validationMetadatasToSchemas();
for (const name of schemasToGet) {
this._configSchema[name] = configSchema[name];
}
Thanks,
My site have a lots of dynamic data transfer entities
I want to use class-validator-jsonschema json schema to save to database then use it to validate schema (without update running code)
But i see that class-validator-jsonschema schema is difference from class-validator json schema
So how to use them toghether?
Thank you so much
Hi there! First of all thanks a lot for this library, it comes very handy.
However, when generating the schema with a nested property type (and using @type) as specified in the docs it breaks giving an error:
Reflect.getMetadata is not a function
If I remove the @type(() => BlogPost) then it works but does not nest the object.
Any suggestions?
import { Type } from 'class-transformer'
import { validationMetadatasToSchemas } from 'class-validator-jsonschema'
const { defaultMetadataStorage } = require('class-transformer/cjs/storage') // See https://github.com/typestack/class-transformer/issues/563 for alternatives
class User {
@ValidateNested({ each: true })
@Type(() => BlogPost) // 1) Explicitly define the nested property type
blogPosts: BlogPost[]
}
const schemas = validationMetadatasToSchemas({
classTransformerMetadataStorage: defaultMetadataStorage, // 2) Define class-transformer metadata in options
})
I'm experiencing an issue where two entirely unrelated schemas are getting merged together.
I'm using this library along with routing-controllers-openapi
, so it's still a bit unsure if the issue is with this library or somewhere else. Regardless, here's the bug I'm experiencing:
I have the following class, representing a product:
//Product.ts
@JSONSchema({
description: 'A product object',
})
export class Product {
@Type(() => LocaleField)
@ValidateNested()
@JSONSchema({
description: 'The name of the product',
example: {
def: 'Road bicycle',
},
})
name: LocaleField;
@Type(() => ProductVariant)
@ValidateNested({ each: true })
@IsOptional()
@JSONSchema({
description: 'Variants of this product',
})
variants?: ProductVariant[];
/** ...other fields */
constructor(product: ProductApi) {
this.name = new LocaleField(product.name);
this.variants = product.variants?.map(v => new ProductVariant(v))
/** ...other fields */
}
}
And the sub-types are as follows:
//LocaleField.ts
@JSONSchema({
description: 'A localized string',
})
export class LocaleField {
@IsString()
@JSONSchema({
description: 'The default value for this string',
})
def: string;
@IsString()
@IsOptional()
@JSONSchema({
description: 'The english translation for this string',
})
en?: string;
constructor(data: LocaleFieldType) {
this.def = data?.def ?? '';
this.en = data?.en;
}
}
//ProductVariant.ts
@JSONSchema({
description: 'A product variant object',
})
export class ProductVariant {
@IsString()
name: string;
@IsInt()
price: number;
constructor(data: ProductVariantType) {
this.name = data.name;
this.price = data.price || 0;
}
}
This configuration should clearly result in two different components being defined in my OpenAPI schema, but the resulting schema looks like this:
For some reason, the two classes are merged together.
Here is the relevant code I'm using to generate the schema:
//app.ts
import 'reflect-metadata'; // this shim is required
import { Request, Response } from 'express';
import { createExpressServer, getMetadataArgsStorage } from 'routing-controllers';
import { routingControllersToSpec } from 'routing-controllers-openapi';
import { defaultMetadataStorage } from 'class-transformer/storage';
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
import { ProductController, ShopController } from './controllers';
import { ErrorHandler, ResponseTransformer } from './middlewares';
import { RouteNotFoundError } from './errors';
import { openApiConfig } from './config';
const routingControllersOptions = {
cors: true,
controllers: [ProductController, ShopController],
middlewares: [ErrorHandler],
interceptors: [ResponseTransformer],
defaultErrorHandler: false,
};
const app = createExpressServer(routingControllersOptions);
const storage = getMetadataArgsStorage();
const schemas = validationMetadatasToSchemas({
refPointerPrefix: '#/components/schemas/',
classTransformerMetadataStorage: defaultMetadataStorage,
});
const spec = routingControllersToSpec(storage, routingControllersOptions, {
components: { schemas },
...openApiConfig,
});
app.get('/docs', (req: Request, res: Response) => {
res.status(200).json(spec);
});
app.all('*', (req: Request, res: Response) => {
const error = new RouteNotFoundError(
'This route does not exist, or it may have been deprecated.',
);
res.status(404).send({
status: 'error',
details: error.toJSON(req.originalUrl),
});
});
export default app;
One possible cause could be that I'm using getMetadataArgsStorage
for generating the actual spec, but defaultMetadataStorage
as an argument to validationMetadatasToSchemas
, which seems a bit off. I'm unsure what would be the correct way to do this however.
According to:
classTransformer.test.ts
nested classes currently generate ref's pointing at the json-schema definitions field but doesn't auto include the definitions. Is there any plan to include those in the future? Currently I am hand adding them post schemas being generated but would be amazing if they could be added automatically during generation.
Hi,
It would be great to include the error message inside the generated JSON.
Example:
@isemail({}, {message: "Please fill a correct email"})
Actualy the generated JSON doesn't contains the message.
Hi,
it seems class-validator-jsonschema
does not take inhherited class-validator
decorated properties into account.
E.g. converting
export class IApplicationCreateDto {
@IsString()
name: string;
}
export class IApplicationDto extends IApplicationCreateDto {
@IsString()
uuid: string;
}
leads to
{
"IApplicationCreateDto": {
"properties": {
"name": {
"type": "string"
},
},
"required": [
"name",
],
"type": "object"
},
"IApplicationDto": {
"properties": {
"uuid": {
"type": "string",
}
},
"required": [
"uuid"
],
"type": "object"
},
}
I would expect the json-schema of IApplicationDto
to also contain meta-info for the name
property.
https://github.com/typestack/class-validator#inheriting-validation-decorators suggests inherited properties should work in general. Is this a known limitation of class-validator-jsonschema
?
From the docs:
JSONSchema decorators also flow down from parent classes into inherited validation decorators. Note though that if the inherited class uses JSONSchema to redecorate a property from the parent class, the parent class JSONSchema gets overwritten - i.e. there's no merging logic.
This makes sense in theory, but in reality the fact that the parent class decorator doesn't take precedence is a bit backwards, I feel.
For example, I have this kind of class representing an object of localized strings:
// simplified
@JSONSchema({
title: 'Localized string',
description: 'An object of translations'
})
class LocalizedString {
en: string;
de: string;
fr: string;
}
This field is used in many of my other classes, for example for the name of a product:
// simplified
class Product {
@JSONSchema({
title: 'Product name',
description: 'The name of the product and its translations.'
})
name: LocalizedString;
...
}
With this kind of setup, the resulting JSON schema has the name
field of the product described with the generic title and description from LocalizedString
instead of the one I define in Product
. I think it should work in exactly the opposite preference, as the case where I'm using it within a Product object is the more specific case, if you see what I mean.
One solution would be to just remove the JSONSchema decorator from LocalizedString
entirely, but I would still like to have at as I have a generic Model reference in my docs where it would be useful.
Question is:
export class ActivityEventVisibilityRequest {
@IsNumber({
maxDecimalPlaces: 0,
})
@IsDefined()
version: number;
@ValidateNested({ each: true })
@Type(() => ActivityEvent, {
keepDiscriminatorProperty: true,
discriminator: {
property: 'name',
subTypes: [
{ value: ActivityEventVisibility, name: EActivityType.VISIBILITY },
{ value: ActivityEventCiccio, name: EActivityType.TEST },
],
},
})
@JSONSchema({
type: 'array',
items: {
anyOf: [
{
$ref: '#/components/schemas/ActivityEventCiccio',
},
{ $ref: '#/components/schemas/ActivityEventVisibility' },
],
},
})
events: ActivityEvent[];
}
ActivityEventVisibility
e ActivityEventCiccio
are subclass of ActivityEvent
. In OpenApi doc i see this.
What i need to do for see ActivityEventVisibility
e ActivityEventCiccio
in events: ActivityEvent[];
type? Actually i see ActivityEvent in schema space and in example area.
Hey,
First of all many thanks for your great work. I spotted that additionalProperties has incorrect type of array and as a result transforms to incorrect openapi output. According to latest specification https://swagger.io/specification/#schemaObject
additionalProperties - Value can be boolean or object. Inline or referenced schema MUST be of a Schema Object and not a standard JSON Schema.
Is it possible to fix it?
On version 14 of node we receive a Warning but with new onces node stop working!
Any idea how to solve it?
(node:78907) UnhandledPromiseRejectionWarning: TypeError: typeMeta.typeFunction is not a function
at nestedValidation (/Users/olla/work/cavepot/topolgy/api/node_modules/class-validator-jsonschema/build/defaultConverters.js:19:28)
(node:78907) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
<node_internals>/internal/process/warning.js:41
(node:78907) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
I gave up trying to use your library because it didnʻt work. (angular land) ...then I realized 5 hours later that I needed to do this instead:
const metadatas = (getMetadataStorage() as any).validationMetadatas;
const constraintMetadatas = (getMetadataStorage() as any).constraintMetadatas;
const schemas = validationMetadatasToSchemas(metadatas)
..which did work..maybe add this to your readme @epiphone?
PS - excellent job with this!
The current package working only on MacOS, is used fsevents (Native access to MacOS FSEvents in Node.js).
Is there a workaround?
Regular expression for IS_DATE_STRING is has a dot that should be escaped and the time should be optional
Replace lodash function calls with native Javascript code.
Hi! 👋
Firstly, thanks for your work on this project! 🙂
Today I used patch-package to patch [email protected]
for the project I'm working on.
I wanted class transformer discriminators in my documentation.
Here is the diff that solved my problem:
diff --git a/node_modules/class-validator-jsonschema/build/defaultConverters.js b/node_modules/class-validator-jsonschema/build/defaultConverters.js
index 09dc4b7..0569977 100644
--- a/node_modules/class-validator-jsonschema/build/defaultConverters.js
+++ b/node_modules/class-validator-jsonschema/build/defaultConverters.js
@@ -19,6 +19,36 @@ exports.defaultConverters = {
const childType = typeMeta
? typeMeta.typeFunction()
: getPropType(meta.target.prototype, meta.propertyName);
+
+ const { options: { discriminator } } = typeMeta;
+
+ if (discriminator)
+ {
+ const { subTypes } = discriminator;
+
+ const discriminatorType = {
+ oneOf: subTypes.map(({value, name}) => targetToSchema(value, options)),
+ discriminator: {
+ propertyName: discriminator.property,
+ mapping: subTypes.reduce((acc, {value, name}) => {
+ if (!value || !name)
+ return acc;
+
+ const ref = targetToSchema(value, options).$ref;
+
+ if (!ref)
+ return acc;
+
+ acc[name] = ref;
+
+ return acc;
+ }, {})
+ }
+ }
+
+ return discriminatorType;
+ }
+
return targetToSchema(childType, options);
}
},
This issue body was partially generated by patch-package.
Hey, first of all thanks for the great library ❤️
I had a question on using @ValidateNested()
decorators. Currently my code is
import { IsString, ValidateNested } from "class-validator"
class ValidationError {
@IsString({ each: true })
public path!: string[]
@IsString({ each: true })
public constraints!: string[]
}
export class ValidationErrorModel {
@IsString()
public name!: "ValidationError"
@ValidateNested({ each: true })
public errorList!: ValidationError[]
}
But that generates the following schemas:
"ValidationError": {
"properties": {
"path": {
"items": {
"type": "string"
},
"type": "array"
},
"constraints": {
"items": {
"type": "string"
},
"type": "array"
}
},
"type": "object",
"required": [
"path",
"constraints"
]
},
"ValidationErrorModel": {
"properties": {
"name": {
"type": "string"
},
"errorList": {
"type": "array"
}
},
"type": "object",
"required": [
"name",
"errorList"
]
}
The errorList
doesn't have a type for the array items:
"errorList": {
"type": "array"
}
So i'd want it to be this instead:
"errorList": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationError"
}
}
Do you know if this is possible?
Hey thanks for the great work on this project!
I ran into an issue using this library in my NestJS project due to a version miss-match in the class-transformer
library. My base project was using the latest minor version 0.2.3
and this project is locked to the previous minor version 0.1.9
. Because of this I couldn't pass the defaultMetadataStorage
to the validationMetadatasToSchemas
method because the instances were different.
I've currently downgraded my project to use the 0.1.9
version which is working but it would be great if we could either remove the lock file (always pull latest based on package.json rules) or update the lock to use the latest versions.
I am using class-validator-jsonschema version 3.1.0 and class-transformer 0.3.1
When I am trying to run tsc, a file named options.d.ts in directory node_modules>class-validator-jsonschema>build>options.d.ts
line 1 import type { MetadataStorage as ClassTransformerMetadataStorage } from 'class-transformer/types/MetadataStorage';
is making an error, because MetadataStorage is in the route class-transformer>metadata>MetadataStorage
is it because I am using a different version?
Hello,
The generation works fine, but I would like to generate from a specific class.
// blogPost.ts
class Foo {
@IsString() id: string
}
export class BlogPost {
@ValidateNested()
@Type(() => Foo)
foo: Foo
}
const metadatas = (getFromContainer(MetadataStorage) as any).validationMetadatas
const schemas2 = validationMetadatasToSchemas(metadatas, {
classTransformerMetadataStorage: defaultMetadataStorage,
}) // => Works well
import {BlogPost} from './blogPost'
const jsonSchema = generateJSONSchema(BlogPost); // Is it possible to generate a schema with a class passed in params ?
Thank you
Why add "type": "string"?
Fastify validator does not work correctly with it
Enum is given:
enum Test {
A = 'aaa',
B = 'bbb',
}
And validated class:
class TestClass {
@IsEnum(Test)
type: Test;
}
Result json schema:
"TestClass": {
"properties": {
"type": {
"enum": [
"A",
"B"
],
"type": "string"
}
},
"type": "object",
"required": [
"type"
]
},
I suppose in result schema enum values should be used, but not keys. However I'm not 100% sure about all cases.
If my assumption is correct, then it can be fixed by changing Object.keys
to Object.values
here:
Hi @epiphone :),
thank you for all your work on this project. I have been using this project successfully for quite some time, but recently we have noticed some behavior that we think is a bug. Consider the code below:
import { IsOptional, IsString, MaxLength } from "class-validator";
import { targetConstructorToSchema } from "class-validator-jsonschema";
class Post {
@IsString() id!: string;
}
class BlogPost extends Post {
@IsOptional()
@MaxLength(20, { each: true })
tags?: string[];
}
class JournalPost extends BlogPost {}
const schema = targetConstructorToSchema(JournalPost);
console.log(JSON.stringify(schema, null, 2));
I would expect that the console.log
logs the following schema (same as for BlogPost
):
{
"properties": {
"tags": {
"items": {
"maxLength": 20,
"type": "string"
},
"type": "array"
},
"id": {
"type": "string"
}
},
"type": "object",
"required": [
"id"
]
}
But instead, it logs this schema:
{
"properties": {
"id": {
"type": "string"
}
},
"type": "object",
"required": [
"id"
]
}
It looks like the metadata for the BlogPost
class is not considered during schema generation. This behavior produces incorrect OpenAPI schemas in our application. Is this expected behavior or is it a bug?
By the way, validations using JournalPost
are working correctly.
Thank you.
Hi I use routing-controllers-openapi, routing-controllers, class-validator, class-validator-jsonschema and class-validator to make swagger
This is my Array of object is declared
export default class UserInformation{
@IsString()
name: string
@IsString()
code: string
}
export default class User {
@IsString()
email: string
@IsString()
pass: string
@ValidateNested()
user: UserInformation
@ValidateNested({ each: true })
@Type(() => UserInformation)
users: UserInformation[]
}
Users (user3s) not working. It is always equal to null
I use swagger-ui-express to create swagger pages
Could you help me.
Thank
I upgrade from 2.0.3
to 2.1.0
to fix the Object type issue reported in routing-controller-openapi. But 2.1.0
breaks the reference to schemas. I dont change anithing in the code, now downgrade to 2.0.3
and works properly the schema reference
2.0.3
GetBundleResponse:
properties:
data:
$ref: '#/components/schemas/BundleResponse'
message:
type: string
error:
$ref: '#/components/schemas/DefaultErrorResponse'
type: object
required:
- data
description: Find bundle response
example:
message: Operation Success
data:
url: 'https://storage.example.net/patchs'
checksum:
- full_path: /
file_name: appmanifest_1217060.acf
hash: bfd3b83480c54d290d3070d46419749b
size: 605
- full_path: /
file_name: appmanifest_1237970.acf
hash: 8993f8e743c1edbe599a948072a00a28
size: 834
status: FINISHED
platform: android
version: 100
id: 5feb8b3125b80b1fa89ab40d
2.1.0
GetBundleResponse:
properties:
data: {}
message:
type: string
error: {}
type: object
required:
- data
description: Find bundle response
example:
message: Operation Success
data:
url: 'https://storage.example.net/patchs'
checksum:
- full_path: /
file_name: appmanifest_1217060.acf
hash: bfd3b83480c54d290d3070d46419749b
size: 605
- full_path: /
file_name: appmanifest_1237970.acf
hash: 8993f8e743c1edbe599a948072a00a28
size: 834
status: FINISHED
platform: android
version: 100
id: 5feb8b3125b80b1fa89ab40d
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.