lib/type/info.js
"use strict";
var InfoError = require( "../error/info" );
var Type = require( "./type" );
module.exports = TypeInfo;
/**
* Construct a new TypeInfo instance from a type description.
* @param {String} typeName The name of the described typed.
* @param {Object} typeDescription The type description. This is usually the
* same structure you create the mongoose schema from.
* @constructor
*/
function TypeInfo( typeName, typeDescription ) {
// An identifier that is supposed to uniquely identify the type being described.
this.typeName = typeName.toLowerCase();
this.typeDescription = typeDescription;
this.typeInfo = {};
var propertyInfo = this.parsePropertyDescription( typeDescription );
if( propertyInfo ) {
this.typeInfo.__ROOT__ = propertyInfo;
}
for( var propertyName in typeDescription ) {
propertyInfo = this.parsePropertyDescription( typeDescription[ propertyName ] );
if( propertyInfo ) {
this.typeInfo[ propertyName ] = propertyInfo;
}
}
if( typeDescription.__COMPOSITES__ && Array.isArray( typeDescription.__COMPOSITES__ ) ) {
for( var compositeIndex in typeDescription.__COMPOSITES__ ) {
this.typeInfo[ typeDescription.__COMPOSITES__[ compositeIndex ] ] =
this.parsePropertyDescription( typeDescription[ typeDescription.__COMPOSITES__[ compositeIndex ] ] );
}
}
this.USERCLASS_USER = TypeInfo.USERCLASS_USER;
this.USERCLASS_ADMIN = TypeInfo.USERCLASS_ADMIN;
TypeInfo.types[ typeName ] = Type.for( this );
}
//noinspection JSUnusedGlobalSymbols
/**
* Check if a given type name is a valid match for the name of this type.
* @param {String} typeName The name of the type.
* @returns {boolean}
*/
TypeInfo.prototype.is = function TypeInfo$is( typeName ) {
return this.typeName === typeName.toLowerCase();
};
/**
* Parse a property description
* @param property
* @returns {*}
*/
TypeInfo.prototype.parsePropertyDescription = function TypeInfo$parsePropertyDescription( property ) {
if( !property ) {
return null;
}
if( Array.isArray( property ) ) {
return this.parsePropertyDescription( property[ 0 ] );
}
if( property.hasOwnProperty( TypeInfo.INFO_PROPERTY ) ) {
return property[ TypeInfo.INFO_PROPERTY ];
}
if( property.hasOwnProperty( "ref" ) ) {
// If the property is a reference to another type, store the name of the referenced type and mark the result as a complex type.
var result = {};
result[ TypeInfo.COMPLEX ] = property.ref;
return result;
}
return null;
};
/**
* Determine if a given property in a type is supposed to be hidden.
* Hidden properties are not communicated out of the application.
* @param {String} propertyName The name of the property.
* @param {String} [accessClass="user"] The access class for which to check the read-only attribute.
* @returns {Boolean} true if the property is marked as hidden; false otherwise.
*/
TypeInfo.prototype.isHidden = function TypeInfo$isHidden( propertyName, accessClass ) {
accessClass = ( "undefined" === typeof accessClass ) ? TypeInfo.USERCLASS_USER : accessClass;
return (
this.typeInfo.hasOwnProperty( propertyName ) &&
this.typeInfo[ propertyName ].hasOwnProperty( accessClass ) &&
-1 < this.typeInfo[ propertyName ][ accessClass ].indexOf( TypeInfo.HIDDEN )
);
};
/**
* Determine if a given property in a type is supposed to be read-only.
* Read-only properties are not persisted to the database when they enter the application.
* @param {String} propertyName The name of the property.
* @param {String} [accessClass="user"] The access class for which to check the read-only attribute.
* @returns {Boolean} true if the property is marked as read-only; false otherwise.
*/
TypeInfo.prototype.isReadOnly = function TypeInfo$isReadOnly( propertyName, accessClass ) {
// Concealed properties are always read-only.
if( this.isConcealed( propertyName, accessClass ) ) {
return true;
}
accessClass = ( "undefined" === typeof accessClass ) ? TypeInfo.USERCLASS_USER : accessClass;
return (
this.typeInfo.hasOwnProperty( propertyName ) &&
this.typeInfo[ propertyName ].hasOwnProperty( accessClass ) &&
-1 < this.typeInfo[ propertyName ][ accessClass ].indexOf( TypeInfo.READ_ONLY )
);
};
/**
* Determine if a given property in a type is supposed to be concealed.
* Concealed properties are supposed to have their values replaced before leaving the application.
* Concealed properties are inherently read-only!
* @param {String} propertyName The name of the property.
* @param {String} [accessClass="user"] The access class for which to check the read-only attribute.
* @returns {Boolean} true if the property is marked as read-only; false otherwise.
*/
TypeInfo.prototype.isConcealed = function TypeInfo$isConcealed( propertyName, accessClass ) {
accessClass = ( "undefined" === typeof accessClass ) ? TypeInfo.USERCLASS_USER : accessClass;
return (
this.typeInfo.hasOwnProperty( propertyName ) &&
this.typeInfo[ propertyName ].hasOwnProperty( accessClass ) &&
-1 < this.typeInfo[ propertyName ][ accessClass ].indexOf( TypeInfo.CONCEALED )
);
};
/**
* Determine if the given property is marked as complex (references another type).
* @param {String} propertyName The name of the property to check.
* @returns {boolean} true if the property is complex; false otherwise.
*/
TypeInfo.prototype.isComplex = function TypeInfo$isComplex( propertyName ) {
return (
this.typeInfo.hasOwnProperty( propertyName ) &&
this.typeInfo[ propertyName ].hasOwnProperty( TypeInfo.COMPLEX )
);
};
/**
* Returns the complex (referenced) type.
* Assumes that the given property is marked as complex.
* @param {String} propertyName The name of the property that contains the complex type reference.
* @returns {*}
*/
TypeInfo.prototype.complex = function TypeInfo$complex( propertyName ) {
return this.typeInfo[ propertyName ][ TypeInfo.COMPLEX ];
};
/**
* Mark a property as complex, regardless of the parsed type description.
* @param {String} propertyName The property to mark as complex.
* @param {*} complexType The complex type.
*/
TypeInfo.prototype.markComplex = function TypeInfo$markComplex( propertyName, complexType ) {
if( !complexType ) {
throw new InfoError( "You need to provide the name of the complex type." );
}
this.typeInfo[ propertyName ] = this.typeInfo[ propertyName ] || {};
this.typeInfo[ propertyName ][ TypeInfo.COMPLEX ] = complexType;
};
/**
* The name of the property in which decoration data will be stored.
* @type {string}
*/
TypeInfo.INFO_PROPERTY = "__info";
/**
* The read-only attribute.
* @type {string}
*/
TypeInfo.READ_ONLY = "readonly";
/**
* The hidden attribute.
* @type {string}
*/
TypeInfo.HIDDEN = "hidden";
/**
* The complex marker.
* This marker is used internally when a property is a reference to another type.
* @type {string}
*/
TypeInfo.COMPLEX = "complex";
/**
* The concealed attribute.
* @type {string}
*/
TypeInfo.CONCEALED = "concealed";
/**
* The access class for a user of the application.
* @type {string}
*/
TypeInfo.USERCLASS_USER = "user";
/**
* The access class for users of the admin area.
* @type {string}
*/
TypeInfo.USERCLASS_ADMIN = "admin";
/**
* An array into which types can (and should) store their type-related data.
* The data stored in it should be identical to the object that is return by require()ing the module of the type.
* This array is intended as a convenience lookup by a type name.
* @type {Object}
*/
TypeInfo.types = {};