From 512a866f2452f992e21527ca040b158eef31a7ab Mon Sep 17 00:00:00 2001 From: Alec Collins Date: Thu, 10 Jul 2025 20:17:36 +0000 Subject: [PATCH 1/2] Simple Surpress Errors Config --- expression-src/main/api/Configuration.cls | 6 ++ .../main/api/tests/ConfigurationTest.cls | 12 ++++ .../api/tests/EvaluatorRetrieveRecordTest.cls | 32 ++++++++++ .../main/src/resolver/EvaluatorResolver.cls | 64 +++++++++++-------- 4 files changed, 87 insertions(+), 27 deletions(-) diff --git a/expression-src/main/api/Configuration.cls b/expression-src/main/api/Configuration.cls index ace4bef9..3452a1c6 100644 --- a/expression-src/main/api/Configuration.cls +++ b/expression-src/main/api/Configuration.cls @@ -11,6 +11,7 @@ global with sharing class Configuration { global Boolean printAst = false; global Map customContext = new Map(); public Boolean withDiagnostics = false; + global Boolean suppressErrors = false; global Configuration respectSharing(Boolean respect) { sharing = respect ? SharingMode.WITH : SharingMode.WITHOUT; @@ -33,6 +34,11 @@ global with sharing class Configuration { return this; } + global Configuration suppressErrors() { + suppressErrors = true; + return this; + } + public void subscribe(EvaluatorEventNotifier notifier) { // Always subscribe to the event that sets the sharing mode // at the beginning of the evaluation regardless of configuration. diff --git a/expression-src/main/api/tests/ConfigurationTest.cls b/expression-src/main/api/tests/ConfigurationTest.cls index 3657fe20..fa48cfa5 100644 --- a/expression-src/main/api/tests/ConfigurationTest.cls +++ b/expression-src/main/api/tests/ConfigurationTest.cls @@ -20,4 +20,16 @@ private class ConfigurationTest { Configuration config = new Configuration().withCustomContext(null); Assert.isTrue(config.customContext.isEmpty()); } + + @IsTest + static void suppressErrorsDefaultsToFalse() { + Configuration config = new Configuration(); + Assert.isFalse(config.suppressErrors); + } + + @IsTest + static void canEnableSuppressErrors() { + Configuration config = new Configuration().suppressErrors(); + Assert.isTrue(config.suppressErrors); + } } diff --git a/expression-src/main/api/tests/EvaluatorRetrieveRecordTest.cls b/expression-src/main/api/tests/EvaluatorRetrieveRecordTest.cls index af850541..f1048095 100644 --- a/expression-src/main/api/tests/EvaluatorRetrieveRecordTest.cls +++ b/expression-src/main/api/tests/EvaluatorRetrieveRecordTest.cls @@ -117,4 +117,36 @@ private class EvaluatorRetrieveRecordTest { System.assertEquals(null, resultWithNullId, 'Should return null with null recordId'); Test.stopTest(); } + + @IsTest + static void testSuppressErrorsReturnEmptyString() { + Test.startTest(); + String result = (String)Evaluator.run('InvalidFunction()', new Configuration().suppressErrors()); + Test.stopTest(); + + System.assertEquals('', result, 'Should return empty string when errors are suppressed'); + } + + @IsTest + static void testSuppressErrorsWithValidExpression() { + Test.startTest(); + String result = (String)Evaluator.run('"Hello World"', new Configuration().suppressErrors()); + Test.stopTest(); + + System.assertEquals('Hello World', result, 'Should return correct result when no errors occur'); + } + + @IsTest + static void testWithoutSuppressErrorsThrowsException() { + Test.startTest(); + Boolean exceptionThrown = false; + try { + Evaluator.run('InvalidFunction()', new Configuration()); + } catch (Exception e) { + exceptionThrown = true; + } + Test.stopTest(); + + System.assertTrue(exceptionThrown, 'Should throw exception when errors are not suppressed'); + } } \ No newline at end of file diff --git a/expression-src/main/src/resolver/EvaluatorResolver.cls b/expression-src/main/src/resolver/EvaluatorResolver.cls index e920e6b7..a76c3f9a 100644 --- a/expression-src/main/src/resolver/EvaluatorResolver.cls +++ b/expression-src/main/src/resolver/EvaluatorResolver.cls @@ -77,36 +77,46 @@ public with sharing abstract class EvaluatorResolver { eventNotifier.notify(new OnEvaluationStartEvent(config)); - List tokens = this.scan(input); - List parsedExpressions = this.parse(tokens); - - eventNotifier.notify(new OnAfterParseEvent(parsedExpressions)); - - List pipedExpressions = this.desugarPipe(parsedExpressions); - - // This deals with the global environment, which is shared across all evaluations, so it - // it safe to call outside of the loop. - addCustomContextVariablesToGlobalEnvironment(config); - // We only care about the result of the last interpreted expression. - // We assume that the previous results are declarations. - Object result = null; - List customFunctionDeclarations = new List(); - for (Expr pipedExpression : pipedExpressions) { - if (isFunctionDeclaration(pipedExpression)) { - // If we are dealing with a function declaration we do not need to interpret it. - Expr.FunctionDeclaration fnDeclaration = (Expr.FunctionDeclaration) pipedExpression; - Environment.addGlobalVariable(fnDeclaration.functionName, fnDeclaration); - customFunctionDeclarations.add(fnDeclaration); - continue; - } + try { + List tokens = this.scan(input); + List parsedExpressions = this.parse(tokens); + + eventNotifier.notify(new OnAfterParseEvent(parsedExpressions)); + + List pipedExpressions = this.desugarPipe(parsedExpressions); + + // This deals with the global environment, which is shared across all evaluations, so it + // it safe to call outside of the loop. + addCustomContextVariablesToGlobalEnvironment(config); + // We only care about the result of the last interpreted expression. + // We assume that the previous results are declarations. + Object result = null; + List customFunctionDeclarations = new List(); + for (Expr pipedExpression : pipedExpressions) { + if (isFunctionDeclaration(pipedExpression)) { + // If we are dealing with a function declaration we do not need to interpret it. + Expr.FunctionDeclaration fnDeclaration = (Expr.FunctionDeclaration) pipedExpression; + Environment.addGlobalVariable(fnDeclaration.functionName, fnDeclaration); + customFunctionDeclarations.add(fnDeclaration); + continue; + } - Environment anEnvironment = this.prepareEnvironment(pipedExpression, customFunctionDeclarations); - result = this.interpret(anEnvironment, pipedExpression); - } + Environment anEnvironment = this.prepareEnvironment(pipedExpression, customFunctionDeclarations); + result = this.interpret(anEnvironment, pipedExpression); + } - eventNotifier.notify(new OnEvaluationEndEvent()); + eventNotifier.notify(new OnEvaluationEndEvent()); - return new EvaluationResult(result, additionalResultData); + return new EvaluationResult(result, additionalResultData); + } catch (Exception e) { + eventNotifier.notify(new OnEvaluationEndEvent()); + + if (config.suppressErrors) { + return new EvaluationResult('', additionalResultData); + } else { + throw e; + } + } } private static void addCustomContextVariablesToGlobalEnvironment(Configuration config) { From bc198bea759d763a4fc878b26d247ae0c1bafe50 Mon Sep 17 00:00:00 2001 From: Alec Collins Date: Thu, 10 Jul 2025 20:27:08 +0000 Subject: [PATCH 2/2] Use Object Instead Of String to avoid possible casting issue. --- .../main/api/tests/EvaluatorRetrieveRecordTest.cls | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/expression-src/main/api/tests/EvaluatorRetrieveRecordTest.cls b/expression-src/main/api/tests/EvaluatorRetrieveRecordTest.cls index f1048095..55b2b255 100644 --- a/expression-src/main/api/tests/EvaluatorRetrieveRecordTest.cls +++ b/expression-src/main/api/tests/EvaluatorRetrieveRecordTest.cls @@ -121,7 +121,7 @@ private class EvaluatorRetrieveRecordTest { @IsTest static void testSuppressErrorsReturnEmptyString() { Test.startTest(); - String result = (String)Evaluator.run('InvalidFunction()', new Configuration().suppressErrors()); + Object result = Evaluator.run('"a" & 1', new Configuration().suppressErrors()); Test.stopTest(); System.assertEquals('', result, 'Should return empty string when errors are suppressed'); @@ -130,7 +130,7 @@ private class EvaluatorRetrieveRecordTest { @IsTest static void testSuppressErrorsWithValidExpression() { Test.startTest(); - String result = (String)Evaluator.run('"Hello World"', new Configuration().suppressErrors()); + Object result = Evaluator.run('"Hello World"', new Configuration().suppressErrors()); Test.stopTest(); System.assertEquals('Hello World', result, 'Should return correct result when no errors occur'); @@ -141,7 +141,7 @@ private class EvaluatorRetrieveRecordTest { Test.startTest(); Boolean exceptionThrown = false; try { - Evaluator.run('InvalidFunction()', new Configuration()); + Evaluator.run('"a" & 1', new Configuration()); } catch (Exception e) { exceptionThrown = true; }