src/lib/managers/Phrase.js
'use strict'
var phraseValidator = require('../validators/phrase.validator')
var PhraseModel = require('../models/PhraseModel')
var phraseDao = require('../daos/phraseDao')
var BaseManager = require('./base.manager')
var queryString = require('query-string')
// var ComposrError = require('../ComposrError')
// var MetricsFirer = require('../MetricsFirer')
var phrasesStore = require('../stores/phrases.store')
// var parseToComposrError = require('../parseToComposrError')
var WrappedResponse = require('../mock').res
var WrappedRequest = require('../mock').req
var corbel = require('corbel-js')
var utils = require('../utils')
var _ = require('lodash')
var PhraseManager = function PhraseManager (options) {
this.events = options.events
this.requirer = options.requirer
this.config = options.config || {}
}
PhraseManager.prototype = new BaseManager({
itemName: 'phrase',
store: phrasesStore,
model: PhraseModel,
dao: phraseDao,
validator: phraseValidator
})
PhraseManager.prototype.configure = function configure (config) {
this.config = {
urlBase: config.urlBase
}
}
PhraseManager.prototype.__preAdd = function __preAdd (domain, phraseModel) {
var phrasesWithTheSamePath = this._filterByRegexp(domain, phraseModel.getRegexp())
if (phrasesWithTheSamePath.length > 0) {
this.events.emit('warn', 'phrase:path:duplicated', phraseModel.getId())
}
}
PhraseManager.prototype._compile = function _compile (domain, phrase) {
try {
var PhraseModel = this.model
var phraseInstance = new PhraseModel(phrase, domain)
phraseInstance.compile(this.events)
this.events.emit('debug', 'phrase:compiled', phraseInstance.getId(), Object.keys(phraseInstance.compiled.codes))
return phraseInstance
} catch (e) {
// Somehow it has tried to compile an invalid phrase. Notify it and return false.
// Catching errors and returning false here is important for not having an unstable phrases stack.
this.events.emit('errore', 'phrase:not:usable', phrase.url, e)
return false
}
}
// Executes a phrase by id
PhraseManager.prototype.runById = function runById (id, verb, params, cb) {
if (utils.values.isFalsy(verb)) {
verb = 'get'
}
var parameters = getDefaultPhraseParams(params)
var phrase = this.getById(id)
if (phrase && phrase.canRun(verb)) {
var domain = this._extractDomainFromId(id)
this._run(phrase, verb, parameters, domain, cb)
} else {
// @TODO: See if we want to return that error directly or a wrappedResponse with 404 status (or invalid VERB)
cb('phrase:cant:be:runned')
}
}
// Executes a phrase matching a path
PhraseManager.prototype.runByPath = function runByPath (domain, path, verb, params, version, cb) {
if (utils.values.isFalsy(verb)) {
verb = 'get'
}
var phrase = this.getByMatchingPath(domain, path, verb, version)
if (phrase) {
var parameters = getDefaultPhraseParams(params)
var queryParamsString = path.indexOf('?') !== -1 ? path.substring(path.indexOf('?'), path.length) : ''
if (!parameters.query && !(parameters.req && parameters.req.query && Object.keys(parameters.req.query).length > 0)) {
// If no reqQuery object or req.querty params are sent, extract them
parameters.query = queryString.parse(queryParamsString)
}
if (!parameters.params) {
// extract params from path
var sanitizedPath = path.replace(queryParamsString, '')
parameters.params = phrase.extractParamsFromPath(sanitizedPath)
}
this._run(phrase, verb, parameters, domain, cb)
} else {
cb('phrase:cant:be:runned')
}
}
function getDefaultPhraseParams (options) {
var defaultParameters = {
functionMode: true,
query: null,
params: null,
headers: null,
corbelDriver: null,
domain: null,
timeout: null,
body: null
}
return Object.assign({}, defaultParameters, options)
}
/*
Fills the sandbox with parameters
*/
/* function buildSandbox (options, urlBase, domain, requirer, reqWrapper, resWrapper, version) {
// The object that will be inject on the phrase itself.
var sb = {
req: reqWrapper,
res: resWrapper,
// next: nextWrapper.resolve,
domain: domain,
config: {
urlBase: urlBase
},
// metrics: new MetricsFirer(domain)
metrics: null
}
// sb.require = options.functionMode ? requirer.forDomain(domain, version, true) : requirer.forDomain(domain, version)
// sb.require = requirer(domain, version, options.functionMode)
if (!options.corbelDriver && reqWrapper.get('Authorization')) {
sb.corbelDriver = corbel.getDriver({
urlBase: urlBase,
iamToken: {
accessToken: reqWrapper.get('Authorization')
},
domain: domain
})
} else {
sb.corbelDriver = options.corbelDriver
}
return sb
}*/
PhraseManager.prototype._run = function _runPhrase (phrase, verb, params, domain, cb) {
this.events.emit('debug', 'running:phrase:' + phrase.getId() + ':' + verb)
var urlBase = params.config && params.config.urlBase ? params.config.urlBase : this.config.urlBase
var resWrapper = new WrappedResponse(params.res)
var reqWrapper = new WrappedRequest(params.req, params)
// Fill the sandbox params
var sandbox = {
req: reqWrapper,
res: resWrapper,
// next: params.next,
require: this.requirer(domain, phrase.getVersion(), params.functionMode),
domain: domain,
config: {
urlBase: urlBase
}
}
if (!params.corbelDriver && reqWrapper.get('Authorization')) {
sandbox.corbelDriver = corbel.getDriver({
urlBase: urlBase,
iamToken: {
accessToken: reqWrapper.get('Authorization')
},
domain: domain
})
} else {
sandbox.corbelDriver = params.corbelDriver
}
sandbox.res.on('end', function (resp) {
cb(null, resp)
})
// Execute the phrase
if (params.functionMode) {
phrase.__executeFunctionMode(verb, sandbox, params.timeout, params.file)
} else {
phrase.__executeScriptMode(verb, sandbox, params.timeout, params.file, this.events)
/* try {
} catch (e) {
// console.log(e)
// @TODO this errors can be:
// - corbel errors
// - Any thrown error in phrase
// How do we handle it?
if (params.functionMode) {
// Function mode only throws an error when errored
this.events.emit('warn', 'phrase:internal:error', e, phrase.getUrl())
var error = parseToComposrError(e, 'error:phrase:exception:' + phrase.getUrl())
sandbox.res.send(error.status, error)
} else {
// vm throws an error when timedout
}
}*/
}
}
// Returns a list of elements matching the same regexp
PhraseManager.prototype._filterByRegexp = function _filterByRegexp (domain, regexp) {
var candidates = this.store.getAsList(domain)
return _.filter(candidates, function (candidate) {
return candidate.getRegexp() === regexp
})
}
// Get all the phrases, or all the phrases for one domain
PhraseManager.prototype.getPhrases = function getPhrases (domain) {
return this.store.getAsList(domain)
}
/**
CORE Entry point. One of the purposes of composr-core is to provide a fast and reliable
getByMatchingPath method.
**/
PhraseManager.prototype.getByMatchingPath = function getByMatchingPath (domain, path, verb, version) {
var candidate = null
if (!verb) {
verb = 'get'
}
domain = utils.values.isFalsy(domain) ? null : domain
this.events.emit('debug', 'phrase:getByMatchingPath:' + domain + ':' + path + ':' + verb)
if (utils.values.isFalsy(path)) {
this.events.emit('error', 'phrase:getByMatchingPath:path:undefined')
return candidate
}
var queryParamsString = path.indexOf('?') !== -1 ? path.substring(path.indexOf('?'), path.length) : ''
path = path.replace(queryParamsString, '')
if (domain === null) {
this.events.emit('warn', 'phrase:getByMatchingPath:noDomain:matchingAgainstAll:expensiveMethod')
}
var candidates = this.store.getAsList(domain)
this.events.emit('debug', 'evaluating:' + candidates.length + ':candidates')
candidates = _.compact(candidates.map(function (phrase) {
if (phrase.canRun(verb) && phrase.matchesPath(path)) {
if (!version || (version && phrase.getVersion() === version)) {
return phrase
}
}
}))
this.events.emit('debug', 'found:' + candidates.length + ':candidates')
if (candidates.length === 0) {
this.events.emit('debug', 'notfound:candidates:path:' + path + ':' + verb)
return candidate
} else {
candidate = candidates[0]
this.events.emit('debug', 'using:candidate:' + candidate.getId() + ':' + verb)
return candidate
}
}
// Counts all the loaded phrases
PhraseManager.prototype.count = function count () {
return this.store.getAsList().length
}
// TODO: Remove for using the MD5 check defined in BaseManager
PhraseManager.prototype.__shouldSave = function __shouldSave () {
return true
}
module.exports = PhraseManager