Skip to content

Commit 53ac940

Browse files
committed
feat(repository-json-schema): add an option to exclude properties from schema
Add a new option `exclude: []` so that `getJsonSchema` and related helpers can request a model schema that excludes specified properties
1 parent 45977f3 commit 53ac940

File tree

4 files changed

+111
-12
lines changed

4 files changed

+111
-12
lines changed

packages/openapi-v3/src/controller-spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,9 +373,9 @@ export function getControllerSpec(constructor: Function): ControllerSpec {
373373
* @param modelCtor - The model constructor (e.g. `Product`)
374374
* @param options - Additional options
375375
*/
376-
export function getModelSchemaRef(
376+
export function getModelSchemaRef<T extends object>(
377377
modelCtor: Function,
378-
options?: JsonSchemaOptions,
378+
options?: JsonSchemaOptions<T>,
379379
): SchemaRef {
380380
const jsonSchema = getJsonSchemaRef(modelCtor, options);
381381
return jsonToSchemaObject(jsonSchema) as SchemaRef;

packages/repository-json-schema/src/__tests__/integration/build-schema.integration.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,5 +932,74 @@ describe('build-schema', () => {
932932
expect(partialSchema.required).to.equal(undefined);
933933
expect(partialSchema.title).to.equal('ProductPartial');
934934
});
935+
936+
context('exclude properties when option "exclude" is set', () => {
937+
@model()
938+
class Product extends Entity {
939+
@property({id: true, required: true})
940+
id: number;
941+
942+
@property()
943+
name: string;
944+
945+
@property()
946+
description: string;
947+
}
948+
949+
it('excludes one property when the option "exclude" is set to exclude one property', () => {
950+
const originalSchema = getJsonSchema(Product);
951+
expect(originalSchema.properties).to.deepEqual({
952+
id: {type: 'number'},
953+
name: {type: 'string'},
954+
description: {type: 'string'},
955+
});
956+
expect(originalSchema.title).to.equal('Product');
957+
958+
const excludeIdSchema = getJsonSchema(Product, {exclude: ['id']});
959+
expect(excludeIdSchema.properties).to.deepEqual({
960+
name: {type: 'string'},
961+
description: {type: 'string'},
962+
});
963+
expect(excludeIdSchema.title).to.equal('ProductExcluding[id]');
964+
});
965+
966+
it('excludes multiple properties when the option "exclude" is set to exclude multiple properties', () => {
967+
const originalSchema = getJsonSchema(Product);
968+
expect(originalSchema.properties).to.deepEqual({
969+
id: {type: 'number'},
970+
name: {type: 'string'},
971+
description: {type: 'string'},
972+
});
973+
expect(originalSchema.title).to.equal('Product');
974+
975+
const excludeIdAndNameSchema = getJsonSchema(Product, {
976+
exclude: ['id', 'name'],
977+
});
978+
expect(excludeIdAndNameSchema.properties).to.deepEqual({
979+
description: {type: 'string'},
980+
});
981+
expect(excludeIdAndNameSchema.title).to.equal(
982+
'ProductExcluding[id,name]',
983+
);
984+
});
985+
986+
it('doesn\'t exclude properties when the option "exclude" is set to exclude no properties', () => {
987+
const originalSchema = getJsonSchema(Product);
988+
expect(originalSchema.properties).to.deepEqual({
989+
id: {type: 'number'},
990+
name: {type: 'string'},
991+
description: {type: 'string'},
992+
});
993+
expect(originalSchema.title).to.equal('Product');
994+
995+
const excludeNothingSchema = getJsonSchema(Product, {exclude: []});
996+
expect(excludeNothingSchema.properties).to.deepEqual({
997+
id: {type: 'number'},
998+
name: {type: 'string'},
999+
description: {type: 'string'},
1000+
});
1001+
expect(excludeNothingSchema.title).to.equal('Product');
1002+
});
1003+
});
9351004
});
9361005
});

packages/repository-json-schema/src/__tests__/unit/build-schema.unit.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,13 +232,28 @@ describe('build-schema', () => {
232232
expect(key).to.equal('modelPartial');
233233
});
234234

