From 478f886ec1fac7f075c133fbc2611118232686ab Mon Sep 17 00:00:00 2001 From: Dave Longley Date: Sun, 19 Jul 2026 20:55:43 -0400 Subject: [PATCH] Fix `@context` matching. --- CHANGELOG.md | 7 +++++++ lib/query/match.js | 8 ++++++++ lib/query/presentationExchange.js | 2 +- lib/query/util.js | 23 ++++++++++++++++++----- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d9f064..6c389a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # @digitalbazaar/oid4-client Changelog +## 5.13.2 - 2026-mm-dd + +### Fixed +- Fix `@context` matching in `credentialMatches()`. Now non-flat JSON pointer + maps properly preserve `@context` inputs as ordered lists (using arrays) + instead of expressing them as sets. + ## 5.13.1 - 2026-06-09 ### Fixed diff --git a/lib/query/match.js b/lib/query/match.js index 0a21896..7cb75cb 100644 --- a/lib/query/match.js +++ b/lib/query/match.js @@ -55,6 +55,14 @@ function _match({cursor, matchValue, options}) { } // normalize value to an array for matching const values = Array.isArray(value) ? value : [value]; + // `matchValue` can only be an array for the `@context` case + if(Array.isArray(matchValue)) { + // each element of `matchValue` must be equal to the element in + // `values` at the same index (note: `values` may have more elements + // than `matchValue` and still match) + return matchValue.every((mv, i) => values[i] === mv); + } + // handle matching each individual value on its own return values.some(v => _match({cursor: v, matchValue, options})); }); } diff --git a/lib/query/presentationExchange.js b/lib/query/presentationExchange.js index a1f5c08..b041b3a 100644 --- a/lib/query/presentationExchange.js +++ b/lib/query/presentationExchange.js @@ -279,7 +279,7 @@ export function _fromQueryByExampleQuery({credentialQuery, prefixJwtVcPath}) { field.path.push(JSONPath.toPathString(['$', 'vc', ...path])); } - if(value instanceof Set) { + if(Array.isArray(value) || value instanceof Set) { field.filter = { type: 'array', allOf: [...value].map(value => ({ diff --git a/lib/query/util.js b/lib/query/util.js index e639fa6..22f7950 100644 --- a/lib/query/util.js +++ b/lib/query/util.js @@ -72,13 +72,26 @@ function _toPointers({ cursor, map, tokens = [], pointer = '/', flat = false }) { if(!flat && Array.isArray(cursor)) { - const set = new Set(); - // when `map` is not set, case is array of arrays; return a new map - const result = map ? set : (map = new Map()); - map.set(pointer, set); + // when producing non-flat output, every array is treated as a `Set` except + // if the pointer points at an `@context` array (this is the only ordered + // list case) + let container; + let add; + if(pointer.endsWith('/@context')) { + container = []; + add = container.push; + } else { + container = new Set(); + add = container.add; + } + add = add.bind(container); + // result is `container` if `map` is defined, if not, then case is + // array of arrays and result is a new map + const result = map ? container : (map = new Map()); + map.set(pointer, container); for(const element of cursor) { // reset map, tokens, and pointer for array elements - set.add(_toPointers({cursor: element, flat})); + add(_toPointers({cursor: element, flat})); } return result; }