UNPKG

sluicebox

Version:

Quickly and elegantly add search, sort and pagination to your plural resource endpoints.

329 lines (316 loc) 9.38 kB
(function() { var Errors, isEmpty, numerify, ref, regify, trimStart; ref = require('lodash'), isEmpty = ref.isEmpty, trimStart = ref.trimStart; if (trimStart == null) { trimStart = require('lodash').trimLeft; } Errors = require('./errors'); regify = function(it) { return it.replace(/(\\|\[|\^|\$|\.|\||\?|\*|\+|\(|\))/g, '\\$1'); }; numerify = function(it) { var num; num = parseFloat(it, 10); if (num.toString() === it) { return num; } else { return it; } }; module.exports = function(field_for, value_for) { var ParseContext, group_handler, handler_for, predicate_handler; ParseContext = (function() { function ParseContext(query1, idx) { this.query = query1; this.idx = idx != null ? idx : 0; this.buffer = ''; this.state = ['normal']; this.state.peek = (function(_this) { return function() { return _this.state[_this.state.length - 1]; }; })(this); } ParseContext.prototype.process_buffer = function() { var value; value = this.buffer; this.buffer = ''; this.idx--; return { type: 'string', value: value }; }; ParseContext.prototype.next_token = function() { var char; while (this.idx < this.query.length) { char = this.query[this.idx++]; switch (this.state.peek()) { case 'escape': this.state.pop(); this.buffer += char; break; case 'quote': switch (char) { case '\\': this.state.push('escape'); break; case '\'': this.state.pop(); break; default: this.buffer += char; } break; default: switch (char) { case '\\': this.state.push('escape'); break; case '\'': this.state.push('quote'); break; case '(': if (this.buffer.length) { return this.process_buffer(); } return { type: 'group_start' }; case ')': if (this.buffer.length) { return this.process_buffer(); } return { type: 'group_end' }; case ';': if (this.buffer.length) { return this.process_buffer(); } return { type: 'and' }; case '|': if (this.buffer.length) { return this.process_buffer(); } return { type: 'or' }; case ':': if (this.buffer.length) { return this.process_buffer(); } return { type: 'equal' }; case '!': if (this.buffer.length) { return this.process_buffer(); } return { type: 'not' }; case '*': if (this.buffer.length) { return this.process_buffer(); } return { type: 'contains' }; case '^': if (this.buffer.length) { return this.process_buffer(); } return { type: 'starts_with' }; case '$': if (this.buffer.length) { return this.process_buffer(); } return { type: 'ends_with' }; case '<': if (this.buffer.length) { return this.process_buffer(); } return { type: 'less_than' }; case '>': if (this.buffer.length) { return this.process_buffer(); } return { type: 'greater_than' }; default: this.buffer += char; } } } if (!this.buffer.length) { return null; } return { type: 'string', value: this.buffer }; }; return ParseContext; })(); predicate_handler = function(token, context) { var current, field, is_contains, is_ends_with, is_equal, is_greater_than, is_less_than, is_not, is_starts_with, obj, obj1, operator, pred, value; field = field_for(token.value); operator = context.next_token(); is_equal = false; is_not = false; is_contains = false; is_starts_with = false; is_ends_with = false; is_less_than = false; is_greater_than = false; current = operator; while (current.type !== 'string' && context.idx < context.query.length) { switch (current.type) { case 'equal': is_equal = true; break; case 'not': is_not = true; break; case 'contains': is_contains = true; break; case 'starts_with': is_starts_with = true; break; case 'ends_with': is_ends_with = true; break; case 'less_than': is_less_than = true; break; case 'greater_than': is_greater_than = true; } current = context.next_token(); } pred = {}; value = value_for(field, current.value); if (is_contains) { value = RegExp("" + (regify(value))); if (is_not) { value = { $not: value }; } } else if (is_starts_with) { value = RegExp("^" + (regify(value))); if (is_not) { value = { $not: value }; } } else if (is_ends_with) { value = RegExp((regify(value)) + "$"); if (is_not) { value = { $not: value }; } } else if (is_less_than) { value = ( obj = {}, obj["" + (is_equal ? '$lte' : '$lt')] = numerify(value), obj ); if (is_not) { value = { $not: value }; } } else if (is_greater_than) { value = ( obj1 = {}, obj1["" + (is_equal ? '$gte' : '$gt')] = numerify(value), obj1 ); if (is_not) { value = { $not: value }; } } else if (is_not) { value = { '$ne': numerify(value) }; } else { value = numerify(value); } pred[field] = value; return pred; }; group_handler = function(token, context) { var current, i, len, name, operator, predicate, predicates, result, value; operator = 'and'; current = context.next_token(); predicates = []; while (current !== null && current.type !== 'group_end' && context.idx < context.query.length) { if (current.type === 'and') { } else if (current.type === 'or') { operator = '$or'; } else { predicates.push(handler_for(current.type)(current, context)); } current = context.next_token(); } if (predicates.length === 1) { return predicates[0]; } result = {}; if (operator === 'and') { for (i = 0, len = predicates.length; i < len; i++) { predicate = predicates[i]; for (name in predicate) { value = predicate[name]; result[name] = value; } } } else { result[operator] = predicates; } return result; }; handler_for = function(type) { switch (type) { case 'string': return predicate_handler; case 'group_start': return group_handler; } }; return function(query, required, valid, invalid) { var context, err, ref1, result, token; if (invalid == null) { ref1 = [false, required, valid], required = ref1[0], valid = ref1[1], invalid = ref1[2]; } query = "(" + (trimStart(query != null ? query : '')) + ")"; try { context = new ParseContext(query); token = context.next_token(); result = handler_for(token.type)(token, context); if (required && isEmpty(result)) { return invalid(Errors.wrap('query', 'A search query is required.')); } return valid(result); } catch (_error) { err = _error; return invalid(Errors.wrap('query', 'The search query is invalid.')); } }; }; }).call(this);