Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error creating a custom sum function in Project Nessie CEL #623

Open
EduMacedoEng opened this issue Nov 11, 2024 · 0 comments
Open

Error creating a custom sum function in Project Nessie CEL #623

EduMacedoEng opened this issue Nov 11, 2024 · 0 comments

Comments

@EduMacedoEng
Copy link

Description

In this project, a custom sum function was implemented using CEL (Common Expression Language) to compute the sum of integer lists. The function was configured with a specific overload (sum_list_int) in the CustomLib class to handle integer lists by defining the behavior of the sum function for this purpose.

However, when attempting to execute the rule teste_score.sum() with an expression calling this function, the execution fails with an overload error. The application log shows that the sum calculation completed successfully before the failure, but an error occurs when trying to execute the overloaded sum function.

Steps to Reproduce

  1. Configured the custom sum function in the CustomLib class, defining an overload declaration (sum_list_int) for integer lists that returns an Int value.
  2. Registered the sum function and relevant variables in the constructor of the ProjectNessieCelExecutor class.
  3. Executed the rule with the expression teste_score.sum(), where teste_score is a list of integers.

Observed Behavior

The sum function is correctly invoked, and the values are summed as expected. The log shows the sum of teste_score as 20. However, when attempting to execute the expression teste_score.sum(), the following error occurs:

org.projectnessie.cel.tools.ScriptExecutionException: no such overload: list.sum

The full log provides detailed information on the execution, including:

  • The converted list (teste_score) with values [1, 2, 3, 4, 5, 5].
  • The calculated sum as 20.
  • The overload error: no such overload: list.sum.

2024-11-11T14:51:47.015-03:00 INFO 85578 --- Arguments built for CEL execution: {teste_score=[1, 2, 3, 4, 5, 5], teste_score_sum=20}

2024-11-11T14:51:47.023-03:00 ERROR 85578 --- org.projectnessie.cel.tools.ScriptExecutionException: no such overload: list.sum

The error indicates that the sum function overload is not recognized, despite the custom CustomLib implementation registering the sum_list_int function for integer lists. The function seems to be unavailable during execution, suggesting that the overload is not correctly applied.

CustomLib

@Sl4fj
public class CustomLib implements Library {

    private static final String SUM = "sum";

    public CustomLib() {
    }

    public static EnvOption strings() {
        return Library.Lib(new CustomLib());
    }

    @Override
    public List<EnvOption> getCompileOptions() {
        List<EnvOption> compileOptions = new ArrayList<>();

        EnvOption option = EnvOption.declarations(Decls.newFunction(
          SUM,
          Decls.newInstanceOverload(
            "sum_list_int",
            List.of(Decls.newListType(Decls.Int)),
            Decls.Int
          )));

        compileOptions.add(option);
        return compileOptions;
    }

    @Override
    public List<ProgramOption> getProgramOptions() {
        List<ProgramOption> programOptions = new ArrayList<>();

        ProgramOption sumFunctionOption = ProgramOption.functions(
          Overload.function(
            "sum_list_int",
            CustomGuards.callInListIntOutInt(this::sumFunction))
        );

        programOptions.add(sumFunctionOption);
        return programOptions;
    }

    public IntT sumFunction(ListT list) {
        long sum = 0;

        for (int i = 0; i < list.size().intValue(); i++) {
            Val val = list.get(IntT.intOf(i));
            if (val instanceof IntT intVal) {
                sum += intVal.intValue();
            } else {
                return IntT.intOf(0);  // Retorna um valor padrão, ou lance um erro
            }
        }
        log.info("Valor da soma: {}", sum);
        return IntT.intOf(sum);
    }
}

ProjectNessieCelExecutor

@Sl4fj
public class ProjectNessieCelExecutor implements RuleExecutor {

    @Getter
    private final RuleContext context;

    private Script script;

