Skip to content

Commit a53b491

Browse files
authored
Merge pull request #94 from pharindoko/feat/add-pre-validation
Feat/add pre validation
2 parents dbd4065 + 4b7e7b8 commit a53b491

File tree

13 files changed

+275
-25
lines changed

13 files changed

+275
-25
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ curl -H "x-api-key: {API-KEY}" -H "Content-Type: application/json" https://xxxxx
171171
| enableSwagger | Enable swagger and swagger UI support | string | true |
172172
| enableApiKeyAuth | Make your routes private by using an additional ApiKey | boolean | false |
173173
| jsonFile | path of json file that will be used | string | db.json |
174+
| enableJSONValidation | validate JSON file at start | boolean | true |
174175

175176
## Used Packages
176177

src/server/app/app.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export class AppConfig {
33
enableSwagger = true;
44
enableApiKeyAuth = false;
55
jsonFile = 'db.json';
6+
enableJSONValidation = true;
67

78
static merge = <T, U>(t: T, u: U) => Object.assign({}, t, u);
89
}

src/server/app/cloud.app.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { CoreApp } from './core.app';
2-
2+
import { Output } from '../utils/output';
33
export class CloudApp extends CoreApp {
44
request = async () => {
55
try {
66
const { middlewares, router } = await this.initializeLayers();
77
this.setupServer(middlewares, router);
88
} catch (e) {
99
if (e.code === 'ExpiredToken') {
10-
this.logger.error(
10+
Output.setError(
1111
`Please add valid credentials for AWS. Error: ${e.message}`
1212
);
1313
} else {
14-
this.logger.error(e);
14+
Output.setError(e.message);
1515
}
1616
}
1717
};

src/server/app/core.app.ts

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ import express from 'express';
55
import jsonServer = require('json-server');
66
import { StorageAdapter } from '../storage/storage';
77
import { ApiSpecification } from '../specifications/apispecification';
8+
import { JSONValidator } from '../validations/json.validator';
9+
import { Output } from '../utils/output';
10+
811
export class CoreApp {
912
storageAdapter: StorageAdapter;
1013
static storage = {} as lowdb.AdapterAsync;
14+
static adapter = {} as lowdb.LowdbAsync<{}>;
1115
logger = new Logger().logger;
1216
appConfig: AppConfig;
1317
protected server: express.Express;
@@ -27,19 +31,38 @@ export class CoreApp {
2731

2832
async setup(): Promise<void> {
2933
await this.setupStorage();
30-
const json = await this.setupApp();
31-
await this.setupSwagger(json);
32-
await this.setupRoutes();
34+
const json = await this.getJSON();
35+
const isValid = this.validateJSON(json);
36+
if (isValid) {
37+
await this.setupApp();
38+
this.setupSwagger(json);
39+
await this.setupRoutes();
40+
} else {
41+
Output.setError('provided json is not valid - see validation checks');
42+
throw Error('provided json is not valid - see validation checks');
43+
}
3344
}
3445

3546
protected async setupStorage() {
3647
CoreApp.storage = await this.storageAdapter.init();
48+
CoreApp.adapter = await lowdb.default(CoreApp.storage);
3749
}
3850

39-
protected async setupApp(): Promise<object> {
40-
const { middlewares, router, adapter } = await this.initializeLayers();
51+
protected async setupApp(): Promise<void> {
52+
const { middlewares, router } = await this.initializeLayers();
4153
this.setupServer(middlewares, router);
42-
const json = await adapter.getState();
54+
}
55+
56+
protected validateJSON(db: {}): boolean {
57+
let isValid = true;
58+
if (this.appConfig.enableJSONValidation) {
59+
isValid = JSONValidator.validate(db);
60+
}
61+
return isValid;
62+
}
63+
64+
protected async getJSON(): Promise<object> {
65+
const json = await CoreApp.adapter.getState();
4366
return json;
4467
}
4568

@@ -56,13 +79,18 @@ export class CoreApp {
5679
}
5780

5881
protected async initializeLayers() {
59-
this.logger.trace('initLayer: ' + JSON.stringify(CoreApp.storage));
60-
const adapter = await lowdb.default(CoreApp.storage);
61-
const router = jsonServer.router(adapter);
82+
if (
83+
CoreApp.adapter &&
84+
Object.entries(CoreApp.adapter).length === 0 &&
85+
CoreApp.adapter.constructor === Object
86+
) {
87+
CoreApp.adapter = await lowdb.default(CoreApp.storage);
88+
}
89+
const router = jsonServer.router(CoreApp.adapter);
6290
const middlewares = jsonServer.defaults({
6391
readOnly: this.appConfig.readOnly,
6492
});
65-
return { middlewares, router, adapter };
93+
return { middlewares, router };
6694
}
6795

6896
protected setupServer(

src/server/specifications/swagger/swagger.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import express from 'express';
66
import { Spec, ApiKeySecurity } from 'swagger-schema-official';
77
import { SwaggerConfig } from './swagger.config';
88
import { ApiSpecification } from '../apispecification';
9+
import { Output } from '../../utils/output';
910

1011
export class Swagger implements ApiSpecification {
1112
private swaggerSpec = new SwaggerSpec();
@@ -27,7 +28,7 @@ export class Swagger implements ApiSpecification {
2728

2829
generateSpecification = (json: object, regenerate: boolean) => {
2930
if (!this.spec || regenerate) {
30-
this.logger.info('init Swagger ');
31+
Output.setInfo('Init Swagger');
3132
const swaggerSchemaDefinitions = this.swaggerDefGen.generateDefinitions(
3233
json
3334
);

src/server/utils/logger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export class Logger {
22
logger = require('pino')(
33
{
4-
prettyPrint: true,
4+
prettyPrint: { colorize: true },
55
},
66
process.stderr
77
);

src/server/utils/output.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
function output(data: {}) {
2-
console.log('Received Stack Output', data);
3-
}
1+
import { Logger } from './logger';
42

5-
module.exports = { output };
3+
export class Output {
4+
static setWarning(message: string) {
5+
new Logger().logger.warning(message);
6+
}
7+
static setError(message: string) {
8+
new Logger().logger.error(message);
9+
}
10+
static setInfo(message: string) {
11+
new Logger().logger.info(message);
12+
}
13+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {
2+
HasIdAttributeRule,
3+
HasObjectKeyRule,
4+
IsObjectRule,
5+
ValidationRule,
6+
} from './validationrule';
7+
import { RuleResultSeverity } from './ruleevent';
8+
import { Output } from '../utils/output';
9+
export class JSONValidator {
10+
static validate(json: {}): boolean {
11+
let isValid = true;
12+
const rules = new Array<ValidationRule>();
13+
rules.push(new IsObjectRule(json));
14+
rules.push(new HasObjectKeyRule(json));
15+
rules.push(new HasIdAttributeRule(json));
16+
17+
Output.setInfo(
18+
'ValidationRule:' +
19+
'Result'.padStart(60 - 'ValidationRule'.length) +
20+
'Message'.padStart(80)
21+
);
22+
for (const rule of rules) {
23+
const results = rule.executeValidation();
24+
for (const result of results.events) {
25+
Output.setInfo(
26+
results.validationRule +
27+
':' +
28+
result.result
29+
.toString()
30+
.padStart(60 - results.validationRule!.length) +
31+
result.message.padStart(80)
32+
);
33+
if (result.result === RuleResultSeverity.ALERT) {
34+
isValid = false;
35+
}
36+
}
37+
}
38+
return isValid;
39+
}
40+
}

src/server/validations/ruleevent.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export class RuleEventList {
2+
validationRule?: string;
3+
events = new Array<RuleEvent>();
4+
}
5+
6+
export class RuleEvent {
7+
result: RuleResultSeverity;
8+
message = '' as string;
9+
10+
constructor(result: RuleResultSeverity, message?: string) {
11+
this.result = result;
12+
this.message = message !== undefined ? message : '';
13+
}
14+
}
15+
16+
export enum RuleResultSeverity {
17+
OK = 'OK',
18+
WARNING = 'WARNING',
19+
ALERT = 'ALERT',
20+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { RuleEvent, RuleResultSeverity, RuleEventList } from './ruleevent';
2+
3+
export abstract class ValidationRule {
4+
protected jsonObject = {} as object;
5+
ruleEvents = new Array<RuleEvent>();
6+
constructor(jsonObject: {}) {
7+
this.jsonObject = jsonObject;
8+
}
9+
events = new Array<string>();
10+
executeValidation(): RuleEventList {
11+
const ruleEventList = new RuleEventList();
12+
const result = this.validate();
13+
ruleEventList.events = ruleEventList.events.concat(result);
14+
ruleEventList.validationRule = this.constructor.name;
15+
return ruleEventList;
16+
}
17+
18+
protected abstract validate(): RuleEvent[];
19+
}
20+
21+
export class IsObjectRule extends ValidationRule {
22+
validate(): RuleEvent[] {
23+
let ruleSeverity = RuleResultSeverity.OK;
24+
let message = '';
25+
try {
26+
if (
27+
this.jsonObject &&
28+
typeof this.jsonObject === 'object' &&
29+
this.jsonObject.constructor !== Object
30+
) {
31+
ruleSeverity = RuleResultSeverity.ALERT;
32+
message = 'root level of json content must be a json object';
33+
}
34+
} catch (e) {
35+
ruleSeverity = RuleResultSeverity.ALERT;
36+
message = e.message;
37+
}
38+
this.ruleEvents.push(new RuleEvent(ruleSeverity, message));
39+
return this.ruleEvents;
40+
}
41+
}
42+
43+
export class HasObjectKeyRule extends ValidationRule {
44+
validate(): RuleEvent[] {
45+
let ruleSeverity = RuleResultSeverity.OK;
46+
let message = '';
47+
try {
48+
if (this.jsonObject && typeof this.jsonObject === 'object') {
49+
if (Object.keys(this.jsonObject).length === 0) {
50+
ruleSeverity = RuleResultSeverity.ALERT;
51+
message = 'no root properties found - no endpoints can be created';
52+
}
53+
}
54+
} catch (e) {
55+
ruleSeverity = RuleResultSeverity.ALERT;
56+
message = e.message;
57+
}
58+
this.ruleEvents.push(new RuleEvent(ruleSeverity, message));
59+
return this.ruleEvents;
60+
}
61+
}
62+
63+
export class HasIdAttributeRule extends ValidationRule {
64+
validate(): RuleEvent[] {
65+
try {
66+
if (
67+
this.jsonObject &&
68+
typeof this.jsonObject === 'object' &&
69+
Object.keys(this.jsonObject).length !== 0
70+
) {
71+
Object.keys(this.jsonObject).forEach(item => {
72+
let ruleSeverity = RuleResultSeverity.OK;
73+
let message = '';
74+
if (
75+
Array.isArray(this.jsonObject[item]) &&
76+
this.jsonObject[item].length > 0 &&
77+
!this.jsonObject[item][0].hasOwnProperty('id')
78+
) {
79+
(ruleSeverity = RuleResultSeverity.WARNING),
80+
(message =
81+
item +
82+
' is missing id attribute - not possible to do POST, PUT, PATCH');
83+
}
84+
this.ruleEvents.push(new RuleEvent(ruleSeverity, message));
85+
});
86+
}
87+
} catch (e) {
88+
const ruleSeverityError = RuleResultSeverity.ALERT;
89+
const messageError = e.message;
90+
this.ruleEvents.push(new RuleEvent(ruleSeverityError, messageError));
91+
}
92+
return this.ruleEvents;
93+
}
94+
}

0 commit comments

Comments
 (0)