Skip to content

Commit

Permalink
feat: validate API requests and responses according to the OpenApi sp…
Browse files Browse the repository at this point in the history
…ec (#1579)

Signed-off-by: Philippe Martin <[email protected]>
  • Loading branch information
feloy authored Aug 26, 2024
1 parent f3a37a1 commit 4e20f77
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 11 deletions.
2 changes: 2 additions & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"dependencies": {
"@huggingface/gguf": "^0.1.9",
"express": "^4.19.2",
"express-openapi-validator": "^5.3.1",
"isomorphic-git": "^1.27.1",
"mustache": "^4.2.0",
"openai": "^4.56.0",
Expand All @@ -83,6 +84,7 @@
},
"devDependencies": {
"@podman-desktop/api": "1.12.0",
"@rollup/plugin-replace": "^5.0.7",
"@types/express": "^4.17.21",
"@types/js-yaml": "^4.0.9",
"@types/mustache": "^4.2.5",
Expand Down
9 changes: 8 additions & 1 deletion packages/backend/src/managers/apiServer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,16 @@ test('/api/version endpoint when getting package.json file fails', async () => {
expect(res.body.errors[0]).toEqual('an error getting package file');
});

test('/api/version endpoint with unexpected param', async () => {
expect(server.getListener()).toBeDefined();
const res = await request(server.getListener()!).get('/api/version?wrongParam').expect(400);
expect(res.body.message).toEqual(`Unknown query parameter 'wrongParam'`);
});

test('/api/wrongEndpoint', async () => {
expect(server.getListener()).toBeDefined();
await request(server.getListener()!).get('/api/wrongEndpoint').expect(404);
const res = await request(server.getListener()!).get('/api/wrongEndpoint').expect(404);
expect(res.body.message).toEqual('not found');
});

test('/', async () => {
Expand Down
27 changes: 26 additions & 1 deletion packages/backend/src/managers/apiServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
***********************************************************************/

import type { Disposable } from '@podman-desktop/api';
import type { Request, Response } from 'express';
import type { NextFunction, Request, Response } from 'express';
import express from 'express';
import type { Server } from 'http';
import path from 'node:path';
Expand All @@ -30,6 +30,8 @@ import type { components } from '../../src-generated/openapi';
import type { ModelInfo } from '@shared/src/models/IModelInfo';
import type { ConfigurationRegistry } from '../registries/ConfigurationRegistry';
import { getFreeRandomPort } from '../utils/ports';
import * as OpenApiValidator from 'express-openapi-validator';
import type { HttpError, OpenApiRequest } from 'express-openapi-validator/dist/framework/types';

const SHOW_API_INFO_COMMAND = 'ai-lab.show-api-info';
const SHOW_API_ERROR_COMMAND = 'ai-lab.show-api-error';
Expand Down Expand Up @@ -68,6 +70,29 @@ export class ApiServer implements Disposable {
const router = express.Router();
router.use(express.json());

// validate requests / responses based on openapi spec
router.use(
OpenApiValidator.middleware({
apiSpec: this.getSpecFile(),
validateRequests: true,
validateResponses: {
onError: (error, body, req) => {
console.error(`Response body fails validation: `, error);
console.error(`Emitted from:`, req.originalUrl);
console.error(body);
},
},
}),
);

router.use((err: HttpError, _req: OpenApiRequest, res: Response, _next: NextFunction) => {
// format errors from validator
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
});

// declare routes
router.get('/version', this.getVersion.bind(this));
router.get('/tags', this.getModels.bind(this));
Expand Down
13 changes: 13 additions & 0 deletions packages/backend/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import {join} from 'path';
import {builtinModules} from 'module';
import replace from '@rollup/plugin-replace';

const PACKAGE_ROOT = __dirname;

Expand Down Expand Up @@ -59,6 +60,18 @@ const config = {
emptyOutDir: true,
reportCompressedSize: false,
},
plugins: [
// This is to apply the patch https://github.com/JS-DevTools/ono/pull/20
// can be removed when the patch is merged
replace({
delimiters: ['', ''],
preventAssignment: true,
values: {
'if (typeof module === "object" && typeof module.exports === "object") {':
'if (typeof module === "object" && typeof module.exports === "object" && typeof module.exports.default === "object") {',
},
}),
],
};

export default config;
Loading

0 comments on commit 4e20f77

Please sign in to comment.