    public ProjectNessieCelExecutor(RuleContext context) throws ScriptCreateException {
        this.context = context;

        ScriptHost scriptHost = ScriptHost.newBuilder().build();

        ScriptBuilder scriptBuilder = scriptHost
          .buildScript(context.getRuleExpression())
          .withLibraries(new StringsLib(), new CustomLib());

        log.info("Initializing ProjectNessieCelExecutor with expression: {}", context.getRuleExpression());

        context.getExpressionVariableTypes().forEach((name, type) -> {
            Type celType = getCelType(type);
            scriptBuilder.withDeclarations(Decls.newVar(name, celType));
            log.info("Registered variable '{}' of type '{}'", name, celType);
        });

        script = scriptBuilder.build();
    }

    @Override
    public Boolean execute(Map<String, Object> args) {
        try {
            log.info("Executing script with arguments: {}", args);
            return script.execute(Boolean.class, convertArgsIfNeeded(args));
        } catch (ScriptException e) {
            log.error("Script execution failed", e);
            throw new RuntimeException(e);
        }
    }

    private Type getCelType(JsonSchema schema) {
        return switch (schema) {
            case ArraySchema arraySchema -> Decls.newListType(getCelType(arraySchema.getItems()));
            case BooleanSchema ignored -> Decls.Bool;
            case IntegerSchema ignored -> Decls.Int;
            case NumberSchema ignored -> Decls.Double;
            case ObjectSchema ignored -> Decls.Dyn;
            case StringSchema stringSchema -> switch (Objects.requireNonNullElse(stringSchema.getFormat(), "none")) {
                case DATE, DATE_TIME, TIME -> Decls.Timestamp;
                case DURATION -> Decls.Duration;
                default -> Decls.String;
            };
            default -> Decls.Dyn;
        };
    }

    private Map<String, Object> convertArgsIfNeeded(Map<String, Object> args) {
        Map<String, Object> newMap = new LinkedHashMap<>();
        args.forEach((name, value) -> {
            Object convertedValue = convertValueIfNeeded(value);
            log.info("Converting argument '{}': {}", name, convertedValue);
            newMap.put(name, convertedValue);
        });
        return newMap;
    }

    private Object convertValueIfNeeded(Object value) {
        TypeAdapter adapter = new CustomTypeAdapter();

        return switch (value) {
            case LocalDate localDate -> localDate.atStartOfDay().toInstant(ZoneOffset.UTC);
            case OffsetDateTime offsetDateTime -> offsetDateTime.toInstant();
            case Integer integer -> Long.valueOf(integer);
            case Long longValue -> longValue;  // Mantém o valor Long
            case List<?> list -> {
                log.info("Converting list with {} elements", list.size());
                // Convertendo para ListT, que é o tipo esperado pelo CEL
                List<Val> convertedList = new ArrayList<>();
                for (Object item : list) {
                    log.info("Item type: {}", item == null ? "null" : item.getClass().getName());
                    if (item instanceof Integer) {
                        convertedList.add(IntT.intOf((Integer) item));
                    } else if (item instanceof Long) {
                        convertedList.add(IntT.intOf((Long) item));  // Tratando Long de forma similar ao Integer
                    } else {
                        log.error("Unsupported item type in list: {}", item == null ? "null" : item.getClass().getName());
                        throw new IllegalArgumentException("Lista contém elementos que não são inteiros");
                    }
                }
                yield ListT.newGenericArrayList(adapter, convertedList.toArray(new Val[0]));
            }
            case Map<?, ?> map -> convertMap(map);
            case null, default -> {
                log.info("Default case, value type: {}", value == null ? "null" : value.getClass().getName());
                yield value;
            }
        };
    }


    private Map<Object, Object> convertMap(Map<?, ?> map) {
        Map<Object, Object> newMap = new LinkedHashMap<>();

        map.forEach((key, value) -> {
            Object convertedValue = convertValueIfNeeded(value);
            newMap.put(key, convertedValue);
            log.info("Converting map entry '{}' -> '{}'", key, convertedValue);
        });

        return newMap;
    }
}

I would like to know based on what was done, if anyone has any suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant