Skip to content

Commit ddad02a

Browse files
committed
Initial commit
0 parents  commit ddad02a

10 files changed

+937
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/node_modules
2+
/coverage
3+
/.vscode
4+
/lib

.npmignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/__test__
2+
/node_modules
3+
/coverage

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Wait a second, still setting up

package.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "koa-oas3",
3+
"version": "0.1.0",
4+
"main": "index.js",
5+
"author": "<[email protected]>",
6+
"license": "MIT",
7+
"scripts": {
8+
"test": "echo 'noooooooooooooo'",
9+
"prepublishOnly": "rm -rf lib && tsc"
10+
},
11+
"dependencies": {
12+
"@types/koa": "^2.0.44",
13+
"oas3-chow-chow": "^0.2.5",
14+
"swagger2openapi": "^2.11.16"
15+
},
16+
"devDependencies": {
17+
"@types/js-yaml": "^3.10.1",
18+
"typescript": "^2.7.2"
19+
}
20+
}

src/config.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export interface Config {
2+
swaggerFile: string;
3+
swaggerPath: string;
4+
swaggerUIPath: string;
5+
validateResponse: boolean;
6+
validatePaths: string[];
7+
}
8+
9+
export function validateConfig(cfg: Partial<Config>): Config {
10+
if (!cfg.swaggerFile) {
11+
throw new Error('You must configure a Swagger file');
12+
}
13+
return {
14+
swaggerFile: cfg.swaggerFile,
15+
swaggerPath: cfg.swaggerPath || '/swagger.json',
16+
swaggerUIPath: cfg.swaggerUIPath || '/swagger.html',
17+
validateResponse: cfg.validateResponse || false,
18+
validatePaths: cfg.validatePaths || ['/'],
19+
};
20+
}

src/index.ts

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import * as koa from 'koa';
2+
import { Config, validateConfig } from './config';
3+
import ChowChow from 'oas3-chow-chow';
4+
import { swaggerUI } from './swagger-ui';
5+
import * as fs from 'fs';
6+
import * as path from 'path';
7+
import * as yaml from 'js-yaml';
8+
import * as converter from 'swagger2openapi';
9+
10+
export class ValidationError extends Error {
11+
constructor(message: string, public status: number, public validationErrors?: any[]) {
12+
super(message);
13+
}
14+
}
15+
16+
export async function oas(cfg: Partial<Config>): Promise<koa.Middleware> {
17+
18+
const config = validateConfig(cfg);
19+
const { compiled, doc } = await compileOas(config.swaggerFile);
20+
21+
return async (ctx: koa.Context, next: () => Promise<any>): Promise<void> => {
22+
23+
if (ctx.path === config.swaggerPath) {
24+
ctx.body = doc;
25+
return;
26+
}
27+
28+
if (ctx.path === config.swaggerUIPath) {
29+
ctx.body = swaggerUI({
30+
title: doc.info ? doc.info.title : 'Swagger UI',
31+
url: config.swaggerPath,
32+
});
33+
return;
34+
}
35+
36+
if (!config.validatePaths.some(path => ctx.path.startsWith(path))) {
37+
// Skip validation if no path matches
38+
return await next();
39+
}
40+
41+
compiled.validateRequest(ctx.path, {
42+
method: ctx.request.method,
43+
header: ctx.request.header,
44+
query: ctx.request.query,
45+
path: ctx.params,
46+
cookie: ctx.cookies
47+
})
48+
49+
await next();
50+
51+
if (config.validateResponse) {
52+
//TODO: Adds support to valiadteResponse
53+
throw new Error('Validate response is under development... :)');
54+
// const error = swagger.validateResponse(compiledPath, ctx.method, ctx.status, ctx.body);
55+
// if (error) {
56+
// throw new ValidationError(`Response validation failed`, 500, [error]);
57+
// }
58+
}
59+
};
60+
}
61+
62+
async function compileOas(file: string) {
63+
// Convert Swagger to OAS
64+
const { openapi } = await converter.convertFile(file);
65+
66+
return {
67+
compiled: new ChowChow(openapi),
68+
doc: openapi,
69+
};
70+
}

