Skip to content

Commit 66d75d7

Browse files
committed
feat(evaluate): ad support for evaluation tracing
Closes #21
1 parent 580db8e commit 66d75d7

File tree

5 files changed

+407
-60
lines changed

5 files changed

+407
-60
lines changed

README.md

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
- [Immutable.js](#immutablejs-evaluation-realm)
4949
- [Custom](#custom-evaluation-realms)
5050
- [Composing Realms](#composing-evaluation-realms)
51-
- [Diagnostics](#diagnostics)
51+
- [Evaluation Diagnostics](#evaluation-diagnostics)
52+
- [Evaluation Tracing](#evaluation-tracing)
5253
- [Compilation](#compilation)
5354
- [Representation](#representation)
5455
- [JSON String](#json-string)
@@ -509,7 +510,7 @@ const structure = [
509510
evaluate(structure, '/0/a/b/1', { realm : compositeRealm }); // => 'd'
510511
```
511512

512-
##### Diagnostics
513+
##### Evaluation Diagnostics
513514

514515
`@swaggerexpert/json-pointer` provides rich diagnostic information to help identify and resolve issues during JSON Pointer evaluation.
515516

@@ -525,6 +526,91 @@ Each error includes:
525526
- `currentValue` – the value being evaluated at the point of failure
526527
- `realm` – the name of the evaluation realm (e.g., "json")
527528

529+
##### Evaluation Tracing
530+
531+
`@swaggerexpert/json-pointer` package supports evaluation tracing, allowing you to inspect each step of JSON Pointer evaluation in detail.
532+
This is especially useful for debugging, error reporting, visualization tools, or implementing custom behavior like fallbacks and partial evaluations.
533+
534+
How it works?
535+
536+
To enable tracing, provide an empty trace object when calling evaluate.
537+
`trace` object is populated with detailed information about each step of the evaluation process:
538+
539+
Tracing `successful` evaluation:
540+
541+
```js
542+
import { evaluate } from '@swaggerexpert/json-pointer';
543+
544+
const trace = {};
545+
evaluate({ a: 'b' }, '/a', { trace });
546+
```
547+
548+
```js
549+
// trace
550+
{
551+
steps: [
552+
{
553+
referenceToken: 'a',
554+
referenceTokenPosition: 0,
555+
input: { a: 'b' },
556+
inputType: 'object',
557+
output: 'b',
558+
success: true
559+
}
560+
],
561+
failed: false,
562+
failedAt: -1,
563+
message: 'JSON Pointer successfully evaluated against the value',
564+
context: {
565+
jsonPointer: '/a',
566+
referenceTokens: [ 'a' ],
567+
strictArrays: true,
568+
strictObjects: true,
569+
realm: 'json',
570+
value: { a: 'b' }
571+
}
572+
}
573+
```
574+
575+
Tracing `failed` evaluation:
576+
577+
```js
578+
import { evaluate } from '@swaggerexpert/json-pointer';
579+
580+
const trace = {};
581+
try {
582+
evaluate({ a: 'b' }, '/c', { trace });
583+
} catch {}
584+
```
585+
586+
```js
587+
// trace
588+
{
589+
steps: [
590+
{
591+
referenceToken: 'c',
592+
referenceTokenPosition: 0,
593+
input: { a: 'b' },
594+
inputType: 'object',
595+
output: undefined,
596+
success: false,
597+
reason: 'Invalid object key "c" at position 0 in "/c": key not found in object'
598+
}
599+
],
600+
failed: true,
601+
failedAt: 0,
602+
message: 'Invalid object key "c" at position 0 in "/c": key not found in object',
603+
context: {
604+
jsonPointer: '/c',
605+
referenceTokens: [ 'c' ],
606+
strictArrays: true,
607+
strictObjects: true,
608+
realm: 'json',
609+
value: { a: 'b' }
610+
}
611+
}
612+
```
613+
528614
#### Compilation
529615

530616
Compilation is the process of transforming a list of reference tokens into a JSON Pointer.

src/evaluate/TraceBuilder.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
class TraceBuilder {
2+
#trace;
3+
#path;
4+
#realm;
5+
6+
constructor(trace, context = {}) {
7+
this.#trace = trace;
8+
this.#trace.steps = [];
9+
this.#trace.failed = false;
10+
this.#trace.failedAt = -1;
11+
this.#trace.message = 'JSON Pointer successfully evaluated against the value';
12+
this.#trace.context = {
13+
...context,
14+
realm: context.realm.name,
15+
};
16+
17+
this.#path = [];
18+
this.#realm = context.realm;
19+
}
20+
21+
step({ referenceToken, input, output, success = true, reason }) {
22+
const position = this.#path.length;
23+
24+
this.#path.push(referenceToken);
25+
26+
const step = {
27+
referenceToken,
28+
referenceTokenPosition: position,
29+
input,
30+
inputType: this.#realm.isObject(input)
31+
? 'object'
32+
: this.#realm.isArray(input)
33+
? 'array'
34+
: 'unrecognized',
35+
output,
36+
success,
37+
};
38+
39+
if (reason) {
40+
step.reason = reason;
41+
}
42+
43+
this.#trace.steps.push(step);
44+
45+
if (!success) {
46+
this.#trace.failed = true;
47+
this.#trace.failedAt = position;
48+
this.#trace.message = reason;
49+
}
50+
}
51+
}
52+
53+
export default TraceBuilder;

0 commit comments

Comments
 (0)