diff --git a/src/dapi/DataApiFactory.js b/src/dapi/DataApiFactory.js
index 446df54..9c7dfb4 100644
--- a/src/dapi/DataApiFactory.js
+++ b/src/dapi/DataApiFactory.js
@@ -19,13 +19,17 @@
* along with this program. If not, see .
*/
+'use strict';
+
const Class = require( 'easejs' ).Class;
const HttpDataApi = require( './http/HttpDataApi' );
const XhrHttpImpl = require( './http/XhrHttpImpl' );
const JsonResponse = require( './format/JsonResponse' );
+const ResponseApply = require( './format/ResponseApply' );
const RestrictedDataApi = require( './RestrictedDataApi' );
const StaticAdditionDataApi = require( './StaticAdditionDataApi' );
const BucketDataApi = require( './BucketDataApi' );
+const QuoteDataApi = require( './QuoteDataApi' );
/**
@@ -36,8 +40,8 @@ module.exports = Class( 'DataApiFactory',
/**
* Return a DataApi instance for the requested service type
*
- * The source and method have type-specific meaning; that is, "source" may
- * be a URL and "method" may be get/post for a RESTful service.
+ * The source and method have type-specific meaning; that is, "source"
+ * may be a URL and "method" may be get/post for a RESTful service.
*
* @param {string} type service type (e.g. "rest")
* @param {Object} desc API description
@@ -46,39 +50,11 @@ module.exports = Class( 'DataApiFactory',
*/
'public fromType': function( type, desc, bucket )
{
- var api = null,
- source = ( desc.source || '' ),
- method = ( desc.method || '' ),
+ const static_data = ( desc['static'] || [] );
+ const nonempty = !!desc.static_nonempty;
+ const multiple = !!desc.static_multiple;
- static_data = ( desc['static'] || [] ),
- nonempty = !!desc.static_nonempty,
- multiple = !!desc.static_multiple;
-
- switch ( type )
- {
- case 'rest':
- const impl = this.createHttpImpl();
-
- api = HttpDataApi.use( JsonResponse )(
- source,
- method.toUpperCase(),
- impl
- );
- break;
-
- case 'local':
- // currently, only local bucket data sources are supported
- if ( source !== 'bucket' )
- {
- throw Error( "Unknown local data API source: " + source );
- }
-
- api = BucketDataApi( bucket, desc.retvals );
- break;
-
- default:
- throw Error( 'Unknown data API type: ' + type );
- };
+ const api = this._createDataApi( type, desc, bucket );
return RestrictedDataApi(
StaticAdditionDataApi( api, nonempty, multiple, static_data ),
@@ -87,6 +63,90 @@ module.exports = Class( 'DataApiFactory',
},
+ /**
+ * Create DataApi instance
+ *
+ * @param {string} type API type
+ * @param {Object} desc API descriptor
+ * @param {Bucket} bucket data bucket
+ *
+ * @return {DataApi}
+ */
+ 'private _createDataApi'( type, desc, bucket )
+ {
+ const source = ( desc.source || '' );
+ const method = ( desc.method || '' );
+ const enctype = ( desc.enctype || '' );
+
+ switch ( type )
+ {
+ case 'rest':
+ return this._createHttp(
+ HttpDataApi.use( JsonResponse ),
+ source,
+ method,
+ enctype
+ );
+
+ case 'local':
+ // currently, only local bucket data sources are supported
+ if ( source !== 'bucket' )
+ {
+ throw Error( "Unknown local data API source: " + source );
+ }
+
+ return BucketDataApi( bucket, desc.retvals );
+
+ case 'quote':
+ return QuoteDataApi(
+ this._createHttp(
+ HttpDataApi
+ .use( JsonResponse )
+ .use( ResponseApply( data => [ data ] ) ),
+ source,
+ method,
+ enctype
+ )
+ );
+
+ default:
+ throw Error( 'Unknown data API type: ' + type );
+ };
+ },
+
+
+ /**
+ * Create HttpDataApi instance
+ *
+ * The `Base` is intended to allow for the caller to mix traits in.
+ *
+ * @param {HttpDataApi} Base HttpDataApi type
+ * @param {string} source URL
+ * @param {string} method HTTP method
+ * @param {string} enctype MIME media type (for POST)
+ *
+ * @return {HttpDataApi}
+ */
+ 'private _createHttp'( Base, source, method, enctype )
+ {
+ const impl = this.createHttpImpl();
+
+ return Base(
+ source,
+ method.toUpperCase(),
+ impl,
+ enctype
+ );
+ },
+
+
+ /**
+ * Create HttpImpl
+ *
+ * This is simply intended to allow subtypes to override the type.
+ *
+ * @return {XhrHttpImpl}
+ */
'virtual protected createHttpImpl'()
{
return XhrHttpImpl( XMLHttpRequest );
diff --git a/src/dapi/http/HttpDataApi.js b/src/dapi/http/HttpDataApi.js
index 48d498b..0db81b1 100644
--- a/src/dapi/http/HttpDataApi.js
+++ b/src/dapi/http/HttpDataApi.js
@@ -19,20 +19,22 @@
* along with this program. If not, see .
*/
-var Class = require( 'easejs' ).Class,
- DataApi = require( '../DataApi' ),
- HttpImpl = require( './HttpImpl' ),
+'use strict';
- // RFC 2616 methods
- rfcmethods = {
- DELETE: true,
- GET: true,
- HEAD: true,
- OPTIONS: true,
- POST: true,
- PUT: true,
- TRACE: true
- };
+const { Class } = require( 'easejs' );
+const DataApi = require( '../DataApi' );
+const HttpImpl = require( './HttpImpl' );
+
+// RFC 2616 methods
+const rfcmethods = {
+ DELETE: true,
+ GET: true,
+ HEAD: true,
+ OPTIONS: true,
+ POST: true,
+ PUT: true,
+ TRACE: true
+};
/**
@@ -61,6 +63,12 @@ module.exports = Class( 'HttpDataApi' )
*/
'private _impl': null,
+ /**
+ * MIME media type
+ * @type {string}
+ */
+ 'private _enctype': '',
+
/**
* Initialize Data API with destination and HTTP implementation
@@ -69,15 +77,18 @@ module.exports = Class( 'HttpDataApi' )
* requests, which permits the user to use whatever implementation works
* well with their existing system.
*
+ * Default `enctype` is `application/x-www-form-urlencoded`.
+ *
* TODO: Accept URI encoder.
*
- * @param {string} url destination URL
- * @param {string} method RFC-2616-compliant HTTP method
- * @param {HttpImpl} impl HTTP implementation
+ * @param {string} url destination URL
+ * @param {string} method RFC-2616-compliant HTTP method
+ * @param {HttpImpl} impl HTTP implementation
+ * @param {string=} enctype MIME media type
*
* @throws {TypeError} when non-HttpImpl is provided
*/
- __construct: function( url, method, impl )
+ __construct: function( url, method, impl, enctype )
{
if ( !( Class.isA( HttpImpl, impl ) ) )
{
@@ -87,6 +98,10 @@ module.exports = Class( 'HttpDataApi' )
this._url = ''+url;
this._method = this._validateMethod( method );
this._impl = impl;
+
+ this._enctype = ( enctype )
+ ? ''+enctype
+ : 'application/x-www-form-urlencoded';
},
@@ -127,7 +142,7 @@ module.exports = Class( 'HttpDataApi' )
this._impl.requestData(
this._url,
this._method,
- this._encodeData( data ),
+ this.encodeData( data ),
callback
);
@@ -164,7 +179,7 @@ module.exports = Class( 'HttpDataApi' )
*/
'private _validateDataType': function( data )
{
- var type = typeof data;
+ const type = typeof data;
if( !( ( type === 'string' ) || ( type === 'object' ) ) )
{
@@ -177,56 +192,57 @@ module.exports = Class( 'HttpDataApi' )
/**
- * If the data are an object, it's converted to an encoded key-value
- * URI; otherwise, the original string datum is returned.
+ * Generate params for URI from key-value `data`
*
- * @param {?Object|string=} data raw data or key-value
+ * Conversion depends on the MIME type (enctype) with which this instance
+ * was initialized. For example, `application/x-www-form-urlencoded`
+ * will result in a urlencoded string, whereas `application/json` will
+ * simply be serialized.
*
- * @return {string} encoded data
+ * If `data` is not an object, it will be returned as a string datum.
+ *
+ * @param {Object|string} data key-value request params
+ *
+ * @return {string} generated URI, or empty if no keys
*/
- 'private _encodeData': function( data )
+ 'protected encodeData': function( data )
{
if ( typeof data !== 'object' )
{
return ''+data;
}
- return this._encodeKeys( data );
+ if ( this._method !== 'POST' )
+ {
+ return this._urlEncode( data );
+ }
+
+ switch ( this._enctype )
+ {
+ case 'application/x-www-form-urlencoded':
+ return this._urlEncode( data );
+
+ case 'application/json':
+ return JSON.stringify( data );
+
+ default:
+ throw Error( 'Unknown enctype for POST: ' + this._enctype );
+ }
},
/**
- * Generate params for URI from key-value DATA
+ * urlencode each key of provided object
*
- * @param {Object} data key-value request params
+ * @param {Object} obj key/value
*
- * @return {string} generated URI, or empty if no keys
+ * @return {string} urlencoded string, joined with '&'
*/
- 'private _encodeKeys': function( obj )
+ 'private _urlEncode'( obj )
{
- var uri = '';
-
- if ( this._method === 'POST' )
- {
- return JSON.stringify( obj );
- }
-
- // ES3 support
- for ( var key in obj )
- {
- if ( !Object.prototype.hasOwnProperty.call( obj, key ) )
- {
- continue;
- }
-
- uri += ( uri )
- ? '&'
- : '';
-
- uri += encodeURIComponent( key ) + '=' +
- encodeURIComponent( obj[ key ] );
- }
-
- return uri;
- }
+ return Object.keys( obj ).map( key =>
+ encodeURIComponent( key ) + '=' +
+ encodeURIComponent( obj[ key ] )
+ ).join( '&' );
+ },
} );
diff --git a/test/dapi/http/HttpDataApiTest.js b/test/dapi/http/HttpDataApiTest.js
index 8e46ca3..df74975 100644
--- a/test/dapi/http/HttpDataApiTest.js
+++ b/test/dapi/http/HttpDataApiTest.js
@@ -119,51 +119,58 @@ describe( 'HttpDataApi', function()
} );
- /**
- * It's nice to do this for the HttpImpl so that they don't have to
- * worry about the proper way to handle it, or duplicate the logic.
- */
- describe( 'given key-value data', function()
+ [
+ // default is urlencoded
+ {
+ enctype: '',
+ method: 'POST',
+ data: { foo: "bar=baz", '&bar': "moo%cow" },
+ expected: 'foo=' + encodeURIComponent( 'bar=baz' ) +
+ '&' + encodeURIComponent( '&bar' ) + '=' +
+ encodeURIComponent( 'moo%cow' )
+ },
+
+ // same as above
+ {
+ enctype: 'application/x-www-form-urlencoded',
+ method: 'POST',
+ data: { foo: "bar=baz", '&bar': "moo%cow" },
+ expected: 'foo=' + encodeURIComponent( 'bar=baz' ) +
+ '&' + encodeURIComponent( '&bar' ) + '=' +
+ encodeURIComponent( 'moo%cow' )
+ },
+
+ // empty string
+ {
+ enctype: 'application/x-www-form-urlencoded',
+ method: 'POST',
+ data: {},
+ expected: "",
+ },
+
+ // json
+ {
+ enctype: 'application/json',
+ method: 'POST',
+ data: { foo: 'bar' },
+ expected: '{"foo":"bar"}',
+ },
+
+ // ignored if GET
+ {
+ enctype: 'application/json',
+ method: 'GET',
+ data: { foo: "bar" },
+ expected: "foo=bar",
+ },
+ ].forEach( ( { enctype, method, data, expected }, i ) =>
{
- it( 'converts data into encoded string', function()
+ it( `${method} encodes (${i})`, () =>
{
- var method = 'GET',
- data = { foo: "bar=baz", '&bar': "moo%cow" },
- c = function() {};
+ Sut( dummy_url, method, impl, enctype )
+ .request( data, _ => {} );
- Sut( dummy_url, method, impl ).request( data, c );
-
- expect( impl.provided[ 2 ] ).to.equal(
- 'foo=' + encodeURIComponent( data.foo ) +
- '&' + encodeURIComponent( '&bar' ) + '=' +
- encodeURIComponent( data[ '&bar' ] )
- );
- } );
-
-
- it( 'with no keys, results in empty string', function()
- {
- var method = 'GET',
- data = {},
- c = function() {};
-
- Sut( dummy_url, method, impl ).request( data, c );
-
- expect( impl.provided[ 2 ] ).to.equal( "" );
- } );
-
-
- it( 'encodes JSON on POST', () =>
- {
- var method = 'POST',
- data = { foo: 'bar' },
- c = () => {};
-
- Sut( dummy_url, method, impl ).request( data, c );
-
- expect( impl.provided[ 2 ] ).to.equal(
- JSON.stringify( data )
- );
+ expect( impl.provided[ 2 ] ).to.equal( expected );
} );
} );