Skip to content

Commit def4bae

Browse files
committed
Add validation by YAVI
1 parent 6fa8702 commit def4bae

File tree

4 files changed

+93
-14
lines changed

4 files changed

+93
-14
lines changed

todo-api/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@
4646
<groupId>org.springframework.boot</groupId>
4747
<artifactId>spring-boot-starter-web</artifactId>
4848
</dependency>
49+
<dependency>
50+
<groupId>am.ik.yavi</groupId>
51+
<artifactId>yavi</artifactId>
52+
<version>0.15.0</version>
53+
</dependency>
4954
<dependency>
5055
<groupId>io.micrometer</groupId>
5156
<artifactId>micrometer-tracing-bridge-brave</artifactId>
Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,58 @@
11
package lol.maki.dev.todo;
22

3-
import java.time.Instant;
4-
3+
import am.ik.yavi.arguments.Arguments7Validator;
4+
import am.ik.yavi.arguments.BooleanValidator;
5+
import am.ik.yavi.arguments.InstantValidator;
6+
import am.ik.yavi.arguments.StringValidator;
7+
import am.ik.yavi.builder.BooleanValidatorBuilder;
8+
import am.ik.yavi.builder.InstantValidatorBuilder;
9+
import am.ik.yavi.builder.StringValidatorBuilder;
510
import com.fasterxml.jackson.annotation.JsonInclude;
11+
import java.time.Instant;
12+
import java.util.function.Function;
613
import org.jilt.Builder;
714

8-
@Builder(toBuilder = "from")
915
@JsonInclude(JsonInclude.Include.NON_NULL)
1016
public record Todo(String todoId, String todoTitle, boolean finished, Instant createdAt, String createdBy,
1117
Instant updatedAt, String updatedBy) {
1218

19+
private static Function<String, InstantValidator<Instant>> instantValidator = (
20+
name) -> InstantValidatorBuilder.of(name, c -> c.notNull()).build();
21+
22+
private static Function<String, StringValidator<String>> usernameValidator = (
23+
name) -> StringValidatorBuilder.of(name, c -> c.email().notBlank().lessThanOrEqual(255)).build();
24+
25+
public static StringValidator<String> todoIdValidator = StringValidatorBuilder
26+
.of("todoId", c -> c.notBlank().lessThanOrEqual(255))
27+
.build();
28+
29+
public static StringValidator<String> todoTitleValidator = StringValidatorBuilder
30+
.of("todoTitle", c -> c.notBlank().lessThanOrEqual(255))
31+
.build();
32+
33+
public static BooleanValidator<Boolean> finishedValidator = BooleanValidatorBuilder.of("finished", c -> c.notNull())
34+
.build();
35+
36+
public static InstantValidator<Instant> createdAtValidator = instantValidator.apply("createdAt");
37+
38+
public static InstantValidator<Instant> updatedAtValidator = instantValidator.apply("updatedAt");
39+
40+
public static StringValidator<String> createdByValidator = usernameValidator.apply("createdBy");
41+
42+
public static StringValidator<String> updatedByValidator = usernameValidator.apply("updatedBy");
43+
44+
public static Arguments7Validator<String, String, Boolean, Instant, String, Instant, String, Todo> validator = todoIdValidator
45+
.split(todoTitleValidator)
46+
.split(finishedValidator)
47+
.split(createdAtValidator)
48+
.split(createdByValidator)
49+
.split(updatedAtValidator)
50+
.split(updatedByValidator)
51+
.apply(Todo::new);
52+
53+
@Builder(className = "TodoBuilder", factoryMethod = "todo", toBuilder = "from", packageName = "lol.maki.dev.todo")
54+
public static Todo create(String todoId, String todoTitle, boolean finished, Instant createdAt, String createdBy,
55+
Instant updatedAt, String updatedBy) {
56+
return validator.validated(todoId, todoTitle, finished, createdAt, createdBy, updatedAt, updatedBy);
57+
}
1358
}

todo-api/src/main/java/lol/maki/dev/todo/TodoController.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package lol.maki.dev.todo;
22

3+
import am.ik.yavi.core.ConstraintViolations;
4+
import am.ik.yavi.core.ConstraintViolationsException;
35
import java.net.URI;
46
import java.util.List;
57
import java.util.Map;
@@ -41,19 +43,20 @@ public ResponseEntity<Todo> getTodo(@PathVariable("todoId") String todoId) {
4143
}
4244

