diff --git a/packages/express-consumer/.gitignore b/packages/express-consumer/.gitignore new file mode 100644 index 0000000..d36f569 --- /dev/null +++ b/packages/express-consumer/.gitignore @@ -0,0 +1,23 @@ +/logs +*.log +lerna-debug.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +/node_modules +/coverage + +yarn.lock +package-lock.json +npm-shrinkwrap.json + +/.eslintcache +/.npm +.yarn-integrity +.env + +/.idea +/.vscode +.DS_Store +Thumbs.db diff --git a/packages/express-consumer/README.md b/packages/express-consumer/README.md new file mode 100644 index 0000000..e59910b --- /dev/null +++ b/packages/express-consumer/README.md @@ -0,0 +1,3 @@ +# Sapphire Package + +A starter package serving as a template for future Sapphire packages. Not intended for public use. \ No newline at end of file diff --git a/packages/express-consumer/index.js b/packages/express-consumer/index.js new file mode 100644 index 0000000..d0fca7b --- /dev/null +++ b/packages/express-consumer/index.js @@ -0,0 +1,7 @@ +const ExpressConsumer = require('./lib/express-consumer') +const helpers = require('./lib/helpers') + +module.exports = { + ExpressConsumer, + helpers +} diff --git a/packages/express-consumer/lib/errors/express-consumer-invalid-arguments.js b/packages/express-consumer/lib/errors/express-consumer-invalid-arguments.js new file mode 100644 index 0000000..c37f9eb --- /dev/null +++ b/packages/express-consumer/lib/errors/express-consumer-invalid-arguments.js @@ -0,0 +1,9 @@ +class ExpressConsumerInvalidArgument extends Error { + constructor(message, code = null) { + super(message, code) + Error.captureStackTrace(this, ExpressConsumerInvalidArgument) + this.code = code + } +} + +module.exports = ExpressConsumerInvalidArgument diff --git a/packages/express-consumer/lib/errors/messages.js b/packages/express-consumer/lib/errors/messages.js new file mode 100644 index 0000000..e85d49a --- /dev/null +++ b/packages/express-consumer/lib/errors/messages.js @@ -0,0 +1,30 @@ +const configExample = `{ + cors: true, + publicFolder: '/public', + port: 4000, +}` + +const errorHandler = `class SampleErrorHandler { + handle() { + + } +}` + +module.exports = { + shouldBeObject: () => ({ + title: `The configuration argument should be an object`, + code: configExample + }), + shouldHaveKey: (key) => ({ + title: `Config object should have: ${key}`, + code: configExample + }), + errorHandler: () => ({ + title: 'Error handler should be an object and have a handle method', + code: errorHandler + }), + container: () => ({ + title: 'Container should be an instance of sapphire container', + code: '' + }) +} diff --git a/packages/express-consumer/lib/express-consumer.js b/packages/express-consumer/lib/express-consumer.js new file mode 100644 index 0000000..167885f --- /dev/null +++ b/packages/express-consumer/lib/express-consumer.js @@ -0,0 +1,200 @@ +const { Request, Adapter } = require('@sapphirejs/request') +const Response = require('@sapphirejs/response') +const { Container, resolver } = require('@sapphirejs/container') + +const express = require('express') +const cors = require('cors') +const is = require('is') + +const ExpressConsumerInvalidArgument = require('./errors/express-consumer-invalid-arguments') +const ExpressResponseConsumer = require('./express-response-consumer') +const message = require('./errors/messages') +const { objectHasMethod } = require('./helpers') + +const methods = { + get: 'get', + post: 'post', + put: 'put', + patch: 'patch', + delete: 'delete' +} + +const type = { + http: 'http', + command: 'command' +} + +/** + * ExpressConsumer class. + * Consume a router and build an express server. + * + * @class ExpressConsumer + */ +class ExpressConsumer { + /** + * + * @param {Object} config + * @param {Object} errorHandler + * @param {Container} container + * @throws {ExpressConsumerInvalidArgument} if arguments are invalid + */ + constructor(config = null, errorHandler = null, container = null) { + this.__config = config + this.__errorHandler = errorHandler + this.__container = container + this.__httpInstance = null + this.__validateConfig() + this.__validateErrorHandler() + this.__validateContainer() + this.__initExpress() + } + + /** + * Validates the config. + * + * @private + * @throws {ExpressConsumerInvalidArgument} if config is invalid + */ + __validateConfig() { + const config = this.__config + + if(!is.object(config)) + throw new ExpressConsumerInvalidArgument( + message.shouldBeObject().title, + message.shouldBeObject().code + ) + + const keys = ['publicFolder', 'port'] + keys.forEach((key) => { + if(!config.hasOwnProperty(key)) + throw new ExpressConsumerInvalidArgument( + message.shouldHaveKey(key).title, + message.shouldHaveKey(key).code + ) + }) + } + + /** + * + * @private + */ + __validateContainer() { + if(!(this.__container instanceof Container)) + throw new ExpressConsumerInvalidArgument( + message.container().title, + message.container().code, + ) + } + + /** + * Validates the error handler. + * + * @private + * @throws {ExpressConsumerInvalidArgument} + */ + __validateErrorHandler() { + if( !( + is.object(this.__errorHandler) && + objectHasMethod(this.__errorHandler, 'handle') + )) + throw new ExpressConsumerInvalidArgument( + message.errorHandler().title, + message.errorHandler().message, + ) + } + + /** + * Creates an express server. + * + * @private + */ + __initExpress() { + const { publicFolder } = this.__config + this.__route = express() + this.__route.use(cors()) + + // this.__route.use((error, req, resp, next) => { + // this.__errorHandler.handle(error) + // }) + + this.__route.use(express.static(publicFolder)) + } + + /** + * Register express routes + * + * @param routes + */ + createRoutes(routes = []) { + const router = this.__route + routes + .filter( route => route.type === type.http) + .forEach( route => { + const { path, meta, handler, middleware } = route + + console.log(middleware) + + const consumableMiddlewares = middleware.map( + item => (req, resp, next) => { + item( + resolver(this.__container, { + Request: new Request(new Adapter.Express(req)), + Response: new Response() + }), + next + ) + } + ) + + //console.log(middleware) + + if(!methods.hasOwnProperty(meta.method)) + throw new Error() + + router[meta.method](path, ...consumableMiddlewares, (req, resp) => { + try{ + ( + new ExpressResponseConsumer( + resp, + handler( + resolver(this.__container, { + Request: new Request(new Adapter.Express(req)), + Response: new Response() + }) + ) + ) + ).send() + }catch(e) { + ( + new ExpressResponseConsumer( + resp, + this.__errorHandler.handle(new Response(), e) + ) + ).send() + } + }) + + }) + } + + /** + * Starts an Express server. + * + * @returns {void} + */ + start() { + this.__httpInstance = this.__route.listen(this.__config.port) + } + + /** + * Closes the express server. + * + * @returns {void} + */ + close() { + if(this.__httpInstance) + this.__httpInstance.close() + } +} + +module.exports = ExpressConsumer diff --git a/packages/express-consumer/lib/express-response-consumer.js b/packages/express-consumer/lib/express-response-consumer.js new file mode 100644 index 0000000..7e92a91 --- /dev/null +++ b/packages/express-consumer/lib/express-response-consumer.js @@ -0,0 +1,13 @@ +class ExpressResponseConsumer { + constructor(expressResponse, response) { + this.__expressResponse = expressResponse + this.__response = response + } + + send() { + console.log(this.__response) + this.__expressResponse.json({ route: '/some-group/lol' }) + } +} + +module.exports = ExpressResponseConsumer diff --git a/packages/express-consumer/lib/helpers.js b/packages/express-consumer/lib/helpers.js new file mode 100644 index 0000000..8330c5c --- /dev/null +++ b/packages/express-consumer/lib/helpers.js @@ -0,0 +1,12 @@ +module.exports = { + /** + * + * @param object {object} + * @param method {string} + * @returns {boolean} + */ + objectHasMethod: (object, method) => + Object.getOwnPropertyNames( + Object.getPrototypeOf(object) + ).filter( key => key === method ).length === 1 +} diff --git a/packages/express-consumer/package.json b/packages/express-consumer/package.json new file mode 100644 index 0000000..553083f --- /dev/null +++ b/packages/express-consumer/package.json @@ -0,0 +1,39 @@ +{ + "name": "@sapphirejs/express-consumer", + "version": "0.0.11", + "description": "A consumer package for @sapphire/router that uses express to build a server", + "license": "MIT", + "author": "Aleksander Koko ", + "keywords": [ + "sapphire", + "framework", + "router", + "router consumer" + ], + "main": "index.js", + "engines": { + "node": ">= 8.3.0" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "test": "jest", + "lint": "eslint ./src", + "test-ci": "jest --coverage && cat ./coverage/lcov.info | coveralls" + }, + "dependencies": { + "@sapphirejs/router": "0.0.12", + "@sapphirejs/request": "0.0.14", + "@sapphirejs/response": "0.0.12", + "@sapphirejs/container": "0.0.14", + "cors": "^2.8.4", + "express": "^4.16.3", + "is": "^3.2.1" + }, + "devDependencies": { + "axios": "^0.18.0", + "coveralls": "^3.0.0", + "jest": "^22.0.4" + } +} diff --git a/packages/express-consumer/test/express-consumer.spec.js b/packages/express-consumer/test/express-consumer.spec.js new file mode 100644 index 0000000..fa6b4b6 --- /dev/null +++ b/packages/express-consumer/test/express-consumer.spec.js @@ -0,0 +1,78 @@ +const { Route } = require('@sapphirejs/router') +const { Container } = require('@sapphirejs/container') +const { Request } = require('@sapphirejs/request') +const Response = require('@sapphirejs/response') +const { ExpressConsumer } = require('../index') +const SampleErrorHandler = require('./helpers/SampleErrorHandler') +const ExampleMiddleware = require('./helpers/ExampleMiddleware') +const axios = require('axios') +axios.defaults.baseURL = 'http://localhost:4000' +const { test, expect } = global + +const generateExpressConsumer = (handler = null, container = null) => new ExpressConsumer({ + publicFolder: '/public', + port: 4000, +}, handler, container) + +test('Test', () => { + + const errorHandler = new SampleErrorHandler() + const container = new Container() + + expect(() => { + new ExpressConsumer({ + publicFolder: '/public', + }, errorHandler, container) + }).toThrow() + + expect(() => { + new ExpressConsumer({ + port: 4000, + }, errorHandler, container) + }).toThrow() + + expect(() => { + new ExpressConsumer({}, errorHandler, container) + }).toThrow() + + expect(() => { + new ExpressConsumer({}, errorHandler, 'not.a.container') + }).toThrow() +}) + +test('Bad error handler throws error', () => { + expect(() => { + generateExpressConsumer( + new class ExampleHandler { + }, new Container() + ) + }).toThrow() +}) + +test('Add routes on express', async () => { + const expressConsumer = generateExpressConsumer( + new SampleErrorHandler(), new Container() + ) + + const route = new Route() + + route.group('/some-group', (route) => { + /** + * @param {Request} Request + * @param {Response} Response + */ + route.get('/lol', [({Request, Response}, next) => { console.log('lol')}, ({Request, Response}, next) => { console.log("Else")}], ({ Request, Response }) => { + //throw new Error() + return Response.json({ route: Request.route.path }) + }) + }) + + expressConsumer.createRoutes(route.export()) + expressConsumer.start() + + const response = await axios.get('/some-group/lol').then(resp => resp.data) //.then(resp => console.log(resp)).catch(error => error) + + expect(response).toEqual({ route: '/some-group/lol' }) + + expressConsumer.close() +}) diff --git a/packages/express-consumer/test/helpers.spec.js b/packages/express-consumer/test/helpers.spec.js new file mode 100644 index 0000000..e89b515 --- /dev/null +++ b/packages/express-consumer/test/helpers.spec.js @@ -0,0 +1,20 @@ +const { helpers } = require('../index') +const { test, expect } = global + +test('Object has method', () => { + expect( + helpers.objectHasMethod( + new class Something { + handle() {} + }, + 'handle' + ) + ).toBe(true) + + expect( + helpers.objectHasMethod( + new class Something {}, + 'handle' + ) + ).toBe(false) +}) diff --git a/packages/express-consumer/test/helpers/ExampleMiddleware.js b/packages/express-consumer/test/helpers/ExampleMiddleware.js new file mode 100644 index 0000000..f9b1f55 --- /dev/null +++ b/packages/express-consumer/test/helpers/ExampleMiddleware.js @@ -0,0 +1,12 @@ +class ExampleMiddleware { + constructor(something) { + this.__data = something + } + + handle({ Request, Response }, next) { + console.log("Hey you, from middleware, " + this.__data) + next() + } +} + +module.exports = ExampleMiddleware diff --git a/packages/express-consumer/test/helpers/SampleErrorHandler.js b/packages/express-consumer/test/helpers/SampleErrorHandler.js new file mode 100644 index 0000000..df33f69 --- /dev/null +++ b/packages/express-consumer/test/helpers/SampleErrorHandler.js @@ -0,0 +1,12 @@ +class SampleErrorHandler { + /** + * + * @param {Response} response + * @param error + */ + handle(response, error) { + return response.json({ error: 'error' }) + } +} + +module.exports = SampleErrorHandler