Commit 769d7b0f authored by Caleb Weeks's avatar Caleb Weeks

wip

parent 50dd245b
......@@ -14,6 +14,11 @@ var debug=dbg("SLING:debug");
var warn=dbg("SLING:warn")
var errorLog=dbg("SLING:error")
const selectors = {
getIfExists: "get-if-exists",
deref: "deref"
}
// require('request-debug')(request, function(type, data, r) {
// // put your request or response handling logic here
......@@ -61,6 +66,7 @@ var SlingConnector = module.exports = function(options){
this.queryType = options.queryType;
this.mode = options.mode;
this.hasDerefCapability = options.hasDerefCapability;
this.derefByDefault = options.derefByDefault;
this.invalidPathRegex = options.invalidPathRegex || /undefined|\[object Object\]/;
this.requestAgent = options.requestAgent || new KeepAliveAgent(options.agentKeepAliveConfig || {
maxSockets: 100,
......@@ -68,6 +74,9 @@ var SlingConnector = module.exports = function(options){
timeout: 60000, // active socket keepalive for 60 seconds
freeSocketTimeout: 30000, // free socket keepalive for 30 seconds
});
this.hasGetIfExistsCapabililty = options.hasGetIfExistsCapabililty;
this.useGetIfExistsByDefault = options.useGetIfExistsByDefault;
this.inMemoryCache = options.inMemoryCache;
if(options.logger){
this.logger = options.logger;
......@@ -123,7 +132,6 @@ var SC=SlingConnector.prototype;
* @param {SlingConnector.slingConnectorCallback} callback - to call upon completion
*/
SC.get = function(path, depth, callback, options){
this.validatePath(path);
options = options || {};
if(_.isFunction(depth)){
callback=depth;
......@@ -131,9 +139,23 @@ SC.get = function(path, depth, callback, options){
}
var sc_self = this;
if(!this.isValidPath(path)){
callback("invalid path provided");
return;
}
if(path[0]=="/") path=path.slice(1);
path = `${path}${(this.hasDerefCapability && options.deref) || (this.derefByDefault && !options.dontDeref) ?'.deref':''}${depth?("." + depth):""}.json`;
//deref selector
if(this.hasDerefCapability){
if((this.derefByDefault && !options.dontDeref) || options.deref){
path = `${path}.${selectors.deref}`;
}
}
//depth selector and JSON ext
path = `${path}${depth?("." + depth):""}.json`;
sc_self.getSling(path,options,callback);
......@@ -155,7 +177,7 @@ SC.get = function(path, depth, callback, options){
* @param {SlingConnector.slingConnectorCallback} callback - to call upon completion
*/
SC.getRaw = function(path, callback){
this.getSling(path, {dontParseBody:true} ,callback);
this.getSling(path, {dontParseBody:true, dontUseGetIfExists: true} ,callback);
};
......@@ -168,45 +190,79 @@ SC.getRaw = function(path, callback){
* @param {SlingConnector.slingConnectorCallback} callback
* @private
*/
SC.getSling = function(path, options, callback){
this.validatePath(path);
SC.getSling = function(path, options = {}, callback){
//setup initial params
var sc_self=this;
if(typeof options === "function"){
callback=options;
options={};
}
var uri=this.buildUri(path);
var fileCache=this.fileCache;
var cacheTTL = this.cacheTTL;
options = options || {};
if(typeof callback !=="function"){throw new Error("Callback function not provided to getSling()");}
//handle Promise case
if(!callback){
return new Promise((resolve, reject) => {
sc_self.getSling(path, options, (err, data) => {
if(!err){
resolve(data);
}else{
reject(new Error(err));
}
})
})
}
if(!this.isValidPath(path)){
callback("invalid path provided");
return;
}
// get if exists selector
const useGetIfExists =
this.hasGetIfExistsCapabililty && //is the capability enabled
path.match(/\.json$/) && // is this request a candidate (anything that just ends with .json)
((this.useGetIfExistsByDefault && !options.dontUseGetIfExists) || options.useGetIfExists); //and this request should use
if(useGetIfExists){
//inject as the first selector
path = path.split(".").splice(1, 0, selectors.getIfExists).join(".");
}
var preprocess = this.preprocess(path, {method:"GET", options, callback});
path=preprocess.path;
if(preprocess.proxy){
preprocess.proxy(preprocess, this, (err, bodyText, res)=>{
if(typeof bodyText==="string"){
//TODO: possibly force fileCache to off in proxy mode?
processBodyText(bodyText, {});
}else{
if(!sc_self.leaveMangledNames && !options.leaveMangledNames){
SC.mangleNamespaces(bodyText);
}
callback(err, bodyText, res);
}
});
if(this.inMemoryCache && !options.inMemoryCacheRefresh){
// if this path hasn't already been requested, stick the promise verision of it
// in the cache.
inMemoryCache[path] = inMemoryCache[path] || sc_self.getSling(path, {...options, inMemoryCacheRefresh: true});
inMemoryCache.then( data => callback(null, data), err => callback(err) );
}else{
/////////////////////////////////////////////////////////
this.checkForBackingFile(path, preprocess, function(err, fileBacked){
if(fileBacked.useImmediately){
getViaFile(fileBacked);
}else{
getViaHTTP(fileBacked);
}
});
if(preprocess.proxy){
preprocess.proxy(preprocess, this, (err, bodyText, res)=>{
if(typeof bodyText==="string"){
//TODO: possibly force fileCache to off in proxy mode?
processBodyText(bodyText, {});
}else{
if(!sc_self.leaveMangledNames && !options.leaveMangledNames){
SC.mangleNamespaces(bodyText);
}
callback(err, bodyText, res);
}
});
}else{
/////////////////////////////////////////////////////////
this.checkForBackingFile(path, preprocess, function(err, fileBacked){
if(fileBacked.useImmediately){
getViaFile(fileBacked);
}else{
getViaHTTP(fileBacked);
}
});
}
}
......@@ -221,6 +277,8 @@ SC.getSling = function(path, options, callback){
/////////////////////////////////////////////////////////
function getViaHTTP(fileBacked){
var uri=sc_self.buildUri(path);
debug("Attempting to get sling data from: " + uri)
//TODO: Handle request timeout, 404, forbidden, etc.
//TODO: allow for a LIST of pub servers, for failover? OR leave that to a load balancer?
......@@ -282,8 +340,8 @@ SC.getSling = function(path, options, callback){
bodyText = bodyText.replace(metaRegex,function(match,p1){if(p1.match(keepRegex)){return("\"_"+p1+"_");}else{return("\"");}});
}
if(fileCache && !res.wasFileBacked){
var cachePath=_path.join(fileCache, path)
if(sc_self.fileCache && !res.wasFileBacked){
var cachePath=_path.join(sc_self.fileCache, path)
mkdirp(_path.dirname(cachePath),function(err){
if(err){debug("Could not create sling cache folder for: ", cachePath)}
else{
......@@ -338,6 +396,22 @@ SC.getSling = function(path, options, callback){
try {
obj=JSON.parse(bodyText);
if(useGetIfExists){
//need to pluck the actual data out.
if(_.has(obj, 'exists')){
if(obj.exists){
//the requested path exists, return the result
obj = obj.data;
} else {
//the requested resource doesn't exists. Return "404"
err = "The requested resource doesn't exist";
}
} else{
//we didn't get the response structure expected. log error, but return
errorLog("sling-connector invoked with .get-if-exists but didn't get expected output. path: " + path);
}
}
}
catch (e) {
obj = {};
......@@ -444,16 +518,13 @@ SC.buildUri=function(path){
}
//////////////////////////////////////////////////////////////////////////////////////////
SC.validatePath=function(path){
if(!path){
throw new Error("path was not provided");
}else if(!_.isString(path)){
throw new Error("path must be a String");
}else if (path.match(this.invalidPathRegex)){
throw new Error(`path is invalid based on configured invalidPathRegex ${this.invalidPathRegex}`)
}
SC.isValidPath= function(path) {
if(!path || !_.isString(path) || path.match(this.invalidPathRegex)){
return false
}else{
return this.baseUri + path;
return true;
}
}
......@@ -573,7 +644,10 @@ SC.import = function(path, data, options, callback){
* @param {function} callback - to call upon completion (err, httpStatusCode, httpResponse, body)
*/
SC.post = function(path,params,cb){
this.validatePath(path);
if(!this.isValidPath(path)){
cb("invalid path provided");
return;
}
var sc_self=this;
params=params||{};
params._charset_ = params._charset_ || "utf-8";
......@@ -769,6 +843,12 @@ SC.clone = function(overrides){
}
//////////////////////////////////////////////////////////////////////////
SC.cacheableClone = function(){
return this.clone({inMemoryCache: {}});
}
//////////////////////////////////////////////////////////////////////////
SC.util.request=request;
......
const SlingConnector = require('../sling-connector');
describe('isValidPath with default invalidPathRegex', () => {
const sc = new SlingConnector({baseUri: "https://localhost:4502"});
test('undefined path', () => {
expect(sc.isValidPath()).toBeFalsy();
});
test('non string path', () => {
expect(sc.isValidPath({})).toBeFalsy();
expect(sc.isValidPath([])).toBeFalsy();
expect(sc.isValidPath(1)).toBeFalsy();
expect(sc.isValidPath(true)).toBeFalsy();
});
test('string path', () => {
expect(sc.isValidPath("/content/some/path")).toBeTruthy();
});
test('invalid path', () => {
expect(sc.isValidPath("/undefined/some/path")).toBeFalsy();
expect(sc.isValidPath("undefined/some/path")).toBeFalsy();
expect(sc.isValidPath("/[object Object]/some/path")).toBeFalsy();
expect(sc.isValidPath(`/${{}}/some/path`)).toBeFalsy();
});
})
describe('isValidPath with custom invalidPathRegex', () => {
const sc = new SlingConnector({baseUri: "https://localhost:4502", invalidPathRegex: /something/});
test('default is now valid path', () => {
expect(sc.isValidPath("/undefined/some/path")).toBeTruthy();
expect(sc.isValidPath("undefined/some/path")).toBeTruthy();
expect(sc.isValidPath("/[object Object]/some/path")).toBeTruthy();
expect(sc.isValidPath(`/${{}}/some/path`)).toBeTruthy();
});
test('invalid path', () => {
expect(sc.isValidPath("/something")).toBeFalsy();
expect(sc.isValidPath("something")).toBeFalsy();
expect(sc.isValidPath("/content/something/path")).toBeFalsy();
});
})
\ No newline at end of file
const SlingConnector = require('../sling-connector');
test('Call cacheableClone from SC with no inMemoryCache', () => {
const sc = new SlingConnector({baseUri: "../__fixtures__"});
const clone = sc.cacheableClone();
Object.keys(clone).forEach( key => {
if(key === "inMemoryCache"){
expect(clone[key]).toEqual({});
}else{
expect(clone[key]).toStrictEqual(sc[key]);
}
});
});
test('Call cacheableClone from SC with inMemoryCache', () => {
const sc = new SlingConnector({baseUri: "../__fixtures__", inMemoryCache: {key: "value"}});
const clone = sc.cacheableClone();
Object.keys(clone).forEach( key => {
if(key === "inMemoryCache"){
expect(clone[key]).toEqual({});
}else{
expect(clone[key]).toStrictEqual(sc[key]);
}
});
});
\ No newline at end of file
const SlingConnector = require('../sling-connector');
const sc = new SlingConnector({baseUri: "../__fixtures__"});
test('Call clone with no overrides', () => {
const clone = sc.clone();
Object.keys(clone).forEach( key => {
expect(clone[key]).toStrictEqual(sc[key]);
});
})
test('Call clone with overrides', () => {
const overrides = {
baseUri: "http://new/",
username: "new",
password: "new",
invalidPathRegex: /^something/
};
const clone = sc.clone(overrides);
Object.keys(clone).forEach( key => {
if(overrides[key]){
expect(clone[key]).toStrictEqual(overrides[key]);
expect(clone[key]).not.toStrictEqual(sc[key]);
} else{
expect(clone[key]).toStrictEqual(sc[key]);
}
});
});
\ No newline at end of file
......@@ -18,7 +18,8 @@ const optionDefaults = {
mode: "undefined",
hasDerefCapability: "undefined",
invalidPathRegex : /undefined|\[object Object\]/,
requestAgent : "defined"
requestAgent : "defined",
inMemoryCache: "undefined"
}
const expectDefaults = (sc, ignoreKeys) => {
......
......@@ -24,11 +24,16 @@ test('default requestAgent', () => {
});
test('Call getSling with invalid path', () => {
expect(() => sc.getSling()).toThrowError()
expect(() => sc.getSling({})).toThrowError()
expect(() => sc.getSling('undefined')).toThrowError()
expect(() => sc.getSling('/content/[object Object]')).toThrowError()
})
expect(sc.getSling()).rejects.toThrow("invalid path provided");
expect(sc.getSling({})).rejects.toThrow("invalid path provided");
expect(sc.getSling('undefined')).rejects.toThrow("invalid path provided");
expect(sc.getSling('/content/[object Object]')).rejects.toThrow("invalid path provided");
});
// test("Call getSling with same params with inMemoryCache", async () => {
// });
......
const SlingConnector = require('../sling-connector');
describe('validatePath with default invalidPathRegex', () => {
const sc = new SlingConnector({baseUri: "https://localhost:4502"});
test('undefined path', () => {
expect(() => sc.validatePath()).toThrowError();
});
test('non string path', () => {
expect(() => sc.validatePath({})).toThrowError();
expect(() => sc.validatePath([])).toThrowError();
expect(() => sc.validatePath(1)).toThrowError();
expect(() => sc.validatePath(true)).toThrowError();
});
test('string path', () => {
expect(() => sc.validatePath("/content/some/path")).not.toThrow();
});
test('invalid path', () => {
expect(() => sc.validatePath("/undefined/some/path")).toThrowError();
expect(() => sc.validatePath("undefined/some/path")).toThrowError();
expect(() => sc.validatePath("/[object Object]/some/path")).toThrowError();
expect(() => sc.validatePath(`/${{}}/some/path`)).toThrowError();
});
})
describe('validatePath with custom invalidPathRegex', () => {
const sc = new SlingConnector({baseUri: "https://localhost:4502", invalidPathRegex: /something/});
test('default is now valid path', () => {
expect(() => sc.validatePath("/undefined/some/path")).not.toThrowError();
expect(() => sc.validatePath("undefined/some/path")).not.toThrowError();
expect(() => sc.validatePath("/[object Object]/some/path")).not.toThrowError();
expect(() => sc.validatePath(`/${{}}/some/path`)).not.toThrowError();
});
test('invalid path', () => {
expect(() => sc.validatePath("/something")).toThrowError();
expect(() => sc.validatePath("something")).toThrowError();
expect(() => sc.validatePath("/content/something/path")).toThrowError();
});
})
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment