Skip to content

Commit

Permalink
Merge 49348fb into 92185d7
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaslagoni committed May 20, 2021
2 parents 92185d7 + 49348fb commit eb1eb63
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/interpretation_of_JSON_Schema_draft_7.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The order of transformation:
- `properties` are determined as is, where duplicate properties for the model are merged.
- [allOf](#allOf-sub-schemas)
- `const` overwrite already interpreted `enums`.
- `items` are determined as is, where more then 1 item are merged.
- [oneOf/anyOf/then/else](#Processing-sub-schemas)

## allOf sub schemas
Expand Down
38 changes: 38 additions & 0 deletions src/interpreter/InterpretItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

import { CommonModel } from '../models/CommonModel';
import { Schema } from '../models/Schema';
import { Interpreter } from './Interpreter';

/**
* Interpreter function for JSON Schema draft 7 items keyword.
*
* @param schema
* @param model
* @param interpreter
*/
export default function interpretItems(schema: Schema | boolean, model: CommonModel, interpreter : Interpreter) {
if (typeof schema === 'boolean' || schema.items === undefined) return;
model.addTypes('array');
interpretArrayItems(schema, schema.items, model, interpreter);
}

/**
* Internal function to process all item schemas
*
* @param rootSchema
* @param itemSchemas
* @param model
* @param interpreter
*/
function interpretArrayItems(rootSchema: Schema, itemSchemas: (Schema | boolean)[] | (Schema | boolean), model: CommonModel, interpreter : Interpreter) {
if (Array.isArray(itemSchemas)) {
for (const itemSchema of itemSchemas) {
interpretArrayItems(rootSchema, itemSchema, model, interpreter);
}
} else {
const itemModels = interpreter.interpret(itemSchemas);
if (itemModels.length > 0) {
model.addItem(itemModels[0], rootSchema);
}
}
}
4 changes: 3 additions & 1 deletion src/interpreter/Interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import interpretProperties from './InterpretProperties';
import interpretAllOf from './InterpretAllOf';
import interpretConst from './InterpretConst';
import { Logger } from '../utils';
import interpretItems from './InterpretItems';

export class Interpreter {
static defaultOptions: SimplificationOptions = {
Expand Down Expand Up @@ -78,7 +79,8 @@ export class Interpreter {
}

model.required = schema.required || model.required;


interpretItems(schema, model, this);
interpretProperties(schema, model, this);
interpretAllOf(schema, model, this);
interpretConst(schema, model);
Expand Down
17 changes: 17 additions & 0 deletions src/models/CommonModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@ export class CommonModel extends CommonSchema<CommonModel> {
return this.required.includes(propertyName);
}

/**
* Adds an item to the model.
*
* If items already exist the two are merged.
*
* @param itemModel
* @param schema schema to the corresponding property model
*/
addItem(itemModel: CommonModel, schema: Schema) {
if (this.items !== undefined) {
Logger.warn(`While trying to add item to model ${this.$id}, duplicate items found. Merging models together to form a unified item model.`, itemModel, schema, this);
this.items = CommonModel.mergeCommonModels(this.items as CommonModel, itemModel, schema);
} else {
this.items = itemModel;
}
}

/**
* Adds a property to the model.
* If the property already exist the two are merged.
Expand Down
8 changes: 8 additions & 0 deletions test/interpreter/Intepreter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {isModelObject, interpretName} from '../../src/interpreter/Utils';
import interpretProperties from '../../src/interpreter/InterpretProperties';
import interpretConst from '../../src/interpreter/InterpretConst';
import interpretAllOf from '../../src/interpreter/InterpretAllOf';
import interpretItems from '../../src/interpreter/InterpretItems';
import { CommonModel, Schema } from '../../src/models';

let mockedIsModelObjectReturn = false;
Expand All @@ -17,6 +18,7 @@ jest.mock('../../src/interpreter/Utils', () => {
jest.mock('../../src/interpreter/InterpretProperties');
jest.mock('../../src/interpreter/InterpretConst');
jest.mock('../../src/interpreter/InterpretAllOf');
jest.mock('../../src/interpreter/InterpretItems');
CommonModel.mergeCommonModels = jest.fn();
/**
* Some of these test are purely theoretical and have little if any merit
Expand Down Expand Up @@ -176,6 +178,12 @@ describe('Interpreter', function() {
interpreter.interpret(schema);
expect(interpretAllOf).toHaveBeenNthCalledWith(1, schema, expect.anything(), expect.anything());
});
test('should always try to interpret items', function() {
const schema = {};
const interpreter = new Interpreter();
interpreter.interpret(schema);
expect(interpretItems).toHaveBeenNthCalledWith(1, schema, expect.anything(), expect.anything());
});

test('should support primitive roots', function() {
const schema = { 'type': 'string' };
Expand Down
86 changes: 86 additions & 0 deletions test/interpreter/InterpretItems.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* eslint-disable no-undef */
import { CommonModel } from '../../src/models/CommonModel';
import { Interpreter } from '../../src/interpreter/Interpreter';
import interpretItems from '../../src/interpreter/InterpretItems';
let mockedReturnModels = [new CommonModel()];
jest.mock('../../src/interpreter/Interpreter', () => {
return {
Interpreter: jest.fn().mockImplementation(() => {
return {
interpret: jest.fn().mockReturnValue(mockedReturnModels)
};
})
};
});
jest.mock('../../src/models/CommonModel');
describe('Interpretation of', () => {
beforeEach(() => {
jest.clearAllMocks();
mockedReturnModels = [new CommonModel()];
});
afterAll(() => {
jest.restoreAllMocks();
})
test('should not do anything if schema does not contain items', function() {
const schema = {};
const model = new CommonModel();
const interpreter = new Interpreter();
interpretItems(schema, model, interpreter);
expect(model.type).toBeUndefined();
expect(model.addItem).not.toHaveBeenCalled();
});
test('should not do anything if schema is boolean', function() {
const schema = true;
const model = new CommonModel();
const interpreter = new Interpreter();
interpretItems(schema, model, interpreter);
expect(model.type).toBeUndefined();
expect(model.addItem).not.toHaveBeenCalled();
expect(JSON.stringify(model)).toEqual(JSON.stringify(new CommonModel()));
});

test('should ignore model if interpreter cannot interpret property schema', () => {
const schema: any = { items: { type: 'string' } };
const model = new CommonModel();
const interpreter = new Interpreter();
mockedReturnModels.pop();
interpretItems(schema, model, interpreter);
expect(model.type).toBeUndefined();
expect(model.addItem).not.toHaveBeenCalled();
});
describe('single item schemas', () => {
test('should set items', () => {
const schema: any = { items: { type: 'string' } };
const model = new CommonModel();
const interpreter = new Interpreter();
interpretItems(schema, model, interpreter);
expect(interpreter.interpret).toHaveBeenNthCalledWith(1, { type: 'string' });
expect(model.addItem).toHaveBeenNthCalledWith(1, mockedReturnModels[0], schema);
});
test('should infer type of model', () => {
const schema: any = { items: { type: 'string' } };
const model = new CommonModel();
const interpreter = new Interpreter();
interpretItems(schema, model, interpreter);
expect(model.addTypes).toHaveBeenNthCalledWith(1, 'array');
});
});
describe('multiple item schemas', () => {
test('should set items', () => {
const schema: any = { items: [{ type: 'string' }, { type: 'number' }] };
const model = new CommonModel();
const interpreter = new Interpreter();
interpretItems(schema, model, interpreter);
expect(interpreter.interpret).toHaveBeenNthCalledWith(1, { type: 'string' });
expect(interpreter.interpret).toHaveBeenNthCalledWith(2, { type: 'number' });
expect(model.addItem).toHaveBeenNthCalledWith(1, mockedReturnModels[0], schema);
});
test('should infer type of model', () => {
const schema: any = { items: [{ type: 'string' }, { type: 'number' }] };
const model = new CommonModel();
const interpreter = new Interpreter();
interpretItems(schema, model, interpreter);
expect(model.addTypes).toHaveBeenNthCalledWith(1, 'array');
});
});
});
26 changes: 26 additions & 0 deletions test/models/CommonModel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,32 @@ describe('CommonModel', function() {
});
});

describe('addItem', function() {
beforeAll(() => {
jest.spyOn(CommonModel, "mergeCommonModels");
});
afterEach(() => {
jest.clearAllMocks();
});
test('should add items to model', function() {
const itemModel = new CommonModel();
itemModel.$id = "test";
const model = new CommonModel();
model.addItem(itemModel, {});
expect(model.items).toEqual(itemModel);
expect(CommonModel.mergeCommonModels).not.toHaveBeenCalled();
});
test('should merge items together', function() {
const itemModel = new CommonModel();
itemModel.$id = "test";
const model = new CommonModel();
model.items = itemModel;
model.addItem(itemModel, {});
model.addItem(itemModel, {});
expect(model.items).toEqual(itemModel);
expect(CommonModel.mergeCommonModels).toHaveBeenNthCalledWith(1, itemModel, itemModel, {});
});
});
describe('addProperty', function() {
beforeAll(() => {
jest.spyOn(CommonModel, "mergeCommonModels");
Expand Down

0 comments on commit eb1eb63

Please sign in to comment.