Browse Source

feat: Validation Messages

tags/v0.1.0
Dale 1 year ago
parent
commit
acfad7611d
8 changed files with 97 additions and 6 deletions
  1. +2
    -0
      package.json
  2. +13
    -3
      src/server/api/v1/schemas/user.json
  3. +11
    -2
      src/server/lib/http/middleware.ts
  4. +56
    -0
      src/server/lib/validation/errors.ts
  5. +7
    -0
      src/shared/lib/const.ts
  6. +1
    -0
      tsconfig.json
  7. +1
    -0
      webpack.config.server.js
  8. +6
    -1
      yarn.lock

+ 2
- 0
package.json View File

@@ -11,12 +11,14 @@
"dependencies": {
"ajv": "^6.10.0",
"express": "^4.17.0",
"ramda": "^0.26.1",
"uuid": "^3.3.2"
},
"devDependencies": {
"@types/ajv": "^1.0.0",
"@types/express": "^4.16.1",
"@types/node": "^12.0.2",
"@types/ramda": "^0.26.8",
"@types/uuid": "^3.4.4",
"json-loader": "^0.5.7",
"nodemon-webpack-plugin": "^4.0.8",


+ 13
- 3
src/server/api/v1/schemas/user.json View File

@@ -1,9 +1,19 @@
{
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string" }
"username": { "type": "string", "maxLength": 4 },
"email": { "type": "number", "maximum": 5, "minimum": 3 },
"password": { "type": "string" },
"obj": {
"type": "object",
"properties": {
"prop1": { "type": "number" },
"prop2": { "type": "string" }
},
"additionalProperties": false,
"required": [ "prop1", "prop2" ]
}
},
"additionalProperties": false,
"required": [ "name" ]
"required": [ "username", "password", "email", "obj" ]
}

+ 11
- 2
src/server/lib/http/middleware.ts View File

@@ -1,6 +1,8 @@
import { Request, Response } from 'express';
import * as AJV from 'ajv';

import { convertErrors } from 'server/lib/validation/errors';

export function JsonApiHeaders (req: Request, res: Response, next: () => void) {
res.setHeader('Content-Type', 'application/json');
res.setHeader('X-Powered-By', 'miracle-tv');
@@ -15,12 +17,19 @@ export const acceptsSchema = (schemaID: object) => (req: Request, res: Response,
if (isValid) {
next();
} else {
const errorsAndMessages = convertErrors(validate.errors);
const errors = Object.keys(errorsAndMessages).reduce((acc, key) => {
return { ...acc, [key]: errorsAndMessages[key].errorCode };
}, {});
const errorMessages = Object.keys(errorsAndMessages).reduce((acc, key) => {
return { ...acc, [key]: errorsAndMessages[key].message };
}, {});
res.status(400);
res.send({
status: 'error',
message: 'Validation Error',
errors: validate.errors,
meta: { schema, }
errors,
errorMessages
});
}
};

+ 56
- 0
src/server/lib/validation/errors.ts View File

@@ -0,0 +1,56 @@
import { identity, uniq, last } from 'ramda';

import {
ERROR_CODE_MISSING_PROPERTY,
ERROR_CODE_EXTRA_PROPERTY,
ERROR_CODE_TYPE,
ERROR_CODE_MAX_NUM, ERROR_CODE_MIN_NUM,
ERROR_CODE_MAX_STRING, ERROR_CODE_MIN_STRING,
} from 'shared/lib/const'

const propertyNameMap = {
'required': 'missingProperty',
'additionalProperties': 'additionalProperty',
}

const keywordErrorMap = {
'required': ERROR_CODE_MISSING_PROPERTY,
'additionalProperties': ERROR_CODE_EXTRA_PROPERTY,
'type': ERROR_CODE_TYPE,
'maximum': ERROR_CODE_MAX_NUM,
'minimum': ERROR_CODE_MIN_NUM,
'maxLength': ERROR_CODE_MAX_STRING,
'minLength': ERROR_CODE_MIN_STRING,
}

function commonErrorHandler (error: any) {
const propertyNameKey = propertyNameMap[error.keyword];
const propertyName: string = error.params[propertyNameKey];
const message: string = error.message;
const parentPath: string[] = error.schemaPath.split('/').filter(
key => key !== '#' && key !== error.keyword && key !== 'properties'
);
const fullPath = propertyName
? uniq([...parentPath, propertyName]).filter(identity).join('.')
: error.dataPath.split('.').filter(identity).join('.');
console.log(`Parent path: ${parentPath}\nPropertyName: ${propertyName}\nDataPath: ${error.dataPath}\n\n`)
return {
path: fullPath,
errorCode: keywordErrorMap[error.keyword] || error.keyword,
message,
};
};

function defaultHandler(error: any) {
return error;
}

export function convertErrors(validationErrors: any): any {
console.log(validationErrors);
const errorsByPath = validationErrors.reduce((acc: any, error: any) => {
const errorHandler = commonErrorHandler;
const { path, ...convertedError } = errorHandler(error);
return { ...acc, [path]: convertedError };
}, {})
return errorsByPath;
}

+ 7
- 0
src/shared/lib/const.ts View File

@@ -0,0 +1,7 @@
export const ERROR_CODE_MISSING_PROPERTY = 'errors.schema.field.missing';
export const ERROR_CODE_EXTRA_PROPERTY = 'errors.schema.field.noExtra';
export const ERROR_CODE_TYPE = 'errors.schema.field.type';
export const ERROR_CODE_MAX_NUM = 'errors.schema.field.maximumNumber';
export const ERROR_CODE_MIN_NUM = 'errors.schema.field.minimumNumber';
export const ERROR_CODE_MAX_STRING = 'errors.schema.field.maximumStringLength';
export const ERROR_CODE_MIN_STRING = 'errors.schema.field.minimumStringLength';

+ 1
- 0
tsconfig.json View File

@@ -8,6 +8,7 @@
"paths": {
"server/*": [ "src/server/*" ],
"client/*": [ "src/client/*" ]
"shared/*": [ "src/shared/*" ]
}
}
}

+ 1
- 0
webpack.config.server.js View File

@@ -21,6 +21,7 @@ module.exports = {
alias: {
server: path.join(__dirname, 'src/server'),
client: path.join(__dirname, 'src/client'),
shared: path.join(__dirname, 'src/shared'),
}
},
plugins: [


+ 6
- 1
yarn.lock View File

@@ -51,6 +51,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.2.tgz#3452a24edf9fea138b48fad4a0a028a683da1e40"
integrity sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==

"@types/ramda@^0.26.8":
version "0.26.8"
resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.26.8.tgz#e002612cca52e52f9176d577f4d6229c8c72a10a"
integrity sha512-PSMkNNhB900U2xcyndlkVjJeCXpVtf1yod0Kdq/ArsLDIE0tW8pLBChmQeJs4o4dfp3AEP+AGm6zIseZPU4ndA==

"@types/range-parser@*":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
@@ -2733,7 +2738,7 @@ querystring@0.2.0:
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=

ramda@0.26.1:
ramda@0.26.1, ramda@^0.26.1:
version "0.26.1"
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==


Loading…
Cancel
Save