UNPKG

cloudevent

Version:

JavaScript/Node.js implementation of the CloudEvents standard format

311 lines (292 loc) 10.9 kB
/* * Copyright 2018-2026 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict' /** * Transformers: * this module exports some useful generic functions for the transformation of objects. */ /** * Get a reference to cloudevent Validator class. * See {@link Validator}. * @private */ const V = require('./validator') // get validator from here /** * Get the host name where this code is runninng. * @private */ const hostname = require('node:os').hostname() /** * Get the process id (pid) where this code is runninng. * @private */ const pid = require('node:process').pid /** * Useful Transformations for CloudEvent objects. * * Note that all methods here are static, so no need to instance this class; * see it as an Utility/Companion class. */ class Transformer { /** * Create a new instance of a Transformer object. * Note that instancing is not allowed for this class because all its methods are static. * * @throws {Error} instancing not allowed for this class * @hideconstructor */ constructor () { throw new Error('Instancing not allowed for this class') } /** * Utility function that return a dump of the given object. * * @static * @param {(?object|Map|Set)} obj the object to dump * @param {string} [name='noname'] the name to assign in the returned string, or 'noname' as default value * @return {string} the dump of the object or a message when obj is undefined/null/not an object */ static dumpObject (obj, name = 'noname') { if (V.isUndefined(obj)) { return `${name}:undefined` } else if (V.isNull(obj)) { return `${name}:null` } else if (V.isObject(obj) || V.isKeyedCollection(obj)) { return `${name}:${JSON.stringify(obj)}` } else { return `${name}:'${obj.toString()}'` } } /** * Utility function that parse a string representation * (compatible with the CloudEvent standard) of the given timestamp (Date) * and returns it (if possible). * * Note that the value returned could be adjusted with the current timezone offset. * * @static * @param {!string} obj the timestamp/date to parse (as a string) * @param {number} [timezoneOffset=0] a timezone offset to add (in msec, default 0) * @return {object} the parsed version, as a timestamp (Date) object, if possible * @throws {TypeError} if obj is undefined or null, or is not a string */ static timestampFromString (obj, timezoneOffset = 0) { if (!V.isStringNotEmpty(obj)) { throw new TypeError(`Missing or wrong timestamp: '${obj}' must be a string and not a: '${typeof obj}'.`) } const timestampMsec = Date.parse(obj) return new Date(timestampMsec + timezoneOffset) } /** * Utility function that return a string representation * (compatible with the CloudEvent standard) * of the given timestamp (Date), or the current one will be used. * * Note that the value returned is in the UTC format. * * @static * @param {?object} obj the timestamp/date to convert, or the current one if not defined or null * @return {string} the string representation of the object * @throws {TypeError} if obj is undefined or null, or is not a Date instance */ static timestampToString (obj) { let timestamp = obj if (V.isUndefinedOrNull(timestamp)) { timestamp = new Date() } if (!V.isDateValid(timestamp)) { throw new TypeError(`Missing or wrong timestamp: '${timestamp}' must be a date and not a: '${typeof timestamp}'.`) } return timestamp.toISOString() } /** * Utility function that parse a number representation * of the given timestamp (Date) * and returns it (if possible). * * Note that the value returned could be adjusted with the current timezone offset. * * @static * @param {!number} obj the timestamp/date to parse (as a number) * @param {number} [timezoneOffset=0] a timezone offset to add (in msec, default 0) * @return {object} the parsed version, as a timestamp (Date) object, if possible * @throws {TypeError} if obj is undefined or null, or is not a number */ static timestampFromNumber (obj, timezoneOffset = 0) { if (!V.isNumber(obj)) { throw new TypeError(`Missing or wrong timestamp: '${obj}' must be a number and not a: '${typeof obj}'.`) } return new Date(obj + timezoneOffset) } /** * Utility function that return a number representation * of the given timestamp (Date), or the current one will be used. * * Note that the value returned is in the UTC format. * * @static * @param {?object} obj the timestamp/date to convert, or the current one if not defined or null * @return {number} the number representation of the object * @throws {TypeError} if obj is not a Date instance */ static timestampToNumber (obj) { let timestamp = obj if (V.isUndefinedOrNull(timestamp)) { timestamp = new Date() } if (!V.isDateValid(timestamp)) { throw new TypeError(`Wrong timestamp: '${timestamp}' must be a date and not a: '${typeof timestamp}'.`) } return timestamp.getTime() } /** * Utility function that encodes a string * in base64 (compatible with the CloudEvent standard). * * @static * @param {?string} obj the string to encode, or '' if not defined or null * @return {string} the string encoded in base64 * @throws {TypeError} if str is not a string instance */ static stringToBase64 (obj) { let str = obj if (V.isUndefinedOrNull(obj)) { str = '' } if (!V.isString(str)) { throw new TypeError(`Missing or wrong argument: '${str}' must be a string and not a: '${typeof str}'.`) } return Buffer.from(str).toString('base64') } /** * Utility function that decodes a base64 string * into a normal string using 'utf8' encoding * (compatible with the CloudEvent standard). * * @static * @param {?string} obj the string to decode, or '' if not defined or null * @return {string} the string decoded from base64 * @throws {TypeError} if str is not a string instance */ static stringFromBase64 (obj) { let str = obj if (V.isUndefinedOrNull(obj)) { str = '' } if (!V.isString(str)) { throw new TypeError(`Missing or wrong argument: '${str}' must be a string and not a: '${typeof str}'.`) } return Buffer.from(str, 'base64').toString('utf8') } /** * Utility function that map an Error into an object * (compatible with the CloudEvent standard), to fill its 'data' attribute. * * @static * @param {!Error} err the Error to transform * @param {object} [options={}] transformation options: * - includeStackTrace flag (default false) to add the StackTrace into the object to return, * - addStatus flag (default true) to add a 'status' attribute into the object to return, * - addTimestamp flag (default false) to add the current 'timestamp' as attribute into the object to return, * @return {object} the object representation of the error * @throws {TypeError} if err is undefined or null, or is not an Error instance */ static errorToData (err, { includeStackTrace = false, addStatus = true, addTimestamp = false } = {} ) { if (!V.isError(err)) { throw new TypeError(`Missing or wrong argument: '${err}' must be an Error and not a: '${typeof err}'.`) } const data = { name: err.name, message: err.message, stack: (includeStackTrace === true) ? err.stack : null } if (V.isDefinedAndNotNull(err.code)) { data.code = err.code } if (addStatus === true) { data.status = 'error' } if (addTimestamp === true) { data.timestamp = Date.now() } return data } /** * Utility function that get some process-related info and wrap into an object * (compatible with the CloudEvent standard), to fill its 'data' attribute. * * @static * @return {object} the object representation of process-related info data */ static processInfoToData () { return { hostname, pid } } /** * Utility function that strip all arguments (if any) from the given URI/URL string * and returns the string without them. * * @static * @param {!string} url the URI/URL (as a string) * @param {object} [options={}] containing: strict (boolean, default false) to check it in a more strict way * @return {string} the parsed version, but without arguments (if any) * @throws {TypeError} if url is undefined or null, or is not a string */ static uriStripArguments (url, { strict = false } = {}) { if (!V.isString(url)) { throw new TypeError(`Missing or wrong URL: '${url}' must be a string and not a: '${typeof url}'.`) } if (strict === true) { if (!V.isURI(url)) { throw new TypeError(`Missing or wrong URL: '${url}'`) } } return url.split('?')[0] } /** * Utility function that merge the given objects (at least one base and another) * and returns the new one (but with the prototype of the first). * * @static * @param {!object} base the first object to merge * @param {object} others all other(s) object to merge (at least one) * @return {object} the new object * @throws {TypeError} if base is undefined or null, or is not an object */ static mergeObjects (base, ...others) { if (!V.isObject(base)) { throw new TypeError(`Missing or wrong argument: '${base}' must be an object and not a: '${typeof base}'.`) } const baseProto = Object.getPrototypeOf(base) return Object.assign(Object.create(baseProto), base, ...others) // set the prototype of the first argument in the clone } } /** * Utility variable that returns the timezone offset value, in msec. * * @readonly * @type {number} * @static */ Transformer.timezoneOffsetMsec = new Date().getTimezoneOffset() * 60 * 1000 module.exports = Transformer