Skip to content

Commit

Permalink
Merge pull request #7 from fabrix-app/v1.1
Browse files Browse the repository at this point in the history
V1.1
  • Loading branch information
scott-wyatt authored Jul 24, 2018
2 parents 4ff5a5a + 0af2b63 commit 8d10b2d
Show file tree
Hide file tree
Showing 13 changed files with 268 additions and 22 deletions.
7 changes: 5 additions & 2 deletions lib/RouterSpool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export class RouterSpool extends SystemSpool {
Validator.validateRouter(this.app.config.get('router')),
Promise.all(
Object.values(this.app.config.get('routes') || {}).map(Validator.validateRoute)
),
Promise.all(
Object.values(this.app.config.get('policies') || {}).map(Validator.validatePolicy)
)
])
}
Expand All @@ -79,8 +82,8 @@ export class RouterSpool extends SystemSpool {
}

sanity () {
if (!isObject(this.app.routes)) {
throw new Error('Sanity Failed: app.routes is not an array!')
if (!(this.app.routes instanceof Map)) {
throw new Error('Sanity Failed: app.routes is not a Map!')
}
}
}
2 changes: 2 additions & 0 deletions lib/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { policies } from './policies'
export { router } from './router'
export { routes } from './routes'
export { spool } from './spool'

2 changes: 2 additions & 0 deletions lib/config/policies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const policies = {
}
64 changes: 64 additions & 0 deletions lib/schemas/policy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as joi from 'joi'

export const policySchema = joi.object().keys({
'*': joi.alternatives().try(
joi.func(),
joi.string(),
joi.object(),
joi.array().items(joi.string())
),
GET: joi.alternatives().try(
joi.func(),
joi.string(),
joi.object(),
joi.array().items(joi.string())
),
HEAD: joi.alternatives().try(
joi.func(),
joi.string(),
joi.object(),
joi.array().items(joi.string())
),
POST: joi.alternatives().try(
joi.func(),
joi.string(),
joi.object(),
joi.array().items(joi.string())
),
PUT: joi.alternatives().try(
joi.func(),
joi.string(),
joi.object(),
joi.array().items(joi.string())
),
DELETE: joi.alternatives().try(
joi.func(),
joi.string(),
joi.object(),
joi.array().items(joi.string())
),
CONNECT: joi.alternatives().try(
joi.func(),
joi.string(),
joi.object(),
joi.array().items(joi.string())
),
OPTIONS: joi.alternatives().try(
joi.func(),
joi.string(),
joi.object(),
joi.array().items(joi.string())
),
TRACE: joi.alternatives().try(
joi.func(),
joi.string(),
joi.object(),
joi.array().items(joi.string())
),
PATCH: joi.alternatives().try(
joi.func(),
joi.string(),
joi.object(),
joi.array().items(joi.string())
)
}).unknown()
75 changes: 66 additions & 9 deletions lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FabrixApp } from '@fabrix/fabrix'
import { get, omit } from 'lodash'
import { get, omit, isString } from 'lodash'
import { Router } from 'call'
import { IRoute } from './interfaces/IRoute'

Expand All @@ -16,6 +16,13 @@ export const Utils = {
'PATCH'
],

/**
*
*/
stringToArray(strOrArray): string[] {
return isString(strOrArray) ? [ strOrArray ] : strOrArray
},

/**
* Build a complete route, with bound handler and attached preconditions
*/
Expand All @@ -37,13 +44,14 @@ export const Utils = {
Utils.getHandlerFromString(app, orgRoute)

orgRoute.config.pre = orgRoute.config.pre
.map(pre => Utils.getHandlerFromPrerequisite(app, pre))
.map(pre => Utils.getPolicyFromPrerequisite(app, pre))
.filter(handler => !!handler)

const orgRouteHandlers = Object.keys(orgRoute).filter(value => -1 !== Utils.methods.indexOf(value))
const orgRouteHandlers = Object.keys(orgRoute)
.filter(value => -1 !== Utils.methods.indexOf(value))

if (!orgRouteHandlers.some(v => Utils.methods.indexOf(v) >= 0 || !!orgRoute[v])) {
app.log.error('spool-orgRouter: orgRoute ', path, ' handler [', orgRouteHandlers.join(', '), ']',
app.log.error('spool-router: route ', path, ' handler [', orgRouteHandlers.join(', '), ']',
'does not correspond to any defined Controller handler')
return {}
}
Expand All @@ -53,7 +61,8 @@ export const Utils = {
orgRoute[method].config = orgRoute[method].config || orgRoute.config
orgRoute[method].config.pre = orgRoute[method].config.pre || orgRoute.config.pre
orgRoute[method].config.pre = orgRoute[method].config.pre
.map(pre => Utils.getHandlerFromPrerequisite(app, pre))
.map(pre => Utils.getPolicyFromPrerequisite(app, pre))
// .map(pre => Utils.getPolicyFromPrerequisite(app, pre))
.filter(handler => !!handler)
}
})
Expand Down Expand Up @@ -116,14 +125,41 @@ export const Utils = {
return get(app.policies, handler)
},

