mirror of
https://github.com/alexgo-io/stacks-blockchain-api.git
synced 2026-04-30 13:52:28 +08:00
feat: add schema validation to api responses
This commit is contained in:
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
@@ -8,10 +8,11 @@
|
|||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Launch Program",
|
"name": "Launch Program",
|
||||||
"runtimeArgs": ["-r", "ts-node/register/transpile-only"],
|
"runtimeArgs": ["-r", "ts-node/register/transpile-only", "-r", "tsconfig-paths/register"],
|
||||||
"args": ["${workspaceFolder}/src/index.ts"],
|
"args": ["${workspaceFolder}/src/index.ts"],
|
||||||
"outputCapture": "std",
|
"outputCapture": "std",
|
||||||
"internalConsoleOptions": "openOnSessionStart",
|
"internalConsoleOptions": "openOnSessionStart",
|
||||||
|
"preLaunchTask": "generate:schemas",
|
||||||
"env": {
|
"env": {
|
||||||
"NODE_ENV": "development",
|
"NODE_ENV": "development",
|
||||||
"TS_NODE_SKIP_IGNORE": "true"
|
"TS_NODE_SKIP_IGNORE": "true"
|
||||||
@@ -21,10 +22,11 @@
|
|||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Launch Program - memory-db",
|
"name": "Launch Program - memory-db",
|
||||||
"runtimeArgs": ["-r", "ts-node/register/transpile-only"],
|
"runtimeArgs": ["-r", "ts-node/register/transpile-only", "-r", "tsconfig-paths/register"],
|
||||||
"args": ["${workspaceFolder}/src/index.ts", "--compile-schemas"],
|
"args": ["${workspaceFolder}/src/index.ts", "--compile-schemas"],
|
||||||
"outputCapture": "std",
|
"outputCapture": "std",
|
||||||
"internalConsoleOptions": "openOnSessionStart",
|
"internalConsoleOptions": "openOnSessionStart",
|
||||||
|
"preLaunchTask": "generate:schemas",
|
||||||
"env": {
|
"env": {
|
||||||
"STACKS_SIDECAR_DB": "memory",
|
"STACKS_SIDECAR_DB": "memory",
|
||||||
"NODE_ENV": "development",
|
"NODE_ENV": "development",
|
||||||
|
|||||||
15
.vscode/tasks.json
vendored
Normal file
15
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "generate:schemas",
|
||||||
|
"type": "npm",
|
||||||
|
"script": "generate:schemas",
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "silent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"description": "GET request that returns transactions",
|
"description": "GET request that returns transactions",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
|
||||||
"required": ["results"],
|
"required": ["results"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"results": {
|
"results": {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Abstract transaction event. Only present in `smart_contract` and `contract_call` tx types.",
|
"description": "Abstract transaction event. Only present in `smart_contract` and `contract_call` tx types.",
|
||||||
"additionalProperties": false,
|
|
||||||
"required": ["events"],
|
"required": ["events"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"events": {
|
"events": {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": ["tx_type", "token_transfer"],
|
"required": ["tx_type", "token_transfer"],
|
||||||
"additionalProperties": false,
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"tx_type": {
|
"tx_type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
|
||||||
"required": ["tx_type", "smart_contract"],
|
"required": ["tx_type", "smart_contract"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"tx_type": {
|
"tx_type": {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
|
||||||
"required": ["tx_type", "contract_call"],
|
"required": ["tx_type", "contract_call"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"tx_type": {
|
"tx_type": {
|
||||||
|
|||||||
@@ -31,5 +31,5 @@ function clean() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exports.default = parallel(flattenSchemas, copyFiles);
|
exports.default = parallel(flattenSchemas, copyFiles);
|
||||||
|
exports.flattenSchemas = flattenSchemas;
|
||||||
exports.deployDocs = series(deployToGithubPages, clean);
|
exports.deployDocs = series(deployToGithubPages, clean);
|
||||||
|
|||||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -874,6 +874,15 @@
|
|||||||
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
|
"integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/ajv": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ajv/-/ajv-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-T7JEB0Ly9sMOf7B5e4OfxvaWaCo=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ajv": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/babel__core": {
|
"@types/babel__core": {
|
||||||
"version": "7.1.6",
|
"version": "7.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.6.tgz",
|
||||||
@@ -1012,15 +1021,6 @@
|
|||||||
"@types/range-parser": "*"
|
"@types/range-parser": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/fs-extra": {
|
|
||||||
"version": "8.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.0.tgz",
|
|
||||||
"integrity": "sha512-UoOfVEzAUpeSPmjm7h1uk5MH6KZma2z2O7a75onTGjnNvAvMVrPzPL/vBbT65iIGHWj6rokwfmYcmxmlSf2uwg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"@types/node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@types/glob": {
|
"@types/glob": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
|
||||||
|
|||||||
@@ -14,8 +14,9 @@
|
|||||||
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx -f codeframe --fix",
|
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx -f codeframe --fix",
|
||||||
"migrate": "ts-node node_modules/.bin/node-pg-migrate -j ts",
|
"migrate": "ts-node node_modules/.bin/node-pg-migrate -j ts",
|
||||||
"generate:types": "ts-node ./scripts/generate-types.ts",
|
"generate:types": "ts-node ./scripts/generate-types.ts",
|
||||||
|
"generate:schemas": "gulp --silent && npm run generate:types",
|
||||||
"generate:docs": "redoc-cli bundle --output .tmp/index.html docs/openapi.yaml",
|
"generate:docs": "redoc-cli bundle --output .tmp/index.html docs/openapi.yaml",
|
||||||
"validate:schemas": "gulp --silent && ./scripts/validate-schemas.ts",
|
"validate:schemas": "gulp flattenSchemas --silent && ./scripts/validate-schemas.ts",
|
||||||
"deploy:docs": "npm run generate:types && npm run generate:docs && gulp deployDocs"
|
"deploy:docs": "npm run generate:types && npm run generate:docs && gulp deployDocs"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -55,6 +56,7 @@
|
|||||||
"@apidevtools/json-schema-ref-parser": "^8.0.0",
|
"@apidevtools/json-schema-ref-parser": "^8.0.0",
|
||||||
"@blockstack/eslint-config": "^1.0.3",
|
"@blockstack/eslint-config": "^1.0.3",
|
||||||
"@blockstack/prettier-config": "0.0.6",
|
"@blockstack/prettier-config": "0.0.6",
|
||||||
|
"@types/ajv": "^1.0.0",
|
||||||
"@types/bluebird": "^3.5.30",
|
"@types/bluebird": "^3.5.30",
|
||||||
"@types/bn.js": "^4.11.6",
|
"@types/bn.js": "^4.11.6",
|
||||||
"@types/compression": "^1.7.0",
|
"@types/compression": "^1.7.0",
|
||||||
@@ -88,6 +90,7 @@
|
|||||||
"redoc-cli": "^0.9.7",
|
"redoc-cli": "^0.9.7",
|
||||||
"ts-jest": "^25.2.1",
|
"ts-jest": "^25.2.1",
|
||||||
"ts-node": "^8.8.1",
|
"ts-node": "^8.8.1",
|
||||||
|
"tsconfig-paths": "^3.9.0",
|
||||||
"yaml-lint": "^1.2.4"
|
"yaml-lint": "^1.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import * as Bluebird from 'bluebird';
|
|||||||
import { DataStore, DbTx } from '../../datastore/common';
|
import { DataStore, DbTx } from '../../datastore/common';
|
||||||
import { getTxFromDataStore } from '../controllers/db-controller';
|
import { getTxFromDataStore } from '../controllers/db-controller';
|
||||||
import { timeout, waiter } from '../../helpers';
|
import { timeout, waiter } from '../../helpers';
|
||||||
|
import { validate } from '../validate';
|
||||||
|
|
||||||
|
import * as txSchema from '@schemas/entities/transactions/transaction.schema.json';
|
||||||
|
import * as txResultsSchema from '@schemas/api/transaction/get-transactions.schema.json';
|
||||||
|
|
||||||
export function createTxRouter(db: DataStore): RouterWithAsync {
|
export function createTxRouter(db: DataStore): RouterWithAsync {
|
||||||
const router = addAsync(express.Router());
|
const router = addAsync(express.Router());
|
||||||
@@ -12,10 +16,11 @@ export function createTxRouter(db: DataStore): RouterWithAsync {
|
|||||||
|
|
||||||
router.getAsync('/', async (req, res) => {
|
router.getAsync('/', async (req, res) => {
|
||||||
const txs = await db.getTxList();
|
const txs = await db.getTxList();
|
||||||
const fullTxs = await Bluebird.mapSeries(txs.results, async tx => {
|
const results = await Bluebird.mapSeries(txs.results, async tx => {
|
||||||
return await getTxFromDataStore(tx.tx_id, db);
|
return await getTxFromDataStore(tx.tx_id, db);
|
||||||
});
|
});
|
||||||
res.json({ results: fullTxs });
|
await validate(txResultsSchema, { results });
|
||||||
|
res.json({ results });
|
||||||
});
|
});
|
||||||
|
|
||||||
router.getAsync('/stream', async (req, res) => {
|
router.getAsync('/stream', async (req, res) => {
|
||||||
@@ -69,6 +74,7 @@ export function createTxRouter(db: DataStore): RouterWithAsync {
|
|||||||
router.getAsync('/:tx_id', async (req, res) => {
|
router.getAsync('/:tx_id', async (req, res) => {
|
||||||
const { tx_id } = req.params;
|
const { tx_id } = req.params;
|
||||||
const txResponse = await getTxFromDataStore(tx_id, db);
|
const txResponse = await getTxFromDataStore(tx_id, db);
|
||||||
|
await validate(txSchema, txResponse);
|
||||||
res.json(txResponse);
|
res.json(txResponse);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
8
src/api/validate.ts
Normal file
8
src/api/validate.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import * as Ajv from 'ajv';
|
||||||
|
|
||||||
|
export async function validate(schema: any, data: any) {
|
||||||
|
if (process.env.NODE_ENV !== 'development') return;
|
||||||
|
const ajv = new Ajv({ schemaId: 'auto' });
|
||||||
|
const valid = await ajv.validate(schema, data);
|
||||||
|
if (!valid) throw new Error(`Schema validation:\n\n ${JSON.stringify(ajv.errors, null, 2)}`);
|
||||||
|
}
|
||||||
@@ -26,10 +26,9 @@ import { TransactionPayloadTypeID } from './p2p/tx';
|
|||||||
loadDotEnv();
|
loadDotEnv();
|
||||||
|
|
||||||
const compileSchemas = process.argv.includes('--compile-schemas');
|
const compileSchemas = process.argv.includes('--compile-schemas');
|
||||||
const generateSchemas = () => exec('npm run generate:types');
|
const generateSchemas = () => exec('npm run generate:schemas');
|
||||||
|
|
||||||
if (compileSchemas) {
|
if (compileSchemas) {
|
||||||
generateSchemas();
|
|
||||||
watch('./docs', { recursive: true, filter: /\.schema\.json$/ }, () => generateSchemas());
|
watch('./docs', { recursive: true, filter: /\.schema\.json$/ }, () => generateSchemas());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,10 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@entities": [".tmp/index.ts"]
|
"@entities": [".tmp/index.ts"],
|
||||||
|
"@schemas/*": [".tmp/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "migrations/**/*", "tests/**/*"],
|
"include": ["src/**/*", "migrations/**/*", "tests/**/*", ".tmp/**/*.schema.json"],
|
||||||
"exclude": ["lib", ".tmp/**/*"]
|
"exclude": ["lib", ".tmp"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user