/*jshint laxcomma: true, smarttabs: true, node: true, unused: true, esnext: true*/
'use strict';
/**
* resource field definitions
* @module tastypie/fields/api
* @author Eric Satterwhite
* @since 0.0.1
* @requires events
* @requires path
* @requires util
* @requires mout/lang/isFunction
* @requires mout/lang/isString
* @requires mout/lang/kindOf
* @requires mout/date/strftime
* @requires mout/object/get
* @requires mout/object/hasOwn
* @requires tastypie/lib/class
* @requires tastypie/lib/class/options
* @requires tastypie/lib/class/parent
*/
const events = require( 'events' )
, util = require( 'util' )
, boom = require( 'boom' )
, get = require( 'mout/object/get' )
, hasOwn = require( 'mout/object/hasOwn' )
, isFunction = require( 'mout/lang/isFunction' )
, isUndefined = require( 'mout/lang/isUndefined' )
, Class = require( '../class' )
, Options = require( '../class/options' )
, Parent = require( '../class/parent' )
, STRING = 'string'
, FUNCTION = 'function'
;
let ApiField;
/**
* Base field providing generic functionality around value serialization / deserialization
* @constructor
* @alias module:tastypie/fields/api
* @param {Object} options
* @param {Boolean} [options.readonly=false] Vales will be omitted during the hydration cycle
* @param {Boolean} [options.nullable=false] true of the field should allow for a null vlaue
* @param {?String|Function} [options.attribute=null] A name path or function to be used to extract values during dehydration
* @param {String} [options.name=null] The name of the field. This is automatically set when added to a resource
* @param {String} [options.help=A general no-op field] Help text to include in the schema defintion for this field
* @param {!Mixed} [options.default=null] A default value for the field to fill in if none is provided
* @param {Boolean} [options.exclude=false] If true the field values will be excluded from dehydration & response payloads
* @mixes module:tastypie/lib/class/options
* @mixes module:tastypie/lib/class/parent
* @example var x = new tastypie/fields.ApiField();
*/
ApiField = new Class({
inherits: events.EventEmitter
, mixin:[ Parent, Options ]
, options:{
'readonly' : false
, 'nullable' : false
, 'attribute' : null
, 'default' : undefined
, 'blank' : false
, 'name' : null
, 'exclude' : false
, help:'A general no op field'
}
, constructor: function( options ){
this.setOptions( options );
}
/**
* used to determine serialization type
* @private
* @method module:tastypie/fields/api#type
* @return {String} internal data type
**/
, type: function( ){
return 'string';
}
/**
* returns a sample of the format expected and returned by a field
* @method module:tastypie/fields/api#format
* @return {String|undefined} The format expected for a particular field type
**/
,format: function(){
}
/**
* Converts data value into the serialization specific type.
* @method module:tastypie/fields/api#convert
* @param {Mixed} val value to convert
* @returns {Mixed} value The converted field value
**/
, convert: function convert( val ){
return val;
}
/**
* Converts a serialized value in to a javascript object value
* @method module:tastypie/fields/api#hydrate
* @param {Bundle} bundle a bundle representing the current request
**/
, hydrate: function hydrate( bundle, cb ){
var name = this.options.name
, attr = this.options.attribute
, obj = bundle.object
, value
;
// if readonly, don't care!
if( this.options.readonly ){
return setImmediate(cb, null );
}
// if there is a value in data, return it.
if( hasOwn(bundle.data, name ) ){
return setImmediate(cb, null, bundle.data[ name ] );
}
if( value = get( bundle.data, attr ) ){
return setImmediate(cb, null, value );
}
// if bundle data doesn't have a matching property.
// look for one.
// NOTE: we can't use hasOwnProperty here because we care inherited properties
if( attr && attr in obj ){
return setImmediate(cb, null, obj[ attr ] );
} else if ( name in obj ){
return setImmediate( cb, null, obj[ name ] );
} else if( hasOwn(this.options, 'default') && !isUndefined( this.options.default ) ){
return setImmediate( cb, null, isFunction( this.default ) ? this.default( bundle.data ) : this.default );
} else if( this.options.nullable ){
return setImmediate( cb, null, null );
} else {
let err = boom.create(
400
, util.format(
"Field %s has no data, default value and is not nullable "
, this.options.name
)
);
return setImmediate(cb, err)
}
}
/**
* Converts a javascript object / value into something sutable for seriaation. ex. Date formatting
* @method module:tastypie/fields/api#dehydrate
* @param {Object} object Object to dehydrate before serialization
* @param {Sting|Function} attribute a name path or function to use to retrieve a value from the object
* @return {Mixed} value object value converted to its internal type
**/
, dehydrate: function dehydrate( obj, cb ){
var current, attribute;
attribute = this.options.attribute;
if( typeof attribute === STRING ){
current = get( obj, attribute );
if( current == null ){
if( this.options.hasOwnProperty( 'default' ) ){
current = this.options.default;
} else if( this.options.nullable ){
current = null;
}
}
current = typeof current === FUNCTION ? current( obj ) : current;
} else if( typeof attribute === FUNCTION ){
current = attribute();
}
return setImmediate(cb, null, this.convert( current ));
}
/**
* Injects dynamic properties onto field instance for later use
* @protected
* @method module:tastypie/fields/api#augment
* @param {Resource} resource the resource instance this field is attached to
* @param {String} name The property name this field is associated to
**/
, augment: function augment( cls, name ){
this.name = this.name || name;
this.resource = this.resource || cls;
return this;
}
});
Object.defineProperties(ApiField.prototype,{
default:{
enumerable:false
,writeable:false
,get: function( ){
return this.options.default;
}
}
});
module.exports = ApiField;