Skip to content

Commit 8b6bebb

Browse files
committed
fix: add information for extra fields as extensions to openapi specs
Signed-off-by: Muhammad Aaqil <[email protected]>
1 parent 3cf7046 commit 8b6bebb

File tree

7 files changed

+148
-5
lines changed

7 files changed

+148
-5
lines changed

packages/openapi-v3/src/__tests__/unit/json-to-schema.unit.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import {JsonSchema} from '@loopback/repository-json-schema';
77
import {expect} from '@loopback/testlab';
8+
import {PropertyDefinition} from '@loopback/repository';
9+
810
import {jsonOrBooleanToJSON, jsonToSchemaObject, SchemaObject} from '../..';
911

1012
describe('jsonToSchemaObject', () => {
@@ -308,6 +310,29 @@ describe('jsonToSchemaObject', () => {
308310
propertyConversionTest(itemsDef, expectedItems);
309311
});
310312

313+
it('add extra properties as extension properties', () => {
314+
const properties: PropertyDefinition = {
315+
type: 'string',
316+
defaultFn: 'guid',
317+
index: false,
318+
precision: 10,
319+
scale: 10,
320+
generated: true,
321+
hidden: false,
322+
};
323+
324+
const expectedItems: SchemaObject = {
325+
type: 'string',
326+
'x-defaultFn': 'guid',
327+
'x-generated': true,
328+
'x-hidden': false,
329+
'x-index': false,
330+
'x-precision': 10,
331+
'x-scale': 10,
332+
};
333+
propertyConversionTest(properties, expectedItems);
334+
});
335+
311336
// Helper function to check conversion of JSON Schema properties
312337
// to Swagger versions
313338
function propertyConversionTest(property: object, expected: object) {

packages/openapi-v3/src/json-to-schema.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,21 @@ export function jsonToSchemaObject(
140140
delete result[converted];
141141
// Check if the description contains information about TypeScript type
142142
const matched = result.description?.match(/^\(tsType: (.+), schemaOptions:/);
143+
const extensionProperties = [
144+
'defaultFn',
145+
'index',
146+
'length',
147+
'precision',
148+
'scale',
149+
'generated',
150+
'hidden',
151+
];
152+
Object.keys(result).forEach(key => {
153+
if (extensionProperties.includes(key)) {
154+
result[`x-${key}`] = result[key];
155+
delete result[key];
156+
}
157+
});
143158
if (matched) {
144159
result['x-typescript-type'] = matched[1];
145160
}

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,17 +203,28 @@ describe('build-schema', () => {
203203
});
204204
});
205205

206-
it('show generated', () => {
206+
it('keeps extensions on property', () => {
207207
expect(
208208
metaToJsonProperty({
209209
type: String,
210-
description: 'test',
210+
defaultFn: 'guid',
211+
index: false,
212+
length: 50,
213+
precision: 10,
214+
scale: 0,
211215
generated: true,
216+
hidden: true,
212217
}),
213218
).to.eql({
214219
type: 'string',
215-
description: 'test',
220+
defaultFn: 'guid',
221+
index: false,
222+
length: 50,
223+
precision: 10,
224+
scale: 0,
225+
generated: true,
216226
readOnly: true,
227+
hidden: true,
217228
});
218229
});
219230

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from '@loopback/repository';
1717
import debugFactory from 'debug';
1818
import {inspect} from 'util';
19-
import {JsonSchema} from './index';
19+
import {JsonSchema, ExtensionProperties} from './index';
2020
import {JSON_SCHEMA_KEY} from './keys';
2121
const debug = debugFactory('loopback:repository-json-schema:build-schema');
2222

@@ -255,9 +255,25 @@ export function isArrayType(type: string | Function | PropertyType) {
255255
* @param meta
256256
*/
257257
export function metaToJsonProperty(meta: PropertyDefinition): JsonSchema {
258-
const propDef: JsonSchema = {};
258+
const propDef: JsonSchema & ExtensionProperties = {};
259259
let result: JsonSchema;
260260
let propertyType = meta.type as string | Function;
261+
const propertiesToCopy = [
262+
'default',
263+
'defaultFn',
264+
'index',
265+
'length',
266+
'precision',
267+
'scale',
268+
'generated',
269+
'hidden',
270+
];
271+
272+
propertiesToCopy.forEach(prop => {
273+
if (meta[prop] !== undefined) {
274+
propDef[prop] = meta[prop];
275+
}
276+
});
261277

262278
if (isArrayType(propertyType) && meta.itemType) {
263279
if (isArrayType(meta.itemType) && !meta.jsonSchema) {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,5 @@ export type Optional<T extends object, K extends keyof T = keyof T> = Omit<
4545
K
4646
> &
4747
Partial<Pick<T, K>>;
48+
49+
export type ExtensionProperties = {[x: string]: number | boolean | string};

packages/rest/src/__tests__/unit/ajv-factory.provider.unit.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,4 +290,56 @@ describe('Ajv factory', () => {
290290
ctx = new Context();
291291
ctx.bind(RestBindings.AJV_FACTORY).toProvider(AjvFactoryProvider);
292292
}
293+
294+
it('validate extension properties', async () => {
295+
const ajvFactory = await ctx.get(RestBindings.AJV_FACTORY);
296+
const validator = ajvFactory().compile({
297+
type: 'object',
298+
properties: {
299+
validation: {
300+
type: 'object',
301+
properties: {
302+
'x-default': {
303+
type: 'string',
304+
},
305+
'x-defaultFn': {
306+
type: 'string',
307+
enum: ['guid', 'uuid', 'uuidv4', 'now'],
308+
},
309+
'x-index': {
310+
type: 'boolean',
311+
},
312+
'x-length': {
313+
type: 'number',
314+
},
315+
'x-precision': {
316+
type: 'number',
317+
},
318+
'x-scale': {
319+
type: 'number',
320+
},
321+
'x-generated': {
322+
type: 'boolean',
323+
},
324+
'x-hidden': {
325+
type: 'boolean',
326+
},
327+
},
328+
},
329+
},
330+
});
331+
const result = validator({
332+
validation: {
333+
'x-default': 'default value',
334+
'x-defaultFn': 'guid',
335+
'x-index': false,
336+
'x-length': 54,
337+
'x-precision': 10,
338+
'x-scale': 0,
339+
'x-generated': true,
340+
'x-hidden': false,
341+
},
342+
});
343+
expect(result).to.be.true();
344+
});
293345
});

packages/rest/src/validation/ajv-factory.provider.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,28 @@ export class AjvFactoryProvider implements Provider<AjvFactory> {
6363
const ajvInst = new AjvCtor(ajvOptions);
6464
ajvInst.addKeyword('components');
6565
ajvInst.addKeyword('x-typescript-type');
66+
ajvInst.addKeyword({
67+
keyword: 'x-default',
68+
schemaType: [
69+
'string',
70+
'number',
71+
'integer',
72+
'boolean',
73+
'null',
74+
'object',
75+
'array',
76+
],
77+
});
78+
ajvInst.addKeyword({keyword: 'x-defaultFn', schemaType: ['string']});
79+
ajvInst.addKeyword({
80+
keyword: 'x-index',
81+
schemaType: ['boolean', 'object'],
82+
});
83+
ajvInst.addKeyword({keyword: 'x-length', schemaType: ['number']});
84+
ajvInst.addKeyword({keyword: 'x-precision', schemaType: ['number']});
85+
ajvInst.addKeyword({keyword: 'x-scale', schemaType: ['number']});
86+
ajvInst.addKeyword({keyword: 'x-generated', schemaType: 'boolean'});
87+
ajvInst.addKeyword({keyword: 'x-hidden', schemaType: ['boolean']});
6688

6789
ajvKeywords(ajvInst, validationOptions.ajvKeywords);
6890

0 commit comments

Comments
 (0)