@haystacks/async
Version:
A framework to build any number or any kind of native application or automation solution.
451 lines (426 loc) • 24.3 kB
JavaScript
/**
* @file dataStringParsing.js
* @module dataStringParsing
* @description Contains all system defined business rules for parsing strings as related to data.
* @requires module:ruleParsing
* @requires module:loggers
* @requires {@link https://www.npmjs.com/package/@haystacks/constants|@haystacks/constants}
* @requires {@link https://nodejs.org/api/crypto.html|crypto}
* @requires {@link https://nodejs.org/api/buffer.html|buffer}
* @requires {@link https://www.npmjs.com/package/path|path}
* @author Seth Hollingsead
* @date 2022/04/25
* @copyright Copyright © 2022-… by Seth Hollingsead. All rights reserved
*/
// Internal imports
import ruleParsing from '../ruleParsing.js';
import loggers from '../../../executrix/loggers.js';
// External imports
import hayConst from '@haystacks/constants';
import crypto from 'crypto';
import { Buffer } from 'buffer';
import path from 'path';
const {bas, biz, gen, msg, num, sys, wrd} = hayConst;
const baseFileName = path.basename(import.meta.url, path.extname(import.meta.url));
// framework.businessRules.rules.stringParsing.dataStringParsing.
const namespacePrefix = wrd.cframework + bas.cDot + sys.cbusinessRules + bas.cDot + wrd.crules + bas.cDot + wrd.cstring + wrd.cParsing + bas.cDot + baseFileName + bas.cDot;
/**
* @function getAttributeName
* @description Takes a string representation of a JSON attribute and gets the name (left hand assignment key).
* @param {string} inputData The string representation of the JSON attribute that should be parsed.
* @param {string} inputMetaData Not used for this business rule.
* @return {string} The name of the attribute.
* @author Seth Hollingsead
* @date 2022/01/25
*/
async function getAttributeName(inputData, inputMetaData) {
let functionName = getAttributeName.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = false;
if (inputData) {
let attributeArray = inputData.split(bas.cColon);
// attributeArray is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cattributeArrayIs + JSON.stringify(attributeArray));
// attributeArray[0] is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cattributeArray0Is + attributeArray[0]);
returnData = await ruleParsing.processRulesInternal([attributeArray[0], [/"/g, '']], [biz.creplaceCharacterWithCharacter]);
returnData = returnData.trim();
} // End-if (inputData)
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function getAttributeValue
* @description Takes a string representation of a JSON attribute and gets the value (Right hand assignment value).
* @param {string} inputData The string representation of the JSON attribute that should be parsed.
* @param {string} inputMetaData Not used for this business rule.
* @return {string} The value of the attribute.
* @author Seth Hollingsead
* @date 2022/01/10
*/
async function getAttributeValue(inputData, inputMetaData) {
let functionName = getAttributeValue.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = false;
if (inputData) {
let attributeArray = inputData.split(bas.cColon);
// attributeArray is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cattributeArrayIs + attributeArray);
// attributeArray[0] is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cattributeArray1Is + attributeArray[1]);
returnData = await ruleParsing.processRulesInternal([attributeArray[1], [/"/g, '']], [biz.creplaceCharacterWithCharacter]);
returnData = returnData.trim();
} // End-if (inputData)
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function getValueFromAssignmentOperationString
* @description Parses the input string and finds the value on the right side of the '=' sign.
* @param {string} inputData The string that should be parsed for the value on the right side of the assignment operator.
* @param {string} inputMetaData Not used for this business rule.
* @return {string} The string value of whatever is on the right side of the assignment operator.
* @author Seth Hollingsead
* @date 2022/01/23
*/
async function getValueFromAssignmentOperationString(inputData, inputMetaData) {
let functionName = getValueFromAssignmentOperationString.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = false;
if (inputData) {
let parsedString = inputData.split(bas.cEqual);
await loggers.consoleLog(namespacePrefix + functionName, msg.cparsedStringSpaceTerm + bas.cSpace + num.c1 + msg.cSpaceIsColonSpace + parsedString[0]);
await loggers.consoleLog(namespacePrefix + functionName, msg.cparsedStringSpaceTerm + bas.cSpace + num.c2 + msg.cSpaceIsColonSpace + parsedString[1]);
returnData = parsedString[1].replace(/['"]+/g, '');
} // End-if (inputData)
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function getDataCategoryFromDataContextName
* @description Gets the data category from the context name, E.g. Input: Page_ProjectList, data category is 'Page'.
* @param {string} inputData The data context name, which should also contain the data category separated by underscore.
* @param {string} inputMetaData Not used for this business rule.
* @return {string} The data category, such as Page or Test.
* @author Seth Hollingsead
* @date 2022/01/24
*/
async function getDataCategoryFromDataContextName(inputData, inputMetaData) {
let functionName = getDataCategoryFromDataContextName.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = '';
if (inputData) {
let dataCategory = inputData.split(bas.cUnderscore);
returnData = dataCategory[0];
// Data Category should be:
await loggers.consoleLog(namespacePrefix + functionName, msg.cDataCategoryShouldBe + dataCategory[0]);
} // End-if (inputData)
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function getDataCategoryDetailNameFromDataContextName
* @description Gets the data category detail name from the context name, E.G. Input: Page_ProjectList, data category is 'ProjectList'.
* @param {string} inputData The data context name, which should also contain the
* data category and data category detail name separated by an underscore.
* @param {string} inputMetaData Not used for this business rule.
* @return {string} The data category detail name, such as ProjectDetails or ProjectList.
* @author Seth Hollingsead
* @date 2022/01/24
*/
async function getDataCategoryDetailNameFromDataContextName(inputData, inputMetaData) {
let functionName = getDataCategoryDetailNameFromDataContextName.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = '';
if (inputData) {
let dataCategoryDetailName = inputData.split(bas.cUnderscore);
returnData = dataCategoryDetailName[1];
// Data Category Detail Name should be:
await loggers.consoleLog(namespacePrefix + functionName, msg.cDataCategoryDetailNameShouldBe + dataCategoryDetailName[1]);
} // End-if (inputData)
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function getKeywordNameFromDataContextName
* @description Gets the keyword name from the context name, E.g. Input: Keywords_ProjectDetails_DeleteEntireProject, keyword is: 'DeleteEntireProject'.
* @param {string} inputData The data context name, which should also contain the
* data category and data category detail name and keyword name separated by an underscore.
* @param {string} inputMetaData Not used for this business rule.
* @return {string} The keyword name, such as DeleteEntireProject or EditProjectInfoClick.
* @author Seth Hollingsead
* @date 2022/01/24
*/
async function getKeywordNameFromDataContextName(inputData, inputMetaData) {
let functionName = getKeywordNameFromDataContextName.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = '';
if (inputData) {
let dataCategoryKeywordName = inputData.split(bas.cUnderscore);
returnData = dataCategoryKeywordName[2];
// Keyword Name should be:
await loggers.consoleLog(namespacePrefix + functionName, msg.cKeywordNameShouldBe + dataCategoryKeywordName[2]);
} // End-if (inputData)
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function loadDataFile
* @description Loads data from a specified file and stores it in the specified data hive path.
* @param {string} inputData The full path and file name for the file that should be loaded into memory.
* @param {string} inputMetaData The data hive path where the data should be stored once it is loaded.
* @return {boolean} The data that was loaded, because sometimes a client command might need to use this to load data.
* @author Seth Hollingsead
* @date 2022/01/25
*/
async function loadDataFile(inputData, inputMetaData) {
let functionName = loadDataFile.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = false;
if (!inputData) {
// WARNING: No data to load, please specify a valid path & filename!
await loggers.consoleLog(namespacePrefix + functionName, msg.cLoadDataFileMessage1 + msg.cloadDataFileMessage2);
} else { // Else-clause if (!inputData)
let loadedData = {};
if (inputData.includes(gen.cDotxml) || inputData.includes(gen.cDotXml) || inputData.includes(gen.cDotXML)) {
// Attempting to load XML data!
await loggers.consoleLog(namespacePrefix + functionName, msg.cAttemptingToLoadXmlData);
loadedData = await ruleParsing.processRulesInternal([inputData, ''], [biz.cgetXmlData]);
} else if (inputData.includes(gen.cDotcsv) || inputData.includes(gen.cDotCsv) || inputData.includes(gen.cDotCSV)) {
// Attempting to load CSV data!
await loggers.consoleLog(namespacePrefix + functionName, msg.cAttemptingToLoadCsvData);
loadedData = await ruleParsing.processRulesInternal([inputData, ''], [biz.cgetCsvData]);
} else if (inputData.includes(gen.cDotjson) || inputData.includes(gen.cDotJson) || inputData.includes(gen.cDotJSON)) {
// Attempting to load JSON data!
await loggers.consoleLog(namespacePrefix + functionName, msg.cAttemptingToLoadJsonData);
loadedData = await ruleParsing.processRulesInternal([inputData, ''], [biz.cgetJsonData]);
} else {
// WARNING: Invalid file format, file formats supported are:
await loggers.consoleLog(namespacePrefix + functionName, msg.cloadedDataFileMessage3 + await ruleParsing.processRulesInternal(['', ''], [biz.csupportedFileFormatsAre]));
}
// Loaded data is:
await loggers.consoleLog(namespacePrefix + functionName, msg.cLoadedDataIs + JSON.stringify(loadedData));
returnData = loadedData;
if (loadedData !== null && loadedData && inputMetaData) {
await ruleParsing.processRulesInternal([inputMetaData, loadedData], [biz.cstoreData]);
}
} // End-else-clause if (!inputData)
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function saveDataFile
* @description Saves data from a specified data to a specified path and file name.
* @param {string} inputData The full path and file name were the data should be saved.
* @param {object} inputMetaData The data that should be saved out to the specified file.
* @return {boolean} True or False value to indicate if the file was saved successfully or not.
* @author Seth Hollingsead
* @date 2022/03/17
*/
async function saveDataFile(inputData, inputMetaData) {
let functionName = saveDataFile.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = false;
if (!inputData) {
// WARNING: No data to save, please specify a valid path & filename!
await loggers.consoleLog(namespacePrefix + functionName, msg.csaveDataFileMessage1 + msg.cloadDataFileMessage2);
} else {
let supportedFileFormats = await ruleParsing.processRulesInternal(['', ''], [biz.csupportedFileFormatsAre]);
if (inputData.includes(gen.cDotxml) || inputData.includes(gen.cDotXml) || inputData.includes(gen.cDotXML)) {
// WARNING: Invalid file format, file formats supported are:
await loggers.consoleLog(namespacePrefix + functionName, msg.cloadedDataFileMessage3 + supportedFileFormats);
} else if (inputData.includes(gen.cDotcsv) || inputData.includes(gen.cDotCsv) || inputData.includes(gen.cDotCSV)) {
// WARNING: Invalid file format, file formats supported are:
await loggers.consoleLog(namespacePrefix + functionName, msg.cloadedDataFileMessage3 + supportedFileFormats);
} else if (inputData.includes(gen.cDotjson) || inputData.includes(gen.cDotJson) || inputData.includes(gen.cDotJSON)) {
returnData = await ruleParsing.processRulesInternal([inputData, inputMetaData], [biz.cwriteJsonData]); // Should return true if the write is successful.
} else {
// WARNING: Invalid file format, file formats supported are:
await loggers.consoleLog(namespacePrefix + functionName, msg.cloadedDataFileMessage3 + supportedFileFormats);
}
}
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + JSON.stringify(returnData));
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function getUserNameFromEmail
* @description Converts an email input into a username.
* @param {string} inputData A string that contains an email address value.
* @param {string} inputMetaData Not used for this business rule.
* @return {string} A string value of the sub-string from before the '@' symbol.
* @author Seth Hollingsead
* @date 2022/01/21
*/
async function getUserNameFromEmail(inputData, inputMetaData) {
let functionName = getUserNameFromEmail.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = false;
if (inputData) {
await loggers.consoleLog(namespacePrefix + functionName, msg.cIndexOfTheSpace + bas.cAt +
sys.cSpaceIsColonSpace + inputData.indexOf(bas.cAt));
returnData = inputData.substring(0, inputData.indexOf(bas.cAt));
} // End-if (inputData)
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function encryptStringAes256
* @description Takes a string and runs it through an encryption algorithm, returning an encrypted version of the string.
* The algorithm used for the encryption is AES-256, I've included it as part of the function/rule name because
* we want to be able to distinguish it from other algorithms that might implement other encryption algorithms, such as RSA.
* @param {string} inputData The string to be encrypted.
* @param {string} inputMetaData Another string that will be used as the encryption seed.
* Can be formatted with and "_", with a prefix and post-fix for public/private keys.
* @return {string} The encrypted string.
* @author Seth Hollingsead
* @date 2024/09/23
*/
async function encryptStringAes256(inputData, inputMetaData) {
let functionName = encryptStringAes256.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = false;
if (inputData && inputMetaData && inputData !== '' && inputMetaData !== '') {
try {
// Treat the entire inputMetaData (public + private keys combined) as the encryption key
const encryptionKey = crypto.createHash(gen.csha256).update(inputMetaData).digest(); // Create a 256-bit key
const iv = crypto.randomBytes(16); // Generate a random IV
// Create the cipher using AES-256-CTR
const cipher = crypto.createCipheriv(gen.caes_256_ctr, encryptionKey, iv);
// Encrypt the inputData
const encrypted = Buffer.concat([cipher.update(inputData, gen.cUTF8), cipher.final()]);
// Combine the IV and the encrypted data, encode them in hex format for storage
returnData = iv.toString(gen.chex) + bas.cColon + encrypted.toString(gen.chex);
// Encryption successful
await loggers.consoleLog(namespacePrefix + functionName, msg.cEncryptionSuccessful);
} catch (error) {
// ERROR: Encryption failed:
console.log(msg.cErrorEncryptionFailed + error.message);
await loggers.consoleLog(namespacePrefix + functionName, msg.cErrorEncryptionFailed + error.message);
}
} else {
// ERROR: Invalid input strings.
console.log(msg.cErrorInvalidInputStrings)
await loggers.consoleLog(namespacePrefix + functionName, msg.cErrorInvalidInputStrings);
}
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function decryptStringAes256
* @description Takes an encrypted string and decrypts it using the AES-256 algorithm, returning the original string.
* @param {string} inputData The encrypted string to decrypt.
* @param {string} inputMetaData The string used as the decryption seed (same key used for encryption).
* @return {string} The decrypted string.
* @author Seth Hollingsead
* @date 2024/09/23
*/
export async function decryptStringAes256(inputData, inputMetaData) {
let functionName = decryptStringAes256.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = false;
if (inputData && inputMetaData && inputData !== '' && inputMetaData !== '') {
try {
// Split the encrypted data into the IV and the encrypted string
const [ivHex, encryptedHex] = inputData.split(bas.cColon);
const iv = Buffer.from(ivHex, gen.chex);
const encryptedText = Buffer.from(encryptedHex, gen.chex);
// Derive the encryption key
const encryptionKey = crypto.createHash(gen.csha256).update(inputMetaData).digest();
// Create decipher using AES-256-CTR
const decipher = crypto.createDecipheriv(gen.caes_256_ctr, encryptionKey, iv);
// Decrypt the encrypted text
const decrypted = Buffer.concat([decipher.update(encryptedText), decipher.final()]);
returnData = decrypted.toString(gen.cUTF8);
// Decryption successful
await loggers.consoleLog(namespacePrefix + functionName, msg.cDecryptionSuccessful);
} catch (error) {
// ERROR: Decryption failed:
console.log(msg.cErrorEncryptionFailed + error.message);
await loggers.consoleLog(namespacePrefix + functionName, msg.cErrorEncryptionFailed + error.message);
}
} else {
// ERROR: Invalid input strings.
console.log(msg.cErrorInvalidInputStrings);
await loggers.consoleLog(namespacePrefix + functionName, msg.cErrorInvalidInputStrings);
}
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
/**
* @function obfuscateString
* @description Takes a string and converts it to an obfuscated version of the same string.
* This means converting the string into something that is unintelligible or unreadable from the original string.
* In our case we will convert the string into all stars '*', just like as if the string was a password.
* @param {string} inputData The string to be obfuscated, or converted into all stars of the same length.
* @param {string} inputMetaData Not used for this business rule.
* @return {string} A string of '*' characters of the same length as the input string.
* @author Seth Hollingsead
* @date 2024/09/23
*/
export async function obfuscateString(inputData, inputMetaData) {
let functionName = obfuscateString.name;
await loggers.consoleLog(namespacePrefix + functionName, msg.cBEGIN_Function);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputDataIs + inputData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cinputMetaDataIs + inputMetaData);
let returnData = false;
if (inputData && inputData !== '') {
// Convert the input string to a string of '*' characters of the same length
returnData = bas.cStar.repeat(inputData.length);
// Obfuscation successful
await loggers.consoleLog(namespacePrefix + functionName, msg.cObfuscationSuccessful);
} else {
// ERROR: Invalid input string.
console.log(msg.cErrorInvalidInputString);
await loggers.consoleLog(namespacePrefix + functionName, msg.cErrorInvalidInputString);
}
await loggers.consoleLog(namespacePrefix + functionName, msg.creturnDataIs + returnData);
await loggers.consoleLog(namespacePrefix + functionName, msg.cEND_Function);
return returnData;
}
export default {
getAttributeName,
getAttributeValue,
getValueFromAssignmentOperationString,
getDataCategoryFromDataContextName,
getDataCategoryDetailNameFromDataContextName,
getKeywordNameFromDataContextName,
loadDataFile,
saveDataFile,
getUserNameFromEmail,
encryptStringAes256,
decryptStringAes256,
obfuscateString
};