Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Custom validation have incomplete proprety path #205
Browse files Browse the repository at this point in the history
Reproducer for #205
altro3 committed Nov 12, 2024
1 parent 06be39e commit 97b1540
Showing 28 changed files with 1,132 additions and 0 deletions.
3 changes: 3 additions & 0 deletions test-suite/build.gradle
Original file line number Diff line number Diff line change
@@ -3,9 +3,12 @@ plugins {
}

dependencies {
testAnnotationProcessor("org.projectlombok:lombok")
testAnnotationProcessor projects.micronautValidationProcessor
testAnnotationProcessor mn.micronaut.inject.java

testCompileOnly("org.projectlombok:lombok")

testImplementation mn.micronaut.inject
testImplementation mn.micronaut.core.reactive
testImplementation libs.managed.validation
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.micronaut.docs.validation.path;

import io.micronaut.docs.validation.path.model.Dag;
import io.micronaut.docs.validation.path.model.Flow;
import io.micronaut.docs.validation.path.model.Log;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import jakarta.validation.Validator;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;

@MicronautTest(startApplication = false)
public class ValidationTest {
@Inject
Validator validator;

@Test
void testValidationOk() {
Flow f = Flow.builder()
.id("id")
.namespace("namespace")
.tasks(List.of(Log.builder().id("task").type(Log.class.getName()).message("").build()))
.build();

var violation = validator.validate(f).stream().findFirst().get();
assertEquals("tasks[0].message", violation.getPropertyPath().toString());
assertEquals("must not be blank", violation.getMessage());
}

@Test
void testValidationKo() {
Flow f = Flow.builder()
.id("id")
.namespace("namespace")
.tasks(List.of(
Dag.builder()
.id("dag")
.type(Dag.class.getName())
.tasks(List.of(
Dag.DagTask.builder().task(Log.builder().id("cycle").type(Log.class.getName()).message("").build()).dependsOn(List.of("cycle")).build()
))
.build())
)
.build();

var violation = validator.validate(f).stream().findFirst().get();
assertEquals("tasks[0].tasks", violation.getPropertyPath().toString());
assertEquals("Cyclic dependency detected: cycle", violation.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.micronaut.docs.validation.path.model;

import io.micronaut.core.annotation.Introspected;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

import java.util.List;

@SuperBuilder
@Getter
@NoArgsConstructor
@Introspected
abstract public class AbstractTrigger {
@NotNull
@NotBlank
@Pattern(regexp="[a-zA-Z0-9_-]+")
protected String id;

@NotNull
@NotBlank
@Pattern(regexp="\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*")
protected String type;

private String description;

@Valid
private List<Condition> conditions;

@NotNull
@Builder.Default
private boolean disabled = false;

@Valid
private WorkerGroup workerGroup;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.micronaut.docs.validation.path.model;

import io.micronaut.core.annotation.Introspected;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

@SuperBuilder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Introspected
public abstract class Condition {
@NotNull
@Pattern(regexp="\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*")
protected String type;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package io.micronaut.docs.validation.path.model;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.docs.validation.path.validations.DagTaskValidation;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

@SuperBuilder
@ToString
@EqualsAndHashCode
@Getter
@NoArgsConstructor
@DagTaskValidation
public class Dag extends Task{
@NotNull
@Builder.Default
private final Integer concurrent = 0;

@NotEmpty
@Valid
private List<DagTask> tasks;

@Valid
protected List<Task> errors;

public List<String> dagCheckNotExistTask(List<DagTask> taskDepends) {
List<String> dependenciesIds = taskDepends
.stream()
.map(DagTask::getDependsOn)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.toList();

List<String> tasksIds = taskDepends
.stream()
.map(taskDepend -> taskDepend.getTask().getId())
.toList();

return dependenciesIds.stream()
.filter(dependencyId -> !tasksIds.contains(dependencyId))
.collect(Collectors.toList());
}

public ArrayList<String> dagCheckCyclicDependencies(List<DagTask> taskDepends) {
ArrayList<String> cyclicDependency = new ArrayList<>();
taskDepends.forEach(taskDepend -> {
if (taskDepend.getDependsOn() != null) {
List<String> nestedDependencies = this.nestedDependencies(taskDepend, taskDepends, new ArrayList<>());
if (nestedDependencies.contains(taskDepend.getTask().getId())) {
cyclicDependency.add(taskDepend.getTask().getId());
}
}
});

return cyclicDependency;
}

private ArrayList<String> nestedDependencies(DagTask taskDepend, List<DagTask> tasks, List<String> visited) {
final ArrayList<String> localVisited = new ArrayList<>(visited);
if (taskDepend.getDependsOn() != null) {
taskDepend.getDependsOn()
.stream()
.filter(depend -> !localVisited.contains(depend))
.forEach(depend -> {
localVisited.add(depend);
Optional<DagTask> task = tasks
.stream()
.filter(t -> t.getTask().getId().equals(depend))
.findFirst();

if (task.isPresent()) {
localVisited.addAll(this.nestedDependencies(task.get(), tasks, localVisited));
}
});
}
return localVisited;
}

@SuperBuilder
@ToString
@EqualsAndHashCode
@Getter
@NoArgsConstructor
@Introspected
public static class DagTask {
@NotNull
private Task task;

private List<String> dependsOn;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.micronaut.docs.validation.path.model;

public interface DeletedInterface {
boolean isDeleted();
}
Loading

0 comments on commit 97b1540

Please sign in to comment.