From d58f0cc39bb9759ebd46d7a871af37f63e64ccaa Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Tue, 28 Jan 2025 00:06:37 -0500 Subject: [PATCH] JSON-encode exchange variables if they include illegal mongodb key chars. --- CHANGELOG.md | 6 ++++++ lib/exchanges.js | 56 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c06b4da..26e00a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # bedrock-vc-delivery ChangeLog +## 6.5.0 - 2025-01-dd + +### Added +- Allow exchange variables to contain JSON key values that are not + supported in mongoDB. + ## 6.4.1 - 2025-01-27 ### Fixed diff --git a/lib/exchanges.js b/lib/exchanges.js index ca8fdb8..625c31a 100644 --- a/lib/exchanges.js +++ b/lib/exchanges.js @@ -1,5 +1,5 @@ /*! - * Copyright (c) 2022-2024 Digital Bazaar, Inc. All rights reserved. + * Copyright (c) 2022-2025 Digital Bazaar, Inc. All rights reserved. */ import * as bedrock from '@bedrock/core'; import * as database from '@bedrock/mongodb'; @@ -35,6 +35,8 @@ const LAST_ERROR_UPDATE_CONSTRAINTS = { updateTimeLimit: 1000 }; +const MONGODB_ILLEGAL_KEY_CHAR_REGEX = /[%$.]/; + bedrock.events.on('bedrock-mongodb.ready', async () => { await database.openCollections([COLLECTION_NAME]); @@ -110,14 +112,14 @@ export async function insert({workflowId, exchange}) { // backwards compatibility: enable existing systems to find record localExchangerId: localWorkflowId, meta, - exchange + exchange: _encodeVariables({exchange}) }; // insert the exchange and get the updated record try { const collection = database.collections[COLLECTION_NAME]; - const result = await collection.insertOne(record); - return result.ops[0]; + await collection.insertOne(record); + return record; } catch(e) { if(!database.isDuplicateError(e)) { throw e; @@ -199,6 +201,8 @@ export async function get({ }); } + record.exchange = _decodeVariables({exchange: record.exchange}); + // backwards compatibility; initialize `sequence` if(record.exchange.sequence === undefined) { const query = { @@ -237,6 +241,9 @@ export async function update({workflowId, exchange, explain = false} = {}) { assert.object(exchange, 'exchange'); const {id} = exchange; + // encode variable content for storage in mongoDB + exchange = _encodeVariables({exchange}); + // build update const update = _buildUpdate({exchange, complete: false}); @@ -566,13 +573,13 @@ function _buildUpdate({exchange, complete}) { $set: {'exchange.state': exchange.state, 'meta.updated': now}, $unset: {} }; - if(complete) { - // exchange complete, only update results + if(complete && typeof exchange.variables !== 'string') { + // exchange complete and variables not encoded, so only update results if(exchange.variables?.results) { update.$set['exchange.variables.results'] = exchange.variables.results; } } else { - // exchange not complete, update all variables + // exchange not complete or variables are encoded, so update all variables if(exchange.variables) { update.$set['exchange.variables'] = exchange.variables; } @@ -600,6 +607,41 @@ function _buildUpdate({exchange, complete}) { return update; } +function _encodeVariables({exchange}) { + // if any JSON object any variable uses a character that is not legal in + // a JSON key in mongoDB then stringify all the variables + if(_hasIllegalMongoDBKeyChar(exchange.variables)) { + return {...exchange, variables: JSON.stringify(exchange.variables)}; + } + return exchange; +} + +function _decodeVariables({exchange}) { + if(typeof exchange.variables === 'string') { + return {...exchange, variables: JSON.parse(exchange.variables)}; + } + return exchange; +} + +function _hasIllegalMongoDBKeyChar(value) { + if(Array.isArray(value)) { + for(const e of value) { + if(_hasIllegalMongoDBKeyChar(e)) { + return true; + } + } + } else if(value && typeof value === 'object') { + const keys = Object.keys(value); + for(const key of keys) { + if(MONGODB_ILLEGAL_KEY_CHAR_REGEX.test(key) || + _hasIllegalMongoDBKeyChar(value[key])) { + return true; + } + } + } + return false; +} + /** * An object containing information on the query plan. *