diff --git a/src/main/java/it/cnr/isti/workflow/manager/assistant/AssistantFlowValidation.java b/src/main/java/it/cnr/isti/workflow/manager/assistant/AssistantFlowValidation.java new file mode 100644 index 0000000..893ee35 --- /dev/null +++ b/src/main/java/it/cnr/isti/workflow/manager/assistant/AssistantFlowValidation.java @@ -0,0 +1,52 @@ +package it.cnr.isti.workflow.manager.assistant; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import it.cnr.isti.workflow.manager.flows.model.FlowCreateRequest; +import it.cnr.isti.workflow.manager.flows.validation.FlowExecutionValidator; +import it.cnr.isti.workflow.manager.flows.validation.ValidationError; +import it.cnr.isti.workflow.manager.flows.validation.ValidationErrorCodec; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validator; + +final class AssistantFlowValidation { + + private AssistantFlowValidation() { + } + + static List validate(FlowCreateRequest flow, Validator validator, + FlowExecutionValidator flowExecutionValidator) { + List errors = new ArrayList<>(); + + Set> violations = validator.validate(flow); + for (ConstraintViolation violation : violations) { + List decoded = ValidationErrorCodec.decode(violation.getMessage()); + if (decoded.isEmpty()) { + errors.add(toFallbackError(violation)); + continue; + } + for (ValidationError error : decoded) { + if (error.message() == null || Objects.equals(error.message(), violation.getMessage())) { + errors.add(toFallbackError(violation)); + } else { + errors.add(error); + } + } + } + + // Bean validation alone misses structural/execution issues (dangling connections, + // an unconnected BranchRejoin input, global-input reference mismatches, container + // subflow rules, ...) that only surface at actual execution time otherwise. + if (flow != null && flow.flow() != null) { + errors.addAll(flowExecutionValidator.collectErrors(flow.flow())); + } + return errors; + } + + private static ValidationError toFallbackError(ConstraintViolation violation) { + return new ValidationError("flow", null, violation.getPropertyPath().toString(), violation.getMessage()); + } +} diff --git a/src/main/java/it/cnr/isti/workflow/manager/assistant/FlowAssistantService.java b/src/main/java/it/cnr/isti/workflow/manager/assistant/FlowAssistantService.java index 3847c28..5fc21d3 100644 --- a/src/main/java/it/cnr/isti/workflow/manager/assistant/FlowAssistantService.java +++ b/src/main/java/it/cnr/isti/workflow/manager/assistant/FlowAssistantService.java @@ -61,12 +61,10 @@ import it.cnr.isti.workflow.manager.flows.model.FlowData; import it.cnr.isti.workflow.manager.flows.validation.FlowExecutionValidator; import it.cnr.isti.workflow.manager.flows.validation.ValidationError; import it.cnr.isti.workflow.manager.flows.validation.ValidationErrorCode; -import it.cnr.isti.workflow.manager.flows.validation.ValidationErrorCodec; import it.cnr.isti.workflow.manager.ios.IODescriptor; import it.cnr.isti.workflow.manager.ios.IOType; import it.cnr.isti.workflow.manager.llms.providers.LLMProvider; import it.cnr.isti.workflow.manager.vault.UserSecretService; -import jakarta.validation.ConstraintViolation; import jakarta.validation.Validator; @Service @@ -355,7 +353,7 @@ public class FlowAssistantService { public AssistantFlowResponse fix(AssistantFixRequest request, String owner, ProgressListener progressListener) { List initialErrors = request.validationErrors() == null || request.validationErrors().isEmpty() - ? validate(request.flow()) + ? AssistantFlowValidation.validate(request.flow(), validator, flowExecutionValidator) : request.validationErrors(); boolean directMdc = ensureAssistantRequestMdc(OperationMode.FIX.name()); try { @@ -416,7 +414,7 @@ public class FlowAssistantService { assembled = assembleFlow(provider, authorization, assistantModel, generatedFlowProvider, generatedFlowModel, phaseModels, mode, userPrompt, flowContext, errorContext, progressListener); progressListener.onProgress("validating", "Validating the assembled flow"); - errors = validate(assembled.flow()); + errors = AssistantFlowValidation.validate(assembled.flow(), validator, flowExecutionValidator); if (errors.isEmpty() || repairs >= allowedRepairs) { break; } @@ -1038,39 +1036,6 @@ public class FlowAssistantService { return SharedMemoryIntentClassifier.isSharedMemoryRequest(flowText.toString()); } - private List validate(FlowCreateRequest flow) { - List errors = new ArrayList<>(); - - Set> violations = validator.validate(flow); - for (ConstraintViolation violation : violations) { - List decoded = ValidationErrorCodec.decode(violation.getMessage()); - if (decoded.isEmpty()) { - errors.add(toFallbackError(violation)); - continue; - } - for (ValidationError error : decoded) { - if (error.message() == null || Objects.equals(error.message(), violation.getMessage())) { - errors.add(toFallbackError(violation)); - } else { - errors.add(error); - } - } - } - - // Bean validation alone misses structural/execution issues (dangling connections, - // an unconnected BranchRejoin input, global-input reference mismatches, container - // subflow rules, ...) that only surface at actual execution time otherwise. - if (flow != null && flow.flow() != null) { - errors.addAll(flowExecutionValidator.collectErrors(flow.flow())); - } - return errors; - } - - private ValidationError toFallbackError(ConstraintViolation violation) { - return new ValidationError("flow", null, violation.getPropertyPath().toString(), violation.getMessage()); - } - - private void appendRationale(List target, String rationale) { if (rationale != null && !rationale.isBlank()) { target.add(rationale.trim());