manyfest 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,9 @@
3
3
  * @author <steven@velozo.com>
4
4
  */
5
5
  let libSimpleLog = require('./Manyfest-LogToConsole.js');
6
+ let libObjectAddressResolver = require('./Manyfest-ObjectAddressResolver.js');
7
+ let libHashTranslation = require('./Manyfest-HashTranslation.js');
8
+ let libSchemaManipulation = require('./Manyfest-SchemaManipulation.js');
6
9
 
7
10
  /**
8
11
  * Manyfest object address-based descriptions and manipulations.
@@ -11,15 +14,31 @@ let libSimpleLog = require('./Manyfest-LogToConsole.js');
11
14
  */
12
15
  class Manyfest
13
16
  {
14
- constructor(pManifest, pInfoLog, pErrorLog)
17
+ constructor(pManifest, pInfoLog, pErrorLog, pOptions)
15
18
  {
16
19
  // Wire in logging
17
20
  this.logInfo = (typeof(pInfoLog) === 'function') ? pInfoLog : libSimpleLog;
18
21
  this.logError = (typeof(pErrorLog) === 'function') ? pErrorLog : libSimpleLog;
19
22
 
23
+ // Create an object address resolver and map in the functions
24
+ this.objectAddressResolver = new libObjectAddressResolver(this.logInfo, this.logError);
25
+
20
26
  this.options = (
21
27
  {
22
- strict: false
28
+ strict: false,
29
+ defaultValues:
30
+ {
31
+ "String": "",
32
+ "Number": 0,
33
+ "Float": 0.0,
34
+ "Integer": 0,
35
+ "Boolean": false,
36
+ "Binary": 0,
37
+ "DateTime": 0,
38
+ "Array": [],
39
+ "Object": {},
40
+ "Null": null
41
+ }
23
42
  });
24
43
 
25
44
  this.scope = undefined;
@@ -33,6 +52,10 @@ class Manyfest
33
52
  {
34
53
  this.loadManifest(pManifest);
35
54
  }
55
+
56
+ this.schemaManipulations = new libSchemaManipulation(this.logInfo, this.logError);
57
+
58
+ this.hashTranslations = new libHashTranslation(this.logInfo, this.logError);
36
59
  }
37
60
 
38
61
  /*************************************************************************
@@ -72,12 +95,12 @@ class Manyfest
72
95
  }
73
96
  else
74
97
  {
75
- this.logError(`(${this.scope}) Error loading scope from manifest; expecting a string but property was type ${typeof(pManifest.Scope)}.`);
98
+ this.logError(`(${this.scope}) Error loading scope from manifest; expecting a string but property was type ${typeof(pManifest.Scope)}.`, pManifest);
76
99
  }
77
100
  }
78
101
  else
79
102
  {
80
- this.logError(`(${this.scope}) Error loading scope from manifest object. Property "Scope" does not exist in the root of the object.`);
103
+ this.logError(`(${this.scope}) Error loading scope from manifest object. Property "Scope" does not exist in the root of the object.`, pManifest);
81
104
  }
82
105
 
83
106
  if (pManifest.hasOwnProperty('Descriptors'))
@@ -92,12 +115,12 @@ class Manyfest
92
115
  }
93
116
  else
94
117
  {
95
- 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)}.`);
118
+ 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);
96
119
  }
97
120
  }
98
121
  else
99
122
  {
100
- this.logError(`(${this.scope}) Error loading object description from manifest object. Property "Descriptors" does not exist in the root of the object.`);
123
+ this.logError(`(${this.scope}) Error loading object description from manifest object. Property "Descriptors" does not exist in the root of the Manifest object.`, pManifest);
101
124
  }
102
125
  }
103
126
 
@@ -136,14 +159,18 @@ class Manyfest
136
159
  this.elementDescriptors[pAddress] = pDescriptor;
137
160
 
138
161
  // Always add the address as a hash
139
- // TODO: Check if this is a good idea or not.
140
- // Collisions are bound to happen with both representations of the address/hash in here.
141
162
  this.elementHashes[pAddress] = pAddress;
142
163
 
143
164
  if (pDescriptor.hasOwnProperty('Hash'))
144
165
  {
166
+ // TODO: Check if this is a good idea or not..
167
+ // Collisions are bound to happen with both representations of the address/hash in here and developers being able to create their own hashes.
145
168
  this.elementHashes[pDescriptor.Hash] = pAddress;
146
169
  }
170
+ else
171
+ {
172
+ pDescriptor.Hash = pAddress;
173
+ }
147
174
 
148
175
  return true;
149
176
  }
@@ -156,9 +183,9 @@ class Manyfest
156
183
 
157
184
  getDescriptorByHash(pHash)
158
185
  {
159
- if (this.elementHashes.hasOwnProperty(pHash))
186
+ if (this.elementHashes.hasOwnProperty(pHash) || this.hashTranslations.translationTable.hasOwnProperty(pHash))
160
187
  {
161
- return this.getDescriptor(this.elementHashes[pHash]);
188
+ return this.getDescriptor(this.elementHashes[this.hashTranslations.translate(pHash)]);
162
189
  }
163
190
  else
164
191
  {
@@ -175,209 +202,53 @@ class Manyfest
175
202
  /*************************************************************************
176
203
  * Beginning of Object Manipulation (read & write) Functions
177
204
  */
178
- // Get the value of an element by its hash
179
- getValueByHash (pObject, pHash)
205
+ // Check if an element exists by its hash
206
+ checkAddressExistsByHash (pObject, pHash)
180
207
  {
181
- if (this.elementHashes.hasOwnProperty(pHash))
208
+ if (this.elementHashes.hasOwnProperty(pHash) || this.hashTranslations.translationTable.hasOwnProperty(pHash))
182
209
  {
183
- return this.getValueAtAddress(pObject, this.elementHashes[pHash]);
210
+ return this.checkAddressExists(pObject, this.elementHashes[this.hashTranslations.translate(pHash)]);
184
211
  }
185
212
  else
186
213
  {
187
- this.logError(`(${this.scope}) Error in getValueByHash; the Hash ${pHash} doesn't exist in the schema.`);
214
+ this.logError(`(${this.scope}) Error in checkAddressExistsByHash; the Hash ${pHash} doesn't exist in the schema.`);
188
215
  return undefined;
189
216
  }
190
217
  }
191
218
 
219
+ // Check if an element exists at an address
220
+ checkAddressExists (pObject, pAddress)
221
+ {
222
+ return this.objectAddressResolver.checkAddressExists(pObject, pAddress);
223
+ }
224
+
192
225
 
193
- cleanWrapCharacters (pCharacter, pString)
226
+ // Get the value of an element by its hash
227
+ getValueByHash (pObject, pHash)
194
228
  {
195
- if (pString.startsWith(pCharacter) && pString.endsWith(pCharacter))
229
+ if (this.elementHashes.hasOwnProperty(pHash) || this.hashTranslations.translationTable.hasOwnProperty(pHash))
196
230
  {
197
- return pString.substring(1, pString.length - 1);
231
+ return this.getValueAtAddress(pObject, this.elementHashes[this.hashTranslations.translate(pHash)]);
198
232
  }
199
233
  else
200
234
  {
201
- return pString;
235
+ this.logError(`(${this.scope}) Error in getValueByHash; the Hash ${pHash} doesn't exist in the schema.`);
236
+ return undefined;
202
237
  }
203
238
  }
204
239
 
205
240
  // Get the value of an element at an address
206
241
  getValueAtAddress (pObject, pAddress)
207
242
  {
208
- // Make sure pObject is an object
209
- if (!typeof(pObject) === 'object') return undefined;
210
- // Make sure pAddress is a string
211
- if (!typeof(pAddress) === 'string') return undefined;
212
-
213
- // TODO: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"]
214
- let tmpSeparatorIndex = pAddress.indexOf('.');
215
-
216
- // This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow)
217
- if (tmpSeparatorIndex === -1)
218
- {
219
- // Check if it's a boxed property
220
- let tmpBracketStartIndex = pAddress.indexOf('[');
221
- let tmpBracketStopIndex = pAddress.indexOf(']');
222
- // Boxed elements look like this:
223
- // MyValues[10]
224
- // MyValues['Name']
225
- // MyValues["Age"]
226
- // MyValues[`Cost`]
227
- //
228
- // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
229
- // The requirements to detect a boxed element are:
230
- // 1) The start bracket is after character 0
231
- if ((tmpBracketStartIndex > 0)
232
- // 2) The end bracket has something between them
233
- && (tmpBracketStopIndex > tmpBracketStartIndex)
234
- // 3) There is data
235
- && (tmpBracketStopIndex - tmpBracketStartIndex > 0))
236
- {
237
- // The "Name" of the Object contained too the left of the bracket
238
- let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
239
-
240
- // If the subproperty doesn't test as a proper Object, none of the rest of this is possible.
241
- // This is a rare case where Arrays testing as Objects is useful
242
- if (typeof(pObject[tmpBoxedPropertyName]) !== 'object')
243
- {
244
- return undefined;
245
- }
246
-
247
- // The "Reference" to the property within it, either an array element or object property
248
- let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
249
- // Attempt to parse the reference as a number, which will be used as an array element
250
- let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
251
-
252
- // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
253
- // This seems confusing to me at first read, so explaination:
254
- // Is the Boxed Object an Array? TRUE
255
- // And is the Reference inside the boxed Object not a number? TRUE
256
- // --> So when these are in agreement, it's an impossible access state
257
- if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
258
- {
259
- return undefined;
260
- }
261
-
262
- // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
263
- // otherwise we will try to treat it as a dynamic object property.
264
- if (isNaN(tmpBoxedPropertyNumber))
265
- {
266
- // This isn't a number ... let's treat it as a dynamic object property.
267
- // We would expect the property to be wrapped in some kind of quotes so strip them
268
- tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
269
- tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
270
- tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
271
-
272
- // Return the value in the property
273
- return pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference];
274
- }
275
- else
276
- {
277
- return pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber];
278
- }
279
- }
280
- else
281
- {
282
- // Now is the point in recursion to return the value in the address
283
- return pObject[pAddress];
284
- }
285
- }
286
- else
287
- {
288
- let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
289
- let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1);
290
-
291
- // Test if the tmpNewAddress is an array or object
292
- // Check if it's a boxed property
293
- let tmpBracketStartIndex = tmpSubObjectName.indexOf('[');
294
- let tmpBracketStopIndex = tmpSubObjectName.indexOf(']');
295
- // Boxed elements look like this:
296
- // MyValues[42]
297
- // MyValues['Color']
298
- // MyValues["Weight"]
299
- // MyValues[`Diameter`]
300
- //
301
- // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
302
- // The requirements to detect a boxed element are:
303
- // 1) The start bracket is after character 0
304
- if ((tmpBracketStartIndex > 0)
305
- // 2) The end bracket has something between them
306
- && (tmpBracketStopIndex > tmpBracketStartIndex)
307
- // 3) There is data
308
- && (tmpBracketStopIndex - tmpBracketStartIndex > 0))
309
- {
310
- let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
311
-
312
- let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
313
-
314
- let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
315
-
316
- // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
317
- // This seems confusing to me at first read, so explaination:
318
- // Is the Boxed Object an Array? TRUE
319
- // And is the Reference inside the boxed Object not a number? TRUE
320
- // --> So when these are in agreement, it's an impossible access state
321
- // This could be a failure in the recursion chain because they passed something like this in:
322
- // StudentData.Sections.Algebra.Students[1].Tardy
323
- // BUT
324
- // StudentData.Sections.Algebra.Students[1] is an object, so the .Tardy is not possible to access
325
- // This could be a failure in the recursion chain because they passed something like this in:
326
- // StudentData.Sections.Algebra.Students["JaneDoe"].Grade
327
- // BUT
328
- // StudentData.Sections.Algebra.Students["JaneDoe"] is an array, so the .Grade is not possible to access
329
- // TODO: Should this be an error or something? Should we keep a log of failures like this?
330
- if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
331
- {
332
- return undefined;
333
- }
334
-
335
- //This is a bracketed value
336
- // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
337
- // otherwise we will try to reat it as a dynamic object property.
338
- if (isNaN(tmpBoxedPropertyNumber))
339
- {
340
- // This isn't a number ... let's treat it as a dynanmic object property.
341
- tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
342
- tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
343
- tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
344
-
345
- // Recurse directly into the subobject
346
- return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress);
347
- }
348
- else
349
- {
350
- // We parsed a valid number out of the boxed property name, so recurse into the array
351
- return this.getValueAtAddress(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress);
352
- }
353
- }
354
-
355
- // If there is an object property already named for the sub object, but it isn't an object
356
- // then the system can't set the value in there. Error and abort!
357
- if (pObject.hasOwnProperty(tmpSubObjectName) && typeof(pObject[tmpSubObjectName]) !== 'object')
358
- {
359
- return undefined;
360
- }
361
- else if (pObject.hasOwnProperty(tmpSubObjectName))
362
- {
363
- // If there is already a subobject pass that to the recursive thingy
364
- return this.getValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress);
365
- }
366
- else
367
- {
368
- // Create a subobject and then pass that
369
- pObject[tmpSubObjectName] = {};
370
- return this.getValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress);
371
- }
372
- }
243
+ return this.objectAddressResolver.getValueAtAddress(pObject, pAddress);
373
244
  }
374
245
 
375
246
  // Set the value of an element by its hash
376
247
  setValueByHash(pObject, pHash, pValue)
377
248
  {
378
- if (this.elementHashes.hasOwnProperty(pHash))
249
+ if (this.elementHashes.hasOwnProperty(pHash) || this.hashTranslations.translationTable.hasOwnProperty(pHash))
379
250
  {
380
- return this.setValueAtAddress(pObject, this.elementHashes[pHash], pValue);
251
+ return this.setValueAtAddress(pObject, this.elementHashes[this.hashTranslations.translate(pHash)], pValue);
381
252
  }
382
253
  else
383
254
  {
@@ -386,79 +257,13 @@ class Manyfest
386
257
  }
387
258
  }
388
259
 
260
+
389
261
  // Set the value of an element at an address
390
262
  setValueAtAddress (pObject, pAddress, pValue)
391
263
  {
392
- // Make sure pObject is an object
393
- if (!typeof(pObject) === 'object') return false;
394
- // Make sure pAddress is a string
395
- if (!typeof(pAddress) === 'string') return false;
396
-
397
- let tmpSeparatorIndex = pAddress.indexOf('.');
398
-
399
- if (tmpSeparatorIndex === -1)
400
- {
401
- // Now is the time to set the value in the object
402
- pObject[pAddress] = pValue;
403
- return true;
404
- }
405
- else
406
- {
407
- let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
408
- let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1);
409
-
410
- // If there is an object property already named for the sub object, but it isn't an object
411
- // then the system can't set the value in there. Error and abort!
412
- if (pObject.hasOwnProperty(tmpSubObjectName) && typeof(pObject[tmpSubObjectName]) !== 'object')
413
- {
414
- if (!pObject.hasOwnProperty('__ERROR'))
415
- pObject['__ERROR'] = {};
416
- // Put it in an error object so data isn't lost
417
- pObject['__ERROR'][pAddress] = pValue;
418
- return false;
419
- }
420
- else if (pObject.hasOwnProperty(tmpSubObjectName))
421
- {
422
- // If there is already a subobject pass that to the recursive thingy
423
- return this.setValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, pValue);
424
- }
425
- else
426
- {
427
- // Create a subobject and then pass that
428
- pObject[tmpSubObjectName] = {};
429
- return this.setValueAtAddress(pObject[tmpSubObjectName], tmpNewAddress, pValue);
430
- }
431
- }
264
+ return this.objectAddressResolver.setValueAtAddress(pObject, pAddress, pValue);
432
265
  }
