Skip to content

Commit

Permalink
refactor lib/actions to only use transforms (instead of callbacks)
Browse files Browse the repository at this point in the history
  • Loading branch information
basti1302 committed Aug 25, 2015
1 parent 21b6d7c commit 418abd5
Show file tree
Hide file tree
Showing 12 changed files with 257 additions and 184 deletions.
161 changes: 37 additions & 124 deletions lib/actions.js
Original file line number Diff line number Diff line change
@@ -1,137 +1,68 @@
'use strict';

var minilog = require('minilog')
, url = require('url')
, checkHttpStatus = require('./transforms/check_http_status')
, convertEmbeddedDocToResponse =
require('./transforms/convert_embedded_doc_to_response')
, log = minilog('traverson')
, abortTraversal = require('./abort_traversal')
, applyTransforms = require('./transforms/apply_transforms')
, httpRequests = require('./http_requests')
, isContinuation = require('./is_continuation')
, parse = require('./transforms/parse')
, walker = require('./walker');

var log = minilog('traverson');
var checkHttpStatus = require('./transforms/check_http_status')
, continuationToDoc =
require('./transforms/continuation_to_doc')
, continuationToResponse =
require('./transforms/continuation_to_response')
, convertEmbeddedDocToResponse =
require('./transforms/convert_embedded_doc_to_response')
, extractDoc = require('./transforms/extract_doc')
, extractResponse = require('./transforms/extract_response')
, extractUrl = require('./transforms/extract_url')
, fetchLastResource = require('./transforms/fetch_last_resource')
, executeLastHttpRequest = require('./transforms/execute_last_http_request')
, parse = require('./transforms/parse');

/**
* Starts the link traversal process and passes the last HTTP response to the
* callback.
*/
exports.get = function(t, callback) {
t.callback =
createWalkStateCallback(t, afterGet, callback);
walker.walk(t);
t.callback = callback;
walker.walk(t, [
continuationToResponse,
fetchLastResource,
convertEmbeddedDocToResponse,
extractResponse,
]);
return createTraversalHandle(t);
};

function afterGet(t, callback) {
if (isContinuation(t)) {
// follow() call without links after continue(). Actually, there is nothing
// to do here since we should have fetched everything last time.
log.debug('continuing from last traversal process (actions)');
t.continuation = null;
convertEmbeddedDocToResponse(t);
return callback(null, t.step.response);
}

httpRequests.fetchResource(t, function(err, t) {
log.debug('fetchResource returned');
if (err) {
if (!err.aborted) {
log.debug('error while processing step ', t.step);
log.error(err);
}
return t.callback(err);
}
convertEmbeddedDocToResponse(t);
callback(null, t.step.response);
});
}


/**
* Special variant of get() that does not yield the full http response to the
* callback but instead the already parsed JSON as an object.
*/
exports.getResource = function(t, callback) {
t.callback =
createWalkStateCallback(t, afterGetResource, callback);
walker.walk(t);
t.callback = callback;
walker.walk(t, [
continuationToDoc,
fetchLastResource,
checkHttpStatus,
parse,
extractDoc,
]);
return createTraversalHandle(t);
};

function afterGetResource(t, callback) {
if (isContinuation(t)) {
// follow() call without links after continue(). Actually, there is nothing
// to do here since we should have fetched everything last time.
log.debug('continuing from last traversal process (actions)');
t.continuation = null;
return callback(null, t.step.doc);
}

// TODO Remove duplication: This duplicates the fetchResource/checkHttpStatus/
// parse sequence from lib/walker.js (executeNextStep/applyTransforms
// methods).
httpRequests.fetchResource(t, function(err, t) {
log.debug('fetchResource returned.');
if (err) {
if (!err.aborted) {
log.debug('error while processing step ', t.step);
log.error(err);
}
return t.callback(err);
}

if (t.step.doc) {
// return an embedded doc immediately
return callback(null, t.step.doc);
}

if (!checkHttpStatus(t)) return;
if (!parse(t)) return;
return callback(null, t.step.doc);
});
}

/**
* Special variant of get() that does not execute the last request but instead
* yields the last URL to the callback.
*/
exports.getUrl = function(t, callback) {
t.callback =
createWalkStateCallback(t, afterGetUrl, callback);
walker.walk(t);
t.callback = callback;
walker.walk(t, [ extractUrl ]);
return createTraversalHandle(t);
};

function afterGetUrl(t, callback) {
log.debug('returning url');
if (t.step.url) {
return callback(null, t.step.url);
} else if (t.step.doc &&
// TODO actually this is very HAL specific :-/
t.step.doc._links &&
t.step.doc._links.self &&
t.step.doc._links.self.href) {
return callback(null, url.resolve(t.startUrl, t.step.doc._links.self.href));
} else {
return callback(new Error('You requested an URL but the last ' +
'resource is an embedded resource and has no URL of its own ' +
'(that is, it has no link with rel=\"self\"'));
}
}

function createWalkStateCallback(t, fn, callback) {
return function(err) {
log.debug('walker.walk returned');
if (err) {
callback(err);
} else {
fn(t, callback);
}
};
}

