Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discriminator validation issue #10655

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright IBM Corp. and LoopBack contributors 2020. All Rights Reserved.
// Node module: @loopback/example-validation-app
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Client, expect} from '@loopback/testlab';
import {ValidationApplication} from '../..';
import {setupApplication} from './test-helper';

const validCat = {
name: 'Kitty',
weight: 5,
kind: 'Cat',
animalProperties: {
color: 'grey',
whiskerLength: 2,
},
};

const validDog = {
name: 'Rex',
weight: 5,
kind: 'Dog',
animalProperties: {
breed: 'poodle',
barkVolume: 5,
},
};

describe('validate properties based on discriminated schemas', () => {
let client: Client;
let app: ValidationApplication;

before(givenAClient);

after(async () => {
await app.stop();
});

async function givenAClient() {
({app, client} = await setupApplication());
}

it('should pass with valid cat properties', async () => {
await client.post('/pets').send(validCat).expect(200);
});

it('should pass with valid dog properties', async () => {
await client.post('/pets').send(validDog).expect(200);
});

it('should fail with error indicating invalid barkVolume type', async () => {
const invalidDog = {...validDog};
invalidDog.animalProperties.barkVolume = 'loud' as unknown as number;
const response = await client.post('/pets').send(invalidDog).expect(422);

expect(response.body.error.details.length).to.equal(1);
expect(response.body.error.details[0].message).to.equal('must be number');
expect(response.body.error.details).to.deepEqual([
{
code: 'type',
info: {
type: 'number',
},
message: 'must be number',
path: '/animalProperties/barkVolume',
},
]);
});

it('should fail with error indicating invalid whiskerLength type', async () => {
const invalidCat = {...validCat};
invalidCat.animalProperties.whiskerLength = 'long' as unknown as number;
const response = await client.post('/pets').send(invalidCat).expect(422);

expect(response.body.error.details.length).to.equal(1);
expect(response.body.error.details[0].message).to.equal('must be number');
expect(response.body.error.details).to.deepEqual([
{
code: 'type',
info: {
type: 'number',
},
message: 'must be number',
path: '/animalProperties/whiskerLength',
},
]);
});
});
8 changes: 7 additions & 1 deletion examples/validation-app/src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import {BootMixin} from '@loopback/boot';
import {ApplicationConfig, createBindingFromClass} from '@loopback/core';
import {RepositoryMixin} from '@loopback/repository';
import {RestApplication} from '@loopback/rest';
import {RestApplication, RestBindings} from '@loopback/rest';
import {
RestExplorerBindings,
RestExplorerComponent,
Expand All @@ -32,6 +32,12 @@ export class ValidationApplication extends BootMixin(
// Set up default home page
this.static('/', path.join(__dirname, '../public'));

this.bind(RestBindings.REQUEST_BODY_PARSER_OPTIONS).to({
validation: {
discriminator: true,
},
});

// Customize @loopback/rest-explorer configuration here
this.configure(RestExplorerBindings.COMPONENT).to({
path: '/explorer',
Expand Down
1 change: 1 addition & 0 deletions examples/validation-app/src/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
// License text available at https://opensource.org/licenses/MIT

export * from './coffee-shop.controller';
export * from './pet.controller';
30 changes: 30 additions & 0 deletions examples/validation-app/src/controllers/pet.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright IBM Corp. and LoopBack contributors 2020. All Rights Reserved.
// Node module: @loopback/example-validation-app
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {post, requestBody} from '@loopback/rest';
import {Cat, Dog, Pet} from '../models';

export class PetController {
constructor() {}

@post('/pets')
async create(
@requestBody({
content: {
'application/json': {
schema: {
discriminator: {
propertyName: 'kind',
},
oneOf: [{'x-ts-type': Cat}, {'x-ts-type': Dog}],
},
},
},
})
request: Pet,
): Promise<Pet> {
return request;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ export class ValidationErrorMiddlewareProvider implements Provider<Middleware> {
err: HttpErrors.HttpError,
): Response | undefined {
// 2. customize error for particular endpoint
if (context.request.url === '/coffee-shops') {
if (
context.request.url === '/coffee-shops' ||
context.request.url === '/pets'
) {
// if this is a validation error from the PATCH method, customize it
// for other validation errors, the default AJV error object will be sent
if (err.statusCode === 422 && context.request.method === 'PATCH') {
Expand Down
1 change: 1 addition & 0 deletions examples/validation-app/src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
// License text available at https://opensource.org/licenses/MIT

export * from './coffee-shop.model';
export * from './pet.model';
86 changes: 86 additions & 0 deletions examples/validation-app/src/models/pet.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {Model, model, property} from '@loopback/repository';

@model()
export class CatProperties extends Model {
@property({
type: String,
required: true,
})
color: string;

@property({
type: Number,
required: true,
})
whiskerLength: number;
}

@model()
export class DogProperties extends Model {
@property({
type: String,
required: true,
})
breed: string;

@property({
type: Number,
required: true,
})
barkVolume: number;
}

@model()
export class Pet extends Model {
@property({
type: String,
required: true,
})
name: string;

@property({
type: Number,
required: false,
})
weight?: number;

kind: string;

animalProperties: CatProperties | DogProperties;
}

@model()
export class Dog extends Pet {
@property({
type: String,
jsonSchema: {
enum: ['Dog'],
},
required: true,
})
kind: string;

@property({
type: DogProperties,
required: true,
})
animalProperties: DogProperties;
}

@model()
export class Cat extends Pet {
@property({
type: String,
jsonSchema: {
enum: ['Cat'],
},
required: true,
})
kind: string;

@property({
type: CatProperties,
required: true,
})
animalProperties: CatProperties;
}
8 changes: 4 additions & 4 deletions packages/cli/test/fixtures/copyright/single-package/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright IBM Corp. and LoopBack contributors 2020. All Rights Reserved.
// Node module: @loopback/cli
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
// Copyright ACME Inc. 2020,2024. All Rights Reserved.
// Node module: myapp
// This file is licensed under the ISC License.
// License text available at https://www.isc.org/licenses/

// Use the same set of files (except index.js) in this folder
const files = require('../index')(__dirname);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
// Copyright ACME Inc. 2020,2024. All Rights Reserved.
// Node module: myapp
// This file is licensed under the ISC License.
// License text available at https://www.isc.org/licenses/

// XYZ
exports.xyz = {};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Copyright ACME Inc. 2020,2024. All Rights Reserved.
// Node module: myapp
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
// This file is licensed under the ISC License.
// License text available at https://www.isc.org/licenses/

export class MyApplication {}
5 changes: 4 additions & 1 deletion packages/rest/src/validation/request-body.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ export async function validateRequestBody(
* @param openapiSchema - The OpenAPI schema to convert.
*/
function convertToJsonSchema(openapiSchema: SchemaObject) {
const jsonSchema = toJsonSchema(openapiSchema);
const jsonSchema = toJsonSchema(openapiSchema, {
keepNotSupported: ['discriminator'],
});

delete jsonSchema['$schema'];
/* istanbul ignore if */
if (debug.enabled) {
Expand Down