Introduced a pattern based url auto complete backend

This commit is contained in:
Boaz Leskes 2014-02-10 17:59:10 +01:00
parent 8a0136d8d2
commit fd128a52e8
37 changed files with 2977 additions and 1943 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,199 @@
define([
'exports',
'./json_rule_walker',
'kb',
'mappings',
'_'
],
function (exports, json_rule_walker, kb, mappings, _) {
"use strict";
function merge(a, b) {
a.push.apply(a, b);
}
function addMetaToTermsList(list, meta, template) {
return _.map(list, function (t) {
if (typeof t !== "object") {
t = { name: t};
}
return _.defaults(t, { meta: meta, template: template });
});
}
function getRulesForPath(rules, tokenPath, scopeRules) {
// scopeRules are the rules used to resolve relative scope links
var walker = new json_rule_walker.RuleWalker(rules, scopeRules);
walker.walkTokenPath(tokenPath);
return walker.getNormalizedRules();
}
function expandTerm(term, context) {
if (term == "$INDEX$") {
return addMetaToTermsList(mappings.getIndices(), "index");
}
else if (term == "$TYPE$") {
return addMetaToTermsList(mappings.getTypes(context.indices), "type");
}
else if (term == "$FIELD$") {
return _.map(mappings.getFields(context.indices, context.types), function (f) {
return { name: f.name, meta: f.type };
});
}
return [ term ]
}
function addAutocompleteForPath(autocompleteSet, rules, tokenPath, context) {
// extracts the relevant parts of rules for tokenPath
var initialRules = rules;
rules = getRulesForPath(rules, tokenPath);
// apply rule set
var term;
if (rules) {
if (typeof rules == "string") {
merge(autocompleteSet, expandTerm(rules, context));
}
else if (rules instanceof Array) {
if (rules.length > 0 && typeof rules[0] != "object") {// not an array of objects
_.map(rules, function (t) {
merge(autocompleteSet, expandTerm(t, context));
});
}
}
else if (rules.__one_of) {
if (rules.__one_of.length > 0 && typeof rules.__one_of[0] != "object") {
merge(autocompleteSet, rules.__one_of);
}
}
else if (rules.__any_of) {
if (rules.__any_of.length > 0 && typeof rules.__any_of[0] != "object") {
merge(autocompleteSet, rules.__any_of);
}
}
else if (typeof rules == "object") {
for (term in rules) {
if (typeof term == "string" && term.match(/^__|^\*$/)) {
continue;
} // meta term
var rules_for_term = rules[term], template_for_term;
// following linked scope until we find the right template
while (typeof rules_for_term.__template == "undefined" &&
typeof rules_for_term.__scope_link != "undefined"
) {
rules_for_term = json_rule_walker.getLinkedRules(rules_for_term.__scope_link, initialRules);
}
if (typeof rules_for_term.__template != "undefined") {
template_for_term = rules_for_term.__template;
}
else if (rules_for_term instanceof Array) {
template_for_term = [];
if (rules_for_term.length) {
if (rules_for_term[0] instanceof Array) {
template_for_term = [
[]
];
}
else if (typeof rules_for_term[0] == "object") {
template_for_term = [
{}
];
}
else {
template_for_term = [rules_for_term[0]];
}
}
}
else if (_.isObject(rules_for_term)) {
if (rules_for_term.__one_of) {
template_for_term = rules_for_term.__one_of[0];
}
else if (_.isEmpty(rules_for_term))
// term sub rules object. Check if has actual or just meta stuff (like __one_of
{
template_for_term = {};
}
else {
for (var sub_rule in rules_for_term) {
if (!(typeof sub_rule == "string" && sub_rule.substring(0, 2) == "__")) {
// found a real sub element, it's an object.
template_for_term = {};
break;
}
}
}
}
else {
// just add what ever the value is -> default
template_for_term = rules_for_term;
}
switch (term) {
case "$INDEX$":
if (context.indices) {
merge(autocompleteSet,
addMetaToTermsList(context.indices, "index", template_for_term));
}
break;
case "$TYPE$":
merge(autocompleteSet,
addMetaToTermsList(mappings.getTypes(context.indices), "type", template_for_term));
break;
case "$FIELD$":
/* jshint -W083 */
merge(autocompleteSet,
_.map(mappings.getFields(context.indices, context.types), function (f) {
return { name: f.name, meta: f.type, template: template_for_term };
}));
break;
default:
autocompleteSet.push({ name: term, template: template_for_term });
break;
}
}
}
else {
autocompleteSet.push(rules);
}
}
return rules ? true : false;
}
exports.populateContext = function (tokenPath, context) {
var autocompleteSet = [];
tokenPath = _.clone(tokenPath);
// apply global rules first, as they are of lower priority.
// start with one before end as to not to resolve just "{" -> empty path
for (var i = tokenPath.length - 2; i >= 0; i--) {
var subPath = tokenPath.slice(i);
if (addAutocompleteForPath(autocompleteSet, kb.getGlobalAutocompleteRules(), subPath, context)) {
break;
}
}
var pathAsString = tokenPath.join(",");
addAutocompleteForPath(autocompleteSet, (context.endpoint || {}).data_autocomplete_rules,
tokenPath, context);
if (autocompleteSet) {
_.uniq(autocompleteSet, false, function (t) {
return t.name ? t.name : t
});
}
console.log("Resolved token path " + pathAsString + " to ", autocompleteSet,
" (endpoint: ", context.endpoint, ")"
);
context.autoCompleteSet = autocompleteSet;
return context;
}
});

View file

@ -0,0 +1,187 @@
define(['_', 'kb', 'exports'], function (_, kb, exports) {
'use strict';
var WALKER_MODE_EXPECTS_KEY = 1, WALKER_MODE_EXPECTS_CONTAINER = 2, WALKER_MODE_DONE = 3;
function RuleWalker(initialRules, scopeRules) {
// scopeRules are the rules used to resolve relative scope links
if (typeof scopeRules == "undefined") {
scopeRules = initialRules;
}
this._rules = initialRules;
this._mode = WALKER_MODE_EXPECTS_CONTAINER;
this.scopeRules = scopeRules;
}
function getRulesType(rules) {
if (rules == null || typeof rules == undefined) {
return "null";
}
if (rules.__any_of || rules instanceof Array) {
return "list";
}
if (rules.__one_of) {
return getRulesType(rules.__one_of[0]);
}
if (typeof rules == "object") {
return "object";
}
return "value";
}
function getLinkedRules(link, currentRules) {
var link_path = link.split(".");
var scheme_id = link_path.shift();
var linked_rules = currentRules;
if (scheme_id == "GLOBAL") {
linked_rules = kb.getGlobalAutocompleteRules();
}
else if (scheme_id) {
linked_rules = kb.getEndpointDescriptionByEndpoint(scheme_id);
if (!linked_rules) {
throw "Failed to resolve linked scheme: " + scheme_id;
}
linked_rules = linked_rules.data_autocomplete_rules;
if (!linked_rules) {
throw "No autocomplete rules defined in linked scheme: " + scheme_id;
}
}
var walker = new RuleWalker(linked_rules);
var normalized_path = [];
_.each(link_path, function (t) {
normalized_path.push("{", t);
}); // inject { before every step
walker.walkTokenPath(normalized_path);
var rules = walker.getRules();
if (!rules) {
throw "Failed to resolve rules by link: " + link;
}
return rules;
}
_.defaults(RuleWalker.prototype, {
walkByToken: function (token) {
var new_rules;
if (this._mode == WALKER_MODE_EXPECTS_KEY) {
if (token == "{" || token == "[") {
this._rules = null;
this._mode = WALKER_MODE_DONE;
return null;
}
new_rules = this._rules[token] || this._rules["*"]
|| this._rules["$FIELD$"] || this._rules["$TYPE$"]; // we accept anything for a field.
if (new_rules && new_rules.__scope_link) {
new_rules = getLinkedRules(new_rules.__scope_link, this.scopeRules);
}
switch (getRulesType(new_rules)) {
case "object":
case "list":
this._mode = WALKER_MODE_EXPECTS_CONTAINER;
break;
default:
this._mode = WALKER_MODE_DONE;
}
this._rules = new_rules;
return new_rules;
}
else if (this._mode == WALKER_MODE_EXPECTS_CONTAINER) {
var rulesType = getRulesType(this._rules);
if (token == "{") {
if (rulesType != "object") {
this._mode = WALKER_MODE_DONE;
return this._rules = null;
}
this._mode = WALKER_MODE_EXPECTS_KEY;
return this._rules;
}
else if (token == "[") {
if (this._rules.__any_of) {
new_rules = this._rules.__any_of;
}
else if (this._rules instanceof Array) {
new_rules = this._rules;
}
else {
this._mode = WALKER_MODE_DONE;
return this._rules = null;
}
// for now we resolve using the first element in the array
if (new_rules.length == 0) {
this._mode = WALKER_MODE_DONE;
return this._rules = null;
}
else {
if (new_rules[0] && new_rules[0].__scope_link) {
new_rules = [ getLinkedRules(new_rules[0].__scope_link, this.scopeRules) ];
}
switch (getRulesType(new_rules[0])) {
case "object":
this._mode = WALKER_MODE_EXPECTS_CONTAINER;
new_rules = new_rules[0];
break;
case "list":
this._mode = WALKER_MODE_EXPECTS_CONTAINER;
new_rules = new_rules[0];
break;
default:
this._mode = WALKER_MODE_EXPECTS_KEY;
}
}
this._rules = new_rules;
return this._rules;
}
else {
this._rules = null;
this._mode = WALKER_MODE_DONE;
return null;
}
}
else {
this._rules = null;
this._mode = WALKER_MODE_DONE;
return null;
}
},
walkTokenPath: function (tokenPath) {
if (tokenPath.length == 0) {
return;
}
tokenPath = _.clone(tokenPath);
var t;
do {
t = tokenPath.shift();
}
while (this._rules && this.walkByToken(t) != null && tokenPath.length);
},
getRules: function () {
return this._rules;
},
getNormalizedRules: function () {
var rulesType = getRulesType(this._rules);
if (this._mode == WALKER_MODE_EXPECTS_CONTAINER) {
switch (rulesType) {
case "object":
return [ "{" ];
case "list":
return [ "[" ];
}
}
return this._rules;
}
});
exports.RuleWalker = RuleWalker;
exports.getLinkedRules = getLinkedRules;
});

View file

@ -0,0 +1,158 @@
define([
'exports',
'_'
],
function (exports, _) {
"use strict";
exports.AutocompleteComponent = function (name) {
this.name = name;
};
exports.Matcher = function (name, next) {
this.name = name;
this.next = next;
};
exports.AutocompleteComponent.prototype.getTerms = function (context, editor) {
return [];
};
/*
if the current matcher matches this term, this method should return an object with the following keys
{
context_values: {
values extract from term that should be added to the context
}
next: AutocompleteComponent(s) to use next
priority: optional priority to solve collisions between multiple paths. Min value is used across entire chain
}
*/
exports.AutocompleteComponent.prototype.match = function (token, context, editor) {
var r = {};
r[this.name] = token;
return {
next: this.next
};
};
function passThroughContext(context, extensionList) {
function PTC() {
}
PTC.prototype = context;
var r = new PTC();
if (extensionList) {
extensionList.unshift(r);
_.assign.apply(_, extensionList);
extensionList.shift();
}
return r;
}
function WalkingState(parent_name, components, contextExtensionList, depth, priority) {
this.parent_name = parent_name;
this.components = components;
this.contextExtensionList = contextExtensionList;
this.depth = depth || 0;
this.priority = priority;
}
function walkTokenPath(tokenPath, walkingStates, context, editor) {
if (!tokenPath || tokenPath.length === 0) {
return walkingStates
}
var token = tokenPath[0],
nextWalkingStates = [];
_.each(walkingStates, function (ws) {
var contextForState = passThroughContext(context, ws.contextExtensionList);
_.each(ws.components, function (component) {
var result = component.match(token, contextForState, editor);
if (result && !_.isEmpty(result)) {
var next, extensionList;
if (result.next && !_.isArray(result.next)) {
next = [result.next];
}
else {
next = result.next;
}
if (result.context_values) {
extensionList = [];
[].push.apply(extensionList, ws.contextExtensionList);
extensionList.push(result.context_values);
}
else {
extensionList = ws.contextExtensionList;
}
var priority = ws.priority;
if (_.isNumber(result.priority)) {
if (_.isNumber(priority)) {
priority = Math.min(priority, result.priority);
}
else {
priority = result.priority;
}
}
nextWalkingStates.push(new WalkingState(component.name, next, extensionList, ws.depth + 1, priority));
}
});
});
if (nextWalkingStates.length == 0) {
// no where to go, still return context variables returned so far..
return _.map(walkingStates, function (ws) {
return new WalkingState(ws.name, [], ws.contextExtensionList);
})
}
return walkTokenPath(tokenPath.splice(1), nextWalkingStates, context, editor);
}
exports.populateContext = function (tokenPath, context, editor, includeAutoComplete, components) {
var walkStates = walkTokenPath(tokenPath, [new WalkingState("ROOT", components, [])], context, editor);
if (includeAutoComplete) {
var autoCompleteSet = [];
_.each(walkStates, function (ws) {
var contextForState = passThroughContext(context, ws.contextExtensionList);
_.each(ws.components, function (c) {
_.each(c.getTerms(contextForState, editor), function (t) {
if (!_.isObject(t)) {
t = { name: t };
}
autoCompleteSet.push(t);
});
})
});
_.uniq(autoCompleteSet, false);
context.autoCompleteSet = autoCompleteSet;
}
// apply what values were set so far to context, selecting the deepest on which sets the context
if (walkStates.length !== 0) {
var wsToUse;
walkStates = _.sortBy(walkStates, function (ws) {
return _.isNumber(ws.priority) ? ws.priority : Number.MAX_VALUE;
});
wsToUse = _.find(walkStates, function (ws) {
return _.isEmpty(ws.components)
});
if (!wsToUse && walkStates.length > 1 && !includeAutoComplete) {
console.info("more then one context active for current path, but autocomplete isn't requested", walkStates);
}
if (!wsToUse) {
wsToUse = walkStates[0];
}
_.each(wsToUse.contextExtensionList, function (e) {
_.assign(context, e);
});
}
};
});

