Skip to content

Commit

Permalink
+errorReport (#34)
Browse files Browse the repository at this point in the history
* +errorReport

* tests++ README++ cliCore+improve_filepath_handling

* TemplateProcessor.ts + StatedError type

---------

Co-authored-by: Geoffrey Hendrey <[email protected]>
  • Loading branch information
geoffhendrey and Geoffrey Hendrey authored Dec 19, 2023
1 parent 7bad65a commit a9e9a63
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 7 deletions.
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,73 @@ The `.errors` command will produce a report of all errors in the template
"message": "The right side of the \"+\" operator must evaluate to a number"
}
}
```
## $errorReport function
JSONata does not have a `throw/catch` syntax. However, JSONata has a `$error` function that you can use
to throw an error. However, doing so will end the execution of the JSONata expression. If you wish to
simply record the fact that an error occured without exiting the expression, use the `$errorReport` function
which returns an error object but does not throw.
```json
> .init -f example/errorReport.json
{
"a": [
0,
1,
2,
"${$errorReport('oops', 'my_custom_error')}",
4,
5
],
"b": "${($errorReport('e0');$errorReport('e1');$errorReport('e2'))}"
}
> .out
{
"a": [
0,
1,
2,
{
"error": {
"message": "oops",
"name": "my_custom_error"
}
},
4,
5
],
"b": {
"error": {
"message": "e2"
}
}
}
> .errors
{
"/a/3": {
"error": {
"message": "oops",
"name": "my_custom_error"
}
},
"/b": [
{
"error": {
"message": "e0"
}
},
{
"error": {
"message": "e1"
}
},
{
"error": {
"message": "e2"
}
}
]
}

```
## Expressions and Variables
What makes a Stated template different from an ordinary JSON file? JSONata Expressions of course! Stated analyzes the
Expand Down
11 changes: 11 additions & 0 deletions example/errorReport.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"a": [
0,
1,
2,
"${$errorReport('oops', 'my_custom_error')}",
4,
5
],
"b": "${($errorReport('e0');$errorReport('e1');$errorReport('e2'))}"
}
12 changes: 8 additions & 4 deletions src/CliCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default class CliCore {
constructor(templateProcessor: TemplateProcessor = null) {
this.templateProcessor = templateProcessor;
this.logLevel = "info";
this.currentDirectory = path.join(process.cwd(), 'example'); // Default to cwd/example
this.currentDirectory = process.cwd();
}
public close(){
if(this.templateProcessor){
Expand Down Expand Up @@ -117,7 +117,11 @@ export default class CliCore {
if(filepath===undefined){
return undefined;
}
const input = await this.readFileAndParse(filepath, importPath);
let _filepath = filepath;
if(this.currentDirectory){
_filepath = path.join(this.currentDirectory, _filepath);
}
const input = await this.readFileAndParse(_filepath, importPath);
const contextData = contextFilePath ? await this.readFileAndParse(contextFilePath, importPath) : {};
options.importPath = importPath; //path is where local imports will be sourced from. We sneak path in with the options
// if we initialize for the first time, we need to create a new instance of TemplateProcessor
Expand Down Expand Up @@ -364,9 +368,9 @@ public async open(directory: string = this.currentDirectory) {
const fileIndex = parseInt(answer, 10) - 1; // Convert to zero-based index
if (fileIndex >= 0 && fileIndex < templateFiles.length) {
// User has entered a valid file number; initialize with this file
const filepath = path.join(directory, templateFiles[fileIndex]);
const filepath = templateFiles[fileIndex];
try {
const result = await this.init(`-f "${filepath}"`); // Adjust this call as per your init method's expected format
const result = await this.init(`-f "${filepath}"`);
console.log(StatedREPL.stringify(result));
console.log("...try '.out' or 'template.output' to see evaluated template")
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion src/StatedREPL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export default class StatedREPL {
}

async cli(cliCoreMethodName, args){
let result;
let result="";
try{
const method = this.cliCore[cliCoreMethodName].bind(this.cliCore);
result = await method(args);
Expand Down
37 changes: 35 additions & 2 deletions src/TemplateProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ import { stringifyTemplateJSON } from './utils/stringify.js';


type MetaInfoMap = Record<JsonPointerString, MetaInfo[]>;
export type StatedError = {
error: {
message: string;
name?: string;
stack?: string | null;
};
};

export default class TemplateProcessor {
/**
Expand Down Expand Up @@ -121,9 +128,9 @@ export default class TemplateProcessor {
* $import is an example of this kind of behavior.
* When $import('http://mytemplate.com/foo.json') is called, the import function
* is actually genrated on the fly, using knowledge of the json path that it was
* called at, to replace the cotnent of the template at that path with the downloaded
* called at, to replace the content of the template at that path with the downloaded
* content.*/
functionGenerators: Map<string, (metaInfo: MetaInfo, templateProcessor: TemplateProcessor) => Promise<(arg: any) => Promise<any>>> = new Map();
functionGenerators: Map<string, (metaInfo: MetaInfo, templateProcessor: TemplateProcessor) => Promise<(arg: any) => Promise<any>>>;

private changeCallbacks:Map<JsonPointerString, (data:any, jsonPointer: JsonPointerString, removed:boolean)=>void>;

Expand Down Expand Up @@ -989,6 +996,7 @@ export default class TemplateProcessor {
target,
{...this.context,
...{"import": safe(this.getImport(jsonPointer__))},
...{"errorReport": this.generateErrorReportFunction(metaInfo)},
...jittedFunctions
}
);
Expand Down Expand Up @@ -1233,5 +1241,30 @@ export default class TemplateProcessor {
return wrappedFunction;
}

private async generateErrorReportFunction(metaInfo: MetaInfo){
return async (message:string, name?:string, stack?:any):Promise<StatedError>=>{
const error:StatedError = {
error: {
message,
...(name !== undefined && { name }), // Include 'name' property conditionally,
...(stack !== undefined && { stack }), // Include 'stack' property conditionally
}
}
const key = metaInfo.jsonPointer__ as string;
if(this.errorReport[key] === undefined){
this.errorReport[key] = error;
}else if (Array.isArray(this.errorReport[key])){
this.errorReport[key].push(error);
}else{ //if here, we have to take the single existing error and replace it with an array having the existing error and the new one
const tmp = this.errorReport[key];
this.errorReport[key] = [tmp]; //stuff tmp into array
this.errorReport[key].push(error); //append error to array
}

return error;
}
}


}

43 changes: 43 additions & 0 deletions src/test/TemplateProcessor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,49 @@ test("plunked expression", async () => {
});
});

test("errorReport function", async () => {
let template = {
a: "${ [0..2]~>$map(function($i){$errorReport('oops I goofed ' & $i, 'BARFERROR')}) }",
b: "${!*plunked}", //broken on purpose
c: "${$errorReport('noname error occured')}"
};
const tp = new TemplateProcessor(template);
await tp.initialize();
expect(tp.errorReport).toEqual({
"/a": [
{
"error": {
"message": "oops I goofed 0",
"name": "BARFERROR"
}
},
{
"error": {
"message": "oops I goofed 1",
"name": "BARFERROR"
}
},
{
"error": {
"message": "oops I goofed 2",
"name": "BARFERROR"
}
}
],
"/b": {
"error": {
"message": "problem analysing expression : !*plunked",
"name": "badJSONata"
}
},
"/c": {
"error": {
"message": "noname error occured"
}
}
});
});

test("example from README explaining plans", async () => {
let template = {
a: {
Expand Down

0 comments on commit a9e9a63

Please sign in to comment.