1
0
Fork 0

Add HttpDataApiUrlData

master
Jeffrey Fisher 2018-04-24 15:30:19 -04:00 committed by Mike Gerwitz
parent 5b410005cd
commit 7d0dc97162
5 changed files with 276 additions and 7 deletions

View File

@ -139,14 +139,33 @@ module.exports = Class( 'HttpDataApi' )
this._validateDataType( data );
this._impl.requestData(
this._url,
this._method,
this.requestData( this._url, this._method, data, callback );
return this;
},
/**
* Request data from underlying HttpImpl
*
* Subtypes may override this method to alter any aspect of the request
* before sending.
*
* @param {string} url destination URL
* @param {string} method RFC-2616-compliant HTTP method
* @param {Object|string} data request params
* @param {function(?Error, ?string)} callback server response callback
*
* @return {HttpDataApi} self
*/
'virtual protected requestData'( url, method, data, callback )
{
return this._impl.requestData(
url,
method,
this.encodeData( data ),
callback
);
return this;
},

View File

@ -0,0 +1,127 @@
/**
* Provide data as part of URL
*
* Copyright (C) 2018 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework
*
* Liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
const { Trait } = require( 'easejs' );
const HttpDataApi = require( './HttpDataApi' );
/**
* Place fields from given data in the URL
*
* All remaining fields are passed to the underlying supertype.
*/
module.exports = Trait( 'HttpDataApiUrlData' )
.extend( HttpDataApi,
{
/**
* Fields to take from data and place in URL
* @type {string}
*/
'private _fields': [],
/**
* Initialize with URL field list
*
* @param {Array<string>} fields list of fields to include in URL
*/
__mixin( fields )
{
this._fields = fields;
},
/**
* Concatenate chosen fields with URL
*
* The previously specified fields will have their values delimited by '/'
* and will be concatenated with the URL. All used fields in DATA will be
* removed before being passed to the supertype. METHOD and CALLBACK are
* proxied as-is.
*
* @param {string} url destination URL
* @param {string} method RFC-2616-compliant HTTP method
* @param {Object|string} data request params
* @param {function(Error, Object)} callback server response callback
*
* @return {HttpImpl} self
*/
'override public requestData'( url, method, data, callback )
{
const [ values, filtered_data ] = this._getFieldValues( data );
const params = values.map( ( [ , value ] ) => value );
const missing = values.filter( ( [ , value ] ) => value === undefined );
if ( missing.length > 0 )
{
callback(
Error(
"Missing URL parameters: " +
missing.map( ( [ field ] ) => field ).join( ", " )
),
null
);
return this;
}
const built_url = ( params.length > 0 )
? url + '/' + params.join( '/' )
: url;
return this.__super( built_url, method, filtered_data, callback );
},
/**
* Associate fields with their respective values from DATA
*
* The returned values are of the form `[ [ field, value ], ... ]`.
* The returned data object is a copy of the original and is stripped
* of the respective fields.
*
* @param {Object} data source data
*
* @return {Array} values and copy of data stripped of those fields
*/
'private _getFieldValues'( data )
{
const fieldset = new Set( this._fields );
const values = this._fields.map( field => [ field, data[ field ] ] );
// copy of data with fields stripped
const new_data = Object.keys( data ).reduce( ( dest, key ) =>
{
if ( fieldset.has( key ) )
{
return dest;
}
dest[ key ] = data[ key ];
return dest;
}, {} );
return [ values, new_data ];
},
} );

View File

@ -87,7 +87,7 @@ module.exports = Class( 'NodeHttpImpl' )
*
* @return {HttpImpl} self
*/
'public requestData'( url, method, data, callback )
'virtual public requestData'( url, method, data, callback )
{
const options = this._parseUrl( url );
const protocol = options.protocol.replace( /:$/, '' );

View File

@ -71,7 +71,7 @@ module.exports = Class( 'XhrHttpImpl' )
*
* @return {HttpImpl} self
*/
'public requestData': function( url, method, data, callback )
'virtual public requestData': function( url, method, data, callback )
{
if ( typeof data !== 'string' )
{

View File

@ -0,0 +1,123 @@
/**
* Tests HttpDataApiUrlData
*
* Copyright (C) 2018 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict'
const { Class } = require( 'easejs' );
const { expect } = require( 'chai' );
const {
dapi: {
http: {
HttpDataApi,
HttpImpl,
HttpDataApiUrlData: Sut,
},
},
} = require( '../../../' );
describe( 'HttpDataApiUrlData', () =>
{
[
{
fields: [],
data: {
foo: "foodata",
bar: "bardata",
},
base_url: "base",
after_data: "foo=foodata&bar=bardata",
expected: "base",
},
{
fields: [ 'foo', 'bar' ],
data: {
foo: "foodata",
bar: "bardata",
baz: "shouldnotuse",
},
base_url: "base2",
after_data: "baz=shouldnotuse",
expected: "base2/foodata/bardata",
},
].forEach( ( { fields, data, base_url, after_data, expected }, i ) =>
it( `can include portion of data in url (#${i})`, done =>
{
const expected_method = 'PUT';
const impl = Class.implement( HttpImpl ).extend(
{
'virtual requestData'( url, method, given_data, callback )
{
expect( url ).to.equal( expected );
expect( method ).to.equal( expected_method );
expect( given_data ).to.deep.equal( after_data );
// should not have mutated the original object
// (they shouldn't be the same object)
expect( data ).to.not.equal( given_data );
// should be done
callback();
},
setOptions: () => {},
} )();
const sut = HttpDataApi.use( Sut( fields ) )(
base_url, expected_method, impl
);
const result = sut.request( data, done );
} )
);
it( "throws error if param is missing from data", done =>
{
const impl = Class.implement( HttpImpl ).extend(
{
'virtual requestData': ( url, method, data, callback ) => {},
setOptions: () => {},
} )();
const sut = HttpDataApi.use( Sut( [ 'foo' ] ) )(
'', 'PUT', impl
);
// missing `foo` in data
sut.request( { unused: "missing foo" }, ( err, data ) =>
{
expect( err ).to.be.instanceof( Error );
expect( err.message ).to.match( /\bfoo\b/ );
expect( data ).to.equal( null );
done();
} );
} );
} );