Initial content
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { Id, NullableId, Params } from "@feathersjs/feathers";
|
||||
import { WebService } from "./http_adapter";
|
||||
|
||||
export class FeathersWrapper<T> {
|
||||
|
||||
constructor(private ws: WebService<T>) {}
|
||||
|
||||
get(id: Id) {
|
||||
return this.ws.getOne(id);
|
||||
}
|
||||
|
||||
find(params: Params) {
|
||||
return this.ws.getMany(params.query);
|
||||
}
|
||||
|
||||
create(data: any, params: Params) {
|
||||
return this.ws.store(data);
|
||||
}
|
||||
|
||||
remove(id: NullableId, params: Params) {
|
||||
return this.ws.delete(id);
|
||||
}
|
||||
|
||||
update(id: NullableId, data: any, params: Params) {
|
||||
return this.ws.replace(id, data);
|
||||
}
|
||||
|
||||
patch(id: NullableId, data: any, params: Params) {
|
||||
return this.ws.modify(id, data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Express, Response } from "express";
|
||||
import { ValidationError } from "./validation_types";
|
||||
|
||||
export interface WebService<T> {
|
||||
getOne(id: any) : Promise<T | undefined>;
|
||||
getMany(query: any) : Promise<T[]>;
|
||||
store(data: any) : Promise<T | undefined>;
|
||||
delete(id: any): Promise<boolean>;
|
||||
replace(id: any, data: any): Promise<T | undefined>;
|
||||
modify(id: any, data: any): Promise<T | undefined>;
|
||||
}
|
||||
|
||||
export function createAdapter<T>(app: Express, ws: WebService<T>, baseUrl: string) {
|
||||
|
||||
app.get(baseUrl, async (req, resp) => {
|
||||
try {
|
||||
resp.json(await ws.getMany(req.query));
|
||||
resp.end();
|
||||
} catch (err) { writeErrorResponse(err, resp) }
|
||||
});
|
||||
|
||||
app.get(`${baseUrl}/:id`, async (req, resp) => {
|
||||
try {
|
||||
const data = await ws.getOne((req.params.id));
|
||||
if (data == undefined) {
|
||||
resp.writeHead(404);
|
||||
} else {
|
||||
resp.json(data);
|
||||
}
|
||||
resp.end();
|
||||
} catch (err) { writeErrorResponse(err, resp) }
|
||||
});
|
||||
|
||||
app.post(baseUrl, async (req, resp) => {
|
||||
try {
|
||||
const data = await ws.store(req.body);
|
||||
resp.json(data);
|
||||
resp.end();
|
||||
} catch (err) { writeErrorResponse(err, resp) }
|
||||
});
|
||||
|
||||
app.delete(`${baseUrl}/:id`, async (req, resp) => {
|
||||
try {
|
||||
resp.json(await ws.delete(req.params.id));
|
||||
resp.end();
|
||||
} catch (err) { writeErrorResponse(err, resp) }
|
||||
});
|
||||
|
||||
app.put(`${baseUrl}/:id`, async (req, resp) => {
|
||||
try {
|
||||
resp.json(await ws.replace(req.params.id, req.body));
|
||||
resp.end();
|
||||
} catch (err) { writeErrorResponse(err, resp) }
|
||||
});
|
||||
|
||||
app.patch(`${baseUrl}/:id`, async (req, resp) => {
|
||||
try {
|
||||
resp.json(await ws.modify(req.params.id, req.body));
|
||||
resp.end();
|
||||
} catch (err) { writeErrorResponse(err, resp) }
|
||||
});
|
||||
|
||||
const writeErrorResponse = (err: any, resp: Response) => {
|
||||
console.error(err);
|
||||
resp.writeHead(err instanceof ValidationError ? 400 : 500);
|
||||
resp.end();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Express } from "express";
|
||||
import { createAdapter } from "./http_adapter";
|
||||
import { ResultWebService } from "./results_api";
|
||||
import { Validator } from "./validation_adapter";
|
||||
import { ResultWebServiceValidation } from "./results_api_validation";
|
||||
import { FeathersWrapper } from "./feathers_adapter";
|
||||
import { feathers } from "@feathersjs/feathers";
|
||||
import feathersExpress, { rest } from "@feathersjs/express";
|
||||
import { ValidationError } from "./validation_types";
|
||||
|
||||
export const createApi = (app: Express) => {
|
||||
|
||||
// createAdapter(app, new Validator(new ResultWebService(),
|
||||
// ResultWebServiceValidation), "/api/results");
|
||||
|
||||
const feathersApp = feathersExpress(feathers(), app).configure(rest());
|
||||
|
||||
const service = new Validator(new ResultWebService(),
|
||||
ResultWebServiceValidation);
|
||||
|
||||
feathersApp.use('/api/results', new FeathersWrapper(service));
|
||||
|
||||
feathersApp.hooks({
|
||||
error: {
|
||||
all: [(ctx) => {
|
||||
if (ctx.error instanceof ValidationError) {
|
||||
ctx.http = { status: 400};
|
||||
ctx.error = undefined;
|
||||
}
|
||||
}]
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { WebService } from "./http_adapter";
|
||||
import { Result } from "../data/repository";
|
||||
import repository from "../data";
|
||||
import * as jsonpatch from "fast-json-patch";
|
||||
import { validateModel } from "./validation_functions";
|
||||
import { ResultModelValidation } from "./results_api_validation";
|
||||
|
||||
export class ResultWebService implements WebService<Result> {
|
||||
|
||||
getOne(id: any): Promise<Result | undefined> {
|
||||
return repository.getResultById(id);
|
||||
}
|
||||
|
||||
getMany(query: any): Promise<Result[]> {
|
||||
if (query.name) {
|
||||
return repository.getResultsByName(query.name, 10);
|
||||
} else {
|
||||
return repository.getAllResults(10);
|
||||
}
|
||||
}
|
||||
|
||||
async store(data: any): Promise<Result | undefined> {
|
||||
const { name, age, years} = data;
|
||||
const nextage = age + years;
|
||||
const id = await repository.saveResult({ id: 0, name, age,
|
||||
years, nextage});
|
||||
return await repository.getResultById(id);
|
||||
}
|
||||
|
||||
delete(id: any): Promise<boolean> {
|
||||
return repository.delete(Number.parseInt(id));
|
||||
}
|
||||
|
||||
replace(id: any, data: any): Promise<Result | undefined> {
|
||||
const { name, age, years, nextage } = data;
|
||||
const validated = validateModel({ name, age, years, nextage },
|
||||
ResultModelValidation)
|
||||
return repository.update({ id, ...validated });
|
||||
}
|
||||
|
||||
async modify(id: any, data: any): Promise<Result | undefined> {
|
||||
const dbData = await this.getOne(id);
|
||||
if (dbData !== undefined) {
|
||||
return await this.replace(id,
|
||||
jsonpatch.applyPatch(dbData, data).newDocument);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ModelValidation, ValidationRequirements, ValidationRule,
|
||||
WebServiceValidation } from "./validation_types";
|
||||
import validator from "validator";
|
||||
|
||||
const intValidator : ValidationRule = {
|
||||
validation: [val => validator.isInt(val.toString())],
|
||||
converter: (val) => Number.parseInt(val)
|
||||
}
|
||||
|
||||
const partialResultValidator: ValidationRequirements = {
|
||||
name: [(val) => !validator.isEmpty(val)],
|
||||
age: intValidator,
|
||||
years: intValidator
|
||||
}
|
||||
|
||||
export const ResultWebServiceValidation: WebServiceValidation = {
|
||||
|
||||
keyValidator: intValidator,
|
||||
|
||||
store: partialResultValidator,
|
||||
|
||||
replace: {
|
||||
...partialResultValidator,
|
||||
nextage: intValidator
|
||||
}
|
||||
}
|
||||
|
||||
export const ResultModelValidation : ModelValidation = {
|
||||
propertyRules: { ...partialResultValidator, nextage: intValidator },
|
||||
modelRule: [(m: any) => m.nextage === m.age + m.years]
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { WebService } from "./http_adapter";
|
||||
import { validate, validateIdProperty } from "./validation_functions";
|
||||
import { WebServiceValidation } from "./validation_types";
|
||||
|
||||
export class Validator<T> implements WebService<T> {
|
||||
|
||||
constructor(private ws: WebService<T>,
|
||||
private validation: WebServiceValidation) {}
|
||||
|
||||
getOne(id: any): Promise<T | undefined> {
|
||||
return this.ws.getOne(this.validateId(id));
|
||||
}
|
||||
|
||||
getMany(query: any): Promise<T[]> {
|
||||
if (this.validation.getMany) {
|
||||
query = validate(query, this.validation.getMany);
|
||||
}
|
||||
return this.ws.getMany(query);
|
||||
}
|
||||
|
||||
store(data: any): Promise<T | undefined> {
|
||||
if (this.validation.store) {
|
||||
data = validate(data, this.validation.store);
|
||||
}
|
||||
return this.ws.store(data);
|
||||
}
|
||||
|
||||
delete(id: any): Promise<boolean> {
|
||||
return this.ws.delete(this.validateId(id));
|
||||
}
|
||||
|
||||
replace(id: any, data: any): Promise<T | undefined> {
|
||||
if (this.validation.replace) {
|
||||
data = validate(data, this.validation.replace);
|
||||
}
|
||||
return this.ws.replace(this.validateId(id), data);
|
||||
}
|
||||
|
||||
modify(id: any, data: any): Promise<T | undefined> {
|
||||
if (this.validation.modify) {
|
||||
data = validate(data, this.validation.modify);
|
||||
}
|
||||
return this.ws.modify(this.validateId(id), data);
|
||||
}
|
||||
|
||||
validateId(val: any) {
|
||||
return validateIdProperty(val, this.validation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { ModelValidation, ValidationError, ValidationRequirements,
|
||||
ValidationRule, WebServiceValidation } from "./validation_types";
|
||||
|
||||
export type ValidationResult = [valid: boolean, value: any];
|
||||
|
||||
export function validate(data: any, reqs: ValidationRequirements): any {
|
||||
let validatedData: any = {};
|
||||
Object.entries(reqs).forEach(([prop, rule]) => {
|
||||
const [valid, value] = applyRule(data[prop], rule);
|
||||
if (valid) {
|
||||
validatedData[prop] = value;
|
||||
} else {
|
||||
throw new ValidationError(prop, "Validation Error");
|
||||
}
|
||||
});
|
||||
return validatedData;
|
||||
}
|
||||
|
||||
function applyRule(val: any,
|
||||
rule: ValidationRule): ValidationResult {
|
||||
const required = Array.isArray(rule) ? true : rule.required;
|
||||
const checks = Array.isArray(rule) ? rule : rule.validation;
|
||||
const convert = Array.isArray(rule) ? (v: any) => v : rule.converter;
|
||||
if (val === null || val == undefined || val === "") {
|
||||
return [required ? false : true, val];
|
||||
}
|
||||
let valid = true;
|
||||
checks.forEach(check => {
|
||||
if (!check(val)) {
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
return [valid, convert ? convert(val) : val];
|
||||
}
|
||||
|
||||
export function validateIdProperty<T>(val: any,
|
||||
v: WebServiceValidation) : any {
|
||||
if (v.keyValidator) {
|
||||
const [valid, value] = applyRule(val, v.keyValidator);
|
||||
if (valid) {
|
||||
return value;
|
||||
}
|
||||
throw new ValidationError("ID", "Validation Error");
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
export function validateModel(model: any, rules: ModelValidation) : any {
|
||||
if (rules.propertyRules) {
|
||||
model = validate(model, rules.propertyRules);
|
||||
}
|
||||
if (rules.modelRule) {
|
||||
const [valid, data] = applyRule(model, rules.modelRule);
|
||||
if (valid) {
|
||||
return data;
|
||||
}
|
||||
throw new ValidationError("Model", "Validation Error");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
export interface WebServiceValidation {
|
||||
keyValidator?: ValidationRule;
|
||||
getMany?: ValidationRequirements;
|
||||
store?: ValidationRequirements;
|
||||
replace?: ValidationRequirements;
|
||||
modify?: ValidationRequirements;
|
||||
}
|
||||
|
||||
export type ValidationRequirements = {
|
||||
[key: string] : ValidationRule
|
||||
}
|
||||
|
||||
export type ValidationRule =
|
||||
((value: any) => boolean)[] |
|
||||
{
|
||||
required? : boolean,
|
||||
validation: ((value: any) => boolean)[],
|
||||
converter?: (value: any) => any,
|
||||
}
|
||||
|
||||
export class ValidationError implements Error {
|
||||
constructor(public name: string, public message: string) {}
|
||||
stack?: string | undefined;
|
||||
cause?: unknown;
|
||||
}
|
||||
|
||||
export type ModelValidation = {
|
||||
modelRule?: ValidationRule,
|
||||
propertyRules?: ValidationRequirements
|
||||
}
|
||||
Reference in New Issue
Block a user