Skip to content

Commit

Permalink
feat: improve filter schema to allow exclusion
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondfeng committed Feb 28, 2020
1 parent 8e049ec commit 9db4056
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 29 deletions.
24 changes: 24 additions & 0 deletions packages/openapi-v3/src/__tests__/unit/filter-schema.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,30 @@ describe('filterSchema', () => {
});
});

it('generate filter schema excluding where', () => {
const schema = getFilterSchemaFor(MyUserModel, {exclude: ['where']});
expect(MyUserModel.definition.name).to.eql('my-user-model');
expect(schema).to.eql({
title: 'my-user-model.Filter',
properties: {
fields: {
type: 'object',
title: 'my-user-model.Fields',
properties: {
id: {type: 'boolean'},
age: {type: 'boolean'},
},
additionalProperties: false,
},
offset: {type: 'integer', minimum: 0},
limit: {type: 'integer', minimum: 1, example: 100},
skip: {type: 'integer', minimum: 0},
order: {type: 'array', items: {type: 'string'}},
},
additionalProperties: false,
});
});

@model({
name: 'CustomUserModel',
})
Expand Down
9 changes: 7 additions & 2 deletions packages/openapi-v3/src/filter-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// License text available at https://opensource.org/licenses/MIT

import {
FilterSchemaOptions,
getFilterJsonSchemaFor,
getWhereJsonSchemaFor,
Model,
Expand All @@ -19,9 +20,13 @@ import {SchemaObject} from './types';
* a generic json schema allowing any "where" condition.
*
* @param modelCtor - The model constructor to build the filter schema for.
* @param options - Options to build the filter schema.
*/
export function getFilterSchemaFor(modelCtor: typeof Model): SchemaObject {
const jsonSchema = getFilterJsonSchemaFor(modelCtor);
export function getFilterSchemaFor(
modelCtor: typeof Model,
options?: FilterSchemaOptions,
): SchemaObject {
const jsonSchema = getFilterJsonSchemaFor(modelCtor, options);
const schema = jsonToSchemaObject(jsonSchema);
return schema;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ import {
describe('getFilterJsonSchemaFor', () => {
let ajv: Ajv.Ajv;
let customerFilterSchema: JsonSchema;
let customerFilterExcludingWhereSchema: JsonSchema;
let orderFilterSchema: JsonSchema;

beforeEach(() => {
ajv = new Ajv();
customerFilterSchema = getFilterJsonSchemaFor(Customer);
customerFilterExcludingWhereSchema = getFilterJsonSchemaFor(Customer, {
exclude: ['where'],
});
orderFilterSchema = getFilterJsonSchemaFor(Order);
});

Expand Down Expand Up @@ -50,6 +54,21 @@ describe('getFilterJsonSchemaFor', () => {
expectSchemaToAllowFilter(customerFilterSchema, filter);
});

it('disallows "where"', () => {
const filter = {where: {name: 'John'}};
// eslint-disable-next-line @typescript-eslint/no-floating-promises
ajv.validate(customerFilterExcludingWhereSchema, filter);
expect(ajv.errors ?? []).to.containDeep([
{
keyword: 'additionalProperties',
dataPath: '',
schemaPath: '#/additionalProperties',
params: {additionalProperty: 'where'},
message: 'should NOT have additional properties',
},
]);
});

it('describes "where" as an object', () => {
const filter = {where: 'invalid-where'};
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Expand Down Expand Up @@ -182,6 +201,24 @@ describe('getFilterJsonSchemaFor', () => {
}
});

describe('getFilterJsonSchemaFor - excluding where', () => {
let customerFilterSchema: JsonSchema;

it('excludes "where" using string[]', () => {
customerFilterSchema = getFilterJsonSchemaFor(Customer, {
exclude: ['where'],
});
expect(customerFilterSchema.properties).to.not.have.property('where');
});

it('excludes "where" using string', () => {
customerFilterSchema = getFilterJsonSchemaFor(Customer, {
exclude: 'where',
});
expect(customerFilterSchema.properties).to.not.have.property('where');
});
});

describe('getFilterJsonSchemaForOptionsSetTitle', () => {
let customerFilterSchema: JsonSchema;

Expand Down
76 changes: 49 additions & 27 deletions packages/repository-json-schema/src/filter-json-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// License text available at https://opensource.org/licenses/MIT

import {getModelRelations, Model, model} from '@loopback/repository';
import {JSONSchema6 as JsonSchema} from 'json-schema';
import {JSONSchema6 as JsonSchema, JSONSchema6Definition} from 'json-schema';

export interface FilterSchemaOptions {
/**
Expand All @@ -14,6 +14,11 @@ export interface FilterSchemaOptions {
*
*/
setTitle?: boolean;

/**
* To exclude one or more property from `filter`
*/
exclude?: string[] | string;
}

/**
Expand Down Expand Up @@ -50,43 +55,60 @@ export function getScopeFilterJsonSchemaFor(
* a generic json schema allowing any "where" condition.
*
* @param modelCtor - The model constructor to build the filter schema for.
* @param options - Options to build the filter schema.
*/
export function getFilterJsonSchemaFor(
modelCtor: typeof Model,
options: FilterSchemaOptions = {},
): JsonSchema {
const schema: JsonSchema = {
...(options.setTitle !== false && {
title: `${modelCtor.modelName}.Filter`,
}),
properties: {
where: getWhereJsonSchemaFor(modelCtor, options),
let excluded: string[];
if (typeof options.exclude === 'string') {
excluded = [options.exclude];
} else {
excluded = options.exclude ?? [];
}
const properties: Record<string, JSONSchema6Definition> = {
offset: {
type: 'integer',
minimum: 0,
},

fields: getFieldsJsonSchemaFor(modelCtor, options),
limit: {
type: 'integer',
minimum: 1,
examples: [100],
},

offset: {
type: 'integer',
minimum: 0,
},
skip: {
type: 'integer',
minimum: 0,
},

limit: {
type: 'integer',
minimum: 1,
examples: [100],
order: {
type: 'array',
items: {
type: 'string',
},
},
};

skip: {
type: 'integer',
minimum: 0,
},
if (!excluded.includes('where')) {
properties.where = getWhereJsonSchemaFor(modelCtor, options);
}
if (!excluded.includes('fields')) {
properties.fields = getFieldsJsonSchemaFor(modelCtor, options);
}

order: {
type: 'array',
items: {
type: 'string',
},
},
},
// Remove excluded properties
for (const p of excluded) {
delete properties[p];
}

const schema: JsonSchema = {
...(options.setTitle !== false && {
title: `${modelCtor.modelName}.Filter`,
}),
properties,
additionalProperties: false,
};

Expand Down

0 comments on commit 9db4056

Please sign in to comment.