From 45d2fbc5fcf5404f1dc820ee173cc879b2f630c4 Mon Sep 17 00:00:00 2001 From: d98762625 Date: Wed, 6 Jun 2018 16:37:12 +0100 Subject: [PATCH] create SyncDish and wrap results in it. --- Gruntfile.js | 2 +- src/node/apiUtils.mjs | 69 +++----- .../config/scripts/generateNodeIndex.mjs | 7 +- src/node/syncDish.mjs | 151 ++++++++++++++++++ 4 files changed, 177 insertions(+), 52 deletions(-) rename src/{core => node}/config/scripts/generateNodeIndex.mjs (91%) create mode 100644 src/node/syncDish.mjs diff --git a/Gruntfile.js b/Gruntfile.js index d226a31e..3c20e4ff 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -393,7 +393,7 @@ module.exports = function (grunt) { "mkdir -p src/core/config/modules", "echo 'export default {};\n' > src/core/config/modules/OpModules.mjs", "echo '[]\n' > src/core/config/OperationConfig.json", - "node --experimental-modules src/core/config/scripts/generateNodeIndex.mjs", + "node --experimental-modules src/node/config/scripts/generateNodeIndex.mjs", "echo '--- Node index finished. ---\n'" ].join(";"), }, diff --git a/src/node/apiUtils.mjs b/src/node/apiUtils.mjs index a2656939..4ed1f162 100644 --- a/src/node/apiUtils.mjs +++ b/src/node/apiUtils.mjs @@ -1,12 +1,12 @@ /** - * Wrap operations for consumption in Node + * Wrap operations for consumption in Node. * * @author d98762625 [d98762625@gmail.com] * @copyright Crown Copyright 2018 * @license Apache-2.0 */ -import Dish from "../core/Dish"; +import SyncDish from "./SyncDish"; /** * Extract default arg value from operation argument @@ -59,61 +59,36 @@ function transformArgs(originalArgs, newArgs) { * @returns {Function} The operation's run function, wrapped in * some type conversion logic */ -export function wrap(Operation) { +export function wrap(opClass) { /** * Wrapped operation run function + * @param {*} input + * @param {Object[]} args + * @returns {SyncDish} operation's output, on a Dish. + * @throws {OperationError} if the operation throws one. */ - return async (input, args=null, callback) => { + return (input, args=null) => { + const operation = new opClass(); - if (callback && typeof callback !== "function") { - throw TypeError("Expected callback to be a function"); + let dish; + if (input instanceof SyncDish) { + dish = input; + } else { + dish = new SyncDish(); + const type = SyncDish.typeEnum(input.constructor.name); + dish.set(input, type); } - if (!callback && typeof args === "function") { - callback = args; - args = null; - } - - const operation = new Operation(); - const dish = new Dish(); - - const type = Dish.typeEnum(input.constructor.name); - dish.set(input, type); - args = transformArgs(operation.args, args); - const transformedInput = await dish.get(operation.inputType); - - // Allow callback or promsise / async-await - if (callback) { - try { - const out = operation.run(transformedInput, args); - callback(null, out); - } catch (e) { - callback(e); - } - } else { - return operation.run(transformedInput, args); - } + const transformedInput = dish.get(operation.inputType); + const result = operation.run(transformedInput, args); + return new SyncDish({ + value: result, + type: operation.outputType + }); }; } - -/** - * First draft - * @namespace Api - * @param input - * @param type - */ -export async function translateTo(input, type) { - const dish = new Dish(); - - const initialType = Dish.typeEnum(input.constructor.name); - - dish.set(input, initialType); - return await dish.get(type); -} - - /** * Extract properties from an operation by instantiating it and * returning some of its properties for reference. diff --git a/src/core/config/scripts/generateNodeIndex.mjs b/src/node/config/scripts/generateNodeIndex.mjs similarity index 91% rename from src/core/config/scripts/generateNodeIndex.mjs rename to src/node/config/scripts/generateNodeIndex.mjs index 068fe185..80b100a2 100644 --- a/src/core/config/scripts/generateNodeIndex.mjs +++ b/src/node/config/scripts/generateNodeIndex.mjs @@ -13,8 +13,8 @@ import fs from "fs"; import path from "path"; -import * as operations from "../../operations/index"; -import { decapitalise } from "../../../node/apiUtils"; +import * as operations from "../../../core/operations/index"; +import { decapitalise } from "../../apiUtils"; const dir = path.join(`${process.cwd()}/src/node`); if (!fs.existsSync(dir)) { @@ -36,7 +36,7 @@ let code = `/** import "babel-polyfill"; -import { wrap, translateTo } from "./apiUtils"; +import { wrap } from "./apiUtils"; import { `; @@ -76,7 +76,6 @@ code += ` }; } const chef = generateChef(); -chef.translateTo = translateTo; `; Object.keys(operations).forEach((op) => { diff --git a/src/node/syncDish.mjs b/src/node/syncDish.mjs new file mode 100644 index 00000000..536e0f6e --- /dev/null +++ b/src/node/syncDish.mjs @@ -0,0 +1,151 @@ +/** + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Utils from "../core/Utils"; +import Dish from "../core/Dish"; +import BigNumber from "bignumber.js"; +import log from "loglevel"; + +/** + * Subclass of Dish where `get` and `_translate` are synchronous. + * Also define functions to improve coercion behaviour. + */ +class SyncDish extends Dish { + + /** + * Synchronously returns the value of the data in the type format specified. + * + * @param {number} type - The data type of value, see Dish enums. + * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. + * @returns {*} - The value of the output data. + */ + get(type, notUTF8=false) { + if (typeof type === "string") { + type = Dish.typeEnum(type); + } + if (this.type !== type) { + this._translate(type, notUTF8); + } + return this.value; + } + + /** + * alias for get + * @param args see get args + */ + to(...args) { + return this.get(...args); + } + + /** + * Avoid coercion to a String primitive. + */ + toString() { + return this.get(Dish.typeEnum("string")); + } + + /** + * Log only the value to the console in node. + */ + inspect() { + return this.get(Dish.typeEnum("string")); + } + + /** + * Avoid coercion to a Number primitive. + */ + valueOf() { + return this.get(Dish.typeEnum("number")); + } + + /** + * Synchronously translates the data to the given type format. + * + * @param {number} toType - The data type of value, see Dish enums. + * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. + */ + _translate(toType, notUTF8=false) { + log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`); + const byteArrayToStr = notUTF8 ? Utils.byteArrayToChars : Utils.byteArrayToUtf8; + + // Convert data to intermediate byteArray type + switch (this.type) { + case Dish.STRING: + this.value = this.value ? Utils.strToByteArray(this.value) : []; + break; + case Dish.NUMBER: + this.value = typeof this.value === "number" ? Utils.strToByteArray(this.value.toString()) : []; + break; + case Dish.HTML: + this.value = this.value ? Utils.strToByteArray(Utils.unescapeHtml(Utils.stripHtmlTags(this.value, true))) : []; + break; + case Dish.ARRAY_BUFFER: + // Array.from() would be nicer here, but it's slightly slower + this.value = Array.prototype.slice.call(new Uint8Array(this.value)); + break; + case Dish.BIG_NUMBER: + this.value = this.value instanceof BigNumber ? Utils.strToByteArray(this.value.toFixed()) : []; + break; + case Dish.JSON: + this.value = this.value ? Utils.strToByteArray(JSON.stringify(this.value)) : []; + break; + // case Dish.FILE: + // this.value = Utils.readFileSync(this.value); + // this.value = Array.prototype.slice.call(this.value); + // break; + // case Dish.LIST_FILE: + // this.value = this.value.map(f => Utils.readFileSync(f)); + // this.value = this.value.map(b => Array.prototype.slice.call(b)); + // this.value = [].concat.apply([], this.value); + // break; + default: + break; + } + + this.type = Dish.BYTE_ARRAY; + + // Convert from byteArray to toType + switch (toType) { + case Dish.STRING: + case Dish.HTML: + this.value = this.value ? byteArrayToStr(this.value) : ""; + this.type = Dish.STRING; + break; + case Dish.NUMBER: + this.value = this.value ? parseFloat(byteArrayToStr(this.value)) : 0; + this.type = Dish.NUMBER; + break; + case Dish.ARRAY_BUFFER: + this.value = new Uint8Array(this.value).buffer; + this.type = Dish.ARRAY_BUFFER; + break; + case Dish.BIG_NUMBER: + try { + this.value = new BigNumber(byteArrayToStr(this.value)); + } catch (err) { + this.value = new BigNumber(NaN); + } + this.type = Dish.BIG_NUMBER; + break; + case Dish.JSON: + this.value = JSON.parse(byteArrayToStr(this.value)); + this.type = Dish.JSON; + break; + // case Dish.FILE: + // this.value = new File(this.value, "unknown"); + // break; + // case Dish.LIST_FILE: + // this.value = [new File(this.value, "unknown")]; + // this.type = Dish.LIST_FILE; + // break; + default: + break; + } + } + +} + +export default SyncDish;