View file

@ -4,13 +4,117 @@ define([
'mappings',
'es',
'kb/api',
'kb/url_pattern_matcher',
'require'
],
function (_, exports, mappings, es, api, require) {
function (_, exports, mappings, es, api, url_pattern_matcher, require) {
'use strict';
var ACTIVE_API = new api.Api("empty");
function nonValidIndexType(token) {
return !(token === "_all" || token[0] !== "_");
}
function IndexUrlComponent(name, parent, multi_valued) {
url_pattern_matcher.ListComponent.call(this, name, mappings.getIndices, parent, multi_valued);
}
IndexUrlComponent.prototype = _.create(
url_pattern_matcher.ListComponent.prototype,
{ 'constructor': IndexUrlComponent });
(function (cls) {
cls.validateToken = function (token) {
if (!this.multi_valued && token.length > 1) {
return false;
}
return !_.find(token, nonValidIndexType);
};
cls.getDefaultTermMeta = function () {
return "index"
};
cls.getContextKey = function () {
return "indices";
};
})(IndexUrlComponent.prototype);
function TypeGenerator(context) {
return mappings.getTypes(context.indices);
}
function TypeUrlComponent(name, parent, multi_valued) {
url_pattern_matcher.ListComponent.call(this, name, TypeGenerator, parent, multi_valued);
}
TypeUrlComponent.prototype = _.create(
url_pattern_matcher.ListComponent.prototype,
{ 'constructor': TypeUrlComponent });
(function (cls) {
cls.validateToken = function (token) {
if (!this.multi_valued && token.length > 1) {
return false;
}
return !_.find(token, nonValidIndexType);
};
cls.getDefaultTermMeta = function () {
return "type"
};
cls.getContextKey = function () {
return "types";
};
})(TypeUrlComponent.prototype);
function IdUrlComponent(name, parent) {
url_pattern_matcher.SharedComponent.call(this, name, parent);
}
IdUrlComponent.prototype = _.create(
url_pattern_matcher.SharedComponent.prototype,
{ 'constructor': IdUrlComponent });
(function (cls) {
cls.match = function (token, context, editor) {
if (_.isArray(token) || !token) {
return null;
}
if (token.match(/[\/,]/)) {
return null;
}
var r = Object.getPrototypeOf(cls).match.call(this, token, context, editor);
r.context_values = r.context_values || {};
r.context_values['id'] = token;
return r;
};
})(IdUrlComponent.prototype);
var globalUrlComponentFactories = {
'index': function (part, parent, endpoint) {
return new IndexUrlComponent(part, parent, false);
},
'indices': function (part, parent, endpoint) {
return new IndexUrlComponent(part, parent, true);
},
'type': function (part, parent, endpoint) {
return new TypeUrlComponent(part, parent, false);
},
'types': function (part, parent, endpoint) {
return new TypeUrlComponent(part, parent, true);
},
'id': function (part, parent, endpoint) {
return new IdUrlComponent(part, parent);
}
};
function expandAliases(indices) {
if (indices && indices.length > 0) {
@ -23,16 +127,8 @@ define([
return ACTIVE_API.getEndpointDescriptionByEndpoint(endpoint)
}
function getEndpointsForIndicesTypesAndId(indices, types, id) {
return ACTIVE_API.getEndpointsForIndicesTypesAndId(expandAliases(indices), types, id);
}
function getEndpointDescriptionByPath(path, indices, types, id) {
return ACTIVE_API.getEndpointDescriptionByPath(path, expandAliases(indices), types, id);
}
function getEndpointAutocomplete(indices, types, id) {
return ACTIVE_API.getEndpointAutocomplete(expandAliases(indices), types, id);
function getTopLevelUrlCompleteComponents() {
return ACTIVE_API.getTopLevelUrlCompleteComponents();
}
function getGlobalAutocompleteRules() {
@ -40,32 +136,48 @@ define([
}
function setActiveApi(api) {
if (_.isString(api)) {
require([api], setActiveApi);
return;
}
if (_.isFunction(api)) {
/* jshint -W055 */
setActiveApi(new api(globalUrlComponentFactories));
return;
}
ACTIVE_API = api;
console.log("setting api to " + api.name);
}
es.addServerChangeListener(function () {
var version = es.getVersion();
var version = es.getVersion(), api;
if (!version || version.length == 0) {
require(["kb/api_0_90"], setActiveApi);
api = "kb/api_0_90";
}
else if (version[0] === "1") {
require(["kb/api_1_0"], setActiveApi);
api = "kb/api_1_0";
}
else if (version[0] === "2") {
require(["kb/api_1_0"], setActiveApi);
api = "kb/api_1_0";
}
else {
require(["kb/api_0_90"], setActiveApi);
api = "kb/api_0_90";
}
if (api) {
setActiveApi(api);
}
});
exports.setActiveApi = setActiveApi;
exports.getGlobalAutocompleteRules = getGlobalAutocompleteRules;
exports.getEndpointAutocomplete = getEndpointAutocomplete;
exports.getEndpointDescriptionByPath = getEndpointDescriptionByPath;
exports.getEndpointDescriptionByEndpoint = getEndpointDescriptionByEndpoint;
exports.getEndpointsForIndicesTypesAndId = getEndpointsForIndicesTypesAndId;
exports.getTopLevelUrlCompleteComponents = getTopLevelUrlCompleteComponents;
exports._test = {
globalUrlComponentFactories: globalUrlComponentFactories
};
return exports;
});

View file

@ -1,42 +1,42 @@
define([ '_', 'exports'],
function (_, exports) {
define([ '_', 'exports', './url_pattern_matcher'],
function (_, exports, url_pattern_matcher) {
'use strict';
function Api(name) {
this.global_rules = {};
/**
*
* @param name
* @param globalSharedComponentFactories a dictionary of factory functions
* that will be used as fallback for parametrized path part (i.e., {indices} )
* see url_pattern_matcher.UrlPatternMatcher
* @constructor
*/
function Api(name, globalSharedComponentFactories) {
this.globalRules = {};
this.endpoints = {};
this.name = name;
}
function escapeRegex(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
this.urlPatternMatcher = new url_pattern_matcher.UrlPatternMatcher(globalSharedComponentFactories);
}
Api.prototype.addGlobalAutocompleteRules = function (parentNode, rules) {
this.global_rules[parentNode] = rules;
this.globalRules[parentNode] = rules;
};
Api.prototype.getGlobalAutocompleteRules = function () {
return this.global_rules;
return this.globalRules;
};
Api.prototype.addEndpointDescription = function (endpoint, description) {
if (!description.endpoint_autocomplete) {
description.endpoint_autocomplete = [endpoint];
}
if (!description.match) {
var l = _.map(description.endpoint_autocomplete, escapeRegex);
description.match = "(?:" + l.join(")|(?:") + ")";
}
if (typeof description.match == "string") {
description.match = new RegExp(description.match);
}
var copiedDescription = {};
_.extend(copiedDescription, description);
copiedDescription._id = endpoint;
_.extend(copiedDescription, description || {});
_.defaults(copiedDescription, {
id: endpoint,
patterns: [endpoint],
methods: [ 'GET' ]
});
_.each(copiedDescription.patterns, function (p) {
this.urlPatternMatcher.addEndpoint(p, copiedDescription);
}, this);
this.endpoints[endpoint] = copiedDescription;
};
@ -45,100 +45,14 @@ define([ '_', 'exports'],
return this.endpoints[endpoint];
};
Api.prototype.getEndpointsForIndicesTypesAndId = function (indices, types, id) {
var ret = [];
var index_mode = "none";
if (indices && indices.length > 0) {
index_mode = typeof indices == "string" ? "single" : "multi";
}
var type_mode = "none";
if (types && types.length > 0) {
type_mode = types.length > 1 ? "multi" : "single";
}
var id_mode = "none";
if (id && id.length > 0) {
id_mode = "single";
}
for (var endpoint in this.endpoints) {
var scheme = this.endpoints[endpoint];
switch (scheme.indices_mode) {
case "none":
if (index_mode !== "none") {
continue;
}
break;
case "single":
if (index_mode !== "single") {
continue;
}
break;
case "required_multi":
if (index_mode === "none") {
continue;
}
break;
case "multi": // always good
break;
}
switch (scheme.types_mode) {
case "none":
if (type_mode !== "none") {
continue;
}
break;
case "single":
if (type_mode !== "single") {
continue;
}
break;
case "multi": // always good
break;
}
switch (scheme.doc_id_mode) {
case "none":
if (id_mode !== "none") {
continue;
}
break;
case "required_single":
if (id_mode === "none") {
continue;
}
break;
}
ret.push(endpoint);
}
return ret;
};
Api.prototype.getEndpointDescriptionByPath = function (path, indices, types, id) {
var endpoints = this.getEndpointsForIndicesTypesAndId(indices, types, id);
for (var i = 0; i < endpoints.length; i++) {
var scheme = this.endpoints[endpoints[i]];
if (scheme.match.test(path || "")) {
return scheme;
}
}
return null;
};
Api.prototype.getEndpointAutocomplete = function (indices, types, id) {
var ret = [];
var endpoints = this.getEndpointsForIndicesTypesAndId(indices, types, id);
for (var i = 0; i < endpoints.length; i++) {
var scheme = this.endpoints[endpoints[i]];
ret.push.apply(ret, scheme.endpoint_autocomplete);
}
return ret;
Api.prototype.getTopLevelUrlCompleteComponents = function () {
return this.urlPatternMatcher.getTopLevelComponents();
};
Api.prototype.clear = function () {
this.endpoints = {};
this.global_rules = {};
this.globalRules = {};
};
exports.Api = Api;

View file

@ -3,6 +3,7 @@ define([
'./api',
'./api_0_90/aliases',
'./api_0_90/cluster',
'./api_0_90/document',
'./api_0_90/facets',
'./api_0_90/filter',
'./api_0_90/globals',
@ -17,11 +18,16 @@ define([
], function (_, api) {
'use strict';
var api_0_90 = new api.Api("api_0_90");
var parts = _(arguments).rest(3);
_(arguments).rest(3).each(function (apiSection) {
apiSection(api_0_90);
});
function Api_0_90(globalSharedComponentFactories) {
api.Api.call(this, "api_0_90", globalSharedComponentFactories);
parts.each(function (apiSection) {
apiSection(this);
}, this);
}
return api_0_90;
Api_0_90.prototype = _.create(api.Api.prototype, { 'constructor': Api_0_90 });
return Api_0_90;
});

View file

@ -3,15 +3,12 @@ define(function () {
return function init(api) {
api.addEndpointDescription('_aliases', {
match: /_aliases/,
def_method: 'GET',
methods: ['GET', 'POST'],
endpoint_autocomplete: [
'_aliases'
patterns: [
"{indices}/_aliases",
"_aliases",
],
indices_mode: 'multi',
types_mode: 'none',
doc_id_mode: 'none',
data_autocomplete_rules: {
'actions': {
__template: [

View file

@ -2,34 +2,17 @@ define(function () {
'use strict';
return function init(api) {
api.addEndpointDescription('_cluster/nodes/stats', {
methods: ['GET'],
indices_mode: 'none',
types_mode: 'none'
});
api.addEndpointDescription('_cluster/nodes/stats');
api.addEndpointDescription('_cluster/state', {
methods: ['GET'],
endpoint_autocomplete: ['_cluster/state'],
indices_mode: 'none',
types_mode: 'none'
});
api.addEndpointDescription('_cluster/state');
api.addEndpointDescription('_cluster/health', {
methods: ['GET'],
endpoint_autocomplete: ['_cluster/health'],
indices_mode: 'none',
types_mode: 'none'
});
api.addEndpointDescription('_cluster/health');
api.addEndpointDescription('_cluster/settings', {
methods: ['GET', 'PUT'],
endpoint_autocomplete: ['_cluster/settings'],
indices_mode: 'none',
types_mode: 'none',
data_autocomplete_rules: {
data_autocomplete_rules: {
persistent: {
'routing.allocation.same_shard.host' : { __one_of: [ false, true ]}
'routing.allocation.same_shard.host': { __one_of: [ false, true ]}
},
transient: {
__scope_link: '.persistent'

View file

@ -0,0 +1,37 @@
define(function () {
'use strict';
return function init(api) {
api.addEndpointDescription('_get_doc', {
methods: ['GET'],
patterns: [
"{index}/{type}/{id}"
]
});
api.addEndpointDescription('_get_doc_source', {
methods: ['GET'],
patterns: [
"{index}/{type}/{id}/_source"
]
});
api.addEndpointDescription('_delete_doc', {
methods: ['DELETE'],
patterns: [
"{index}/{type}/{id}/"
]
});
api.addEndpointDescription('index_doc', {
methods: ['PUT', 'POST'],
patterns: [
"{index}/{type}/{id}"
]
});
api.addEndpointDescription('index_doc_no_id', {
methods: ['POST'],
patterns: [
"{index}/{type}"
]
});
}
});

View file

@ -3,49 +3,44 @@ define(function () {
return function init(api) {
api.addEndpointDescription('_refresh', {
def_method: 'POST',
methods: ['POST'],
endpoint_autocomplete: [
'_refresh'
],
indices_mode: 'multi'
methods: ['POST']
});
api.addEndpointDescription('_stats', {
def_method: 'GET',
methods: ['GET'],
endpoint_autocomplete: [
'_stats'
],
indices_mode: 'multi'
patterns: [
"{indices}/_stats",
"_stats"
]
});
api.addEndpointDescription('_segments', {
def_method: 'GET',
methods: ['GET'],
endpoint_autocomplete: [
'_segments'
],
indices_mode: 'multi'
patterns: [
"{indices}/_segments",
"_segments"
]
});
api.addEndpointDescription('__create_index__', {
methods: ['PUT', 'DELETE'],
indices_mode: 'single',
types_mode: 'none',
match: '^/?$',
endpoint_autocomplete: [
''
methods: ['PUT'],
patterns: [
"{index}"
],
data_autocomplete_rules: {
mappings: {
__scope_link: '_mapping'
__scope_link: '_put_mapping'
},
settings: {
__scope_link: '_settings.index'
__scope_link: '_put_settings.index'
}
}
});
api.addEndpointDescription('__delete_indices__', {
methods: ['DELETE'],
patterns: [
"{indices}"
]
});
};
});

View file

@ -2,11 +2,22 @@ define(function () {
'use strict';
return function init(api) {
api.addEndpointDescription('_mapping', {
def_method: 'GET',
methods: ['GET', 'PUT'],
indices_mode: 'multi',
types_mode: 'multi',
api.addEndpointDescription('_get_mapping', {
methods: ['GET'],
priority: 10, // collides with get doc by id
patterns: [
"{indices}/_mapping",
"{indices}/{types}/_mapping",
"_mapping"
]
});
api.addEndpointDescription('_put_mapping', {
methods: ['PUT'],
patterns: [
"{indices}/_mapping",
"{indices}/{type}/_mapping",
],
priority: 10, // collides with put doc by id
data_autocomplete_rules: {
'$TYPE$': {
__template: {
@ -106,7 +117,7 @@ define(function () {
// objects
properties: {
__scope_link: '_mapping.$TYPE$.properties'
__scope_link: '_put_mapping.$TYPE$.properties'
},
// multi_field
@ -115,7 +126,7 @@ define(function () {
},
fields: {
'*': {
__scope_link: '_mapping.$TYPE$.properties.$FIELD$'
__scope_link: '_put_mapping.$TYPE$.properties.$FIELD$'
}
}
}

View file

@ -2,30 +2,9 @@ define(function () {
'use strict';
return function init(api) {
api.addEndpointDescription('_stats', {
methods: ['GET'],
endpoint_autocomplete: ['_stats'],
indices_mode: 'multi',
types_mode: 'none',
doc_id_mode: 'none'
});
api.addEndpointDescription('_cache/clear', {
methods: ['GET'],
endpoint_autocomplete: ['_cache/clear'],
indices_mode: 'multi',
types_mode: 'none',
doc_id_mode: 'none'
});
api.addEndpointDescription('_status', {
methods: ['GET'],
indices_mode: 'multi',
types_mode: 'none',
doc_id_mode: 'none',
endpoint_autocomplete: ['_status']
});
api.addEndpointDescription('_cache/clear');
api.addEndpointDescription('_status');
};

View file

@ -3,14 +3,13 @@ define(function () {
return function init(api) {
api.addEndpointDescription('_search', {
def_method: 'POST',
methods: ['GET', 'POST'],
endpoint_autocomplete: [
'_search'
priority: 10, // collides with get doc by id
patterns: [
"{indices}/{types}/_search",
"{indices}/_search",
"_search"
],
indices_mode: 'multi',
types_mode: 'multi',
doc_id_mode: 'none',
data_autocomplete_rules: {
query: {
// populated by a global rule

View file

@ -2,13 +2,18 @@ define(function () {
'use strict';
return function init(api) {
api.addEndpointDescription('_settings', {
match: /_settings/,
methods: ['GET', 'PUT'],
endpoint_autocomplete: ['_settings'],
indices_mode: 'multi',
types_mode: 'none',
doc_id_mode: 'none',
api.addEndpointDescription('_get_settings', {
patterns: [
"{indices}/_settings",
"_settings"
]
});
api.addEndpointDescription('_put_settings', {
methods: ['PUT'],
patterns: [
"{indices}/_settings",
"_settings"
],
data_autocomplete_rules: {
index: {
refresh_interval: '1s',

View file

@ -2,16 +2,24 @@ define(function () {
'use strict';
return function init(api) {
api.addEndpointDescription('_template', {
match: /\/?_template/,
def_method: 'PUT',
methods: ['GET', 'PUT', 'DELETE'],
endpoint_autocomplete: [
'_template/TEMPLATE_ID'
api.addEndpointDescription('_delete_template', {
methods: ['DELETE'],
patterns: [
"_template/{id}",
]
});
api.addEndpointDescription('_get_template', {
methods: ['GET'],
patterns: [
"_template/{id}",
"_template",
]
});
api.addEndpointDescription('_put_template', {
methods: ['PUT'],
patterns: [
"_template/{id}",
],
indices_mode: 'none',
types_mode: 'none',
doc_id_mode: 'none',
data_autocomplete_rules: {
template: 'index*',
warmers: { __scope_link: '_warmer' },

View file

@ -2,16 +2,23 @@ define(function () {
'use strict';
return function init(api) {
api.addEndpointDescription('_warmer', {
match: /_warmer/,
def_method: 'PUT',
methods: ['GET', 'PUT', 'DELETE'],
endpoint_autocomplete: [
'_warmer', '_warmer/WARMER_ID'
api.addEndpointDescription('_get_warmer', {
patterns: ["_warmer", "_warmer/{id}"]
});
api.addEndpointDescription('_delete_warmer', {
methods: ['DELETE'],
patterns: [
"{indices}/_warmer",
"{indices}/_warmer/{id}"
]
});
api.addEndpointDescription('_put_warmer', {
methods: ['PUT'],
patterns: [
"{indices}/_warmer",
"{indices}/_warmer/{id}",
"{indices}/{types}/_warmer/{id}"
],
indices_mode: 'required_multi',
types_mode: 'none',
doc_id_mode: 'none',
data_autocomplete_rules: {
query: {
// populated by a global rule

View file

@ -4,6 +4,7 @@ define([
'./api_1_0/aliases',
'./api_1_0/cat',
'./api_1_0/cluster',
'./api_1_0/document',
'./api_1_0/facets',
'./api_1_0/aggregations',
'./api_1_0/filter',
@ -19,12 +20,17 @@ define([
], function (_, api) {
'use strict';
var api_1_0 = new api.Api("api_1_0");
var parts = _(arguments).rest(3);
function Api_1_0(globalSharedComponentFactories) {
api.Api.call(this, "api_1_0", globalSharedComponentFactories);
parts.each(function (apiSection) {
apiSection(this);
}, this);
}
_(arguments).rest(3).each(function (apiSection) {
apiSection(api_1_0);
});
Api_1_0.prototype = _.create(api.Api.prototype, { 'constructor': Api_1_0 });
return Api_1_0;
return api_1_0;
});

View file

@ -3,15 +3,12 @@ define(function () {
return function init(api) {
api.addEndpointDescription('_aliases', {
match: /_aliases/,
def_method: 'GET',
methods: ['GET', 'POST'],
endpoint_autocomplete: [
'_aliases'
patterns: [
"{indices}/_aliases",
"_aliases",
],
indices_mode: 'multi',
types_mode: 'none',
doc_id_mode: 'none',
data_autocomplete_rules: {
'actions': {
__template: [

View file

@ -3,24 +3,27 @@ define(function () {
function addSimple(endpoint, api) {
api.addEndpointDescription(endpoint, {
def_method: 'GET',
methods: ['GET' ],
indices_mode: 'none',
});
}
return function init(api) {
addSimple('_cluster/nodes/stats', api);
addSimple('_cluster/state', api);
addSimple('_cluster/health', api);
addSimple('_cluster/pending_tasks', api);
api.addEndpointDescription('_cluster/nodes/stats');
api.addEndpointDescription('_cluster/state');
api.addEndpointDescription('_cluster/health');
api.addEndpointDescription('_cluster/pending_tasks');
api.addEndpointDescription('get_cluster/settings', {
patterns: [
'_cluster/settings'
]
});
api.addEndpointDescription('_cluster/settings', {
methods: ['GET', 'PUT'],
endpoint_autocomplete: ['_cluster/settings'],
indices_mode: 'none',
types_mode: 'none',
api.addEndpointDescription('put_cluster/settings', {
methods: ['PUT'],
patterns: [
'_cluster/settings'
],
data_autocomplete_rules: {
persistent: {
'routing.allocation.same_shard.host': { __one_of: [ false, true ]}

View file

@ -0,0 +1,37 @@
define(function () {
'use strict';
return function init(api) {
api.addEndpointDescription('_get_doc', {
methods: ['GET'],
patterns: [
"{index}/{type}/{id}"
]
});
api.addEndpointDescription('_get_doc_source', {
methods: ['GET'],
patterns: [
"{index}/{type}/{id}/_source"
]
});
api.addEndpointDescription('_delete_doc', {
methods: ['DELETE'],
patterns: [
"{index}/{type}/{id}/"
]
});
api.addEndpointDescription('index_doc', {
methods: ['PUT', 'POST'],
patterns: [
"{index}/{type}/{id}"
]
});
api.addEndpointDescription('index_doc_no_id', {
methods: ['POST'],
patterns: [
"{index}/{type}"
]
});
}
});

View file

@ -3,49 +3,44 @@ define(function () {
return function init(api) {
api.addEndpointDescription('_refresh', {
def_method: 'POST',
methods: ['POST'],
endpoint_autocomplete: [
'_refresh'
],
indices_mode: 'multi'
methods: ['POST']
});
api.addEndpointDescription('_stats', {
def_method: 'GET',
methods: ['GET'],
endpoint_autocomplete: [
'_stats'
],
indices_mode: 'multi'
patterns: [
"{indices}/_stats",
"_stats"
]
});
api.addEndpointDescription('_segments', {
def_method: 'GET',
methods: ['GET'],
endpoint_autocomplete: [
'_segments'
],
indices_mode: 'multi'
patterns: [
"{indices}/_segments",
"_segments"
]
});
api.addEndpointDescription('__create_index__', {
methods: ['PUT', 'DELETE'],
indices_mode: 'single',
types_mode: 'none',
match: '^/?$',
endpoint_autocomplete: [
''
methods: ['PUT'],
patterns: [
"{index}"
],
data_autocomplete_rules: {
mappings: {
__scope_link: '_mapping'
__scope_link: '_put_mapping'
},
settings: {
__scope_link: '_settings.index'
__scope_link: '_put_settings.index'
}
}
});
api.addEndpointDescription('__delete_indices__', {
methods: ['DELETE'],
patterns: [
"{indices}"
]
});
};
});

View file

@ -2,11 +2,22 @@ define(function () {
'use strict';
return function init(api) {
api.addEndpointDescription('_mapping', {
def_method: 'GET',
methods: ['GET', 'PUT'],
indices_mode: 'multi',
types_mode: 'multi',
api.addEndpointDescription('_get_mapping', {
methods: ['GET'],
priority: 10, // collides with get doc by id
patterns: [
"{indices}/_mapping",
"{indices}/{types}/_mapping",
"_mapping"
]
});
api.addEndpointDescription('_put_mapping', {
methods: ['PUT'],
patterns: [
"{indices}/_mapping",
"{indices}/{type}/_mapping",
],
priority: 10, // collides with put doc by id
data_autocomplete_rules: {
'$TYPE$': {
__template: {
@ -106,7 +117,7 @@ define(function () {
// objects
properties: {
__scope_link: '_mapping.$TYPE$.properties'
__scope_link: '_put_mapping.$TYPE$.properties'
},
// multi_field
@ -115,7 +126,7 @@ define(function () {
},
fields: {
'*': {
__scope_link: '_mapping.$TYPE$.properties.$FIELD$'
__scope_link: '_put_mapping.$TYPE$.properties.$FIELD$'
}
}
}

View file

@ -3,30 +3,24 @@ define(function () {
return function init(api) {
api.addEndpointDescription('_stats', {
methods: ['GET'],
endpoint_autocomplete: ['_stats'],
indices_mode: 'multi',
types_mode: 'none',
doc_id_mode: 'none'
patterns: [
"_stats",
"{indices}/_stats"
]
});
api.addEndpointDescription('_cache/clear', {
methods: ['GET'],
endpoint_autocomplete: ['_cache/clear'],
indices_mode: 'multi',
types_mode: 'none',
doc_id_mode: 'none'
patterns: [
"_cache/clear",
"{indices}/_cache/clear"
]
});
api.addEndpointDescription('_status', {
methods: ['GET'],
indices_mode: 'multi',
types_mode: 'none',
doc_id_mode: 'none',
endpoint_autocomplete: ['_status']
patterns: [
"_status",
"{indices}/_status"
]
});
};
});

View file

@ -3,14 +3,13 @@ define(function () {
return function init(api) {
api.addEndpointDescription('_search', {
def_method: 'POST',
methods: ['GET', 'POST'],
endpoint_autocomplete: [
'_search'
priority: 10, // collides with get doc by id
patterns: [
"{indices}/{types}/_search",
"{indices}/_search",
"_search"
],
indices_mode: 'multi',
types_mode: 'multi',
doc_id_mode: 'none',
data_autocomplete_rules: {
query: {
// populated by a global rule

View file

@ -2,13 +2,18 @@ define(function () {
'use strict';
return function init(api) {
api.addEndpointDescription('_settings', {
match: /_settings/,
methods: ['GET', 'PUT'],
endpoint_autocomplete: ['_settings'],
indices_mode: 'multi',
types_mode: 'none',
doc_id_mode: 'none',
api.addEndpointDescription('_get_settings', {
patterns: [
"{indices}/_settings",
"_settings"
]
});
api.addEndpointDescription('_put_settings', {
methods: ['PUT'],
patterns: [
"{indices}/_settings",
"_settings"
],
data_autocomplete_rules: {
index: {
refresh_interval: '1s',

View file

@ -2,16 +2,24 @@ define(function () {
'use strict';
return function init(api) {
api.addEndpointDescription('_template', {
match: /\/?_template/,
def_method: 'PUT',
methods: ['GET', 'PUT', 'DELETE'],
endpoint_autocomplete: [
'_template/TEMPLATE_ID'
api.addEndpointDescription('_delete_template', {
methods: ['DELETE'],
patterns: [
"_template/{id}",
]
});
api.addEndpointDescription('_get_template', {
methods: ['GET'],
patterns: [
"_template/{id}",
"_template",
]
});
api.addEndpointDescription('_put_template', {
methods: ['PUT'],
patterns: [
"_template/{id}",
],
indices_mode: 'none',
types_mode: 'none',
doc_id_mode: 'none',
data_autocomplete_rules: {
template: 'index*',
warmers: { __scope_link: '_warmer' },

View file

@ -2,16 +2,23 @@ define(function () {
'use strict';
return function init(api) {
api.addEndpointDescription('_warmer', {
match: /_warmer/,
def_method: 'PUT',
methods: ['GET', 'PUT', 'DELETE'],
endpoint_autocomplete: [
'_warmer', '_warmer/WARMER_ID'
api.addEndpointDescription('_get_warmer', {
patterns: ["_warmer", "_warmer/{id}"]
});
api.addEndpointDescription('_delete_warmer', {
methods: ['DELETE'],
patterns: [
"{indices}/_warmer",
"{indices}/_warmer/{id}"
]
});
api.addEndpointDescription('_put_warmer', {
methods: ['PUT'],
patterns: [
"{indices}/_warmer",
"{indices}/_warmer/{id}",
"{indices}/{types}/_warmer/{id}"
],
indices_mode: 'required_multi',
types_mode: 'none',
doc_id_mode: 'none',
data_autocomplete_rules: {
query: {
// populated by a global rule

View file

@ -0,0 +1,276 @@
define([
"_", "exports", "autocomplete/url_path_autocomplete"
], function (_, exports, url_path_autocomplete) {
"use strict";
exports.URL_PATH_END_MARKER = "__url_path_end__";
function SharedComponent(name, parent) {
url_path_autocomplete.AutocompleteComponent.call(this, name);
this._nextDict = {};
if (parent) {
parent.addComponent(this);
}
// for debugging purposes
this._parent = parent;
}
SharedComponent.prototype = _.create(
url_path_autocomplete.AutocompleteComponent.prototype,
{ 'constructor': SharedComponent });
(function (cls) {
cls.getComponent = function (name) {
return this._nextDict[name];
};
cls.addComponent = function (c) {
this._nextDict[c.name] = c;
this.next = _.values(this._nextDict);
};
})(SharedComponent.prototype);
/** A component that suggests one of the give options, but accepts anything */
function ListComponent(name, list, parent, multi_valued) {
SharedComponent.call(this, name, parent);
this.listGenerator = _.isArray(list) ? function () {
return list
} : list;
this.multi_valued = _.isUndefined(multi_valued) ? true : multi_valued;
}
ListComponent.prototype = _.create(SharedComponent.prototype, { "constructor": ListComponent });
(function (cls) {
cls.getTerms = function (context, editor) {
if (!this.multi_valued && context.otherTokenValues) {
// already have a value -> no suggestions
return []
}
var already_set = context.otherTokenValues || [];
if (_.isString(already_set)) {
already_set = [already_set];
}
var ret = _.difference(this.listGenerator(context, editor), already_set);
if (this.getDefaultTermMeta()) {
var meta = this.getDefaultTermMeta();
ret = _.map(ret, function (t) {
if (_.isString(t)) {
t = { "name": t};
}
return _.defaults(t, { meta: meta });
});
}
return ret;
};
cls.validateToken = function (token, context, editor) {
if (!this.multi_valued && token.length > 1) {
return false;
}
// verify we have all tokens
var list = this.listGenerator();
var not_found = _.any(token, function (p) {
return list.indexOf(p) == -1;
});
if (not_found) {
return false;
}
return true;
};
cls.getContextKey = function (context, editor) {
return this.name;
};
cls.getDefaultTermMeta = function (context, editor) {
return null;
};
cls.match = function (token, context, editor) {
if (!_.isArray(token)) {
token = [ token ]
}
if (!this.validateToken(token, context, editor)) {
return null
}
var r = Object.getPrototypeOf(cls).match.call(this, token, context, editor);
r.context_values = r.context_values || {};
r.context_values[this.getContextKey()] = token;
return r;
}
})(ListComponent.prototype);
function SimpleParamComponent(name, parent) {
SharedComponent.call(this, name, parent);
}
SimpleParamComponent.prototype = _.create(SharedComponent.prototype, { "constructor": SimpleParamComponent });
(function (cls) {
cls.match = function (token, context, editor) {
var r = Object.getPrototypeOf(cls).match.call(this, token, context, editor);
r.context_values = r.context_values || {};
r.context_values[this.name] = token;
return r;
}
})(SimpleParamComponent.prototype);
function SimplePartComponent(name, parent, options) {
SharedComponent.call(this, name, parent);
if (_.isString(options)) {
options = [options];
}
this.options = options || [name];
}
SimplePartComponent.prototype = _.create(SharedComponent.prototype, { "constructor": SimplePartComponent });
(function (cls) {
cls.getTerms = function () {
return this.options;
};
cls.addOption = function (options) {
if (!_.isArray(options)) {
options = [options];
}
[].push.apply(this.options, options);
this.options = _.uniq(this.options);
};
cls.match = function (token, context, editor) {
if (token !== this.name) {
return null;
}
return Object.getPrototypeOf(cls).match.call(this, token, context, editor);
}
})(SimplePartComponent.prototype);
function AcceptEndpointComponent(endpoint, parent) {
SharedComponent.call(this, endpoint.id, parent);
this.endpoint = endpoint
}
AcceptEndpointComponent.prototype = _.create(SharedComponent.prototype, { "constructor": AcceptEndpointComponent });
(function (cls) {
cls.match = function (token, context, editor) {
if (token !== exports.URL_PATH_END_MARKER) {
return null;
}
if (this.endpoint.methods && -1 === _.indexOf(this.endpoint.methods, context.method)) {
return null;
}
var r = Object.getPrototypeOf(cls).match.call(this, token, context, editor);
r.context_values = r.context_values || {};
r.context_values['endpoint'] = this.endpoint;
if (_.isNumber(this.endpoint.priority)) {
r.priority = this.endpoint.priority;
}
return r;
}
})(AcceptEndpointComponent.prototype);
/**
* @param globalSharedComponentFactories a dict of the following structure
* that will be used as a fall back for pattern parameters (i.e.: {indices})
* {
* indices: function (part, parent, endpoint) {
* return new SharedComponent(part, parent)
* }
* }
* @constructor
*/
function UrlPatternMatcher(globalSharedComponentFactories) {
// This is not really a component, just a handy container to make iteration logic simpler
this.rootComponent = new SharedComponent("ROOT");
this.globalSharedComponentFactories = globalSharedComponentFactories || {};
}
(function (cls) {
cls.addEndpoint = function (pattern, endpoint) {
var c,
active_component = this.rootComponent,
endpointComponents = endpoint.url_components || {};
var partList = pattern.split("/");
_.each(partList, function (part, partIndex) {
if (part.search(/^{.+}$/) >= 0) {
part = part.substr(1, part.length - 2);
if (active_component.getComponent(part)) {
// we already have something for this, reuse
active_component = active_component.getComponent(part);
return;
}
// a new path, resolve.
if ((c = endpointComponents[part])) {
// endpoint specific. Support list
if (_.isArray(c)) {
c = new ListComponent(part, c, active_component);
}
else {
console.warn("incorrectly configured url component ", part, " in endpoint", endpoint);
c = new SharedComponent(part);
}
}
else if ((c = this.globalSharedComponentFactories[part])) {
// c is a f
c = c(part, active_component, endpoint);
}
else {
// just accept whatever with not suggestions
c = new SimpleParamComponent(part, active_component);
}
active_component = c;
}
else {
// not pattern
var lookAhead = part, s;
for (partIndex++; partIndex < partList.length; partIndex++) {
s = partList[partIndex];
if (s.indexOf("{") >= 0) {
break;
}
lookAhead += "/" + s;
}
if (active_component.getComponent(part)) {
// we already have something for this, reuse
active_component = active_component.getComponent(part);
active_component.addOption(lookAhead);
}
else {
c = new SimplePartComponent(part, active_component, lookAhead);
active_component = c;
}
}
}, this);
// mark end of endpoint path
new AcceptEndpointComponent(endpoint, active_component);
};
cls.getTopLevelComponents = function () {
return this.rootComponent.next;
}
})(UrlPatternMatcher.prototype);
exports.UrlPatternMatcher = UrlPatternMatcher;
exports.SharedComponent = SharedComponent;
exports.ListComponent = ListComponent;
});

View file

@ -148,7 +148,7 @@ define([
if (requestRange) {
var pos = editor.getCursorPosition();
editor.getSession().replace(requestRange, text);
var max_row = Math.max(requestRange.start.row + text.split('\n').length -1 , 0);
var max_row = Math.max(requestRange.start.row + text.split('\n').length - 1, 0);
pos.row = Math.min(pos.row, max_row);
editor.moveCursorToPosition(pos);
// ACE UPGRADE - check if needed - at the moment the above may trigger a selection.
@ -267,10 +267,10 @@ define([
var maxLines = session.getLength();
for (; curRow < maxLines - 1; curRow++) {
var curRowMode = editor.parser.getRowParseMode(curRow, editor);
if ((curRowMode & RowParser.MODE_REQUEST_END) > 0) {
if ((curRowMode & editor.parser.MODE.REQUEST_END) > 0) {
break;
}
if (curRow != pos.row && (curRowMode & RowParser.MODE_REQUEST_START) > 0) {
if (curRow != pos.row && (curRowMode & editor.parser.MODE.REQUEST_START) > 0) {
break;
}
}
@ -290,10 +290,10 @@ define([
if ((curRowMode & RowParser.REQUEST_END) > 0) {
break;
}
if ((curRowMode & RowParser.MODE_MULTI_DOC_CUR_DOC_END) > 0) {
if ((curRowMode & editor.parser.MODE.MULTI_DOC_CUR_DOC_END) > 0) {
break;
}
if (curRow != pos.row && (curRowMode & RowParser.MODE_REQUEST_START) > 0) {
if (curRow != pos.row && (curRowMode & editor.parser.MODE.REQUEST_START) > 0) {
break;
}
}

View file

@ -13,7 +13,9 @@ define(['require', 'exports', 'module' , 'ace'], function (require, exports, mod
}
function addEOL(tokens, reg, nextIfEOL, normalNext) {
if (typeof reg == "object") reg = reg.source;
if (typeof reg == "object") {
reg = reg.source;
}
return [
{ token: tokens.concat(["whitespace"]), regex: reg + "(\\s*)$", next: nextIfEOL },
{ token: tokens, regex: reg, next: normalNext }
@ -41,33 +43,12 @@ define(['require', 'exports', 'module' , 'ace'], function (require, exports, mod
}
]),
"method_sep": mergeTokens(
addEOL(["whitespace", "url.slash"], /(\s+)(\/)/, "start", "indices"),
addEOL(["whitespace"], /(\s+)/, "start", "indices")
addEOL(["whitespace", "url.slash"], /(\s+)(\/)/, "start", "url"),
addEOL(["whitespace"], /(\s+)/, "start", "url")
),
"indices": mergeTokens(
addEOL(["url.scheme", "url.host", "url.slash"], /([^:]+:\/\/)([^?\/\s]*)(\/?)/, "start"),
addEOL(["url.index"], /(_all)/, "start"),
addEOL(["url.endpoint"], /(_[^\/?]+)/, "start", "urlRest"),
addEOL(["url.index"], /([^\/?,]+)/, "start"),
"url": mergeTokens(
addEOL(["url.part"], /([^?\/,]+)/, "start"),
addEOL(["url.comma"], /(,)/, "start"),
addEOL(["url.slash"], /(\/)/, "start", "types"),
addEOL(["url.questionmark"], /(\?)/, "start", "urlParams")
),
"types": mergeTokens(
addEOL(["url.endpoint"], /(_[^\/?]+)/, "start", "urlRest"),
addEOL(["url.type"], /([^\/?,]+)/, "start"),
addEOL(["url.comma"], /(,)/, "start"),
addEOL(["url.slash"], /(\/)/, "start", "id"),
addEOL(["url.questionmark"], /(\?)/, "start", "urlParams")
),
"id": mergeTokens(
addEOL(["url.endpoint"], /(_[^\/?]+)/, "start", "urlRest"),
addEOL(["url.id"], /([^\/?]+)/, "start"),
addEOL(["url.slash"], /(\/)/, "start", "urlRest"),
addEOL(["url.questionmark"], /(\?)/, "start", "urlParams")
),
"urlRest": mergeTokens(
addEOL(["url.part"], /([^?\/]+)/, "start"),
addEOL(["url.slash"], /(\/)/, "start"),
addEOL(["url.questionmark"], /(\?)/, "start", "urlParams")
),
@ -165,8 +146,9 @@ define(['require', 'exports', 'module' , 'ace'], function (require, exports, mod
]
}
if (this.constructor === SenseJsonHighlightRules)
if (this.constructor === SenseJsonHighlightRules) {
this.normalizeRules();
}
};
oop.inherits(SenseJsonHighlightRules, TextHighlightRules);

View file

@ -1,21 +1,39 @@
define([], function () {
'use strict';
var MODE = {
REQUEST_START: 2,
IN_REQUEST: 4,
MULTI_DOC_CUR_DOC_END: 8,
REQUEST_END: 16,
BETWEEN_REQUESTS: 32
};
function RowParser(editor) {
var defaultEditor = editor;
this.getRowParseMode = function (row) {
if (row == null || typeof row == "undefined") row = editor.getCursorPosition().row;
if (row == null || typeof row == "undefined") {
row = editor.getCursorPosition().row;
}
var session = editor.getSession();
if (row >= session.getLength()) return RowParser.MODE_BETWEEN_REQUESTS;
if (row >= session.getLength() || row < 0) {
return MODE.BETWEEN_REQUESTS;
}
var mode = session.getState(row);
if (!mode)
return RowParser.MODE_BETWEEN_REQUESTS; // shouldn't really happen
if (!mode) {
return MODE.BETWEEN_REQUESTS;
} // shouldn't really happen
if (mode !== "start") return RowParser.MODE_IN_REQUEST;
if (mode !== "start") {
return MODE.IN_REQUEST;
}
var line = (session.getLine(row) || "").trim();
if (!line || line[0] === '#') return RowParser.MODE_BETWEEN_REQUESTS; // empty line or a comment waiting for a new req to start
if (!line || line[0] === '#') {
return MODE.BETWEEN_REQUESTS;
} // empty line or a comment waiting for a new req to start
if (line.indexOf("}", line.length - 1) >= 0) {
// check for a multi doc request (must start a new json doc immediately after this one end.
@ -23,59 +41,59 @@ define([], function () {
if (row < session.getLength()) {
line = (session.getLine(row) || "").trim();
if (line.indexOf("{") === 0) { // next line is another doc in a multi doc
return RowParser.MODE_MULTI_DOC_CUR_DOC_END | RowParser.MODE_IN_REQUEST;
return MODE.MULTI_DOC_CUR_DOC_END | MODE.IN_REQUEST;
}
}
return RowParser.MODE_REQUEST_END | RowParser.MODE_MULTI_DOC_CUR_DOC_END; // end of request
return MODE.REQUEST_END | MODE.MULTI_DOC_CUR_DOC_END; // end of request
}
// check for single line requests
row++;
if (row >= session.getLength()) {
return RowParser.MODE_REQUEST_START | RowParser.MODE_REQUEST_END;
return MODE.REQUEST_START | MODE.REQUEST_END;
}
line = (session.getLine(row) || "").trim();
if (line.indexOf("{") !== 0) { // next line is another request
return RowParser.MODE_REQUEST_START | RowParser.MODE_REQUEST_END;
return MODE.REQUEST_START | MODE.REQUEST_END;
}
return RowParser.MODE_REQUEST_START;
}
return MODE.REQUEST_START;
};
this.rowPredicate = function (row, editor, value) {
var mode = this.getRowParseMode(row, editor);
return (mode & value) > 0;
}
};
this.isEndRequestRow = function (row, _e) {
var editor = _e || defaultEditor;
return this.rowPredicate(row, editor, RowParser.MODE_REQUEST_END);
return this.rowPredicate(row, editor, MODE.REQUEST_END);
};
this.isRequestEdge = function (row, _e) {
var editor = _e || defaultEditor;
return this.rowPredicate(row, editor, RowParser.MODE_REQUEST_END | RowParser.MODE_REQUEST_START);
return this.rowPredicate(row, editor, MODE.REQUEST_END | MODE.REQUEST_START);
};
this.isStartRequestRow = function (row, _e) {
var editor = _e || defaultEditor;
return this.rowPredicate(row, editor, RowParser.MODE_REQUEST_START);
return this.rowPredicate(row, editor, MODE.REQUEST_START);
};
this.isInBetweenRequestsRow = function (row, _e) {
var editor = _e || defaultEditor;
return this.rowPredicate(row, editor, RowParser.MODE_BETWEEN_REQUESTS);
return this.rowPredicate(row, editor, MODE.BETWEEN_REQUESTS);
};
this.isInRequestsRow = function (row, _e) {
var editor = _e || defaultEditor;
return this.rowPredicate(row, editor, RowParser.MODE_IN_REQUEST);
return this.rowPredicate(row, editor, MODE.IN_REQUEST);
};
this.isMultiDocDocEndRow = function (row, _e) {
var editor = _e || defaultEditor;
return this.rowPredicate(row, editor, RowParser.MODE_MULTI_DOC_CUR_DOC_END);
return this.rowPredicate(row, editor, MODE.MULTI_DOC_CUR_DOC_END);
};
this.isEmptyToken = function (tokenOrTokenIter) {
@ -103,11 +121,7 @@ define([], function () {
};
}
RowParser.MODE_REQUEST_START = 2;
RowParser.MODE_IN_REQUEST = 4;
RowParser.MODE_MULTI_DOC_CUR_DOC_END = 8;
RowParser.MODE_REQUEST_END = 16;
RowParser.MODE_BETWEEN_REQUESTS = 32;
RowParser.prototype.MODE = MODE;
return RowParser;
})
});

View file

@ -15,6 +15,7 @@
z-index: 200;
border: 1px solid #333;
}
#output_container {
display: none;
position: absolute;
@ -25,52 +26,59 @@
z-index: 201;
border: 1px solid #333;
}
#editor, #output {
height: 100%;
width: 100%;
position: relative;
}
</style>
</style>
</head>
<body>
<div id="qunit"></div>
<div id="editor_container"><div id="editor"></div></div>
<div id="output_container"><div id="output"></div></div>
<div id="editor_container">
<div id="editor"></div>
</div>
<div id="output_container">
<div id="output"></div>
</div>
<script src="../vendor/require/require.js"></script>
<script src="../app/require.config.js"></script>
<script src="lib/qunit-1.10.0.js"></script>
<script>
/* global QUnit */
QUnit.config.autostart = false;
/* global QUnit */
QUnit.config.autostart = false;
require.config({
baseUrl: '../app'
});
require(["require","ace"], function (require) {
require.config({
baseUrl: '../app'
});
require(["require", "ace"], function (require) {
'use strict';
'use strict';
var tests = [
'../tests/src/curl_tests.js',
'../tests/src/kb_tests.js',
'../tests/src/mapping_tests.js',
'../tests/src/editor_tests.js',
'../tests/src/tokenization_tests.js',
'../tests/src/integration_tests.js'
];
var tests = [
'../tests/src/url_autocomplete_tests.js',
'../tests/src/curl_tests.js',
'../tests/src/kb_tests.js',
'../tests/src/mapping_tests.js',
'../tests/src/editor_tests.js',
'../tests/src/tokenization_tests.js',
'../tests/src/integration_tests.js'
];
// load the tests in series
(function next(){
if (tests.length) {
require(tests.splice(0, 1), next);
} else {
console.log('all tests loaded');
QUnit.start();
}
}());
// load the tests in series
(function next() {
if (tests.length) {
require(tests.splice(0, 1), next);
}
else {
console.log('all tests loaded');
QUnit.start();
}
}());
});
});
</script>
</body>
</html>

View file

@ -36,16 +36,19 @@ define([
mappings.clear();
mappings.loadMappings(mapping);
var test_api = new api.Api();
var test_api = new api.Api('test', kb._test.globalUrlComponentFactories);
if (kb_schemes) {
if (kb_schemes.globals)
if (kb_schemes.globals) {
$.each(kb_schemes.globals, function (parent, rules) {
test_api.addGlobalAutocompleteRules(parent, rules);
});
if (kb_schemes.endpoints)
}
if (kb_schemes.endpoints) {
$.each(kb_schemes.endpoints, function (endpoint, scheme) {
_.defaults(scheme, { methods: null }); // disable method testing unless specified in test
test_api.addEndpointDescription(endpoint, scheme);
});
}
}
kb.setActiveApi(test_api);
@ -56,74 +59,80 @@ define([
setTimeout(function () {
input.completer = {
base: {},
changeListener : function () {}
changeListener: function () {
}
}; // mimic auto complete
input.autocomplete._test.getCompletions(input, input.getSession(), test.cursor, "",
function (err, terms) {
if (test.no_context) {
ok(!terms || terms.length === 0, "Expected no context bug got terms.");
}
else {
ok(terms && terms.length > 0, "failed to extract terms ...");
}
if (!terms || terms.length === 0) {
start();
return;
}
if (test["autoCompleteSet"]) {
var expected_terms = _.map(test["autoCompleteSet"], function (t) {
if (typeof t !== "object") {
t = { "name": t };
}
return t;
});
if (terms.length != expected_terms.length) {
equal(_.pluck(terms, 'name'), _.pluck(expected_terms, 'name'), "list of completion terms is not of equal size");
} else {
var filtered_actual_terms = _.map(terms, function (actual_term,i) {
var expected_term = expected_terms[i];
var filtered_term = {};
_.each(expected_term, function (v,p) { filtered_term[p] = actual_term[p]; });
return filtered_term;
});
deepEqual(filtered_actual_terms, expected_terms);
if (test.no_context) {
ok(!terms || terms.length === 0, "Expected no context bug got terms.");
}
else {
ok(terms && terms.length > 0, "failed to extract terms ...");
}
}
var context = terms[0].context;
input.autocomplete._test.addReplacementInfoToContext(context, test.cursor, terms[0].value);
if (!terms || terms.length === 0) {
start();
return;
}
function ac(prop, prop_test) {
if (typeof test[prop] != "undefined")
if (prop_test)
prop_test(context[prop], test[prop], prop);
else
deepEqual(context[prop], test[prop], 'context.' + prop + ' should equal ' + JSON.stringify(test[prop]));
}
function pos_compare(actual, expected, name) {
equal(actual.row, expected.row + rowOffset, "row of " + name + " position is not as expected");
equal(actual.column, expected.column, "column of " + name + " position is not as expected");
}
if (test["autoCompleteSet"]) {
var expected_terms = _.map(test["autoCompleteSet"], function (t) {
if (typeof t !== "object") {
t = { "name": t };
}
return t;
});
if (terms.length != expected_terms.length) {
equal(_.pluck(terms, 'name'), _.pluck(expected_terms, 'name'), "list of completion terms is not of equal size");
}
else {
var filtered_actual_terms = _.map(terms, function (actual_term, i) {
var expected_term = expected_terms[i];
var filtered_term = {};
_.each(expected_term, function (v, p) {
filtered_term[p] = actual_term[p];
});
return filtered_term;
});
deepEqual(filtered_actual_terms, expected_terms);
}
}
function range_compare(actual, expected, name) {
pos_compare(actual.start, expected.start, name + ".start");
pos_compare(actual.end, expected.end, name + ".end");
}
var context = terms[0].context;
input.autocomplete._test.addReplacementInfoToContext(context, test.cursor, terms[0].value);
ac("prefixToAdd");
ac("suffixToAdd");
ac("addTemplate");
ac("textBoxPosition", pos_compare);
ac("rangeToReplace", range_compare);
function ac(prop, prop_test) {
if (typeof test[prop] != "undefined") {
if (prop_test) {
prop_test(context[prop], test[prop], prop);
}
else {
deepEqual(context[prop], test[prop], 'context.' + prop + ' should equal ' + JSON.stringify(test[prop]));
}
}
}
start();
});
function pos_compare(actual, expected, name) {
equal(actual.row, expected.row + rowOffset, "row of " + name + " position is not as expected");
equal(actual.column, expected.column, "column of " + name + " position is not as expected");
}
function range_compare(actual, expected, name) {
pos_compare(actual.start, expected.start, name + ".start");
pos_compare(actual.end, expected.end, name + ".end");
}
ac("prefixToAdd");
ac("suffixToAdd");
ac("addTemplate");
ac("textBoxPosition", pos_compare);
ac("rangeToReplace", range_compare);
start();
});
});
});
@ -131,7 +140,9 @@ define([
}
function context_tests(data, mapping, kb_schemes, request_line, tests) {
if (data != null && typeof data != "string") data = JSON.stringify(data, null, 3);
if (data != null && typeof data != "string") {
data = JSON.stringify(data, null, 3);
}
for (var t = 0; t < tests.length; t++) {
process_context_test(data, mapping, kb_schemes, request_line, tests[t]);
}
@ -140,6 +151,11 @@ define([
var SEARCH_KB = {
endpoints: {
_search: {
patterns: [
"{indices}/{types}/_search",
"{indices}/_search",
"_search"
],
data_autocomplete_rules: {
query: { match_all: {}, term: { "$FIELD$": ""}},
size: {},
@ -314,6 +330,9 @@ define([
{
endpoints: {
_test: {
patterns: [
"_test"
],
data_autocomplete_rules: {
object: { bla: 1 },
array: [ 1 ],
@ -381,7 +400,9 @@ define([
prefixToAdd: "",
suffixToAdd: "",
rangeToReplace: { start: { row: 5, column: 15 }, end: { row: 5, column: 15 }},
autoCompleteSet: [{ name: "terms", meta: "API" }]
autoCompleteSet: [
{ name: "terms", meta: "API" }
]
}
]
);
@ -394,6 +415,9 @@ define([
{
endpoints: {
_test: {
patterns: [
"_test"
],
data_autocomplete_rules: {
index: "$INDEX$"
}
@ -405,7 +429,10 @@ define([
{
name: "$INDEX$ matching",
cursor: { row: 1, column: 15},
autoCompleteSet: [{ name: "index1", meta: "index"}, { name: "index2", meta: "index"}]
autoCompleteSet: [
{ name: "index1", meta: "index"},
{ name: "index2", meta: "index"}
]
}
]
);
@ -425,6 +452,9 @@ define([
{
endpoints: {
_endpoint: {
patterns: [
"_endpoint"
],
data_autocomplete_rules: {
array: [ "a", "b"],
number: 1,
@ -441,7 +471,7 @@ define([
name: "Templates 1",
cursor: { row: 1, column: 0},
autoCompleteSet: [
tt("array", [ "a" ]), tt("fixed", { a: 1 }), tt("number",1), tt("object", {}), tt("oneof", "o1")
tt("array", [ "a" ]), tt("fixed", { a: 1 }), tt("number", 1), tt("object", {}), tt("oneof", "o1")
]
},
{
@ -468,6 +498,9 @@ define([
{
endpoints: {
_endpoint: {
patterns: [
"_endpoint"
],
data_autocomplete_rules: {
any_of_numbers: { __template: [1, 2], __any_of: [1, 2, 3]},
any_of_obj: { __template: [
@ -486,9 +519,11 @@ define([
name: "Any of - templates",
cursor: { row: 1, column: 0},
autoCompleteSet: [
tt("any_of_numbers", [ 1, 2 ]),
tt("any_of_obj", [ { c: 1} ])
]
tt("any_of_numbers", [ 1, 2 ]),
tt("any_of_obj", [
{ c: 1}
])
]
},
{
name: "Any of - numbers",
@ -499,7 +534,7 @@ define([
name: "Any of - object",
cursor: { row: 6, column: 2},
autoCompleteSet: [
tt("a", 1),tt("b", 2 )
tt("a", 1), tt("b", 2)
]
}
]
@ -511,6 +546,7 @@ define([
{
endpoints: {
_endpoint: {
patterns: [ "_endpoint" ],
data_autocomplete_rules: {
"query": ""
}
@ -522,7 +558,7 @@ define([
{
name: "Empty string as default",
cursor: { row: 0, column: 1},
autoCompleteSet: [tt("query","")]
autoCompleteSet: [tt("query", "")]
}
]
);
@ -548,8 +584,8 @@ define([
},
endpoints: {
_current: {
_id: "POST _current",
patterns: [ "_current" ],
id: "POST _current",
data_autocomplete_rules: {
"a": {
"b": {
@ -573,6 +609,7 @@ define([
}
},
ext: {
patterns: [ "ext" ],
data_autocomplete_rules: {
target: {
t2: 1
@ -586,8 +623,10 @@ define([
{
name: "Relative scope link test",
cursor: { row: 2, column: 12},
autoCompleteSet:[
tt("b", {}), tt("c", {}), tt("d", {}), tt("e", {}), tt("f", [ {} ])
autoCompleteSet: [
tt("b", {}), tt("c", {}), tt("d", {}), tt("e", {}), tt("f", [
{}
])
]
},
{
@ -621,6 +660,7 @@ define([
{
endpoints: {
_endpoint: {
patterns: ["_endpoint"],
data_autocomplete_rules: {
"a": {},
"b": {}
@ -666,6 +706,7 @@ define([
{
endpoints: {
_endpoint: {
patterns: ["_endpoint"],
data_autocomplete_rules: {
"a": [
{ b: 1}
@ -726,10 +767,10 @@ define([
context_tests(
"",
"POST _search",
MAPPING,
SEARCH_KB,
"POST _search",
null,
[
{
name: "initial doc start",
@ -772,15 +813,18 @@ define([
var CLUSTER_KB = {
endpoints: {
"_search": {
patterns: ["_search", "{indices}/{types}/_search", "{indices}/_search"],
data_autocomplete_rules: {
}
},
"_cluster/stats": {
patterns: ["_cluster/stats"],
indices_mode: "none",
data_autocomplete_rules: {
}
},
"_cluster/nodes/stats": {
patterns: ["_cluster/nodes/stats"],
data_autocomplete_rules: {
}
}
@ -796,7 +840,7 @@ define([
{
name: "Endpoints with slashes - no slash",
cursor: { row: 0, column: 8},
autoCompleteSet: [ "_cluster/nodes/stats", "_cluster/stats", "_search"],
autoCompleteSet: [ "_cluster/nodes/stats", "_cluster/stats", "_search", "index1", "index2"],
prefixToAdd: "",
suffixToAdd: ""
}
@ -812,14 +856,21 @@ define([
{
name: "Endpoints with slashes - before slash",
cursor: { row: 0, column: 8},
autoCompleteSet: [ "_cluster/nodes/stats", "_cluster/stats", "_search"],
autoCompleteSet: [ "_cluster/nodes/stats", "_cluster/stats", "_search", "index1", "index2"],
prefixToAdd: "",
suffixToAdd: ""
},
{
name: "Endpoints with slashes - on slash",
cursor: { row: 0, column: 13},
autoCompleteSet: [ "_cluster/nodes/stats", "_cluster/stats", "_search"],
autoCompleteSet: [ "_cluster/nodes/stats", "_cluster/stats", "_search", "index1", "index2"],
prefixToAdd: "",
suffixToAdd: ""
},
{
name: "Endpoints with slashes - after slash",
cursor: { row: 0, column: 14},
autoCompleteSet: [ "nodes/stats", "stats"],
prefixToAdd: "",
suffixToAdd: ""
}
@ -835,7 +886,10 @@ define([
{
name: "Endpoints with slashes - after slash",
cursor: { row: 0, column: 15},
autoCompleteSet: [ { name: "nodes/stats", meta: "endpoint" } , { name: "stats", meta: "endpoint" } ],
autoCompleteSet: [
{ name: "nodes/stats", meta: "endpoint" } ,
{ name: "stats", meta: "endpoint" }
],
prefixToAdd: "",
suffixToAdd: "",
initialValue: "no"
@ -860,6 +914,29 @@ define([
]
);
context_tests(
null,
MAPPING,
CLUSTER_KB,
"POST ",
[
{
name: "Immediately after space + method",
cursor: { row: 0, column: 5},
autoCompleteSet: [
{ name: "_cluster/nodes/stats", meta: "endpoint" },
{ name: "_cluster/stats", meta: "endpoint" },
{ name: "_search", meta: "endpoint" },
{ name: "index1", meta: "index" },
{ name: "index2", meta: "index" }
],
prefixToAdd: "",
suffixToAdd: "",
initialValue: ""
}
]
);
context_tests(
null,
MAPPING,

View file

@ -1,8 +1,9 @@
define([
'kb',
'mappings',
'kb/api'
], function (kb, mappings, api) {
'kb/api',
'autocomplete/url_path_autocomplete'
], function (kb, mappings, api, url_path_autocomplete) {
'use strict';
module("Knowledge base", {
@ -16,102 +17,160 @@ define([
}
});
var MAPPING = {
"index1": {
"type1.1": {
"properties": {
"field1.1.1": { "type": "string" },
"field1.1.2": { "type": "string" }
}
},
"type1.2": {
"properties": {
}
}
},
"index2": {
"type2.1": {
"properties": {
"field2.1.1": { "type": "string" },
"field2.1.2": { "type": "string" }
}
}
}
};
test("Index mode filters", function () {
var test_api = new api.Api();
test_api.addEndpointDescription("_multi_indices", {
indices_mode: "multi"
});
test_api.addEndpointDescription("_one_or_more_indices", {
indices_mode: "required_multi"
});
test_api.addEndpointDescription("_single_index", {
match: "_single_index",
endpoint_autocomplete: [
"_single_index"
],
indices_mode: "single"
});
test_api.addEndpointDescription("_no_index", {
indices_mode: "none"
});
kb.setActiveApi(test_api);
function testContext(tokenPath, otherTokenValues, expectedContext) {
deepEqual(kb.getEndpointAutocomplete([], [], null).sort(), ["_multi_indices", "_no_index" ]);
deepEqual(kb.getEndpointAutocomplete(["index"], [], null).sort(), ["_multi_indices", "_one_or_more_indices", "_single_index"]);
deepEqual(kb.getEndpointAutocomplete(["index1", "index2"], [], null).sort(), ["_multi_indices", "_one_or_more_indices"]);
deepEqual(kb.getEndpointAutocomplete(["index1", "index2"], ["type"], null).sort(), ["_multi_indices", "_one_or_more_indices"]);
});
if (expectedContext.autoCompleteSet) {
expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function (t) {
if (_.isString(t)) {
t = { name: t}
}
return t;
})
}
test("Type mode filters", function () {
var test_api = new api.Api();
var context = { otherTokenValues: otherTokenValues};
url_path_autocomplete.populateContext(tokenPath, context, null,
expectedContext.autoCompleteSet, kb.getTopLevelUrlCompleteComponents()
);
test_api.addEndpointDescription("_multi_types", {
indices_mode: "single",
types_mode: "multi"
// override context to just check on id
if (context.endpoint) {
context.endpoint = context.endpoint.id;
}
delete context.otherTokenValues;
function norm(t) {
if (_.isString(t)) {
return { name: t };
}
return t;
}
if (context.autoCompleteSet) {
context.autoCompleteSet = _.sortBy(_.map(context.autoCompleteSet, norm), 'name');
}
if (expectedContext.autoCompleteSet) {
expectedContext.autoCompleteSet = _.sortBy(_.map(expectedContext.autoCompleteSet, norm), 'name');
}
deepEqual(context, expectedContext);
}
function t(term) {
return { name: term, meta: "type"};
}
function i(term) {
return { name: term, meta: "index"};
}
function index_test(name, tokenPath, otherTokenValues, expectedContext) {
test(name, function () {
var test_api = new api.Api("text", kb._test.globalUrlComponentFactories);
test_api.addEndpointDescription("_multi_indices", {
patterns: ["{indices}/_multi_indices"]
});
test_api.addEndpointDescription("_single_index", {
patterns: ["{index}/_single_index"]
});
test_api.addEndpointDescription("_no_index", {
// testing default patterns
// patterns: ["_no_index"]
});
kb.setActiveApi(test_api);
mappings.loadMappings(MAPPING);
testContext(tokenPath, otherTokenValues, expectedContext);
});
test_api.addEndpointDescription("_single_type", {
endpoint_autocomplete: [
"_single_type"
],
indices_mode: "single",
types_mode: "single"
});
test_api.addEndpointDescription("_no_types", {
indices_mode: "single",
types_mode: "none"
}
index_test("Index integration 1", [], [],
{ autoCompleteSet: ["_no_index", i("index1"), i("index2")]}
);
index_test("Index integration 2", [], ["index1"],
// still return _no_index as index1 is not committed to yet.
{ autoCompleteSet: ["_no_index", i("index2")]}
);
index_test("Index integration 2", ["index1"], [],
{ indices: ["index1"], autoCompleteSet: ["_multi_indices", "_single_index"]}
);
index_test("Index integration 2", [
["index1", "index2"]
], [],
{ indices: ["index1", "index2"], autoCompleteSet: ["_multi_indices"]}
);
function type_test(name, tokenPath, otherTokenValues, expectedContext) {
test(name, function () {
var test_api = new api.Api("type_test", kb._test.globalUrlComponentFactories);
test_api.addEndpointDescription("_multi_types", {
patterns: ["{indices}/{types}/_multi_types"]
});
test_api.addEndpointDescription("_single_type", {
patterns: ["{indices}/{type}/_single_type"]
});
test_api.addEndpointDescription("_no_types", {
patterns: ["{indices}/_no_types"]
});
kb.setActiveApi(test_api);
mappings.loadMappings(MAPPING);
testContext(tokenPath, otherTokenValues, expectedContext);
});
kb.setActiveApi(test_api);
}
type_test("Type integration 1", ["index1"], [],
{ indices: ["index1"], autoCompleteSet: ["_no_types", t("type1.1"), t("type1.2")]}
);
type_test("Type integration 2", ["index1"], ["type1.2"],
// we are not yet comitted to type1.2, so _no_types is returned
{ indices: ["index1"], autoCompleteSet: ["_no_types", t("type1.1")]}
);
deepEqual(kb.getEndpointAutocomplete(["index"], [], null).sort(), ["_multi_types", "_no_types" ]);
deepEqual(kb.getEndpointAutocomplete(["index"], ["type"], null).sort(), ["_multi_types", "_single_type"]);
deepEqual(kb.getEndpointAutocomplete(["index"], ["type", "type1"], null).sort(), ["_multi_types"]);
});
type_test("Type integration 3", ["index2"], [],
{ indices: ["index2"], autoCompleteSet: ["_no_types", t("type2.1")]}
);
test("Id mode filters", function () {
var test_api = new api.Api();
type_test("Type integration 4", ["index1", "type1.2"], [],
{ indices: ["index1"], types: ["type1.2"], autoCompleteSet: ["_multi_types", "_single_type"]}
);
test_api.addEndpointDescription("_single_id", {
indices_mode: "single",
types_mode: "single",
doc_id_mode: "required_single"
});
test_api.addEndpointDescription("_no_id", {
indices_mode: "single",
types_mode: "single",
doc_id_mode: "none"
type_test("Type integration 5", [
["index1", "index2"],
["type1.2", "type1.1"]
], [],
{ indices: ["index1", "index2"], types: ["type1.2", "type1.1"], autoCompleteSet: ["_multi_types"]}
);
});
kb.setActiveApi(test_api);
deepEqual(kb.getEndpointAutocomplete(["index"], ["type"], null).sort(), ["_no_id"].sort());
deepEqual(kb.getEndpointAutocomplete(["index"], ["type"], "123").sort(), ["_single_id"].sort());
});
test("Get active scheme by doc id", function () {
var test_api = new api.Api();
test_api.addEndpointDescription("_single_id", {
match: ".*",
indices_mode: "single",
types_mode: "single",
doc_id_mode: "required_single"
});
test_api.addEndpointDescription("_no_id", {
match: ".*",
indices_mode: "single",
types_mode: "single",
doc_id_mode: "none"
});
kb.setActiveApi(test_api);
deepEqual(kb.getEndpointDescriptionByPath("bla", ["index"], ["type"], null).doc_id_mode, "none");
deepEqual(kb.getEndpointDescriptionByPath("bla", ["index"], ["type"], "123").doc_id_mode, "required_single");
});
});

View file

@ -22,7 +22,9 @@ define([
var iter = new token_iterator.TokenIterator(input.getSession(), 0, 0);
var ret = [];
var t = iter.getCurrentToken();
if (input.parser.isEmptyToken(t)) t = input.parser.nextNonEmptyToken(iter);
if (input.parser.isEmptyToken(t)) {
t = input.parser.nextNonEmptyToken(iter);
}
while (t) {
ret.push({ value: t.value, type: t.type });
t = input.parser.nextNonEmptyToken(iter);
@ -34,10 +36,15 @@ define([
var testCount = 0;
function token_test(token_list, prefix, data) {
if (data && typeof data != "string") data = JSON.stringify(data, null, 3);
if (data && typeof data != "string") {
data = JSON.stringify(data, null, 3);
}
if (data) {
if (prefix) data = prefix + "\n" + data;
} else {
if (prefix) {
data = prefix + "\n" + data;
}
}
else {
data = prefix;
}
@ -57,54 +64,54 @@ define([
}
token_test(
[ "method", "GET", "url.endpoint", "_search" ],
[ "method", "GET", "url.part", "_search" ],
"GET _search"
);
token_test(
[ "method", "GET", "url.slash", "/", "url.endpoint", "_search" ],
[ "method", "GET", "url.slash", "/", "url.part", "_search" ],
"GET /_search"
);
token_test(
[ "method", "GET", "url.endpoint", "_cluster", "url.slash", "/", "url.part" , "nodes" ],
[ "method", "GET", "url.part", "_cluster", "url.slash", "/", "url.part" , "nodes" ],
"GET _cluster/nodes"
);
token_test(
[ "method", "GET", "url.slash", "/", "url.endpoint", "_cluster", "url.slash", "/", "url.part" , "nodes" ],
[ "method", "GET", "url.slash", "/", "url.part", "_cluster", "url.slash", "/", "url.part" , "nodes" ],
"GET /_cluster/nodes"
);
token_test(
[ "method", "GET", "url.index", "index", "url.slash", "/", "url.endpoint", "_search" ],
[ "method", "GET", "url.part", "index", "url.slash", "/", "url.part", "_search" ],
"GET index/_search"
);
token_test(
[ "method", "GET", "url.index", "index" ],
[ "method", "GET", "url.part", "index" ],
"GET index"
);
token_test(
[ "method", "GET", "url.index", "index", "url.slash", "/", "url.type", "type" ],
[ "method", "GET", "url.part", "index", "url.slash", "/", "url.part", "type" ],
"GET index/type"
);
token_test(
[ "method", "GET", "url.slash", "/", "url.index", "index", "url.slash", "/", "url.type", "type", "url.slash", "/" ],
[ "method", "GET", "url.slash", "/", "url.part", "index", "url.slash", "/", "url.part", "type", "url.slash", "/" ],
"GET /index/type/"
);
token_test(
[ "method", "GET", "url.index", "index", "url.slash", "/", "url.type", "type", "url.slash", "/", "url.endpoint", "_search" ],
[ "method", "GET", "url.part", "index", "url.slash", "/", "url.part", "type", "url.slash", "/", "url.part", "_search" ],
"GET index/type/_search"
);
token_test(
[ "method", "GET", "url.index", "index", "url.slash", "/", "url.type", "type", "url.slash", "/", "url.endpoint", "_search",
[ "method", "GET", "url.part", "index", "url.slash", "/", "url.part", "type", "url.slash", "/", "url.part", "_search",
"url.questionmark", "?", "url.param", "value", "url.equal", "=", "url.value", "1"
],
"GET index/type/_search?value=1"
@ -112,76 +119,76 @@ define([
token_test(
[ "method", "GET", "url.index", "index", "url.slash", "/", "url.type", "type", "url.slash", "/", "url.id", "1" ],
[ "method", "GET", "url.part", "index", "url.slash", "/", "url.part", "type", "url.slash", "/", "url.part", "1" ],
"GET index/type/1"
);
token_test(
[ "method", "GET", "url.slash", "/", "url.index", "index1", "url.comma", ",", "url.index", "index2", "url.slash", "/" ],
[ "method", "GET", "url.slash", "/", "url.part", "index1", "url.comma", ",", "url.part", "index2", "url.slash", "/" ],
"GET /index1,index2/"
);
token_test(
[ "method", "GET", "url.slash", "/", "url.index", "index1", "url.comma", ",", "url.index", "index2", "url.slash", "/",
"url.endpoint", "_search"],
[ "method", "GET", "url.slash", "/", "url.part", "index1", "url.comma", ",", "url.part", "index2", "url.slash", "/",
"url.part", "_search"],
"GET /index1,index2/_search"
);
token_test(
[ "method", "GET", "url.index", "index1", "url.comma", ",", "url.index", "index2", "url.slash", "/",
"url.endpoint", "_search"],
[ "method", "GET", "url.part", "index1", "url.comma", ",", "url.part", "index2", "url.slash", "/",
"url.part", "_search"],
"GET index1,index2/_search"
);
token_test(
[ "method", "GET", "url.slash", "/", "url.index", "index1", "url.comma", ",", "url.index", "index2" ],
[ "method", "GET", "url.slash", "/", "url.part", "index1", "url.comma", ",", "url.part", "index2" ],
"GET /index1,index2"
);
token_test(
[ "method", "GET", "url.index", "index1", "url.comma", ",", "url.index", "index2" ],
[ "method", "GET", "url.part", "index1", "url.comma", ",", "url.part", "index2" ],
"GET index1,index2"
);
token_test(
[ "method", "GET", "url.slash", "/", "url.index", "index1", "url.comma", "," ],
[ "method", "GET", "url.slash", "/", "url.part", "index1", "url.comma", "," ],
"GET /index1,"
);
token_test(
[ "method", "PUT", "url.slash", "/", "url.index", "index", "url.slash", "/" ],
[ "method", "PUT", "url.slash", "/", "url.part", "index", "url.slash", "/" ],
"PUT /index/"
);
token_test(
[ "method", "PUT", "url.slash", "/", "url.index", "index" ],
[ "method", "PUT", "url.slash", "/", "url.part", "index" ],
"PUT /index"
);
token_test(
[ "method", "PUT", "url.slash", "/", "url.index", "index1", "url.comma", ",", "url.index", "index2",
"url.slash", "/", "url.type", "type1", "url.comma", ",", "url.type", "type2"],
[ "method", "PUT", "url.slash", "/", "url.part", "index1", "url.comma", ",", "url.part", "index2",
"url.slash", "/", "url.part", "type1", "url.comma", ",", "url.part", "type2"],
"PUT /index1,index2/type1,type2"
);
token_test(
[ "method", "PUT", "url.slash", "/", "url.index", "index1",
"url.slash", "/", "url.type", "type1", "url.comma", ",", "url.type", "type2", "url.comma", ","],
[ "method", "PUT", "url.slash", "/", "url.part", "index1",
"url.slash", "/", "url.part", "type1", "url.comma", ",", "url.part", "type2", "url.comma", ","],
"PUT /index1/type1,type2,"
);
token_test(
[ "method", "PUT", "url.index", "index1", "url.comma", ",", "url.index", "index2",
"url.slash", "/", "url.type", "type1", "url.comma", ",", "url.type", "type2", "url.slash", "/",
"url.id", "1234"],
[ "method", "PUT", "url.part", "index1", "url.comma", ",", "url.part", "index2",
"url.slash", "/", "url.part", "type1", "url.comma", ",", "url.part", "type2", "url.slash", "/",
"url.part", "1234"],
"PUT index1,index2/type1,type2/1234"
);
token_test(
[ "method", "POST", "url.endpoint", "_search", "paren.lparen", "{", "variable", '"q"', "punctuation.colon", ":",
[ "method", "POST", "url.part", "_search", "paren.lparen", "{", "variable", '"q"', "punctuation.colon", ":",
"paren.lparen", "{", "paren.rparen", "}", "paren.rparen", "}"
],
'POST _search\n' +
@ -192,8 +199,8 @@ define([
);
token_test(
[ "method", "POST", "url.endpoint", "_search", "paren.lparen", "{", "variable", '"q"', "punctuation.colon", ":",
"paren.lparen", "{", "variable", '"s"', "punctuation.colon", ":", "paren.lparen", "{", "paren.rparen", "}",
[ "method", "POST", "url.part", "_search", "paren.lparen", "{", "variable", '"q"', "punctuation.colon", ":",
"paren.lparen", "{", "variable", '"s"', "punctuation.colon", ":", "paren.lparen", "{", "paren.rparen", "}",
"paren.rparen", "}", "paren.rparen", "}"
],
'POST _search\n' +
@ -214,10 +221,15 @@ define([
function states_test(states_list, prefix, data) {
if (data && typeof data != "string") data = JSON.stringify(data, null, 3);
if (data && typeof data != "string") {
data = JSON.stringify(data, null, 3);
}
if (data) {
if (prefix) data = prefix + "\n" + data;
} else {
if (prefix) {
data = prefix + "\n" + data;
}
}
else {
data = prefix;
}

View file

@ -0,0 +1,443 @@
define([
'_',
'kb/url_pattern_matcher',
'autocomplete/url_path_autocomplete'
], function (_, url_pattern_matcher, url_path_autocomplete) {
'use strict';
module("Url autocomplete");
function patterns_test(name, endpoints, tokenPath, expectedContext, globalUrlComponentFactories) {
test(name, function () {
var patternMatcher = new url_pattern_matcher.UrlPatternMatcher(globalUrlComponentFactories);
_.each(endpoints, function (e, id) {
e.id = id;
_.each(e.patterns, function (p) {
patternMatcher.addEndpoint(p, e);
});
});
if (typeof tokenPath === "string") {
if (tokenPath[tokenPath.length - 1] == "$") {
tokenPath = tokenPath.substr(0, tokenPath.length - 1) + "/" + url_pattern_matcher.URL_PATH_END_MARKER;
}
tokenPath = _.map(tokenPath.split("/"), function (p) {
p = p.split(",");
if (p.length === 1) {
return p[0];
}
return p;
});
}
if (expectedContext.autoCompleteSet) {
expectedContext.autoCompleteSet = _.map(expectedContext.autoCompleteSet, function (t) {
if (_.isString(t)) {
t = { name: t}
}
return t;
});
expectedContext.autoCompleteSet = _.sortBy(expectedContext.autoCompleteSet, 'name');
}
var context = {};
if (expectedContext.method) {
context.method = expectedContext.method;
}
url_path_autocomplete.populateContext(tokenPath, context, null,
expectedContext.autoCompleteSet, patternMatcher.getTopLevelComponents()
);
// override context to just check on id
if (context.endpoint) {
context.endpoint = context.endpoint.id;
}
if (context.autoCompleteSet) {
context.autoCompleteSet = _.sortBy(context.autoCompleteSet, 'name');
}
deepEqual(context, expectedContext);
});
}
(function () {
var endpoints = {
"1": {
patterns: [
"a/b"
]
}
};
patterns_test("simple single path - completion",
endpoints,
"a/b$",
{ endpoint: "1"}
);
patterns_test("simple single path - completion, with auto complete",
endpoints,
"a/b",
{ autoCompleteSet: [] }
);
patterns_test("simple single path - partial, without auto complete",
endpoints,
"a",
{ }
);
patterns_test("simple single path - partial, with auto complete",
endpoints,
"a",
{ autoCompleteSet: ["b"] }
);
patterns_test("simple single path - partial, with auto complete",
endpoints,
[],
{ autoCompleteSet: ["a/b"] }
);
patterns_test("simple single path - different path",
endpoints,
"a/c",
{ }
);
})();
(function () {
var endpoints = {
"1": {
patterns: [
"a/b",
"a/b/{p}"
]
},
"2": {
patterns: [
"a/c"
]
}
};
patterns_test("shared path - completion 1",
endpoints,
"a/b$",
{ endpoint: "1"}
);
patterns_test("shared path - completion 2",
endpoints,
"a/c$",
{ endpoint: "2"}
);
patterns_test("shared path - completion 1 with param",
endpoints,
"a/b/v$",
{ endpoint: "1", p: "v"}
);
patterns_test("shared path - partial, with auto complete",
endpoints,
"a",
{ autoCompleteSet: ["b", "c"] }
);
patterns_test("shared path - partial, with auto complete of param, no options",
endpoints,
"a/b",
{ autoCompleteSet: [] }
);
patterns_test("shared path - partial, without auto complete",
endpoints,
"a",
{ }
);
patterns_test("shared path - different path - with auto complete",
endpoints,
"a/e",
{ autoCompleteSet: []}
);
patterns_test("shared path - different path - without auto complete",
endpoints,
"a/e",
{ }
);
})();
(function () {
var endpoints = {
"1": {
patterns: [
"a/{p}",
],
url_components: {
p: ["a", "b"]
}
},
"2": {
patterns: [
"a/c"
]
}
};
patterns_test("option testing - completion 1",
endpoints,
"a/a$",
{ endpoint: "1", p: ["a"]}
);
patterns_test("option testing - completion 2",
endpoints,
"a/b$",
{ endpoint: "1", p: ["b"]}
);
patterns_test("option testing - completion 3",
endpoints,
"a/b,a$",
{ endpoint: "1", p: ["b", "a"]}
);
patterns_test("option testing - completion 4",
endpoints,
"a/c$",
{ endpoint: "2"}
);
patterns_test("option testing - completion 5",
endpoints,
"a/d$",
{ }
);
patterns_test("option testing - partial, with auto complete",
endpoints,
"a",
{ autoCompleteSet: ["a", "b", "c"] }
);
patterns_test("option testing - partial, without auto complete",
endpoints,
"a",
{ }
);
patterns_test("option testing - different path - with auto complete",
endpoints,
"a/e",
{ autoCompleteSet: []}
);
})();
(function () {
var endpoints = {
"1": {
patterns: [
"a/{p}",
],
url_components: {
p: ["a", "b"]
}
},
"2": {
patterns: [
"b/{p}",
]
}
};
var globalFactories = {
"p": function (name, parent) {
return new url_pattern_matcher.ListComponent(name, ["g1", "g2"], parent);
}
};
patterns_test("global parameters testing - completion 1",
endpoints,
"a/a$",
{ endpoint: "1", p: ["a"]},
globalFactories
);
patterns_test("global parameters testing - completion 2",
endpoints,
"b/g1$",
{ endpoint: "2", p: ["g1"]},
globalFactories
);
patterns_test("global parameters testing - partial, with auto complete",
endpoints,
"a",
{ autoCompleteSet: ["a", "b"] },
globalFactories
);
patterns_test("global parameters testing - partial, with auto complete 2",
endpoints,
"b",
{ autoCompleteSet: ["g1", "g2"] },
globalFactories
);
})();
(function () {
var endpoints = {
"1": {
patterns: [
"a/b/{p}/c/e"
]
}
};
patterns_test("look ahead - autocomplete before param 1",
endpoints,
"a",
{ autoCompleteSet: [ "b" ]}
);
patterns_test("look ahead - autocomplete before param 2",
endpoints,
[],
{ autoCompleteSet: [ "a/b" ]}
);
patterns_test("look ahead - autocomplete after param 1",
endpoints,
"a/b/v",
{ autoCompleteSet: [ "c/e" ], "p": "v"}
);
patterns_test("look ahead - autocomplete after param 2",
endpoints,
"a/b/v/c",
{ autoCompleteSet: [ "e" ], "p": "v"}
);
})();
(function () {
var endpoints = {
"1_param": {
patterns: [
"a/{p}"
],
methods: ["GET"]
},
"2_explicit": {
patterns: [
"a/b"
],
methods: ["GET"]
}};
var e = _.cloneDeep(endpoints);
e["1_param"].priority = 1;
patterns_test("Competing endpoints - priority 1",
e,
"a/b$",
{ method: "GET", endpoint: "1_param", "p": "b"}
);
e = _.cloneDeep(endpoints);
e["1_param"].priority = 1;
e["2_explicit"].priority = 0;
patterns_test("Competing endpoints - priority 2",
e,
"a/b$",
{ method: "GET", endpoint: "2_explicit"}
);
e = _.cloneDeep(endpoints);
e["2_explicit"].priority = 0;
patterns_test("Competing endpoints - priority 3",
e,
"a/b$",
{ method: "GET", endpoint: "2_explicit"}
);
})();
(function () {
var endpoints = {
"1_GET": {
patterns: [
"a"
],
methods: ["GET"]
},
"1_PUT": {
patterns: [
"a"
],
methods: ["PUT"]
},
"2_GET": {
patterns: [
"a/b"
],
methods: ["GET"]
},
"2_DELETE": {
patterns: [
"a/b"
],
methods: ["DELETE"]
}
};
patterns_test("Competing endpoint - sub url of another - auto complete",
endpoints,
"a",
{ method: "GET", autoCompleteSet: [ "b" ]}
);
patterns_test("Competing endpoint - sub url of another, complete 1",
endpoints,
"a$",
{ method: "GET", endpoint: "1_GET"}
);
patterns_test("Competing endpoint - sub url of another, complete 2",
endpoints,
"a$",
{ method: "PUT", endpoint: "1_PUT"}
);
patterns_test("Competing endpoint - sub url of another, complete 3",
endpoints,
"a$",
{ method: "DELETE" }
);
patterns_test("Competing endpoint - extention of another, complete 1, auto complete",
endpoints,
"a/b$",
{ method: "PUT", autoCompleteSet: []}
);
patterns_test("Competing endpoint - extention of another, complete 1",
endpoints,
"a/b$",
{ method: "GET", endpoint: "2_GET"}
);
patterns_test("Competing endpoint - extention of another, complete 1",
endpoints,
"a/b$",
{ method: "DELETE", endpoint: "2_DELETE"}
);
patterns_test("Competing endpoint - extention of another, complete 1",
endpoints,
"a/b$",
{ method: "PUT"}
);
})();
});