/**
* Starts the link traversal process and sends an HTTP POST request with the
* given body to the last URL. Passes the HTTP response of the POST request to
Expand Down Expand Up @@ -184,33 +115,15 @@ exports.delete = function(t, callback) {
};

function walkAndExecute(t, request, method, callback) {
t.callback = function(err) {
log.debug('walker.walk returned');
if (err) {
return callback(err);
}

if (t.aborted) {
return abortTraversal.callCallbackOnAbort(t);
}

log.debug('executing final request with step: ', t.step);
httpRequests.executeHttpRequest(t, request, method, callback);
};

walker.walk(t);
t.callback = callback;
t.lastMethod = method;
walker.walk(t, [
executeLastHttpRequest
]);
}

function createTraversalHandle(t) {
return {
abort: t.abortTraversal
};
}

function responseFromState(t) {
return t ? (t.step ? t.step.response : null) : null;
}

function urlFromState(t) {
return t ? (t.step ? t.step.url : null) : null;
}
20 changes: 20 additions & 0 deletions lib/transforms/continuation_to_doc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

var minilog = require('minilog')
, log = minilog('traverson')
, isContinuation = require('../is_continuation');

/*
* This transform covers the case of a follow() call *without any links* after
* a continue(). Actually, there is nothing to do here since we should have
* fetched everything last time.
*/
module.exports = function continuationToDoc(t) {
if (isContinuation(t)) {
log.debug('continuing from last traversal process (actions)');
t.continuation = null;
t.callback(null, t.step.doc);
return false;
}
return true;
};
23 changes: 23 additions & 0 deletions lib/transforms/continuation_to_response.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';

var minilog = require('minilog')
, log = minilog('traverson')
, convertEmbeddedDocToResponse =
require('./convert_embedded_doc_to_response')
, isContinuation = require('../is_continuation');

/*
* follow() call without links after continue(). Actually, there is nothing
* to do here since we should have fetched everything last time.
*/
module.exports = function continuationToResponse(t) {
if (isContinuation(t)) {
log.debug('continuing from last traversal process (actions)');
t.continuation = null;
// Hm, a transform using another transform. This feels a bit fishy.
convertEmbeddedDocToResponse(t);
t.callback(null, t.step.response);
return false;
}
return true;
};
27 changes: 27 additions & 0 deletions lib/transforms/execute_last_http_request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';

var minilog = require('minilog')
, log = minilog('traverson')
, abortTraversal = require('../abort_traversal')
, httpRequests = require('../http_requests');

/*
* Execute the last http request in a traversal that ends in
* post/put/patch/delete.
*/
// TODO Why is this different from when do a GET at the end of the traversal?
// Probably only because the HTTP method is configurable here (with
// t.lastMethod), we might be able to unify this with the
// fetch_resource/fetch_last_resource transform.
function executeLastHttpRequest(t, callback) {
// always check for aborted before doing an HTTP request
if (t.aborted) {
return abortTraversal.callCallbackOnAbort(t);
}
httpRequests.executeHttpRequest(
t, t.requestModuleInstance, t.lastMethod, t.callback);
}

executeLastHttpRequest.isAsync = true;

module.exports = executeLastHttpRequest;
20 changes: 20 additions & 0 deletions lib/transforms/extract_doc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

var minilog = require('minilog')
, log = minilog('traverson');

/*
* This transform is meant to be run at the very end of a getResource call. It
* just extracts the last doc from the step and calls t.callback with it.
*/
module.exports = function extractDoc(t) {
log.debug('walker.walk has finished');
/*
TODO Breaks a lot of tests although it seems to make perfect sense?!?
if (!t.doc) {
t.callback(new Error('No document available'));
return false;
}
*/
t.callback(null, t.step.doc);
};
21 changes: 21 additions & 0 deletions lib/transforms/extract_response.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict';

var minilog = require('minilog')
, log = minilog('traverson');

/*
* This transform is meant to be run at the very end of a get/post/put/patch/
* delete call. It just extracts the last response from the step and calls
* t.callback with it.
*/
module.exports = function extractDoc(t) {
log.debug('walker.walk has finished');
/*
TODO Breaks a lot of tests although it seems to make perfect sense?!?
if (!t.response) {
t.callback(new Error('No response available'));
return false;
}
*/
t.callback(null, t.step.response);
};
28 changes: 28 additions & 0 deletions lib/transforms/extract_url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

var minilog = require('minilog')
, log = minilog('traverson')
, url = require('url');

/*
* This transform is meant to be run at the very end of a get/post/put/patch/
* delete call. It just extracts the last accessed url from the step and calls
* t.callback with it.
*/
module.exports = function extractDoc(t) {
log.debug('walker.walk has finished');
if (t.step.url) {
return t.callback(null, t.step.url);
} else if (t.step.doc &&
// TODO actually this is very HAL specific :-/
t.step.doc._links &&
t.step.doc._links.self &&
t.step.doc._links.self.href) {
return t.callback(
null, url.resolve(t.startUrl, t.step.doc._links.self.href));
} else {
return t.callback(new Error('You requested an URL but the last ' +
'resource is an embedded resource and has no URL of its own ' +
'(that is, it has no link with rel=\"self\"'));
}
};
36 changes: 36 additions & 0 deletions lib/transforms/fetch_last_resource.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';

// TODO Only difference to lib/transform/fetch_resource is the continuation
// checking, which is missing here. Maybe we can delete this transform and use
// fetch_resource in its place everywhere?

var minilog = require('minilog')
, log = minilog('traverson')
, abortTraversal = require('../abort_traversal')
, httpRequests = require('../http_requests');

/*
* Execute the last step in a traversal that ends with an HTTP GET.
*/
// This is similar to lib/transforms/fetch_resource.js - refactoring potential?
function fetchLastResource(t, callback) {
// always check for aborted before doing an HTTP request
if (t.aborted) {
return abortTraversal.callCallbackOnAbort(t);
}
httpRequests.fetchResource(t, function(err, t) {
log.debug('fetchResource returned (fetchLastResource).');
if (err) {
if (!err.aborted) {
log.debug('error while processing step ', t.step);
log.error(err);
}
return t.callback(err);
}
callback(t);
});
}

fetchLastResource.isAsync = true;

module.exports = fetchLastResource;
Loading

0 comments on commit 418abd5

Please sign in to comment.