Skip to content

Commit 0d0f32f

Browse files
authored
fix: wrong references crashes library (#180)
1 parent e41ca23 commit 0d0f32f

27 files changed

+338
-1701
lines changed

.github/workflows/coverall.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ on:
22
push:
33
branches:
44
- master
5-
pull_request_target:
5+
pull_request:
66
types: [opened, reopened, synchronize, ready_for_review]
77
name: Check test coverage
88
jobs:

src/processors/InputProcessor.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,38 @@ import { CommonInputModel } from '../models/CommonInputModel';
88
*/
99
export class InputProcessor {
1010
public static processor: InputProcessor = new InputProcessor();
11-
1211
private processors: Map<string, AbstractInputProcessor> = new Map();
1312

1413
constructor() {
15-
this.addProcessor('asyncapi', new AsyncAPIInputProcessor());
16-
this.addProcessor('default', new JsonSchemaInputProcessor());
14+
this.setProcessor('asyncapi', new AsyncAPIInputProcessor());
15+
this.setProcessor('default', new JsonSchemaInputProcessor());
1716
}
18-
17+
1918
/**
20-
* Add a processor.
19+
* Set a processor.
2120
*
2221
* @param type of processor
2322
* @param processor
2423
*/
25-
addProcessor(type: string, processor: AbstractInputProcessor) {
24+
setProcessor(type: string, processor: AbstractInputProcessor) {
2625
this.processors.set(type, processor);
2726
}
2827

28+
/**
29+
*
30+
* @returns all processors
31+
*/
32+
getProcessors() : Map<string, AbstractInputProcessor> {
33+
return this.processors;
34+
}
35+
2936
/**
3037
* The processor code which delegates the processing to the correct implementation.
3138
*
3239
* @param input to process
3340
* @param type of processor to use
3441
*/
35-
process(input: any) : Promise<CommonInputModel> {
42+
async process(input: any): Promise<CommonInputModel> {
3643
for (const [type, processor] of this.processors) {
3744
if (type === 'default') continue;
3845
if (processor.shouldProcess(input)) {
@@ -46,3 +53,4 @@ export class InputProcessor {
4653
throw new Error('No default processor found');
4754
}
4855
}
56+

src/processors/JsonSchemaInputProcessor.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { AbstractInputProcessor } from './AbstractInputProcessor';
2-
import {simplify} from '../simplification/Simplifier';
2+
import { simplify } from '../simplification/Simplifier';
33
import $RefParser from '@apidevtools/json-schema-ref-parser';
44
import path from 'path';
55
import { Schema, CommonModel, CommonInputModel} from '../models';
@@ -58,19 +58,20 @@ export class JsonSchemaInputProcessor extends AbstractInputProcessor {
5858
// eslint-disable-next-line no-undef
5959
const localPath = `${process.cwd()}${path.sep}`;
6060
commonInputModel.originalInput = Schema.toSchema(input);
61-
await refParser.dereference(localPath,
62-
input, {
63-
continueOnError: true,
64-
dereference: { circular: 'ignore' },
65-
});
66-
const parsedSchema = Schema.toSchema(input);
67-
if (refParser.$refs.circular && typeof parsedSchema !== 'boolean') {
68-
const circularOption : $RefParser.Options = {
69-
continueOnError: true,
70-
dereference: { circular: true },
71-
};
72-
await refParser.dereference(localPath, parsedSchema as $RefParser.JSONSchema, circularOption);
61+
const deRefOption: $RefParser.Options = {
62+
continueOnError: true,
63+
dereference: { circular: true },
64+
};
65+
Logger.debug(`Trying to dereference all $ref instances from input, using option ${JSON.stringify(deRefOption)}.`);
66+
try {
67+
await refParser.dereference(localPath, input, deRefOption);
68+
} catch (e) {
69+
const errorMessage = `Could not dereference $ref in input, is all the references correct? ${e.message}`;
70+
Logger.error(errorMessage, e);
71+
throw new Error(errorMessage);
7372
}
73+
const parsedSchema = Schema.toSchema(input);
74+
Logger.debug('Successfully dereferenced all $ref instances from input.', parsedSchema);
7475
commonInputModel.models = JsonSchemaInputProcessor.convertSchemaToCommonModel(parsedSchema);
7576
return commonInputModel;
7677
}
@@ -215,7 +216,12 @@ export class JsonSchemaInputProcessor extends AbstractInputProcessor {
215216
const commonModelsMap: Record<string, CommonModel> = {};
216217
commonModels.forEach(value => {
217218
if (value.$id) {
219+
if (commonModelsMap[value.$id] !== undefined) {
220+
Logger.warn(`Overwriting existing model with $id ${value.$id}, are there two models with the same id present?`, value);
221+
}
218222
commonModelsMap[value.$id] = value;
223+
} else {
224+
Logger.debug('Model did not have $id, ignoring.', value);
219225
}
220226
});
221227
return commonModelsMap;

test/processors/AsyncAPIInputProcessor.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ describe('AsyncAPIInputProcessor', function() {
3131
});
3232

3333
describe('process()', function() {
34+
test('should throw error when trying to process wrong schema', async function () {
35+
const processor = new AsyncAPIInputProcessor();
36+
await expect(processor.process({}))
37+
.rejects
38+
.toThrow('Input is not an AsyncAPI document so it cannot be processed.');
39+
});
3440
test('should be able to process pure object', async function() {
3541
const basicDocString = fs.readFileSync(path.resolve(__dirname, './AsyncAPIInputProcessor/basic.json'), 'utf8');
3642
const expectedCommonInputModelString = fs.readFileSync(path.resolve(__dirname, './AsyncAPIInputProcessor/commonInputModel/basic.json'), 'utf8');
Lines changed: 73 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,81 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
3-
import {parse} from '@asyncapi/parser';
3+
import { CommonInputModel } from '../../src/models';
44
import { InputProcessor } from '../../src/processors/InputProcessor';
5+
import { JsonSchemaInputProcessor } from '../../src/processors/JsonSchemaInputProcessor';
6+
import { AsyncAPIInputProcessor } from '../../src/processors/AsyncAPIInputProcessor';
7+
import { AbstractInputProcessor } from '../../src/processors';
58

6-
describe('InputProcessor', function() {
7-
/**
8-
* The input schema when processed should be equals to the expected CommonInputModel
9-
*
10-
* @param inputSchemaPath
11-
* @param expectedCommonModulePath
12-
*/
13-
const expectFunction = async (inputSchemaPath: string, expectedCommonModulePath: string) => {
14-
const processor = new InputProcessor();
15-
const inputSchemaString = fs.readFileSync(path.resolve(__dirname, inputSchemaPath), 'utf8');
16-
const expectedCommonInputModelString = fs.readFileSync(path.resolve(__dirname, expectedCommonModulePath), 'utf8');
17-
const inputSchema = JSON.parse(inputSchemaString);
18-
const expectedCommonInputModel = JSON.parse(expectedCommonInputModelString);
19-
const commonInputModel = await processor.process(inputSchema);
20-
expect(commonInputModel).toEqual(expectedCommonInputModel);
9+
describe('InputProcessor', function () {
10+
beforeEach(() => {
11+
jest.resetAllMocks();
12+
});
13+
afterAll(() => {
14+
jest.restoreAllMocks();
15+
});
16+
17+
class TempProcessor extends AbstractInputProcessor{
18+
process(input: any): Promise<CommonInputModel> { return Promise.resolve(new CommonInputModel()); }
19+
shouldProcess(input: any): boolean { return true; }
20+
}
21+
test('should add processor to map', async function () {
22+
const testProcessor = new TempProcessor();
23+
const processor = new InputProcessor();
24+
processor.setProcessor('some_key', testProcessor);
25+
const foundProcessor = processor.getProcessors().get('some_key');
26+
expect(foundProcessor).toEqual(testProcessor);
27+
});
28+
test('overwriting processor should use new and not old', async function () {
29+
const testProcessor = new TempProcessor();
30+
const processor = new InputProcessor();
31+
const oldDefaultProcessor = processor.getProcessors().get('default');
32+
processor.setProcessor('default', testProcessor);
33+
const currentDefaultProcessor = processor.getProcessors().get('default');
34+
expect(currentDefaultProcessor?.constructor).not.toEqual(oldDefaultProcessor?.constructor);
35+
expect(oldDefaultProcessor?.constructor).toEqual(oldDefaultProcessor?.constructor);
36+
expect(currentDefaultProcessor?.constructor).toEqual(currentDefaultProcessor?.constructor);
37+
});
38+
describe('process()', function () {
39+
const getProcessors = () => {
40+
const asyncInputProcessor = new AsyncAPIInputProcessor();
41+
jest.spyOn(asyncInputProcessor, 'shouldProcess');
42+
jest.spyOn(asyncInputProcessor, 'process');
43+
const defaultInputProcessor = new JsonSchemaInputProcessor();
44+
jest.spyOn(defaultInputProcessor, 'shouldProcess');
45+
jest.spyOn(defaultInputProcessor, 'process');
46+
const processor = new InputProcessor();
47+
processor.setProcessor('asyncapi', asyncInputProcessor);
48+
processor.setProcessor('default', defaultInputProcessor);
49+
return {processor, asyncInputProcessor, defaultInputProcessor}
2150
}
22-
describe('process()', function() {
23-
describe('should be able to process JSON schema input', function() {
24-
test('with absence types', async function() {
25-
const inputSchemaPath = './JsonSchemaInputProcessor/absence_type.json';
26-
const expectedCommonModulePath = './JsonSchemaInputProcessor/commonInputModel/absence_type.json';
27-
await expectFunction(inputSchemaPath, expectedCommonModulePath);
28-
});
29-
test('with conditional schemas', async function() {
30-
const inputSchemaPath = './JsonSchemaInputProcessor/applying_conditional_schemas.json';
31-
const expectedCommonModulePath = './JsonSchemaInputProcessor/commonInputModel/applying_conditional_schemas.json';
32-
await expectFunction(inputSchemaPath, expectedCommonModulePath);
33-
});
34-
test('with combination schemas', async function() {
35-
const inputSchemaPath = './JsonSchemaInputProcessor/combination_schemas.json';
36-
const expectedCommonModulePath = './JsonSchemaInputProcessor/commonInputModel/combination_schemas.json';
37-
await expectFunction(inputSchemaPath, expectedCommonModulePath);
38-
});
39-
test('with enum schemas', async function() {
40-
const inputSchemaPath = './JsonSchemaInputProcessor/enum.json';
41-
const expectedCommonModulePath = './JsonSchemaInputProcessor/commonInputModel/enum.json';
42-
await expectFunction(inputSchemaPath, expectedCommonModulePath);
43-
});
44-
test('with items schemas', async function() {
45-
const inputSchemaPath = './JsonSchemaInputProcessor/items.json';
46-
const expectedCommonModulePath = './JsonSchemaInputProcessor/commonInputModel/items.json';
47-
await expectFunction(inputSchemaPath, expectedCommonModulePath);
48-
});
49-
test('with multiple objects', async function() {
50-
const inputSchemaPath = './JsonSchemaInputProcessor/multiple_objects.json';
51-
const expectedCommonModulePath = './JsonSchemaInputProcessor/commonInputModel/multiple_objects.json';
52-
await expectFunction(inputSchemaPath, expectedCommonModulePath);
53-
});
54-
});
51+
test('should throw error when no default processor found', async function () {
52+
const processor = new InputProcessor();
53+
const map = processor.getProcessors();
54+
map.delete('default');
55+
await expect(processor.process({}))
56+
.rejects
57+
.toThrow('No default processor found');
58+
});
59+
test('should be able to process default JSON schema input', async function () {
60+
const {processor, asyncInputProcessor, defaultInputProcessor} = getProcessors();
61+
const inputSchemaString = fs.readFileSync(path.resolve(__dirname, './JsonSchemaInputProcessor/basic.json'), 'utf8');
62+
const inputSchema = JSON.parse(inputSchemaString);
63+
await processor.process(inputSchema);
64+
expect(asyncInputProcessor.process).not.toHaveBeenCalled();
65+
expect(asyncInputProcessor.shouldProcess).toHaveBeenNthCalledWith(1, inputSchema);
66+
expect(defaultInputProcessor.process).toHaveBeenNthCalledWith(1, inputSchema);
67+
expect(defaultInputProcessor.shouldProcess).toHaveBeenNthCalledWith(1, inputSchema);
68+
});
5569

56-
describe('should be able to process AsyncAPI schema input', function() {
57-
test('with pure object', async function() {
58-
const processor = new InputProcessor();
59-
const basicDocString = fs.readFileSync(path.resolve(__dirname, './AsyncAPIInputProcessor/basic.json'), 'utf8');
60-
const expectedCommonInputModelString = fs.readFileSync(path.resolve(__dirname, './AsyncAPIInputProcessor/commonInputModel/basic.json'), 'utf8');
61-
const basicDoc = JSON.parse(basicDocString);
62-
const expectedCommonInputModel = JSON.parse(expectedCommonInputModelString);
63-
const commonInputModel = await processor.process(basicDoc);
64-
expect(commonInputModel).toMatchObject(expectedCommonInputModel);
65-
});
66-
test('with parsed document', async function() {
67-
const processor = new InputProcessor();
68-
const basicDocString = fs.readFileSync(path.resolve(__dirname, './AsyncAPIInputProcessor/basic.json'), 'utf8');
69-
const expectedCommonInputModelString = fs.readFileSync(path.resolve(__dirname, './AsyncAPIInputProcessor/commonInputModel/basic.json'), 'utf8');
70-
const expectedCommonInputModel = JSON.parse(expectedCommonInputModelString);
71-
const parsedObject = await parse(basicDocString);
72-
const commonInputModel = await processor.process(parsedObject);
73-
expect(commonInputModel).toMatchObject(expectedCommonInputModel);
74-
});
75-
});
70+
test('should be able to process AsyncAPI schema input', async function () {
71+
const {processor, asyncInputProcessor, defaultInputProcessor} = getProcessors();
72+
const inputSchemaString = fs.readFileSync(path.resolve(__dirname, './AsyncAPIInputProcessor/basic.json'), 'utf8');
73+
const inputSchema = JSON.parse(inputSchemaString);
74+
await processor.process(inputSchema);
75+
expect(asyncInputProcessor.process).toHaveBeenNthCalledWith(1, inputSchema);
76+
expect(asyncInputProcessor.shouldProcess).toHaveBeenNthCalledWith(1, inputSchema);
77+
expect(defaultInputProcessor.process).not.toHaveBeenCalled();
78+
expect(defaultInputProcessor.shouldProcess).not.toHaveBeenCalled();
7679
});
80+
});
7781
});

0 commit comments

Comments
 (0)