4345
@PostMapping(path = "")
44-
public ResponseEntity<Todo> postTodos(@RequestBody Todo todo, @AuthenticationPrincipal Jwt jwt,
46+
public ResponseEntity<Todo> postTodos(@RequestBody Map<String, Object> request, @AuthenticationPrincipal Jwt jwt,
4547
UriComponentsBuilder builder) {
4648
String email = jwt.getClaimAsString("email");
47-
Todo created = this.todoService.create(todo.todoTitle(), email);
49+
Todo created = this.todoService.create((String) request.get("todoTitle"), email);
4850
URI uri = builder.pathSegment("todos", created.todoId()).build().toUri();
4951
return ResponseEntity.created(uri).body(created);
5052
}
5153

5254
@PatchMapping(path = "/{todoId}")
53-
public ResponseEntity<Todo> patchTodo(@PathVariable("todoId") String todoId, @RequestBody Todo todo,
54-
@AuthenticationPrincipal Jwt jwt) {
55+
public ResponseEntity<Todo> patchTodo(@PathVariable("todoId") String todoId,
56+
@RequestBody Map<String, Object> request, @AuthenticationPrincipal Jwt jwt) {
5557
String email = jwt.getClaimAsString("email");
56-
Todo updated = this.todoService.update(todoId, todo.todoTitle(), todo.finished(), email);
58+
Todo updated = this.todoService.update(todoId, (String) request.get("todoTitle"),
59+
(Boolean) request.get("finished"), email);
5760
return ResponseEntity.ok(updated);
5861
}
5962

@@ -68,4 +71,11 @@ public ResponseEntity<?> handleNotFound(TodoService.NotFoundException e) {
6871
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("message", e.getMessage()));
6972
}
7073

74+
@ExceptionHandler(ConstraintViolationsException.class)
75+
public ResponseEntity<?> handleConstraintViolations(ConstraintViolationsException e) {
76+
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
77+
.body(Map.of("message", "Validation failed", "violations",
78+
ConstraintViolations.of(e.violations()).details()));
79+
}
80+
7181
}

todo-api/src/test/java/lol/maki/dev/todo/TodoApiApplicationTests.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,18 @@
11
package lol.maki.dev.todo;
22

3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.node.TextNode;
35
import java.net.URI;
46
import java.time.Instant;
57
import java.util.List;
68
import java.util.Set;
79
import java.util.UUID;
810
import java.util.function.Function;
9-
10-
import com.fasterxml.jackson.databind.JsonNode;
11-
import com.fasterxml.jackson.databind.node.TextNode;
1211
import org.junit.jupiter.api.BeforeEach;
1312
import org.junit.jupiter.api.MethodOrderer;
1413
import org.junit.jupiter.api.Order;
1514
import org.junit.jupiter.api.Test;
1615
import org.junit.jupiter.api.TestMethodOrder;
17-
import org.testcontainers.junit.jupiter.Testcontainers;
18-
import org.zalando.logbook.spring.LogbookClientHttpRequestInterceptor;
19-
2016
import org.springframework.beans.factory.annotation.Autowired;
2117
import org.springframework.beans.factory.annotation.Value;
2218
import org.springframework.boot.test.context.SpringBootTest;
@@ -28,6 +24,8 @@
2824
import org.springframework.http.MediaType;
2925
import org.springframework.http.ResponseEntity;
3026
import org.springframework.web.client.RestClient;
27+
import org.testcontainers.junit.jupiter.Testcontainers;
28+
import org.zalando.logbook.spring.LogbookClientHttpRequestInterceptor;
3129

3230
import static org.assertj.core.api.Assertions.assertThat;
3331

@@ -227,6 +225,27 @@ void shouldReturnNotFoundForNonExistentTodo() {
227225
.isEqualTo(new TextNode("Todo not found: todoId=%s".formatted(todoId)));
228226
}
229227

228+
@Test
229+
void shouldReturnBadRequestForInvalidTodo() {
230+
String accessToken = this.accessTokenSupplier.apply(Set.of("todo:write"));
231+
ResponseEntity<JsonNode> response = this.restClient.post()
232+
.uri("/todos")
233+
.contentType(MediaType.APPLICATION_JSON)
234+
.headers(httpHeaders -> httpHeaders.setBearerAuth(accessToken))
235+
.body("""
236+
{"todoTitle": ""}
237+
""")
238+
.retrieve()
239+
.toEntity(JsonNode.class);
240+
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
241+
assertThat(response.getBody()).isNotNull();
242+
assertThat(response.getBody().get("message")).isEqualTo(new TextNode("Validation failed"));
243+
assertThat(response.getBody().has("violations")).isTrue();
244+
assertThat(response.getBody().get("violations").size()).isEqualTo(1);
245+
assertThat(response.getBody().get("violations").get(0).get("defaultMessage"))
246+
.isEqualTo(new TextNode("\"todoTitle\" must not be blank"));
247+
}
248+
230249
@Test
231250
@Order(3)
232251
void shouldUpdateTodoWithSufficientScope() {

0 commit comments

Comments
 (0)