Skip to content

Commit

Permalink
* Add instanceExists-decorator
Browse files Browse the repository at this point in the history
* Refactor options-mapping for default crud-handlers
  • Loading branch information
onechiporenko committed Jan 19, 2023
1 parent 9b02e75 commit a8f5db5
Show file tree
Hide file tree
Showing 4 changed files with 319 additions and 4 deletions.
58 changes: 58 additions & 0 deletions packages/server/lib/decorators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { NextFunction, Request, Response } from 'express';
import { Lair } from '@swarm-host/lair';
import { assert } from './utils';

export function instanceExists(
factoryName: string | string[],
reqParamName: string,
errorHandler: (
req: Request,
res: Response,
next: NextFunction,
lair: Lair
) => void
) {
assert(
'Factory name must be not empty string or array',
factoryName.length !== 0
);
assert('Request param name must not empty', !!reqParamName);
return function (target: any, key: string, descriptor: PropertyDescriptor) {
if (descriptor === undefined) {
descriptor = Object.getOwnPropertyDescriptor(target, key);
}
const originalMethod = descriptor.value;
descriptor.value = function (
req: Request,
res: Response,
next: NextFunction,
lair: Lair
): any {
assert(
`Request parameter with name "${reqParamName}" does not exist`,
!!req.params[reqParamName]
);
const factoryNames = Array.isArray(factoryName)
? [...factoryName]
: [factoryName];
const entityId = req.params[reqParamName];
let entityExists = false;
let fName = factoryNames.pop();
while (fName) {
const entity = lair.getOne(fName, entityId, {
ignoreRelated: true,
});
if (entity) {
entityExists = true;
break;
}
fName = factoryNames.pop();
}
if (!entityExists) {
return errorHandler(req, res, next, lair);
}
return originalMethod.call(this, req, res, next, lair);
};
return descriptor;
};
}
2 changes: 2 additions & 0 deletions packages/server/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
sequenceItem,
} from '@swarm-host/lair';
import Cron from './cron';
import { instanceExists } from './decorators';
import Job, { JobOptions, tickCallback } from './job';
import Route, { CustomNext, Handler } from './route';
import Server from './server';
Expand Down Expand Up @@ -50,4 +51,5 @@ export {
hasOne,
hasMany,
sequenceItem,
instanceExists,
};
45 changes: 41 additions & 4 deletions packages/server/lib/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,45 @@ function isFactory(v: any): v is Factory {
return false;
}

const getCrudOptionsFromRequest = (req: Request) => {
const {
query: { depth, ignoreRelated, handleNotAttrs },
} = req;
let _ignoreRelated;
if (Array.isArray(ignoreRelated)) {
_ignoreRelated = ignoreRelated;
} else {
if (typeof ignoreRelated === 'string') {
_ignoreRelated = [ignoreRelated];
} else {
if (typeof ignoreRelated === 'boolean') {
_ignoreRelated = ignoreRelated;
} else {
_ignoreRelated = false;
}
}
}
return {
depth: depth ? Number(depth) : Infinity,
handleNotAttrs: !!handleNotAttrs,
ignoreRelated: _ignoreRelated,
};
};

function getAll(req: Request, res: Response): Response {
const { factoryName } = req.params;
return res.json(Lair.getLair().getAll(factoryName, { depth: 1 }));
return res.json(
Lair.getLair().getAll(factoryName, getCrudOptionsFromRequest(req))
);
}

