mirror of
https://github.com/alexgo-io/stacks-blockchain-api.git
synced 2026-01-12 16:53:19 +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",
|
||||
"request": "launch",
|
||||
"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"],
|
||||
"outputCapture": "std",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"preLaunchTask": "generate:schemas",
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
"TS_NODE_SKIP_IGNORE": "true"
|
||||
@@ -21,10 +22,11 @@
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"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"],
|
||||
"outputCapture": "std",
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"preLaunchTask": "generate:schemas",
|
||||
"env": {
|
||||
"STACKS_SIDECAR_DB": "memory",
|
||||
"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#",
|
||||
"description": "GET request that returns transactions",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["results"],
|
||||
"properties": {
|
||||
"results": {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"description": "Abstract transaction event. Only present in `smart_contract` and `contract_call` tx types.",
|
||||
"additionalProperties": false,
|
||||
"required": ["events"],
|
||||
"properties": {
|
||||
"events": {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["tx_type", "token_transfer"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"tx_type": {
|
||||
"type": "string",
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["tx_type", "smart_contract"],
|
||||
"properties": {
|
||||
"tx_type": {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["tx_type", "contract_call"],
|
||||
"properties": {
|
||||
"tx_type": {
|
||||
|
||||
@@ -31,5 +31,5 @@ function clean() {
|
||||
}
|
||||
|
||||
exports.default = parallel(flattenSchemas, copyFiles);
|
||||
|
||||
exports.flattenSchemas = flattenSchemas;
|
||||
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==",
|
||||
"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": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.6.tgz",
|
||||
@@ -1012,15 +1021,6 @@
|
||||
"@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": {
|
||||
"version": "7.1.1",
|
||||
"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",
|
||||
"migrate": "ts-node node_modules/.bin/node-pg-migrate -j 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",
|
||||
"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"
|
||||
},
|
||||
"repository": {
|
||||
@@ -55,6 +56,7 @@
|
||||
"@apidevtools/json-schema-ref-parser": "^8.0.0",
|
||||
"@blockstack/eslint-config": "^1.0.3",
|
||||
"@blockstack/prettier-config": "0.0.6",
|
||||
"@types/ajv": "^1.0.0",
|
||||
"@types/bluebird": "^3.5.30",
|
||||
"@types/bn.js": "^4.11.6",
|
||||
"@types/compression": "^1.7.0",
|
||||
@@ -88,6 +90,7 @@
|
||||
"redoc-cli": "^0.9.7",
|
||||
"ts-jest": "^25.2.1",
|
||||
"ts-node": "^8.8.1",
|
||||
"tsconfig-paths": "^3.9.0",
|
||||
"yaml-lint": "^1.2.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ import * as Bluebird from 'bluebird';
|
||||
import { DataStore, DbTx } from '../../datastore/common';
|
||||
import { getTxFromDataStore } from '../controllers/db-controller';
|
||||
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 {
|
||||
const router = addAsync(express.Router());
|
||||
@@ -12,10 +16,11 @@ export function createTxRouter(db: DataStore): RouterWithAsync {
|
||||
|
||||
router.getAsync('/', async (req, res) => {
|
||||
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);
|
||||
});
|
||||
res.json({ results: fullTxs });
|
||||
await validate(txResultsSchema, { results });
|
||||
res.json({ results });
|
||||
});
|
||||
|
||||
router.getAsync('/stream', async (req, res) => {
|
||||
@@ -69,6 +74,7 @@ export function createTxRouter(db: DataStore): RouterWithAsync {
|
||||
router.getAsync('/:tx_id', async (req, res) => {
|
||||
const { tx_id } = req.params;
|
||||
const txResponse = await getTxFromDataStore(tx_id, db);
|
||||
await validate(txSchema, 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();
|
||||
|
||||
const compileSchemas = process.argv.includes('--compile-schemas');
|
||||
const generateSchemas = () => exec('npm run generate:types');
|
||||
const generateSchemas = () => exec('npm run generate:schemas');
|
||||
|
||||
if (compileSchemas) {
|
||||
generateSchemas();
|
||||
watch('./docs', { recursive: true, filter: /\.schema\.json$/ }, () => generateSchemas());
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@entities": [".tmp/index.ts"]
|
||||
"@entities": [".tmp/index.ts"],
|
||||
"@schemas/*": [".tmp/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*", "migrations/**/*", "tests/**/*"],
|
||||
"exclude": ["lib", ".tmp/**/*"]
|
||||
"include": ["src/**/*", "migrations/**/*", "tests/**/*", ".tmp/**/*.schema.json"],
|
||||
"exclude": ["lib", ".tmp"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user