Skip to content

Commit

Permalink
feat(serialization): offer option to omit stringification
Browse files Browse the repository at this point in the history
fixes #103
  • Loading branch information
basti1302 committed Jul 26, 2018
1 parent d7ebfff commit b0f67cf
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 7 deletions.
19 changes: 16 additions & 3 deletions api.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Client code that wants to check for a particular error type is encouraged to use
Request Builder
---------------

A request builder can be obtained by `traverson.newRequest()` or `traverson.from(url)`. It is used to prepare and execute a single link traversal process. The request builder offers two types of methods: configuration methods and action methods. You can call any number of configuration methods on request builder instance to prepare the link traversal process. You can also chain configuration method calls because they return the request builder instance. When you are done configuring it is time to call one of the action methods. They tell Traverson what to do at the end of the link traversal process. In contrast to the configuration methods you must only call one of the action methods on any request builder instance and should not call a configuration methods after you have called an action method.
A request builder can be obtained by `traverson.newRequest()` or `traverson.from(url)`. It is used to prepare and execute a single link traversal process. The request builder offers two types of methods: configuration methods and action methods. You can call any number of configuration methods on request builder instance to prepare the link traversal process. You can also chain configuration method calls because they return the request builder instance. When you are done configuring, it is time to call one of the action methods. They tell Traverson what to do at the end of the link traversal process. In contrast to the configuration methods you must only call one of the action methods on any request builder instance and should not call a configuration methods after you have called an action method.

### Configuration Methods

Expand All @@ -69,10 +69,10 @@ or addRequestOptions.

The header values depend on the media type (see setMediaType()). For example,
for plain vanilla JSON (that is, when using setMediaType('application/json')
or the corresponding shortcut .json()), both headers will be send with the
or the corresponding shortcut .json()), both headers will be sent with the
value 'application/json'. For HAL (that is, when using
setMediaType('application/hal+json') or the corresponding shortcut
jsonHal()), both headers will be send with the value 'application/hal+json'.
jsonHal()), both headers will be sent with the value 'application/hal+json'.

If the method is called without arguments (or the first argument is undefined
or null), automatic headers are turned on, otherwise the argument is
Expand Down Expand Up @@ -143,6 +143,17 @@ Returns the request builder instance to allow for method chaining.

<a name="builder-withRequestLibrary"></a>`withRequestLibrary(request)`: Injects a custom request library. Returns the request builder instance to allow for method chaining.