433
266
 
434
- setValueAtAddressInContainer(pRecordObject, pFormContainerAddress, pFormContainerIndex, pFormValueAddress, pFormValue)
435
- {
436
- // First see if there *is* a container object
437
- let tmpContainerObject = this.getValueAtAddress(pRecordObject, pFormContainerAddress);
438
-
439
- if (typeof(pFormContainerAddress) !== 'string') return false;
440
-
441
- let tmpFormContainerIndex = parseInt(pFormContainerIndex, 10);
442
- if (isNaN(tmpFormContainerIndex)) return false;
443
-
444
- if ((typeof(tmpContainerObject) !== 'object') || (!Array.isArray(tmpContainerObject)))
445
- {
446
- // Check if there is a value here and we want to store it in the "__OverwrittenData" thing
447
- tmpContainerObject = [];
448
- this.setValueAtAddress(pRecordObject, pFormContainerAddress, tmpContainerObject);
449
- }
450
-
451
- for (let i = 0; (tmpContainerObject.length + i) <= (tmpFormContainerIndex+1); i++)
452
- {
453
- // Add objects to this container until it has enough
454
- tmpContainerObject.push({});
455
- }
456
-
457
- // Now set the value *in* the container object
458
- return this.setValueAtAddress(tmpContainerObject[tmpFormContainerIndex], pFormValueAddress, pFormValue);
459
- }
460
-
461
-
462
267
  // Validate the consistency of an object against the schema