src/swagger-ui.ts

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
export interface SwaggerUIConfig {
2+
title?: string;
3+
url: string;
4+
}
5+
6+
export function swaggerUI(cfg: SwaggerUIConfig): string { return `
7+
<!DOCTYPE html>
8+
<html lang="en">
9+
<head>
10+
<meta charset="UTF-8">
11+
<base href="//unpkg.com/swagger-ui-dist@3/">
12+
<title>${cfg.title || 'Swagger UI'}</title>
13+
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
14+
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
15+
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
16+
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
17+
<style>
18+
html
19+
{
20+
box-sizing: border-box;
21+
overflow: -moz-scrollbars-vertical;
22+
overflow-y: scroll;
23+
}
24+
*,
25+
*:before,
26+
*:after
27+
{
28+
box-sizing: inherit;
29+
}
30+
31+
body {
32+
margin:0;
33+
background: #fafafa;
34+
}
35+
</style>
36+
</head>
37+
38+
<body>
39+
40+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
41+
<defs>
42+
<symbol viewBox="0 0 20 20" id="unlocked">
43+
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
44+
</symbol>
45+
46+
<symbol viewBox="0 0 20 20" id="locked">
47+
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
48+
</symbol>
49+
50+
<symbol viewBox="0 0 20 20" id="close">
51+
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
52+
</symbol>
53+
54+
<symbol viewBox="0 0 20 20" id="large-arrow">
55+
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
56+
</symbol>
57+
58+
<symbol viewBox="0 0 20 20" id="large-arrow-down">
59+
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
60+
</symbol>
61+
62+
63+
<symbol viewBox="0 0 24 24" id="jump-to">
64+
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
65+
</symbol>
66+
67+
<symbol viewBox="0 0 24 24" id="expand">
68+
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
69+
</symbol>
70+
71+
</defs>
72+
</svg>
73+
74+
<div id="swagger-ui"></div>
75+
76+
<script src="./swagger-ui-bundle.js"> </script>
77+
<script src="./swagger-ui-standalone-preset.js"> </script>
78+
<script>
79+
window.onload = function() {
80+
81+
// Build a system
82+
const ui = SwaggerUIBundle({
83+
url: window.location.origin + "${cfg.url}",
84+
dom_id: '#swagger-ui',
85+
deepLinking: true,
86+
presets: [
87+
SwaggerUIBundle.presets.apis,
88+
SwaggerUIStandalonePreset
89+
],
90+
plugins: [
91+
SwaggerUIBundle.plugins.DownloadUrl
92+
],
93+
layout: "StandaloneLayout"
94+
})
95+
96+
window.ui = ui
97+
}
98+
</script>
99+
</body>
100+
101+
</html>
102+
`;
103+
}

tsconfig.json

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"compilerOptions": {
3+
"allowJs": false,
4+
"declaration": true,
5+
"module": "commonjs",
6+
"noFallthroughCasesInSwitch": true,
7+
"noImplicitAny": true,
8+
"outDir": "lib",
9+
"rootDir": "src",
10+
"skipLibCheck": true,
11+
"sourceMap": true,
12+
"strictNullChecks": true,
13+
"target": "es2017"
14+
},
15+
"exclude": [
16+
"__test__",
17+
"lib",
18+
"node_modules"
19+
],
20+
"formatCodeOptions": {
21+
"convertTabsToSpaces": true,
22+
"indentSize": 2,
23+
"insertSpaceAfterCommaDelimiter": true,
24+
"insertSpaceAfterFunctionKeywordForAnonymousFunctions": true,
25+
"insertSpaceAfterKeywordsInControlFlowStatements": true,
26+
"insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false,
27+
"insertSpaceAfterSemicolonInForStatements": true,
28+
"insertSpaceBeforeAndAfterBinaryOperators": true,
29+
"newLineCharacter": "\n",
30+
"placeOpenBraceOnNewLineForControlBlocks": false,
31+
"placeOpenBraceOnNewLineForFunctions": false,
32+
"tabSize": 2
33+
}
34+
}

typings.d.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
declare module "*.json" {
2+
const value: any;
3+
export default value;
4+
}
5+
6+
declare module 'swagger2openapi' {
7+
let x: any;
8+
export = x;
9+
}

0 commit comments

Comments
 (0)