diff --git a/README.md b/README.md
index d461e54f..d20104e3 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,7 @@ Here are the different challenges :
- [Day 16: Make this code immutable.](exercise/day16/docs/challenge.md)
- [Day 17: Design one test that has the impact of thousands.](exercise/day17/docs/challenge.md)
- [Day 18: Automatically detect Linguistic Anti-Patterns (LAP).](exercise/day18/docs/challenge.md)
+- [Day 19: Loosing up dead weight.](exercise/day19/docs/challenge.md)
### Solutions
A solution proposal will be published here every day during the `Advent Of Craft` containing `the code` and a `step by step` guide.
diff --git a/exercise/day19/docs/challenge.md b/exercise/day19/docs/challenge.md
new file mode 100644
index 00000000..f91a2cd3
--- /dev/null
+++ b/exercise/day19/docs/challenge.md
@@ -0,0 +1,19 @@
+## Day 19: Loosing up dead weight.
+
+Today, you embark on the last few days of your journey.
+You have been warned a big storm is brewing your way but home you go.
+
+You will need to leave up extra weight and tighten up the ship
+for the days to come.
+
+The exercise of today deals with exception, again.
+
+You are starting to know the formula. No more exceptions.
+
+This time, the approach might be a bit different.
+
+> **Challenge of day 19: No more exception authorized - use a custom Error.**
+
+May your crafting journey continue!
+
+- 💡HINT: What object can you return to get the error state?
\ No newline at end of file
diff --git a/exercise/day19/docs/snippet.png b/exercise/day19/docs/snippet.png
new file mode 100644
index 00000000..a98e3f44
Binary files /dev/null and b/exercise/day19/docs/snippet.png differ
diff --git a/exercise/day19/pom.xml b/exercise/day19/pom.xml
new file mode 100644
index 00000000..999e44a7
--- /dev/null
+++ b/exercise/day19/pom.xml
@@ -0,0 +1,33 @@
+
+
+ 4.0.0
+
+
+ com.advent-of-craft
+ advent-of-craft2023
+ 1.0-SNAPSHOT
+
+
+ blog-part4
+ 1.0-SNAPSHOT
+
+ 3.0.0
+ 0.10.4
+
+
+
+
+ org.instancio
+ instancio-junit
+ ${instancio-junit.version}
+ test
+
+
+ io.vavr
+ vavr
+ ${vavr.version}
+
+
+
\ No newline at end of file
diff --git a/exercise/day19/src/main/java/blog/Article.java b/exercise/day19/src/main/java/blog/Article.java
new file mode 100644
index 00000000..58f0c557
--- /dev/null
+++ b/exercise/day19/src/main/java/blog/Article.java
@@ -0,0 +1,44 @@
+package blog;
+
+import io.vavr.collection.Seq;
+
+import java.time.LocalDate;
+
+import static io.vavr.collection.List.of;
+
+public class Article {
+ private final String name;
+ private final String content;
+ private final Seq comments;
+
+ public Article(String name, String content) {
+ this(name, content, of());
+ }
+
+ private Article(String name, String content, Seq comments) {
+ this.name = name;
+ this.content = content;
+ this.comments = comments;
+ }
+
+ private Article addComment(
+ String text,
+ String author,
+ LocalDate creationDate) throws CommentAlreadyExistException {
+ var comment = new Comment(text, author, creationDate);
+
+ if (comments.contains(comment)) {
+ throw new CommentAlreadyExistException();
+ }
+ return new Article(name, content, comments.append(comment));
+ }
+
+ public Article addComment(String text, String author) {
+ return addComment(text, author, LocalDate.now());
+ }
+
+ public Seq getComments() {
+ return comments;
+ }
+}
+
diff --git a/exercise/day19/src/main/java/blog/Comment.java b/exercise/day19/src/main/java/blog/Comment.java
new file mode 100644
index 00000000..c6b25bfc
--- /dev/null
+++ b/exercise/day19/src/main/java/blog/Comment.java
@@ -0,0 +1,6 @@
+package blog;
+
+import java.time.LocalDate;
+
+public record Comment(String text, String author, LocalDate creationDate) {
+}
diff --git a/exercise/day19/src/main/java/blog/CommentAlreadyExistException.java b/exercise/day19/src/main/java/blog/CommentAlreadyExistException.java
new file mode 100644
index 00000000..dc8902e9
--- /dev/null
+++ b/exercise/day19/src/main/java/blog/CommentAlreadyExistException.java
@@ -0,0 +1,4 @@
+package blog;
+
+public class CommentAlreadyExistException extends RuntimeException {
+}
\ No newline at end of file
diff --git a/exercise/day19/src/test/java/blog/ArticleBuilder.java b/exercise/day19/src/test/java/blog/ArticleBuilder.java
new file mode 100644
index 00000000..51264af9
--- /dev/null
+++ b/exercise/day19/src/test/java/blog/ArticleBuilder.java
@@ -0,0 +1,33 @@
+package blog;
+
+import java.util.HashMap;
+
+import static org.instancio.Instancio.create;
+
+public class ArticleBuilder {
+ public static final String AUTHOR = "Pablo Escobar";
+ public static final String COMMENT_TEXT = "Amazing article !!!";
+ private final HashMap comments;
+
+ public ArticleBuilder() {
+ comments = new HashMap<>();
+ }
+
+ public static ArticleBuilder anArticle() {
+ return new ArticleBuilder();
+ }
+
+ public ArticleBuilder commented() {
+ this.comments.put(COMMENT_TEXT, AUTHOR);
+ return this;
+ }
+
+ public Article build() {
+ return comments.entrySet()
+ .stream()
+ .reduce(new Article(
+ create(String.class),
+ create(String.class)
+ ), (a, e) -> a.addComment(e.getKey(), e.getValue()), (p, n) -> p);
+ }
+}
diff --git a/exercise/day19/src/test/java/blog/ArticleTests.java b/exercise/day19/src/test/java/blog/ArticleTests.java
new file mode 100644
index 00000000..5d7d3701
--- /dev/null
+++ b/exercise/day19/src/test/java/blog/ArticleTests.java
@@ -0,0 +1,74 @@
+package blog;
+
+import org.assertj.core.api.ThrowingConsumer;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.util.function.Function;
+
+import static blog.ArticleBuilder.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.instancio.Instancio.create;
+
+class ArticleTests {
+ private Article article;
+
+ @Test
+ void should_add_comment_in_an_article() {
+ when(article -> article.addComment(COMMENT_TEXT, AUTHOR));
+ then(article -> {
+ assertThat(article.getComments()).hasSize(1);
+ assertComment(article.getComments().get(0), COMMENT_TEXT, AUTHOR);
+ });
+ }
+
+ @Test
+ void should_add_comment_in_an_article_containing_already_a_comment() {
+ final var newComment = create(String.class);
+ final var newAuthor = create(String.class);
+
+ when(ArticleBuilder::commented, article -> article.addComment(newComment, newAuthor));
+ then(article -> {
+ assertThat(article.getComments()).hasSize(2);
+ assertComment(article.getComments().last(), newComment, newAuthor);
+ });
+ }
+
+ private static void assertComment(Comment comment, String commentText, String author) {
+ assertThat(comment.text()).isEqualTo(commentText);
+ assertThat(comment.author()).isEqualTo(author);
+ }
+
+ private void when(ArticleBuilder articleBuilder, Function act) throws CommentAlreadyExistException {
+ article = act.apply(
+ articleBuilder.build()
+ );
+ }
+
+ private void when(Function act) {
+ when(anArticle(), act);
+ }
+
+ private void when(Function options, Function act) {
+ when(options.apply(anArticle()), act);
+ }
+
+ private void then(ThrowingConsumer act) {
+ act.accept(article);
+ }
+
+ @Nested
+ class Fail {
+ @Test
+ void when__adding_an_existing_comment() {
+ var article = anArticle()
+ .commented()
+ .build();
+
+ assertThatThrownBy(() -> {
+ article.addComment(article.getComments().get(0).text(), article.getComments().get(0).author());
+ }).isInstanceOf(CommentAlreadyExistException.class);
+ }
+ }
+}
\ No newline at end of file
diff --git a/exercise/pom.xml b/exercise/pom.xml
index db756b2a..64e14585 100644
--- a/exercise/pom.xml
+++ b/exercise/pom.xml
@@ -27,6 +27,7 @@
day16
day17
day18
+ day19