463
268
  validate(pObject)
464
269
  {
@@ -475,7 +280,13 @@ class Manyfest
475
280
  tmpValidationData.Errors.push(`Expected passed in object to be type object but was passed in ${typeof(pObject)}`);
476
281
  }
477
282
 
478
- // Now enumerate through the values and check for anomalies
283
+ let addValidationError = (pAddress, pErrorMessage) =>
284
+ {
285
+ tmpValidationData.Error = true;
286
+ tmpValidationData.Errors.push(`Element at address "${pAddress}" ${pErrorMessage}.`);
287
+ };
288
+
289
+ // Now enumerate through the values and check for anomalies based on the schema
479
290
  for (let i = 0; i < this.elementAddresses.length; i++)
480
291
  {
481
292
  let tmpDescriptor = this.getDescriptor(this.elementAddresses[i]);
@@ -483,18 +294,144 @@ class Manyfest
483
294
 
484
295
  if (typeof(tmpValue) == 'undefined')
485
296
  {
297
+ // This will technically mean that `Object.Some.Value = undefined` will end up showing as "missing"
298
+ // TODO: Do we want to do a different message based on if the property exists but is undefined?
486
299
  tmpValidationData.MissingElements.push(tmpDescriptor.Address);
487
-
488
300
  if (tmpDescriptor.Required || this.options.strict)
489
301
  {
490
- tmpValidationData.Error = true;
491
- tmpValidationData.Errors.push(`Element at address '${tmpDescriptor.Address}' is flagged Required but is not present.`);
302
+ addValidationError(tmpDescriptor.Address, 'is flagged REQUIRED but is not set in the object');
303
+ }
304
+ }
305
+
306
+ // Now see if there is a data type specified for this element
307
+ if (tmpDescriptor.DataType)
308
+ {
309
+ let tmpElementType = typeof(tmpValue);
310
+ switch(tmpDescriptor.DataType.toString().trim().toLowerCase())
311
+ {
312
+ case 'string':
313
+ if (tmpElementType != 'string')
314
+ {
315
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is of the type ${tmpElementType}`);
316
+ }
317
+ break;
318
+
319
+ case 'number':
320
+ if (tmpElementType != 'number')
321
+ {
322
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is of the type ${tmpElementType}`);
323
+ }
324
+ break;
325
+
326
+ case 'integer':
327
+ if (tmpElementType != 'number')
328
+ {
329
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is of the type ${tmpElementType}`);
330
+ }
331
+ else
332
+ {
333
+ let tmpValueString = tmpValue.toString();
334
+ if (tmpValueString.indexOf('.') > -1)
335
+ {
336
+ // TODO: Is this an error?
337
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but has a decimal point in the number.`);
338
+ }
339
+ }
340
+ break;
341
+
342
+ case 'float':
343
+ if (tmpElementType != 'number')
344
+ {
345
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is of the type ${tmpElementType}`);
346
+ }
347
+ break;
348
+
349
+ case 'DateTime':
350
+ let tmpValueDate = new Date(tmpValue);
351
+ if (tmpValueDate.toString() == 'Invalid Date')
352
+ {
353
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} but is not parsable as a Date by Javascript`);
354
+ }
355
+
356
+ default:
357
+ // Check if this is a string, in the default case
358
+ // Note this is only when a DataType is specified and it is an unrecognized data type.
359
+ if (tmpElementType != 'string')
360
+ {
361
+ addValidationError(tmpDescriptor.Address, `has a DataType ${tmpDescriptor.DataType} (which auto-converted to String because it was unrecognized) but is of the type ${tmpElementType}`);
362
+ }
363
+ break;
492
364
  }
