Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,27 @@ const map = new Map([
evaluate(map, '/a/1', { realm: new MapSetEvaluationRealm() }); // => 'c'
```

###### Minim Evaluation Realm

The Minim Evaluation Realm extends JSON Pointer evaluation to support Minim data structures,
specifically ObjectElement, ArrayElement, and other element types from the [minim](https://github.com/refractproject/minim).

Minim is widely used in API description languages (e.g., OpenAPI, API Blueprint, AsyncAPI and other API Description processing tools)
to represent structured API data. The Minim Evaluation Realm enables seamless JSON Pointer traversal for these structures.


```js
import { ObjectElement } from 'minim';
import { evaluate } from '@swaggerexpert/json-pointer';
import MinimEvaluationRealm from '@swaggerexpert/json-pointer/evaluate/realms/minim';

const objectElement = new ObjectElement({
a: ['b', 'c']
});

evaluate(objectElement, '/a/1', { realm: new MinimEvaluationRealm() }); // => 'c'
```

###### Custom Evaluation Realms

The evaluation is designed to support **custom evaluation realms**,
Expand Down
19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"chai": "=5.2.0",
"cross-env": "^7.0.3",
"husky": "=9.1.7",
"minim": "^0.23.8",
"mocha": "=11.1.0",
"npm-watch": "^0.13.0",
"prettier": "^3.5.2"
Expand Down
13 changes: 1 addition & 12 deletions src/errors/JSONPointerKeyError.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
import JSONPointerEvaluateError from './JSONPointerEvaluateError.js';

class JSONPointerKeyError extends JSONPointerEvaluateError {
constructor(message, options) {
if (
typeof message === 'undefined' &&
(typeof options?.referenceToken === 'string' || typeof options?.referenceToken === 'number')
) {
message = `Invalid object key: '${options.referenceToken}' not found`;
}

super(message, options);
}
}
class JSONPointerKeyError extends JSONPointerEvaluateError {}

export default JSONPointerKeyError;
2 changes: 1 addition & 1 deletion src/evaluate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const evaluate = (

if (realm.isObject(current)) {
if (!realm.has(current, referenceToken) && strictObjects) {
throw new JSONPointerKeyError(undefined, {
throw new JSONPointerKeyError(`Invalid object key: '${referenceToken}' not found`, {
jsonPointer,
referenceTokens,
referenceToken,
Expand Down
52 changes: 52 additions & 0 deletions src/evaluate/realms/minim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ObjectElement, ArrayElement } from 'minim';

import EvaluationRealm from '../EvaluationRealm.js';
import JSONPointerKeyError from '../../errors/JSONPointerKeyError.js';

class MinimEvaluationRealm extends EvaluationRealm {
name = 'minim';

isArray(node) {
return node instanceof ArrayElement && !(node instanceof ObjectElement);
}

isObject(node) {
return node instanceof ObjectElement;
}

sizeOf(node) {
if (this.isArray(node) || this.isObject(node)) {
return node.length;
}
return 0;
}

has(node, referenceToken) {
if (this.isArray(node)) {
return Number(referenceToken) < this.sizeOf(node);
}
if (this.isObject(node)) {
const keys = node.keys();
const uniqueKeys = new Set(keys);

if (keys.length !== uniqueKeys.size) {
throw new JSONPointerKeyError(`Object keys must be unique for '${referenceToken}'`, {
currentValue: node,
referenceToken,
});
}

return node.hasKey(referenceToken);
}
return false;
}

evaluate(node, referenceToken) {
if (this.isArray(node)) {
return node.get(Number(referenceToken));
}
return node.get(referenceToken);
}
}

export default MinimEvaluationRealm;
50 changes: 0 additions & 50 deletions test/evaluate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,56 +71,6 @@ describe('evaluate', function () {
});
});

context('valid JSON Pointers', function () {
specify('should return entire document for ""', function () {
assert.deepEqual(evaluate(data, ''), data);
});

specify('should return array ["bar", "baz"] for "/foo"', function () {
assert.deepEqual(evaluate(data, '/foo'), ['bar', 'baz']);
});

specify('should return "bar" for "/foo/0"', function () {
assert.strictEqual(evaluate(data, '/foo/0'), 'bar');
});

specify('should return 0 for "/"', function () {
assert.strictEqual(evaluate(data, '/'), 0);
});

specify('should return 1 for "/a~1b"', function () {
assert.strictEqual(evaluate(data, '/a~1b'), 1);
});

specify('should return 2 for "/c%d"', function () {
assert.strictEqual(evaluate(data, '/c%d'), 2);
});

specify('should return 3 for "/e^f"', function () {
assert.strictEqual(evaluate(data, '/e^f'), 3);
});

specify('should return 4 for "/g|h"', function () {
assert.strictEqual(evaluate(data, '/g|h'), 4);
});

specify('should return 5 for "/i\\j"', function () {
assert.strictEqual(evaluate(data, '/i\\j'), 5);
});

specify('should return 6 for "/k\"l"', function () {
assert.strictEqual(evaluate(data, '/k"l'), 6);
});

specify('should return 7 for "/ "', function () {
assert.strictEqual(evaluate(data, '/ '), 7);
});

specify('should return 8 for "/m~0n"', function () {
assert.strictEqual(evaluate(data, '/m~0n'), 8);
});
});

context('given custom evaluator option', function () {
specify('should correctly use a default evaluator', function () {
const result = evaluate(data, '/a~1b', { evaluator: referenceTokenListEvaluator });
Expand Down
50 changes: 0 additions & 50 deletions test/evaluate/realms/map-set.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,56 +71,6 @@ describe('evaluate', function () {
});
});

context('valid JSON Pointers', function () {
specify('should return entire document for ""', function () {
assert.deepEqual(evaluate(data, '', { realm }), data);
});

specify('should return Set(["bar", "baz"]) for "/foo"', function () {
assert.deepEqual(evaluate(data, '/foo', { realm }), new Set(['bar', 'baz']));
});

specify('should return "bar" for "/foo/0"', function () {
assert.strictEqual(evaluate(data, '/foo/0', { realm }), 'bar');
});

specify('should return 0 for "/"', function () {
assert.strictEqual(evaluate(data, '/', { realm }), 0);
});

specify('should return 1 for "/a~1b"', function () {
assert.strictEqual(evaluate(data, '/a~1b', { realm }), 1);
});

specify('should return 2 for "/c%d"', function () {
assert.strictEqual(evaluate(data, '/c%d', { realm }), 2);
});

specify('should return 3 for "/e^f"', function () {
assert.strictEqual(evaluate(data, '/e^f', { realm }), 3);
});

specify('should return 4 for "/g|h"', function () {
assert.strictEqual(evaluate(data, '/g|h', { realm }), 4);
});

specify('should return 5 for "/i\\j"', function () {
assert.strictEqual(evaluate(data, '/i\\j', { realm }), 5);
});

specify('should return 6 for "/k\"l"', function () {
assert.strictEqual(evaluate(data, '/k"l', { realm }), 6);
});

specify('should return 7 for "/ "', function () {
assert.strictEqual(evaluate(data, '/ ', { realm }), 7);
});

specify('should return 8 for "/m~0n"', function () {
assert.strictEqual(evaluate(data, '/m~0n', { realm }), 8);
});
});

context('invalid JSON Pointers (should throw errors)', function () {
specify('should throw JSONPointerEvaluateError for invalid JSON Pointer', function () {
assert.throws(() => evaluate(data, 'invalid-pointer', { realm }), JSONPointerEvaluateError);
Expand Down
Loading