Comparing version 1.0.2 to 1.0.3
{ | ||
"optOut": false, | ||
"lastUpdateCheck": 1665021956542 | ||
"lastUpdateCheck": 1665680610935 | ||
} |
{ | ||
"name": "manyfest", | ||
"version": "1.0.2", | ||
"version": "1.0.3", | ||
"description": "JSON Object Manifest for Data Description and Parsing", | ||
@@ -10,3 +10,4 @@ "main": "source/Manyfest.js", | ||
"test": "./node_modules/mocha/bin/_mocha -u tdd -R spec", | ||
"tests": "./node_modules/mocha/bin/_mocha -u tdd -R spec --grep" | ||
"tests": "./node_modules/mocha/bin/_mocha -u tdd -R spec --grep", | ||
"coverage": "nyc npm run test && nyc report --reporter=lcov" | ||
}, | ||
@@ -13,0 +14,0 @@ "repository": { |
@@ -49,3 +49,3 @@ # Manyfest | ||
{ | ||
"IDAnimal": { "Name":"Database ID", "Description":"The unique integer-based database identifier for an Animal record.", "DataType":"Integer" }, | ||
"IDAnimal": { "Name":"Database ID", "Description":"The unique integer-based database identifier for an Animal record.", "DataType":"Integer", "Default":0 }, | ||
"Name": { "Description":"The animal's colloquial species name (e.g. Rabbit, Dog, Bear, Mongoose)." }, | ||
@@ -74,12 +74,13 @@ "Type": { "Description":"Whether or not the animal is wild, domesticated, agricultural, in a research lab or a part of a zoo.." } | ||
| Term | Description | | ||
| Scope | The scope of this representation; generally the clustered or parent record name (e.g. Animal, User, Transaction, etc.) -- does not have functional purpose; only for information and logging. | | ||
| Schema | The stateful representation of an object's structural definition. | | ||
| Element | A defined element of data in an object. | | ||
| Address | The address where that data lies in the object. | | ||
| Descriptor | A description of an element including data such as Name, NameShort, Hash, Description, and other important properties. | | ||
| Name | The name of the element. Meant to be the most succinct human readable name possible. | | ||
| NameShort | A shorter name for the element. Meant to be useful enough to identify the property in log lines, tabular views, graphs and anywhere where we don't always want to see the full name. | | ||
| Description | A description for the element. Very useful when consuming other APIs with their own terse naming standards (or no naming standards)! | | ||
| Hash | A unique within this scope string-based key for this element. Used for easy access of data. | | ||
| Constraint | A validation constraint for an element such as MaxLength, MinLength, Required, Default and such. | | ||
| ---- | ----------- | | ||
Scope | The scope of this representation; generally the clustered or parent record name (e.g. Animal, User, Transaction, etc.) -- does not have functional purpose; only for information and logging. | ||
Schema | The stateful representation of an object's structural definition. | ||
Element | A defined element of data in an object. | ||
Address | The address where that data lies in the object. | ||
Descriptor | A description of an element including data such as Name, NameShort, Hash, Description, and other important properties. | ||
Name | The name of the element. Meant to be the most succinct human readable name possible. | ||
NameShort | A shorter name for the element. Meant to be useful enough to identify the property in log lines, tabular views, graphs and anywhere where we don't always want to see the full name. | ||
Description | A description for the element. Very useful when consuming other APIs with their own terse naming standards (or no naming standards)! | ||
Hash | A unique within this scope string-based key for this element. Used for easy access of data. | ||
Required | Set to true if this element is required. | ||
@@ -113,2 +114,3 @@ ## A More Advanced Schema Example | ||
"Hash":"ComfET", | ||
"DataType":"Float", | ||
"Description":"The most comfortable temperature for this animal to survive in." | ||
@@ -124,2 +126,18 @@ } | ||
### Data Types | ||
| Type | Description | | ||
| ---- | ----------- | | ||
String | A pretty basic string | ||
Integer | An integer number | ||
Float | A floating point number; does not require a decimal point | ||
Number | A number of any type | ||
Boolean | A boolean value represented by the JSON true or false | ||
Binary | A boolean value represented as 1 or 0 | ||
YesNo | A boolean value represented as Y or N | ||
DateTime | A javascript date | ||
Array | A plain old javascript array | ||
Object | A plain old javascript object | ||
Null | A null value | ||
## Reading and Writing Element Properties | ||
@@ -126,0 +144,0 @@ |
@@ -16,5 +16,5 @@ /** | ||
if (pLogObject) console.log(JSON.stringify(tmpLogObject,null,4)+"\n"); | ||
if (pLogObject) console.log(JSON.stringify(pLogObject)); | ||
}; | ||
module.exports = logToConsole; |
@@ -6,2 +6,3 @@ /** | ||
let libSimpleLog = require('./Manyfest-LogToConsole.js'); | ||
let libObjectAddressResolver = require('./Manyfest-ObjectAddressResolver.js'); | ||
@@ -15,3 +16,3 @@ /** | ||
{ | ||
constructor(pManifest, pInfoLog, pErrorLog) | ||
constructor(pManifest, pInfoLog, pErrorLog, pOptions) | ||
{ | ||
@@ -22,5 +23,21 @@ // Wire in logging | ||
// Create an object address resolver and map in the functions | ||
this.objectAddressResolver = new libObjectAddressResolver(this.logInfo, this.logError); | ||
this.options = ( | ||
{ | ||
strict: false | ||
strict: false, | ||
defaultValues: | ||
{ | ||
"String": "", | ||
"Number": 0, | ||
"Float": 0.0, | ||
"Integer": 0, | ||
"Boolean": false, | ||
"Binary": 0, | ||
"DateTime": 0, | ||
"Array": [], | ||
"Object": {}, | ||
"Null": null | ||
} | ||
}); | ||
@@ -78,3 +95,3 @@ | ||
{ | ||
this.logError(`(${this.scope}) Error loading scope from manifest; expecting a string but property was type ${typeof(pManifest.Scope)}.`); | ||
this.logError(`(${this.scope}) Error loading scope from manifest; expecting a string but property was type ${typeof(pManifest.Scope)}.`, pManifest); | ||
} | ||
@@ -84,3 +101,3 @@ } | ||
{ | ||
this.logError(`(${this.scope}) Error loading scope from manifest object. Property "Scope" does not exist in the root of the object.`); | ||
this.logError(`(${this.scope}) Error loading scope from manifest object. Property "Scope" does not exist in the root of the object.`, pManifest); | ||
} | ||
@@ -100,3 +117,3 @@ | ||
{ | ||
this.logError(`(${this.scope}) Error loading description object from manifest object. Expecting an object in 'Manifest.Descriptors' but the property was type ${typeof(pManifest.Description)}.`); | ||
this.logError(`(${this.scope}) Error loading description object from manifest object. Expecting an object in 'Manifest.Descriptors' but the property was type ${typeof(pManifest.Descriptors)}.`, pManifest); | ||
} | ||
@@ -106,3 +123,3 @@ } | ||
{ | ||
this.logError(`(${this.scope}) Error loading object description from manifest object. Property "Descriptors" does not exist in the root of the object.`); | ||
this.logError(`(${this.scope}) Error loading object description from manifest object. Property "Descriptors" does not exist in the root of the Manifest object.`, pManifest); | ||
} | ||
@@ -185,12 +202,12 @@ } | ||
*/ | ||
// Get the value of an element by its hash | ||
getValueByHash (pObject, pHash) | ||
// Check if an element exists by its hash | ||
checkAddressExistsByHash (pObject, pHash) | ||
{ | ||
if (this.elementHashes.hasOwnProperty(pHash)) | ||
{ | ||
return this.getValueAtAddress(pObject, this.elementHashes[pHash]); | ||
return this.checkAddressExists(pObject, this.elementHashes[pHash]); | ||
} | ||
else | ||
{ | ||
this.logError(`(${this.scope}) Error in getValueByHash; the Hash ${pHash} doesn't exist in the schema.`); | ||
this.logError(`(${this.scope}) Error in checkAddressExistsByHash; the Hash ${pHash} doesn't exist in the schema.`); | ||
return undefined; | ||
@@ -200,12 +217,20 @@ } | ||
// Check if an element exists at an address | ||
checkAddressExists (pObject, pAddress) | ||
{ | ||
return this.objectAddressResolver.checkAddressExists(pObject, pAddress); | ||
} | ||
cleanWrapCharacters (pCharacter, pString) | ||
// Get the value of an element by its hash | ||
getValueByHash (pObject, pHash) | ||
{ | ||
if (pString.startsWith(pCharacter) && pString.endsWith(pCharacter)) | ||
if (this.elementHashes.hasOwnProperty(pHash)) | ||
{ | ||
return pString.substring(1, pString.length - 1); | ||
return this.getValueAtAddress(pObject, this.elementHashes[pHash]); | ||
} | ||
else | ||
{ | ||
return pString; | ||
this.logError(`(${this.scope}) Error in getValueByHash; the Hash ${pHash} doesn't exist in the schema.`); | ||
return undefined; | ||
} | ||
@@ -217,167 +242,3 @@ } | ||
{ | ||
// Make sure pObject is an object | ||
if (!typeof(pObject) === 'object') return undefined; | ||
// Make sure pAddress is a string | ||
if (!typeof(pAddress) === 'string') return undefined; | ||
// TODO: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"] | ||
let tmpSeparatorIndex = pAddress.indexOf('.'); | ||
// This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow) | ||
if (tmpSeparatorIndex === -1) | ||
{ | ||
// Check if it's a boxed property | ||
let tmpBracketStartIndex = pAddress.indexOf('['); | ||
let tmpBracketStopIndex = pAddress.indexOf(']'); | ||
// Boxed elements look like this: | ||
// MyValues[10] | ||
// MyValues['Name'] | ||
// MyValues["Age"] | ||
// MyValues[`Cost`] | ||
// | ||
// When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name | ||
// The requirements to detect a boxed element are: | ||
// 1) The start bracket is after character 0 | ||
if ((tmpBracketStartIndex > 0) | ||
// 2) The end bracket has something between them | ||
&& (tmpBracketStopIndex > tmpBracketStartIndex) | ||
// 3) There is data | ||
&& (tmpBracketStopIndex - tmpBracketStartIndex > 0)) | ||
{ | ||
// The "Name" of the Object contained too the left of the bracket | ||
let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim(); | ||
// If the subproperty doesn't test as a proper Object, none of the rest of this is possible. | ||
// This is a rare case where Arrays testing as Objects is useful | ||
if (typeof(pObject[tmpBoxedPropertyName]) !== 'object') | ||
{ | ||
return undefined; | ||
} | ||
// The "Reference" to the property within it, either an array element or object property | ||
let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim(); | ||
// Attempt to parse the reference as a number, which will be used as an array element | ||
let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10); | ||
// Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined. | ||
// This seems confusing to me at first read, so explaination: | ||
// Is the Boxed Object an Array? TRUE | ||
// And is the Reference inside the boxed Object not a number? TRUE | ||
// --> So when these are in agreement, it's an impossible access state | ||
if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber)) | ||
{ | ||
return undefined; | ||
} | ||
// 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element, | ||
// otherwise we will try to treat it as a dynamic object property. | ||
if (isNaN(tmpBoxedPropertyNumber)) | ||
{ | ||
// This isn't a number ... let's treat it as a dynamic object property. | ||
// We would expect the property to be wrapped in some kind of quotes so strip them | ||
tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference); | ||
tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference); | ||
tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference); | ||
// Return the value in the property | ||
return pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference]; | ||
} | ||
else | ||
{ | ||
return pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber]; | ||
} | ||
} | ||
else | ||
{ | ||
// Now is the point in recursion to return the value in the address | ||
return pObject[pAddress]; | ||
} | ||
} | ||
else | ||
{ | ||
let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex); | ||
let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1); | ||
// Test if the tmpNewAddress is an array or object | ||
// Check if it's a boxed property | ||
let tmpBracketStartIndex = tmpSubObjectName.indexOf('['); | ||
let tmpBracketStopIndex = tmpSubObjectName.indexOf(']'); | ||
// Boxed elements look like this: | ||
// MyValues[42] | ||
// MyValues['Color'] | ||
// MyValues["Weight"] | ||
// MyValues[`Diameter`] | ||
// | ||
// When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name | ||
// The requirements to detect a boxed element are: | ||
// 1) The start bracket is after character 0 | ||
if ((tmpBracketStartIndex > 0) | ||
// 2) The end bracket has something between them | ||
&& (tmpBracketStopIndex > tmpBracketStartIndex) | ||
// 3) There is data | ||
&& (tmpBracketStopIndex - tmpBracketStartIndex > 0)) | ||
{ | ||
let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim(); | ||
let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim(); | ||
let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10); | ||
// Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined. | ||
// This seems confusing to me at first read, so explaination: | ||
// Is the Boxed Object an Array? TRUE | ||
// And is the Reference inside the boxed Object not a number? TRUE | ||
// --> So when these are in agreement, it's an impossible access state | ||
// This could be a failure in the recursion chain because they passed something like this in: | ||
// StudentData.Sections.Algebra.Students[1].Tardy | ||
// BUT | ||
// StudentData.Sections.Algebra.Students[1] is an object, so the .Tardy is not possible to access | ||
// This could be a failure in the recursion chain because they passed something like this in: | ||
// StudentData.Sections.Algebra.Students["JaneDoe"].Grade | ||
// BUT | ||
// StudentData.Sections.Algebra.Students["JaneDoe"] is an array, so the .Grade is not possible to access | ||
// TODO: Should this be an error or something? Should we keep a log of failures like this? | ||
if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber)) | ||
{ | ||
return undefined; | ||
} | ||
//This is a bracketed value | ||
// 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element, | ||
// otherwise we will try to reat it as a dynamic object property. | ||
if (isNaN(tmpBoxedPropertyNumber)) | ||
{ | ||
// This isn't a number ... let's treat it as a dynanmic object property. | ||
tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference); | ||
tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference); | ||
tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference); | ||
// Recurse directly into the subobject | ||
return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress); | ||
} | ||
else | ||
{ | ||
// We parsed a valid number out of the boxed property name, so recurse into the array | ||
return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress); | ||
} | ||
} | ||
// If there is an object property already named for the sub object, but it isn't an object | ||
// then the system can't set the value in there. Error and abort! | ||
if (pObject.hasOwnProperty(tmpSubObjectName) && typeof(pObject[tmpSubObjectName]) !== 'object') | ||
{ | ||
return undefined; | ||
} | ||
else if (pObject.hasOwnProperty(tmpSubObjectName)) | ||
{ | ||
// If there is already a subobject pass that to the recursive thingy | ||
return this.getValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress); | ||
} | ||
else | ||
{ | ||
// Create a subobject and then pass that | ||
pObject[tmpSubObjectName] = {}; | ||
return this.getValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress); | ||
} | ||
} | ||
return this.objectAddressResolver.getValueAtAddress(pObject, pAddress); | ||
} | ||
@@ -399,75 +260,9 @@ | ||
// Set the value of an element at an address | ||
setValueAtAddress (pObject, pAddress, pValue) | ||
{ | ||
// Make sure pObject is an object | ||
if (!typeof(pObject) === 'object') return false; | ||
// Make sure pAddress is a string | ||
if (!typeof(pAddress) === 'string') return false; | ||
let tmpSeparatorIndex = pAddress.indexOf('.'); | ||
if (tmpSeparatorIndex === -1) | ||
{ | ||
// Now is the time to set the value in the object | ||
pObject[pAddress] = pValue; | ||
return true; | ||
} | ||
else | ||
{ | ||
let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex); | ||
let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1); | ||
// If there is an object property already named for the sub object, but it isn't an object | ||
// then the system can't set the value in there. Error and abort! | ||
if (pObject.hasOwnProperty(tmpSubObjectName) && typeof(pObject[tmpSubObjectName]) !== 'object') | ||
{ | ||
if (!pObject.hasOwnProperty('__ERROR')) | ||
pObject['__ERROR'] = {}; | ||
// Put it in an error object so data isn't lost | ||
pObject['__ERROR'][pAddress] = pValue; | ||
return false; | ||
} | ||
else if (pObject.hasOwnProperty(tmpSubObjectName)) | ||
{ | ||
// If there is already a subobject pass that to the recursive thingy | ||
return this.setValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, pValue); | ||
} | ||
else | ||
{ | ||
// Create a subobject and then pass that | ||
pObject[tmpSubObjectName] = {}; | ||
return this.setValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, pValue); | ||
} | ||
} | ||
return this.objectAddressResolver.setValueAtAddress(pObject, pAddress, pValue); | ||
} | ||
setValueAtAddressInContainer(pRecordObject, pFormContainerAddress, pFormContainerIndex, pFormValueAddress, pFormValue) | ||
{ | ||
// First see if there *is* a container object | ||
let tmpContainerObject = this.getValueAtAddress(pRecordObject, pFormContainerAddress); | ||
if (typeof(pFormContainerAddress) !== 'string') return false; | ||
let tmpFormContainerIndex = parseInt(pFormContainerIndex, 10); | ||
if (isNaN(tmpFormContainerIndex)) return false; | ||
if ((typeof(tmpContainerObject) !== 'object') || (!Array.isArray(tmpContainerObject))) | ||
{ | ||
// Check if there is a value here and we want to store it in the "__OverwrittenData" thing | ||
tmpContainerObject = []; | ||
this.setValueAtAddress(pRecordObject, pFormContainerAddress, tmpContainerObject); | ||
} | ||
for (let i = 0; (tmpContainerObject.length + i) <= (tmpFormContainerIndex+1); i++) | ||
{ | ||
// Add objects to this container until it has enough | ||
tmpContainerObject.push({}); | ||
} | ||
// Now set the value *in* the container object | ||
return this.setValueAtAddress(tmpContainerObject[tmpFormContainerIndex], pFormValueAddress, pFormValue); | ||
} | ||
// Validate the consistency of an object against the schema | ||
@@ -489,3 +284,9 @@ validate(pObject) | ||
// Now enumerate through the values and check for anomalies | ||
let addValidationError = (pAddress, pErrorMessage) => | ||
{ | ||
tmpValidationData.Error = true; | ||
tmpValidationData.Errors.push(`Element at address "${pAddress}" ${pErrorMessage}.`); | ||
}; | ||
// Now enumerate through the values and check for anomalies based on the schema | ||
for (let i = 0; i < this.elementAddresses.length; i++) | ||
@@ -498,10 +299,71 @@ { | ||
{ | ||
// This will technically mean that `Object.Some.Value = undefined` will end up showing as "missing" | ||
// TODO: Do we want to do a different message based on if the property exists but is undefined? | ||
tmpValidationData.MissingElements.push(tmpDescriptor.Address); | ||
if (tmpDescriptor.Required || this.options.strict) | ||
{ | ||
tmpValidationData.Error = true; | ||
tmpValidationData.Errors.push(`Element at address '${tmpDescriptor.Address}' is flagged Required but is not present.`); | ||
addValidationError(tmpDescriptor.Address, 'is flagged REQUIRED but is not set in the object'); | ||
} | ||
} | ||
// Now see if there is a data type specified for this element | ||
if (tmpDescriptor.DataType) | ||
{ | ||
let tmpElementType = typeof(tmpValue); | ||
switch(tmpDescriptor.DataType.toString().trim().toLowerCase()) | ||
{ | ||
case 'string': | ||
if (tmpElementType != 'string') | ||
{ | ||
addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is of the type ${tmpElementType}`); | ||
} | ||
break; | ||
case 'number': | ||
if (tmpElementType != 'number') | ||
{ | ||
addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is of the type ${tmpElementType}`); | ||
} | ||
break; | ||
case 'integer': | ||
if (tmpElementType != 'number') | ||
{ | ||
addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is of the type ${tmpElementType}`); | ||
} | ||
else | ||
{ | ||
let tmpValueString = tmpValue.toString(); | ||
if (tmpValueString.indexOf('.') > -1) | ||
{ | ||
// TODO: Is this an error? | ||
addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but has a decimal point in the number.`); | ||
} | ||
} | ||
break; | ||
case 'float': | ||
if (tmpElementType != 'number') | ||
{ | ||
addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is of the type ${tmpElementType}`); | ||
} | ||
break; | ||
case 'DateTime': | ||
let tmpValueDate = new Date(tmpValue); | ||
if (tmpValueDate.toString() == 'Invalid Date') | ||
{ | ||
addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is not parsable as a Date by Javascript`); | ||
} | ||
default: | ||
// Check if this is a string, in the default case | ||
// Note this is only when a DataType is specified and it is an unrecognized data type. | ||
if (tmpElementType != 'string') | ||
{ | ||
addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} (which auto-converted to String because it was unrecognized) but is of the type ${tmpElementType}`); | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
@@ -511,4 +373,69 @@ | ||
} | ||
// Returns a default value, or, the default value for the data type (which is overridable with configuration) | ||
getDefaultValue(pDescriptor) | ||
{ | ||
if (pDescriptor.hasOwnProperty('Default')) | ||
{ | ||
return pDescriptor.Default; | ||
} | ||
else | ||
{ | ||
// Default to a null if it doesn't have a type specified. | ||
// This will ensure a placeholder is created but isn't misinterpreted. | ||
let tmpDataType = (pDescriptor.hasOwnProperty('DataType')) ? pDescriptor.DataType : 'String'; | ||
if (this.options.defaultValues.hasOwnProperty(tmpDataType)) | ||
{ | ||
return this.options.defaultValues[tmpDataType]; | ||
} | ||
else | ||
{ | ||
// give up and return null | ||
return null; | ||
} | ||
} | ||
} | ||
// Enumerate through the schema and populate default values if they don't exist. | ||
populateDefaults(pObject, pOverwriteProperties) | ||
{ | ||
return this.populateObject(pObject, pOverwriteProperties, | ||
// This just sets up a simple filter to see if there is a default set. | ||
(pDescriptor) => | ||
{ | ||
return pDescriptor.hasOwnProperty('Default'); | ||
}); | ||
} | ||
// Forcefully populate all values even if they don't have defaults. | ||
// Based on type, this can do unexpected things. | ||
populateObject(pObject, pOverwriteProperties, fFilter) | ||
{ | ||
// Automatically create an object if one isn't passed in. | ||
let tmpObject = (typeof(pObject) === 'object') ? pObject : {}; | ||
// Default to *NOT OVERWRITING* properties | ||
let tmpOverwriteProperties = (typeof(pOverwriteProperties) == 'undefined') ? false : pOverwriteProperties; | ||
// This is a filter function, which is passed the schema and allows complex filtering of population | ||
// The default filter function just returns true, populating everything. | ||
let tmpFilterFunction = (typeof(fFilter) == 'function') ? fFilter : (pDescriptor) => { return true; }; | ||
this.elementAddresses.forEach( | ||
(pAddress) => | ||
{ | ||
let tmpDescriptor = this.getDescriptor(pAddress); | ||
// Check the filter function to see if this is an address we want to set the value for. | ||
if (tmpFilterFunction(tmpDescriptor)) | ||
{ | ||
// If we are overwriting properties OR the property does not exist | ||
if (tmpOverwriteProperties || !this.checkAddressExists(tmpObject, pAddress)) | ||
{ | ||
this.setValueAtAddress(tmpObject, pAddress, this.getDefaultValue(tmpDescriptor)); | ||
} | ||
} | ||
}); | ||
return tmpObject; | ||
} | ||
}; | ||
module.exports = Manyfest; |
@@ -39,2 +39,13 @@ /** | ||
( | ||
'The class should print an error message with a bad manifest.', | ||
(fTestComplete)=> | ||
{ | ||
let _Manyfest = new libManyfest({Scope:'BadManifest', Descriptors:'BadDescriptors'}); | ||
Expect(_Manyfest) | ||
.to.be.an('object', 'Manyfest should initialize as an object with no parameters.'); | ||
fTestComplete(); | ||
} | ||
); | ||
test | ||
( | ||
'Default properties should be automatically set.', | ||
@@ -51,2 +62,40 @@ (fTestComplete)=> | ||
); | ||
test | ||
( | ||
'Exercise the default logging.', | ||
(fTestComplete)=> | ||
{ | ||
let _Manyfest = new libManyfest(); | ||
_Manyfest.logError('Error...'); | ||
_Manyfest.logInfo('Info...'); | ||
_Manyfest.logInfo(); | ||
fTestComplete(); | ||
} | ||
); | ||
test | ||
( | ||
'Pass in a custom logger.', | ||
(fTestComplete)=> | ||
{ | ||
let tmpLogState = []; | ||
let fWriteLog = (pLogLine, pLogObject) => | ||
{ | ||
tmpLogState.push(pLogLine); | ||
}; | ||
let _Manyfest = new libManyfest(undefined, fWriteLog, fWriteLog); | ||
_Manyfest.logError('Error...'); | ||
Expect(tmpLogState.length) | ||
.to.equal(1); | ||
Expect(tmpLogState[0]) | ||
.to.equal('Error...'); | ||
_Manyfest.logInfo('Info...'); | ||
_Manyfest.logInfo(); | ||
Expect(tmpLogState.length) | ||
.to.equal(3); | ||
fTestComplete(); | ||
} | ||
); | ||
} | ||
@@ -53,0 +102,0 @@ ); |
197973
36
2906
579