493
365
  }
494
366
  }
495
367
 
496
368
  return tmpValidationData;
497
369
  }
370
+
371
+ // Returns a default value, or, the default value for the data type (which is overridable with configuration)
372
+ getDefaultValue(pDescriptor)
373
+ {
374
+ if (pDescriptor.hasOwnProperty('Default'))
375
+ {
376
+ return pDescriptor.Default;
377
+ }
378
+ else
379
+ {
380
+ // Default to a null if it doesn't have a type specified.
381
+ // This will ensure a placeholder is created but isn't misinterpreted.
382
+ let tmpDataType = (pDescriptor.hasOwnProperty('DataType')) ? pDescriptor.DataType : 'String';
383
+ if (this.options.defaultValues.hasOwnProperty(tmpDataType))
384
+ {
385
+ return this.options.defaultValues[tmpDataType];
386
+ }
387
+ else
388
+ {
389
+ // give up and return null
390
+ return null;
391
+ }
392
+ }
393
+ }
394
+
395
+ // Enumerate through the schema and populate default values if they don't exist.
396
+ populateDefaults(pObject, pOverwriteProperties)
397
+ {
398
+ return this.populateObject(pObject, pOverwriteProperties,
399
+ // This just sets up a simple filter to see if there is a default set.
400
+ (pDescriptor) =>
401
+ {
402
+ return pDescriptor.hasOwnProperty('Default');
403
+ });
404
+ }
405
+
406
+ // Forcefully populate all values even if they don't have defaults.
407
+ // Based on type, this can do unexpected things.
408
+ populateObject(pObject, pOverwriteProperties, fFilter)
409
+ {
410
+ // Automatically create an object if one isn't passed in.
411
+ let tmpObject = (typeof(pObject) === 'object') ? pObject : {};
412
+ // Default to *NOT OVERWRITING* properties
413
+ let tmpOverwriteProperties = (typeof(pOverwriteProperties) == 'undefined') ? false : pOverwriteProperties;
414
+ // This is a filter function, which is passed the schema and allows complex filtering of population
415
+ // The default filter function just returns true, populating everything.
416
+ let tmpFilterFunction = (typeof(fFilter) == 'function') ? fFilter : (pDescriptor) => { return true; };
417
+
418
+ this.elementAddresses.forEach(
419
+ (pAddress) =>
420
+ {
421
+ let tmpDescriptor = this.getDescriptor(pAddress);
422
+ // Check the filter function to see if this is an address we want to set the value for.
423
+ if (tmpFilterFunction(tmpDescriptor))
424
+ {
425
+ // If we are overwriting properties OR the property does not exist
426
+ if (tmpOverwriteProperties || !this.checkAddressExists(tmpObject, pAddress))
427
+ {
428
+ this.setValueAtAddress(tmpObject, pAddress, this.getDefaultValue(tmpDescriptor));
429
+ }
430
+ }
431
+ });
432
+
433
+ return tmpObject;
434
+ }
498
435
  };