235+
it('returns "excluding[id,_rev]" when a single option "exclude" is set', () => {
236+
const key = buildModelCacheKey({exclude: ['id', '_rev']});
237+
expect(key).to.equal('modelExcluding[id,_rev]');
238+
});
239+
240+
it('does not include "exclude" in concatenated option names if it is empty', () => {
241+
const key = buildModelCacheKey({
242+
partial: true,
243+
exclude: [],
244+
includeRelations: true,
245+
});
246+
expect(key).to.equal('modelPartialWithRelations');
247+
});
248+
235249
it('returns concatenated option names otherwise', () => {
236250
const key = buildModelCacheKey({
237251
// important: object keys are defined in reverse order
238252
partial: true,
253+
exclude: ['id', '_rev'],
239254
includeRelations: true,
240255
});
241-
expect(key).to.equal('modelPartialWithRelations');
256+
expect(key).to.equal('modelPartialExcluding[id,_rev]WithRelations');
242257
});
243258
});
244259
});

packages/repository-json-schema/src/build-schema.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
import {JSONSchema6 as JSONSchema} from 'json-schema';
1616
import {JSON_SCHEMA_KEY, MODEL_TYPE_KEYS} from './keys';
1717

18-
export interface JsonSchemaOptions {
18+
export interface JsonSchemaOptions<T extends object> {
1919
/**
2020
* Set this flag if you want the schema to define navigational properties
2121
* for model relations.
@@ -28,6 +28,11 @@ export interface JsonSchemaOptions {
2828
*/
2929
partial?: boolean;
3030

31+
/**
32+
* List of model properties to exclude from the schema.
33+
*/
34+
exclude?: (keyof T)[];
35+
3136
/**
3237
* @internal
3338
*/
@@ -37,7 +42,9 @@ export interface JsonSchemaOptions {
3742
/**
3843
* @internal
3944
*/
40-
export function buildModelCacheKey(options: JsonSchemaOptions = {}): string {
45+
export function buildModelCacheKey<T extends object>(
46+
options: JsonSchemaOptions<T> = {},
47+
): string {
4148
// Backwards compatibility: preserve cache key "modelOnly"
4249
if (Object.keys(options).length === 0) {
4350
return MODEL_TYPE_KEYS.ModelOnly;
@@ -54,9 +61,9 @@ export function buildModelCacheKey(options: JsonSchemaOptions = {}): string {
5461
* in a cache. If not, one is generated and then cached.
5562
* @param ctor - Contructor of class to get JSON Schema from
5663
*/
57-
export function getJsonSchema(
64+
export function getJsonSchema<T extends object>(
5865
ctor: Function,
59-
options?: JsonSchemaOptions,
66+
options?: JsonSchemaOptions<T>,
6067
): JSONSchema {
6168
// In the near future the metadata will be an object with
6269
// different titles as keys
@@ -107,9 +114,9 @@ export function getJsonSchema(
107114
* @param modelCtor - The model constructor (e.g. `Product`)
108115
* @param options - Additional options
109116
*/
110-
export function getJsonSchemaRef(
117+
export function getJsonSchemaRef<T extends object>(
111118
modelCtor: Function,
112-
options?: JsonSchemaOptions,
119+
options?: JsonSchemaOptions<T>,
113120
): JSONSchema {
114121
const schemaWithDefinitions = getJsonSchema(modelCtor, options);
115122
const key = schemaWithDefinitions.title;
@@ -255,14 +262,18 @@ export function getNavigationalPropertyForRelation(
255262
}
256263
}
257264

258-
function getTitleSuffix(options: JsonSchemaOptions = {}) {
265+
function getTitleSuffix<T extends object>(options: JsonSchemaOptions<T> = {}) {
259266
let suffix = '';
260267
if (options.partial) {
261268
suffix += 'Partial';
262269
}
270+
if (options.exclude && options.exclude.length) {
271+
suffix += 'Excluding[' + options.exclude + ']';
272+
}
263273
if (options.includeRelations) {
264274
suffix += 'WithRelations';
265275
}
276+
266277
return suffix;
267278
}
268279

@@ -274,9 +285,9 @@ function getTitleSuffix(options: JsonSchemaOptions = {}) {
274285
* reflection API
275286
* @param ctor - Constructor of class to convert from
276287
*/
277-
export function modelToJsonSchema(
288+
export function modelToJsonSchema<T extends object>(
278289
ctor: Function,
279-
jsonSchemaOptions: JsonSchemaOptions = {},
290+
jsonSchemaOptions: JsonSchemaOptions<T> = {},
280291
): JSONSchema {
281292
const options = {...jsonSchemaOptions};
282293
options.visited = options.visited || {};
@@ -300,6 +311,10 @@ export function modelToJsonSchema(
300311
}
301312

302313
for (const p in meta.properties) {
314+
if (options.exclude && options.exclude.includes(p as keyof T)) {
315+
continue;
316+
}
317+
303318
if (!meta.properties[p].type) {
304319
continue;
305320
}

0 commit comments

Comments
 (0)