<a name="builder-sendRawPayload"></a>`sendRawPayload(flag)`: With this option enabled, the payload of the last request at the end of the
traversal will be sent as is, without stringifying it. The default is false, which means that usually Traverson assumes the payload is passed as a JavScript object which will then be stringified (which is the right thing to do for JSON based MIME types like `application/json`. If you want to handle the serialization yourself and don't want Traverson to interfere, this option should be set to true.

If the method is called without arguments (or the first argument is undefined
or null), this option is switched on, otherwise the argument is
interpreted as a boolean flag. If it is a truthy value, the option is
switched to on, if it is a falsy value (but not null or
undefined), the option is switched off.

Returns the request builder instance to allow for method chaining.

<a name="builder-parseResponseBodiesWith"></a>`parseResponseBodiesWith(parser)`: Injects a custom JSON parser. Returns the request builder instance to allow for method chaining.

<a name="builder-convertResponseToObject"></a>`convertResponseToObject(flag)`: With this option enabled, the body of the response at the end of the traversal will be converted into a JavaScript object (for example by passing it into JSON.parse) and passing the resulting object into the callback. The default is false, which means the full response is handed to the callback.
Expand Down Expand Up @@ -179,6 +190,8 @@ If the method is called without arguments (or the first argument is undefined or

<a name="builder-getJsonParser"></a>`getJsonParser()`: Returns the custom JSON parser function set by `parseResponseBodiesWith` or the standard parser function, if a custom one has not been set.

<a name="builder-sendsRawPayload"></a>`sendsRawPayload()`: Returns true if the payload will be sent without stringifying it first.

<a name="builder-convertsResponseToObject"></a>`convertsResponseToObject()`: Returns true if the body of the last response will be converted to a JavaScript before passing the result back to the callback.

<a name="builder-doesResolveRelative"></a>`doesResolveRelative`: Returns the flag controlling if URLs are resolved relative or absolute. A return value of `true` means that URLs are resolved relative, `false` means absolute.
Expand Down
37 changes: 35 additions & 2 deletions lib/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ function Builder(mediaType, linkType) {
this.adapter = this._createAdapter(this.mediaType);
this.autoHeaders = true;
this.contentNegotiation = true;
this.rawPayloadFlag = false;
this.convertResponseToObjectFlag = false;
this.links = [];
this.jsonParser = JSON.parse;
Expand Down Expand Up @@ -336,10 +337,10 @@ Builder.prototype.enableAutoHeaders = function() {
*
* The header values depend on the media type (see setMediaType()). For example,
* for plain vanilla JSON (that is, when using setMediaType('application/json')
* or the corresponding shortcut .json()), both headers will be send with the
* or the corresponding shortcut .json()), both headers will be sent with the
* value 'application/json'. For HAL (that is, when using
* setMediaType('application/hal+json') or the corresponding shortcut
* jsonHal()), both headers will be send with the value 'application/hal+json'.
* jsonHal()), both headers will be sent with the value 'application/hal+json'.
*
* If the method is called without arguments (or the first argument is undefined
* or null), automatic headers are turned on, otherwise the argument is
Expand All @@ -364,6 +365,29 @@ Builder.prototype.useAutoHeaders = function(flag) {
return this;
};

/**
* With this option enabled, the payload of the last request at the end of the
* traversal will be sent as is, without stringifying it. The default is false,
* which means that usually Traverson assumes the payload is passed as a
* JavScript object which will then be stringified (which is the right thing to
* do for JSON based MIME types like application/json. If you want to handle the
* serialization yourself and don't want Traverson to interfere, this option
* should be set to true.
*
* If the method is called without arguments (or the first argument is undefined
* or null), this option is switched on, otherwise the argument is
* interpreted as a boolean flag. If it is a truthy value, the option is
* switched to on, if it is a falsy value (but not null or
* undefined), the option is switched off.
*/
Builder.prototype.sendRawPayload = function(flag) {
if (typeof flag === 'undefined' || flag === null) {
flag = true;
}
this.rawPayloadFlag = !!flag;
return this;
};

/**
* With this option enabled, the body of the response at the end of the
* traversal will be converted into a JavaScript object (for example by passing
Expand Down Expand Up @@ -489,6 +513,13 @@ Builder.prototype.setsAutoHeaders = function() {
return this.autoHeaders;
};

/**
* Returns true if the given payload will be sent without stringifying it first.
*/
Builder.prototype.sendsRawPayload = function() {
return this.rawPayloadFlag;
};

/**
* Returns true if the body of the last response will be converted to a
* JavaScript object before passing the result back to the callback.
Expand Down Expand Up @@ -634,6 +665,7 @@ function createInitialTraversalState(self, body) {
autoHeaders: self.setsAutoHeaders(),
contentNegotiation: self.doesContentNegotiation(),
continuation: null,
rawPayload: self.sendsRawPayload(),
convertResponseToObject: self.convertsResponseToObject(),
links: self.links,
jsonParser: self.getJsonParser(),
Expand Down Expand Up @@ -693,6 +725,7 @@ function initFromTraversalState(self, t) {
self.callbackHasBeenCalledAfterAbort = false;
self.autoHeaders = t.autoHeaders;
self.contentNegotiation = t.contentNegotiation;
self.rawPayload = t.rawPayload;
self.convertResponseToObjectFlag = t.convertResponseToObject;
self.links = [];
self.jsonParser = t.jsonParser;
Expand Down
2 changes: 1 addition & 1 deletion lib/http_requests.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function executeHttpGet(t, callback) {
exports.executeHttpRequest = function(t, request, method, callback) {
var requestOptions = getOptionsForStep(t);
if (t.body !== null && typeof t.body !== 'undefined') {
requestOptions.body = requestOptions.jsonReplacer ?
requestOptions.body = (t.rawPayload || requestOptions.jsonReplacer) ?
t.body : JSON.stringify(t.body);
}

Expand Down
4 changes: 3 additions & 1 deletion release-notes.markdown
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Release Notes
-------------

* Unreleased:
* Add option `sendRawPayload` to skip payload stringification for non-JSON MIME types ([#103](https://github.com/traverson/traverson/issues/103).
* 6.0.4 2018-07-19:
* Update to latest version of `request` ([#104](https://github.com/traverson/traverson/pull/104), thanks to @travi).
* 6.0.3 2017-04-21:
Expand All @@ -11,7 +13,7 @@ Release Notes
* Reduce file size of browser build ([#86](https://github.com/traverson/traverson/pull/86), [#87](https://github.com/traverson/traverson/pull/87), thanks to @iunanua).
* 6.0.0 2017-02-10:
* *Breaking change*: Traverson now sets `Accept` and `Content-Type` headers automatically when the media type has been set explicitly ([#37](https://github.com/traverson/traverson/issues/37)). This might be a breaking change for users that
* *do* set set the media type via `.setMediaType('...')`, `.json()` or `.jsonHal()`,
* *do* set the media type via `.setMediaType('...')`, `.json()` or `.jsonHal()`,
* *do not* set headers explicitly (via `.withRequestOptions` or `.addRequestOptions`) and
* whose backends do not cope well when `Accept` and/or `Content-Type` headers are set to `application/json` or `application/hal+json` respectively.
* Support for link headers ([#84](https://github.com/traverson/traverson/issues/84) and [#85](https://github.com/traverson/traverson/pull/85), thanks to @iunanua and @anderruiz)
Expand Down
24 changes: 24 additions & 0 deletions test/localhost.js
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,30 @@ describe('Traverson (when tested against a local server)', function() {
);
});

it('should post arbitrary data without stringifying it', function(done) {
var payloadString = 'do not stringify me!';
api
.newRequest()
.sendRawPayload()
.follow('post_link')
.withRequestOptions([
{},
{ headers: { 'Content-Type': 'text/plain' } }
])
.post(payloadString, callback);
waitFor(
function() { return callback.called; },
function() {
var resultDoc = checkResponseWithBody(201);
expect(resultDoc.document).to.exist;
expect(resultDoc.document).to.equal('created');
expect(resultDoc.received).to.exist;
expect(resultDoc.received).to.equal('do not stringify me!');
done();
}
);
});

it('should put', function(done) {
var payload = {'updated': 'document'};
api
Expand Down

0 comments on commit b0f67cf

Please sign in to comment.