499
436
 
500
437
  module.exports = Manyfest;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Unit tests for Manyfest
3
+ *
4
+ * @license MIT
5
+ *
6
+ * @author Steven Velozo <steven@velozo.com>
7
+ */
8
+
9
+ var Chai = require("chai");
10
+ var Expect = Chai.expect;
11
+
12
+ let libManyfest = require('../source/Manyfest.js');
13
+
14
+ let _SampleDataArchiveOrgFrankenberry = require('./Data-Archive-org-Frankenberry.json');
15
+
16
+ suite
17
+ (
18
+ 'Manyfest Object Check Element Existence',
19
+ function()
20
+ {
21
+ setup (()=> {} );
22
+
23
+ suite
24
+ (
25
+ 'Basic Check Existence',
26
+ ()=>
27
+ {
28
+ test
29
+ (
30
+ 'It should be easy to check if an element exists by address.',
31
+ (fTestComplete)=>
32
+ {
33
+ let _Manyfest = new libManyfest({ Scope:'Archive.org', Descriptors: {'metadata.creator': {Name:'Creator', Hash:'Creator'}}});
34
+ // Property not in schema:
35
+ let tmpTitleExists = _Manyfest.checkAddressExists(_SampleDataArchiveOrgFrankenberry, 'metadata.title');
36
+ Expect(tmpTitleExists)
37
+ .to.equal(true);
38
+ fTestComplete();
39
+ }
40
+ );
41
+ test
42
+ (
43
+ 'If an element does not exist, checkAddressExists should return false.',
44
+ (fTestComplete)=>
45
+ {
46
+ let _Manyfest = new libManyfest({ Scope:'Archive.org', Descriptors: {'metadata.creator': {Name:'Creator', Hash:'Creator'}}});
47
+ // Property not in schema:
48
+ let tmpTitleExists = _Manyfest.checkAddressExists(_SampleDataArchiveOrgFrankenberry, 'metadata.someStrangeProperty');
49
+ Expect(tmpTitleExists)
50
+ .to.equal(false);
51
+ fTestComplete();
52
+ }
53
+ );
54
+ test
55
+ (
56
+ 'It should be trivial to access subproperties with a schema by hash.',
57
+ (fTestComplete)=>
58
+ {
59
+ let _Manyfest = new libManyfest({ Scope:'Archive.org', Descriptors: {'metadata.creator': {Name:'Creator', Hash:'Creator'}}});
60
+ // Property not schema, accessed by hash:
61
+ let tmpCreator = _Manyfest.checkAddressExistsByHash(_SampleDataArchiveOrgFrankenberry, 'Creator');
62
+ Expect(tmpCreator)
63
+ .to.equal(true);
64
+ fTestComplete();
65
+ }
66
+ );
67
+ }
68
+ );
69
+ }
70
+ );