function getOne(req: Request, res: Response): Response {
const { factoryName, id } = req.params;
const record = Lair.getLair().getOne(factoryName, id, { depth: 1 });
const record = Lair.getLair().getOne(
factoryName,
id,
getCrudOptionsFromRequest(req)
);
if (!record) {
return res.status(404).json();
}
Expand All @@ -59,7 +90,9 @@ function updateOne(req: Request, res: Response): Response {
if (!lair.getOne(factoryName, id, { ignoreRelated: true })) {
return res.status(404).json();
}
return res.json(lair.updateOne(factoryName, id, req.body, { depth: 1 }));
return res.json(
lair.updateOne(factoryName, id, req.body, getCrudOptionsFromRequest(req))
);
}

function deleteOne(req: Request, res: Response): Response {
Expand All @@ -75,7 +108,11 @@ function deleteOne(req: Request, res: Response): Response {
function createOne(req: Request, res: Response): Response {
const { factoryName } = req.params;
return res.json(
Lair.getLair().createOne(factoryName, req.body, { depth: 1 })
Lair.getLair().createOne(
factoryName,
req.body,
getCrudOptionsFromRequest(req)
)
);
}

Expand Down
218 changes: 218 additions & 0 deletions packages/server/tests/decorators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import { expect } from 'chai';
import Route from '../lib/route';
import { Factory, instanceExists, Lair } from '../lib';
import { NextFunction, Request, Response } from 'express';

let lair;
describe('Decorators', () => {
beforeEach(() => {
lair = Lair.getLair();
});

afterEach(() => {
Lair.cleanLair();
});

describe('#instanceExists', () => {
it('should throw an error if factory name is empty string', () => {
expect(() => {
class FactoryNameEmptyStringTestRoute extends Route {
@instanceExists('', 'someName', () => {
/* noop */
})
defaultHandler(req, res, next, lair) {
return super.defaultHandler(req, res, next, lair);
}
}
}).to.throw('Factory name must be not empty string or array');
});

it('should throw an error if factory name is empty array', () => {
expect(() => {
class FactoryNameEmptyArrayTestRoute extends Route {
@instanceExists([], 'someName', () => {
/* noop */
})
defaultHandler(req, res, next, lair) {
return super.defaultHandler(req, res, next, lair);
}
}
}).to.throw('Factory name must be not empty string or array');
});

it('should throw an error if request param name is empty', () => {
expect(() => {
class RequestParamNameEmptyStringRoute extends Route {
@instanceExists('test', '', () => {
/* noop */
})
defaultHandler(req, res, next, lair) {
return super.defaultHandler(req, res, next, lair);
}
}
}).to.throw('Request param name must not empty');
});

it('should throw an error if request parameter does not exist', () => {
class RequestParamNotExistsRoute extends Route {
@instanceExists('some-factory-name', 'NOT_EXISTING', () => {
/* noop */
})
defaultHandler(req, res, next, lair) {
return super.defaultHandler(req, res, next, lair);
}
}

expect(() => {
new RequestParamNotExistsRoute().defaultHandler(
{ params: {} } as Request,
{} as Response,
(() => {
/* noop */
}) as NextFunction,
{} as Lair
);
}).to.throw('Request parameter with name "NOT_EXISTING" does not exist');
});

it('should call default handler if needed instance exists (string)', (done) => {
lair.registerFactory(
class TestNameFactory extends Factory {
static factoryName = 'test-name';
}
);
lair.createRecords('test-name', 1);
class NeededInstanceExistsStringRoute extends Route {
@instanceExists('test-name', 'testNameId', () => {
/* noop */
})
defaultHandler(req, res, next, lair) {
done();
return super.defaultHandler(req, res, next, lair);
}
}
new NeededInstanceExistsStringRoute().defaultHandler(
{
params: {
testNameId: '1',
},
} as unknown as Request,
{} as Response,
(() => {
/* noop */
}) as NextFunction,
lair
);
});

it('should call default handler if needed instance exists (array)', (done) => {
lair.registerFactory(
class TestName1Factory extends Factory {
static factoryName = 'test-name-1';
}
);
lair.createRecords('test-name-1', 1);
lair.registerFactory(
class TestName2Factory extends Factory {
static factoryName = 'test-name-2';
}
);
lair.createRecords('test-name-2', 2);
lair.registerFactory(
class TestName3Factory extends Factory {
static factoryName = 'test-name-3';
}
);
lair.createRecords('test-name-3', 3);
class NeededInstanceExistsStringRoute extends Route {
@instanceExists(
['test-name-1', 'test-name-2', 'test-name-3'],
'testNameId',
() => {
/* noop */
}
)
defaultHandler(req, res, next, lair) {
done();
return super.defaultHandler(req, res, next, lair);
}
}
new NeededInstanceExistsStringRoute().defaultHandler(
{
params: {
testNameId: '3',
},
} as unknown as Request,
{} as Response,
(() => {
/* noop */
}) as NextFunction,
lair
);
});

it('should call error-handler if needed instance does not exist (string)', (done) => {
lair.registerFactory(
class TestNameNotExistFactory extends Factory {
static factoryName = 'test-name';
}
);
lair.createRecords('test-name', 1);
class NeededInstanceExistsStringRoute extends Route {
@instanceExists('test-name', 'testNameId', () => {
done();
})
defaultHandler(req, res, next, lair) {
return super.defaultHandler(req, res, next, lair);
}
}
new NeededInstanceExistsStringRoute().defaultHandler(
{
params: {
testNameId: '100500',
},
} as unknown as Request,
{} as Response,
(() => {
/* noop */
}) as NextFunction,
lair
);
});

it('should call error-handler if needed instance does not exist (array)', (done) => {
lair.registerFactory(
class TestNameNotExistFactory extends Factory {
static factoryName = 'test-name';
}
);
lair.createRecords('test-name', 2);
lair.registerFactory(
class TestNameNotExistFactory extends Factory {
static factoryName = 'test-name-2';
}
);
lair.createRecords('test-name-2', 1);
class NeededInstanceExistsArrayRoute extends Route {
@instanceExists(['test-name-2', 'test-name'], 'testNameId', () => {
done();
})
defaultHandler(req, res, next, lair) {
return super.defaultHandler(req, res, next, lair);
}
}
new NeededInstanceExistsArrayRoute().defaultHandler(
{
params: {
testNameId: '100500',
},
} as unknown as Request,
{} as Response,
(() => {
/* noop */
}) as NextFunction,
lair
);
});
});
});

0 comments on commit a8f5db5

Please sign in to comment.