Coder Social home page Coder Social logo

epiphone / class-validator-jsonschema Goto Github PK

View Code? Open in Web Editor NEW
209.0 6.0 32.0 911 KB

Convert class-validator-decorated classes into JSON schema

License: MIT License

TypeScript 99.18% JavaScript 0.82%
validation typescript-validation openapi3 json-schema api-documentation api-documentation-tool

class-validator-jsonschema's People

Contributors

boyko avatar dependabot[bot] avatar epiphone avatar f10et avatar mcheale-parkit avatar mikeguta avatar nexzhu avatar nicolaiort avatar orgads avatar p-orlov avatar scarych avatar willemgovaerts avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

class-validator-jsonschema's Issues

Support for class-transformer?

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.

Typescript build failing

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';

@epiphone

Empty required fields

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:

https://editor.swagger.io/

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?

Support for Fastify or Fastify-Swagger

Hi,

I am using fastify, i tried the following:

Screenshot 2021-09-22 120148

Screenshot 2021-09-22 154827

Screenshot 2021-09-22 154909

Screenshot 2021-09-22 154928

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.

Screenshot 2021-09-22 155624

Screenshot 2021-09-22 155713

I wonder what is missing here @epiphone

Bug with decorator `@Exclude` processing on classes inheritance

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.

Move inherited properties before sub class properties

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

[BUG] Class-transformer v0.3.2 breaks @ValidateNested and @Type

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

Not compatible with class-validator v0.12.0

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'

Anyway to exclude some decorators

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.

Regular expression for IS_DATE_STRING is missing escape characters

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.

Problem with arrays

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 ?

Sample code is not working

I am trying to work with the current package without success.
I ran the sample code and I don't get the expect result.
Could you assist me please.

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[]
}

const metadatas = (getFromContainer(MetadataStorage) as any).validationMetadatas
const schemas = validationMetadatasToSchemas(metadatas)
console.log(schemas)

tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "./dist",
"rootDir": "./src",
"sourceMap": true,
"noImplicitAny": true
},
"include": [
"src/**/*.ts",
"package.json"
],
"exclude": [
"node_modules"
]
}

{
"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"
}
}

Allow refPointerSuffix

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 #refs so the dereferencer can find the files.
It would be great to either have a refPointerSuffix option to add .schema.json to all #refs, or some guidance on what the preferred way is to link the schemas together and dereference them correctly.

IsOptional support for null

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.

class-transformer package is missing as npm dependency

Steps to reproduce:

  • Remove node_modules
  • Do npm install
  • Run ts compile

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.

Can you convert from JSON back to class?

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?

Issue with routing-controllers-openapi

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?

Regression bug validationMetadatasToSchemas v5.0.0

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 "({}".

incorrect peer dependency "class-validator@^0.9.1"

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".

@JSONSchema not working

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 Groups Feature

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 :)

Exclude/Expose decorator for properties in class inheritance

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.

My example

Classes needed

// 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;
}

Create User API schemas

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 { }

Change Email API schemas

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.

Solution 1

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 {}

Solution 2

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?

New version breaks validation

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.

How to get jsonschema for only one class ?

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,

Convert to class-validator schema

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

Reflect.getMetadata is not a function when using @Type nested property type

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
})

Unrelated schemas get merged together

I'm experiencing an issue where two entirely unrelated schemas are getting merged together.

Details

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:

image

For some reason, the two classes are merged together.

Additional details

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.

Definitions for references?

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.

Converting Subclasses

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?

How to force using JSONSchema decorator from parent?

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:

  • Is there a way to do what I want currently?
  • If not, what do you think it would require to get a workable solution to this?

Mixed array of custom type

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.

Screenshot 2023-02-07 alle 13 38 27

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.

In debug i see this. I think it is wrong.
Screenshot 2023-02-07 alle 13 42 25

additionalProperties has incorrect type of array

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?

Error with latest version of node

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.

[suggestion] Suggestion for readme

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!

class transformer discriminators

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.

ValidateNested arrays

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?

Locked version of class-validator and class-transformer out of date

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.

class-transformer version mismatch

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?

Generate from a specific Class

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

Enum keys are used instead of values

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:

enum: Object.keys(meta.constraints[0]),

Incorrectly generated schema for subclasses

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.

Array of object not working for routing-controllers-openapi

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
image

I use swagger-ui-express to create swagger pages
Could you help me.
Thank

The last release breaks the schema reference

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.