/**
* Get a Controller's method's policies
*/
getControllerPolicy(app: FabrixApp, handler, routeMethod, pre = [ ]) {
const controller = Utils.getControllerFromHandler(handler)

if (app.config.get('policies.*.*')) {
pre = [...new Set([...pre, ...Utils.stringToArray(app.config.get('policies.*.*'))])]
}
if (app.config.get(`policies.*.${routeMethod}`)) {
pre = [...new Set([...pre, ...Utils.stringToArray(app.config.get(`policies.*.${routeMethod}`))])]
}
if (handler && controller && app.config.get(`policies.${controller}.*.*`)) {
pre = [...new Set([...pre, ...Utils.stringToArray(app.config.get(`policies.${controller}.*.*`))])]
}
if (handler && controller && app.config.get(`policies.${controller}.*.${routeMethod}`)) {
pre = [...new Set([...pre, ...Utils.stringToArray(app.config.get(`policies.${controller}.*.${routeMethod}`))])]
}
if (handler && app.config.get(`policies.${handler}.${routeMethod}`)) {
pre = [...new Set([...pre, ...Utils.stringToArray(app.config.get(`policies.${handler}.${routeMethod}`))])]
}
return pre
},

/**
* Get handler method from a "hapi/hapi-like" prerequisite object/string
*/
getHandlerFromPrerequisite (app: FabrixApp, pre) {
getPolicyFromPrerequisite (app: FabrixApp, pre) {
let handler
if (pre && typeof pre === 'string') {
handler = Utils.getPolicyFromString(app, pre)
}
else if (pre && Array.isArray(pre)) {
handler = pre.map(p => Utils.getPolicyFromString(app, p)).filter(p => p)
}
else if (pre && typeof pre.method === 'string') {
handler = Utils.getPolicyFromString(app, pre.method)
}
Expand All @@ -144,6 +180,10 @@ export const Utils = {
return get(app.controllers, handler)
},

getControllerFromHandler(handler) {
return isString(handler) ? handler.split('.')[0] : handler
},

/**
* Get handler method from a controller.method string path
*/
Expand All @@ -158,15 +198,32 @@ export const Utils = {

Utils.methods.forEach(method => {
if (route[method]) {
route.config = route.config || { }
route.config.pre = Utils.getControllerPolicy(app, null, method, route.config.pre)

if (typeof route[method] === 'string') {
return route[method] = { handler: Utils.getControllerFromString(app, route[method]) }
route.config.pre = Utils.getControllerPolicy(app, route[method], method, route.config.pre)
return route[method] = {
handler: Utils.getControllerFromString(app, route[method]),
config: route.config
}
}
else if (route[method] instanceof Object && route[method].hasOwnProperty('handler')) {
route[method].config = route[method].config || route.config
route[method].config.pre = route[method].config.pre || route.config.pre

if (typeof route[method].handler === 'string') {
return route[method].handler = Utils.getControllerFromString(app, route[method].handler)
route.config.pre = Utils.getControllerPolicy(app, route[method].handler, method, route.config.pre)
return route[method] = {
...route[method],
handler: Utils.getControllerFromString(app, route[method].handler)
}
}
else {
return route[method].handler
return route[method] = {
...route[method],
handler: route[method].handler
}
}
}
else {
Expand Down
17 changes: 17 additions & 0 deletions lib/validator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import * as joi from 'joi'
import { Utils } from './utils'

import { policySchema } from './schemas/policy'
import { routeSchema } from './schemas/route'
import { routerSchema } from './schemas/router'


export const Validator = {

/**
Expand All @@ -19,6 +22,20 @@ export const Validator = {
})
})
},
/**
* Validate the structure of an individual route
*/
validatePolicy (policy) {
return new Promise((resolve, reject) => {
joi.validate(policy, policySchema, (err, value) => {
if (err) {
return reject(err)
}

return resolve(value)
})
})
},

/**
* Validate the structure of router
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fabrix/spool-router",
"version": "1.1.2",
"version": "1.1.3",
"description": "Spool - Router for Fabrix",
"scripts": {
"build": "tsc -p ./lib/tsconfig.release.json",
Expand Down Expand Up @@ -49,7 +49,7 @@
"lodash": "^4.17.10"
},
"devDependencies": {
"@fabrix/fabrix": "^1.1.1",
"@fabrix/fabrix": "^1.1.2",
"@fabrix/lint": "^1.0.0-alpha.3",
"@types/lodash": "^4.14.109",
"@types/node": "~10.3.4",
Expand All @@ -64,7 +64,7 @@
"typescript": "~2.8.1"
},
"peerDependencies": {
"@fabrix/fabrix": "^1.1.1"
"@fabrix/fabrix": "^1.1.2"
},
"license": "MIT",
"bugs": {
Expand Down
26 changes: 26 additions & 0 deletions test/fixtures/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ module.exports = {
policies: {
FooPolicy: class FooPolicy extends Policy {
bar () { }
},
GlobalPolicy: class GlobalPolicy extends Policy {
foo () { }
},
GetPolicy: class GetPolicy extends Policy {
foo () { }
},
FooGetPolicy: class FooGetPolicy extends Policy {
foo () { }
},
FooWildCardPolicy: class FooWildCardPolicy extends Policy {
foo () { }
}
}
},
Expand All @@ -37,6 +49,20 @@ module.exports = {
router: {
debug: true
},
policies: {
'*': {
'*': ['GlobalPolicy.foo'],
'GET': ['GetPolicy.foo']
},
'TestController': {
'*': {
'*': ['FooWildCardPolicy.foo']
},
'foo': {
'GET': ['FooGetPolicy.foo']
}
}
},
routes: {
'/test/foo': {
'GET': 'TestController.foo'
Expand Down
39 changes: 39 additions & 0 deletions test/integration/lib/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,44 @@ describe('lib.Util', () => {
assert.equal(route.GET.config.pre[0], global.app.policies.FooPolicy.bar)
})
})
describe('#policies', () => {
it('should inherit the Global Policy on Every Method', () => {
const { path, route} = lib.Utils.buildRoute(global.app, '/foo/bar', {
'*': 'FooController.bar',
config: {
pre: [
{
method: 'FooPolicy.bar'
}
]
}
})
assert.equal(route.GET.config.pre[0], global.app.policies.FooPolicy.bar)
assert.equal(route.GET.config.pre[1], global.app.policies.GlobalPolicy.foo)
assert.equal(route.HEAD.config.pre[0], global.app.policies.FooPolicy.bar)
assert.equal(route.HEAD.config.pre[1], global.app.policies.GlobalPolicy.foo)
assert.equal(route.POST.config.pre[0], global.app.policies.FooPolicy.bar)
assert.equal(route.POST.config.pre[1], global.app.policies.GlobalPolicy.foo)
assert.equal(route.PUT.config.pre[0], global.app.policies.FooPolicy.bar)
assert.equal(route.PUT.config.pre[1], global.app.policies.GlobalPolicy.foo)
})

it('should inherit the global policy and the global GET policy', () => {
const { path, route} = lib.Utils.buildRoute(global.app, '/foo/bar', {
'GET': 'FooController.bar'
})
assert.equal(route.GET.config.pre[0], global.app.policies.GlobalPolicy.foo)
assert.equal(route.GET.config.pre[1], global.app.policies.GetPolicy.foo)
})

it('should inherit the global policy and the global GET policy and the Controller Specific Get Policy', () => {
const { path, route} = lib.Utils.buildRoute(global.app, '/foo/bar', {
'GET': 'TestController.foo'
})
assert.equal(route.GET.config.pre[0], global.app.policies.GlobalPolicy.foo)
assert.equal(route.GET.config.pre[1], global.app.policies.GetPolicy.foo)
assert.equal(route.GET.config.pre[2], global.app.policies.FooWildCardPolicy.foo)
assert.equal(route.GET.config.pre[3], global.app.policies.FooGetPolicy.foo)
})
})
})
Loading

0 comments on commit 8d10b2d

Please sign in to comment.