Skip to content

Commit

Permalink
feat: add /parse path (#83)
Browse files Browse the repository at this point in the history
  • Loading branch information
magicmatatjahu authored Apr 5, 2022
1 parent 4dac650 commit cea6d1a
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 45 deletions.
128 changes: 93 additions & 35 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,34 @@ servers:
- url: https://api.asyncapi.com/v1

paths:
/generate:
/validate:
post:
summary: Generate the given AsyncAPI template.
operationId: generate
summary: Validate the given AsyncAPI document.
operationId: validate
tags:
- generate
- generator
- validate
- parser
externalDocs:
name: Github Repository for the AsyncAPI Generator
url: https://github.com/asyncapi/generator
name: Github Repository for the AsyncAPI Parser
url: https://github.com/asyncapi/parser-js
requestBody:
description: Template details to be generated.
description: Validate the given AsyncAPI document.
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateRequest'
$ref: '#/components/schemas/ValidateRequest'
responses:
"200":
description: Template successfully generated.
content:
application/zip:
schema:
$ref: '#/components/schemas/GenerateResponse'
'400':
description: Failed to generate the given template due to invalid AsyncAPI document.
"204":
description: The given AsyncAPI document is valid.
"400":
description: The given AsyncAPI document is not valid.
content:
application/json:
schema:
$ref: "#/components/schemas/Problem"
"422":
description: Failed to generate the given template due to invalid parameters.
description: The given AsyncAPI document is not valid due to invalid parameters in the request.
content:
application/json:
schema:
Expand All @@ -58,26 +54,30 @@ paths:
schema:
$ref: "#/components/schemas/Problem"

/validate:
/parse:
post:
summary: Validate the given AsyncAPI document.
operationId: validate
summary: Parse the given AsyncAPI document.
operationId: parse
tags:
- validate
- validator
- parse
- parser
externalDocs:
name: Github Repository for the AsyncAPI Parser
url: https://github.com/asyncapi/parser-js
requestBody:
description: Validate the given AsyncAPI document with the AsyncAPI parser.
description: Parse the given AsyncAPI document.
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ValidateRequest'
$ref: '#/components/schemas/ParseRequest'
responses:
"204":
description: The given AsyncAPI document is valid.
"200":
description: AsyncAPI document successfully parsed.
content:
application/json:
schema:
$ref: "#/components/schemas/ParseResponse"
"400":
description: The given AsyncAPI document is not valid.
content:
Expand All @@ -97,6 +97,49 @@ paths:
schema:
$ref: "#/components/schemas/Problem"

/generate:
post:
summary: Generate the given AsyncAPI template.
operationId: generate
tags:
- generate
- generator
externalDocs:
name: Github Repository for the AsyncAPI Generator
url: https://github.com/asyncapi/generator
requestBody:
description: Template details to be generated.
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateRequest'
responses:
"200":
description: Template successfully generated.
content:
application/zip:
schema:
$ref: '#/components/schemas/GenerateResponse'
'400':
description: Failed to generate the given template due to invalid AsyncAPI document.
content:
application/json:
schema:
$ref: "#/components/schemas/Problem"
"422":
description: Failed to generate the given template due to invalid parameters.
content:
application/json:
schema:
$ref: "#/components/schemas/Problem"
default:
description: Unexpected problem.
content:
application/json:
schema:
$ref: "#/components/schemas/Problem"

/convert:
post:
summary: Convert the given AsyncAPI document to the specified version.
Expand Down Expand Up @@ -257,6 +300,29 @@ components:
- '2.3.0'
- 'latest'

ValidateRequest:
type: object
required:
- asyncapi
properties:
asyncapi:
$ref: '#/components/schemas/AsyncAPIDocument'

ParseRequest:
type: object
required:
- asyncapi
properties:
asyncapi:
$ref: '#/components/schemas/AsyncAPIDocument'
ParseResponse:
type: object
required:
- parsed
properties:
parsed:
type: string

GenerateRequest:
type: object
required:
Expand Down Expand Up @@ -291,14 +357,6 @@ components:
type: string
format: binary

ValidateRequest:
type: object
required:
- asyncapi
properties:
asyncapi:
$ref: '#/components/schemas/AsyncAPIDocument'

ConvertRequest:
type: object
required:
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/convert.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class ConvertController implements Controller {
language,
);

res.json({
res.status(200).json({
converted: convertedSpec
});
} catch (err: unknown) {
Expand Down
36 changes: 36 additions & 0 deletions src/controllers/parse.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Request, Response, Router } from 'express';
import { AsyncAPIDocument } from '@asyncapi/parser';

import { validationMiddleware } from '../middlewares/validation.middleware';

import { Controller} from '../interfaces';

/**
* Controller which exposes the Parser functionality, to parse the AsyncAPI document.
*/
export class ParseController implements Controller {
public basepath = '/parse';

private async parse(req: Request, res: Response) {
const stringified = AsyncAPIDocument.stringify(req.asyncapi?.parsedDocument);
res.status(200).json({
parsed: stringified,
});
}

public async boot(): Promise<Router> {
const router = Router();

router.post(
`${this.basepath}`,
await validationMiddleware({
path: this.basepath,
method: 'post',
documents: ['asyncapi'],
}),
this.parse.bind(this)
);

return router;
}
}
79 changes: 79 additions & 0 deletions src/controllers/tests/parse.controller.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import request from 'supertest';

import { App } from '../../app';
import { ProblemException } from '../../exceptions/problem.exception';

import { ParseController } from '../parse.controller';

const validJSONAsyncAPI = {
asyncapi: '2.0.0',
info: {
title: 'My API',
version: '1.0.0'
},
channels: {}
};
const invalidJSONAsyncAPI = {
asyncapi: '2.0.0',
info: {
tite: 'My API', // spelled wrong on purpose to throw an error in the test
version: '1.0.0'
},
channels: {}
};

describe('ParseController', () => {
describe('[POST] /parse', () => {
it('should return stringified AsyncAPI document', async () => {
const app = new App([new ParseController()]);
await app.init();

return request(app.getServer())
.post('/v1/parse')
.send({
asyncapi: validJSONAsyncAPI
})
.expect(200, {
parsed: '{"asyncapi":"2.0.0","info":{"title":"My API","version":"1.0.0"},"channels":{},"x-parser-spec-parsed":true,"x-parser-spec-stringified":true}',
});
});

it('should throw error when sent an invalid AsyncAPI document', async () => {
const app = new App([new ParseController()]);
await app.init();

return request(app.getServer())
.post('/v1/parse')
.send({
asyncapi: invalidJSONAsyncAPI
})
.expect(422, {
type: ProblemException.createType('validation-errors'),
title: 'There were errors validating the AsyncAPI document.',
status: 422,
validationErrors: [
{
title: '/info should NOT have additional properties',
location: {
jsonPointer: '/info'
}
},
{
title: '/info should have required property \'title\'',
location: {
jsonPointer: '/info'
}
}
],
parsedJSON: {
asyncapi: '2.0.0',
info: {
tite: 'My API',
version: '1.0.0'
},
channels: {}
}
});
});
});
});
16 changes: 11 additions & 5 deletions src/middlewares/validation.middleware.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Request, Response, NextFunction } from 'express';
import { AsyncAPIDocument } from '@asyncapi/parser';

import { ProblemException } from '../exceptions/problem.exception';
import { createAjvInstance } from '../utils/ajv';
import { getAppOpenAPI } from '../utils/app-openapi';
import { parse, prepareParserConfig, tryConvertToProblemException } from '../utils/parser';

import type { ValidateFunction } from 'ajv';
import type { AsyncAPIDocument } from '../interfaces';

export interface ValidationMiddlewareOptions {
path: string;
Expand Down Expand Up @@ -78,13 +78,16 @@ async function validateSingleDocument(asyncapi: string | AsyncAPIDocument, parse
if (typeof asyncapi === 'object') {
asyncapi = JSON.parse(JSON.stringify(asyncapi));
}
await parse(asyncapi, parserConfig);
return parse(asyncapi, parserConfig);
}

async function validateListDocuments(asyncapis: Array<string | AsyncAPIDocument>, parserConfig: ReturnType<typeof prepareParserConfig>) {
const parsedDocuments: Array<AsyncAPIDocument> = [];
for (const asyncapi of asyncapis) {
await validateSingleDocument(asyncapi, parserConfig);
const parsed = await validateSingleDocument(asyncapi, parserConfig);
parsedDocuments.push(parsed);
}
return parsedDocuments;
}

/**
Expand All @@ -106,12 +109,15 @@ export async function validationMiddleware(options: ValidationMiddlewareOptions)
// validate AsyncAPI document(s)
const parserConfig = prepareParserConfig(req);
try {
req.asyncapi = req.asyncapi || {};
for (const field of documents) {
const body = req.body[String(field)];
if (Array.isArray(body)) {
await validateListDocuments(body, parserConfig);
const parsed = await validateListDocuments(body, parserConfig);
req.asyncapi.parsedDocuments = parsed;
} else {
await validateSingleDocument(body, parserConfig);
const parsed = await validateSingleDocument(body, parserConfig);
req.asyncapi.parsedDocument = parsed;
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/server-api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { AsyncAPIDocument } from '@asyncapi/parser';

declare module 'express' {
export interface Request {
parsedDocument?: AsyncAPIDocument;
asyncapi?: {
parsedDocument?: AsyncAPIDocument;
parsedDocuments?: Array<AsyncAPIDocument>;
},
}
}
Loading

0 comments on commit cea6d1a

Please sign in to comment.