From a194e5db62e41678b089e6cfb0fe4cb285b4fbba Mon Sep 17 00:00:00 2001
From: Manuel Friedli <manuel@fritteli.ch>
Date: Sat, 8 Apr 2023 09:35:10 +0200
Subject: [PATCH] Refactoring.

---
 pom.xml                                       |   6 +-
 .../ch/fritteli/labyrinth/server/Main.java    |   3 +
 .../fritteli/labyrinth/server/OutputType.java |   2 +-
 .../server/handler/CreateHandler.java         | 106 +----------------
 .../ParametersToLabyrinthExtractor.java       | 111 ++++++++++++++++++
 5 files changed, 123 insertions(+), 105 deletions(-)
 create mode 100644 src/main/java/ch/fritteli/labyrinth/server/handler/ParametersToLabyrinthExtractor.java

diff --git a/pom.xml b/pom.xml
index 39521da..668a742 100644
--- a/pom.xml
+++ b/pom.xml
@@ -20,9 +20,11 @@
         <java.target.version>17</java.target.version>
         <jetbrains-annotations.version>24.0.1</jetbrains-annotations.version>
         <junit-jupiter.version>5.9.2</junit-jupiter.version>
+        <labyrinth-generator.version>0.0.3</labyrinth-generator.version>
         <logback.version>1.4.6</logback.version>
         <lombok.version>1.18.26</lombok.version>
         <slf4j.version>2.0.7</slf4j.version>
+        <undertow.version>2.3.5.Final</undertow.version>
         <vavr.version>0.10.4</vavr.version>
     </properties>
 
@@ -46,7 +48,7 @@
         <dependency>
             <groupId>ch.fritteli.labyrinth</groupId>
             <artifactId>labyrinth-generator</artifactId>
-            <version>0.0.3</version>
+            <version>${labyrinth-generator.version}</version>
         </dependency>
         <dependency>
             <groupId>io.vavr</groupId>
@@ -73,7 +75,7 @@
         <dependency>
             <groupId>io.undertow</groupId>
             <artifactId>undertow-core</artifactId>
-            <version>2.3.5.Final</version>
+            <version>${undertow.version}</version>
         </dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
diff --git a/src/main/java/ch/fritteli/labyrinth/server/Main.java b/src/main/java/ch/fritteli/labyrinth/server/Main.java
index d8c5bfa..9800d8c 100644
--- a/src/main/java/ch/fritteli/labyrinth/server/Main.java
+++ b/src/main/java/ch/fritteli/labyrinth/server/Main.java
@@ -1,9 +1,12 @@
 package ch.fritteli.labyrinth.server;
 
+import lombok.experimental.UtilityClass;
 import lombok.extern.slf4j.Slf4j;
 
 @Slf4j
+@UtilityClass
 public class Main {
+
     public static void main(String[] args) {
         LabyrinthServer.createAndStartServer()
                 .onFailure(e -> log.error("Failed to create server. Stopping.", e));
diff --git a/src/main/java/ch/fritteli/labyrinth/server/OutputType.java b/src/main/java/ch/fritteli/labyrinth/server/OutputType.java
index d40f668..4668bdd 100644
--- a/src/main/java/ch/fritteli/labyrinth/server/OutputType.java
+++ b/src/main/java/ch/fritteli/labyrinth/server/OutputType.java
@@ -88,7 +88,7 @@ public enum OutputType {
     }
 
     @NonNull
-    public byte[] render(@NonNull final Labyrinth labyrinth) throws Exception {
+    public byte[] render(@NonNull final Labyrinth labyrinth) {
         return this.render.apply(labyrinth);
     }
 }
diff --git a/src/main/java/ch/fritteli/labyrinth/server/handler/CreateHandler.java b/src/main/java/ch/fritteli/labyrinth/server/handler/CreateHandler.java
index c5a304a..c1578fd 100644
--- a/src/main/java/ch/fritteli/labyrinth/server/handler/CreateHandler.java
+++ b/src/main/java/ch/fritteli/labyrinth/server/handler/CreateHandler.java
@@ -6,33 +6,20 @@ import io.undertow.server.HttpServerExchange;
 import io.undertow.util.Headers;
 import io.undertow.util.HttpString;
 import io.undertow.util.StatusCodes;
-import io.vavr.Tuple;
 import io.vavr.Tuple2;
-import io.vavr.collection.HashMap;
-import io.vavr.collection.HashMultimap;
-import io.vavr.collection.HashSet;
-import io.vavr.collection.List;
-import io.vavr.collection.Multimap;
-import io.vavr.collection.Set;
-import io.vavr.collection.Stream;
-import io.vavr.control.Option;
 import io.vavr.control.Try;
-import lombok.Getter;
-import lombok.NonNull;
-import lombok.extern.slf4j.Slf4j;
-import org.jetbrains.annotations.Nullable;
-import org.slf4j.MDC;
-
 import java.nio.ByteBuffer;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.Deque;
 import java.util.Map;
-import java.util.Random;
-import java.util.function.Function;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.MDC;
 
 @Slf4j
 public class CreateHandler extends AbstractHttpHandler {
+
     public static final String PATH_TEMPLATE = "/create/{output}";
 
     @Override
@@ -77,89 +64,4 @@ public class CreateHandler extends AbstractHttpHandler {
     private Try<Tuple2<OutputType, Labyrinth>> createLabyrinthFromRequestParameters(final Map<String, Deque<String>> queryParameters) {
         return new ParametersToLabyrinthExtractor(queryParameters).createLabyrinth();
     }
-
-    private enum RequestParameter {
-        WIDTH(p -> Try.of(() -> Integer.parseInt(p)).toOption(), "w", "width"),
-        HEIGHT(p -> Try.of(() -> Integer.parseInt(p)).toOption(), "h", "height"),
-        ID(p -> Try.of(() -> Long.parseLong(p)).toOption(), "i", "id"),
-        OUTPUT(OutputType::ofString, "o", "output");
-        @NonNull
-        private final Function<String, Option<?>> extractor;
-        @Getter
-        @NonNull
-        private final Set<String> names;
-
-        RequestParameter(@NonNull final String... names) {
-            this.extractor = null;
-            this.names = HashSet.of(names);
-        }
-
-        RequestParameter(@NonNull final Function<String, Option<?>> extractor, @NonNull final String... names) {
-            this.extractor = extractor;
-            this.names = HashSet.of(names);
-        }
-
-        static Option<RequestParameter> parseName(@Nullable final String name) {
-            if (name == null) {
-                return Option.none();
-            }
-            return Stream.of(values()).find(param -> param.names.exists(name::equalsIgnoreCase));
-        }
-
-        @NonNull <T> Option<T> extractParameterValue(@NonNull final String parameter) {
-            return (Option<T>) this.extractor.apply(parameter);
-        }
-    }
-
-    private static class ParametersToLabyrinthExtractor {
-        @NonNull
-        private final Multimap<String, String> queryParameters;
-
-        ParametersToLabyrinthExtractor(@NonNull final Map<String, Deque<String>> queryParameters) {
-            this.queryParameters = HashMap.ofAll(queryParameters).foldLeft(HashMultimap.<String>withSet().empty(), (map, tuple) -> {
-                final String key = tuple._1();
-                return Stream.ofAll(tuple._2()).foldLeft(map, (m, value) -> m.put(key, value));
-            });
-        }
-
-        @NonNull
-        Try<Tuple2<OutputType, Labyrinth>> createLabyrinth() {
-            final Option<OutputType> output = getParameterValue(RequestParameter.OUTPUT);
-            final Option<Integer> width = getParameterValue(RequestParameter.WIDTH);
-            final Option<Integer> height = getParameterValue(RequestParameter.HEIGHT);
-            final Option<Long> id = getParameterValue(RequestParameter.ID);
-
-            if (output.isEmpty()) {
-                return Try.failure(new IllegalArgumentException("Path parameter %s is required and must be one of: %s".formatted(
-                        RequestParameter.OUTPUT.getNames().mkString("'", " / ", "'"),
-                        Stream.of(OutputType.values())
-                                .flatMap(OutputType::getNames)
-                                .mkString(", ")
-                )));
-            }
-            if (width.isEmpty()) {
-                return Try.failure(new IllegalArgumentException("Query parameter %s is required and must be a positive integer value".formatted(
-                        RequestParameter.WIDTH.getNames().mkString("'", " / ", "'")
-                )));
-            }
-            if (height.isEmpty()) {
-                return Try.failure(new IllegalArgumentException("Query parameter %s is required and must be a positive integer value".formatted(
-                        RequestParameter.HEIGHT.getNames().mkString("'", " / ", "'")
-                )));
-            }
-
-            return Try.of(() -> Tuple.of(output.get(), new Labyrinth(width.get(), height.get(), id.getOrElse(() -> new Random().nextLong()))));
-        }
-
-        @NonNull
-        private <T> Option<T> getParameterValue(@NonNull final RequestParameter parameter) {
-            return this.getParameterValues(parameter)
-                    .foldLeft(Option.none(), (type, param) -> type.orElse(() -> parameter.extractParameterValue(param)));
-        }
-
-        @NonNull
-        private Stream<String> getParameterValues(@NonNull final RequestParameter parameter) {
-            return parameter.names.toStream().flatMap(name -> Stream.ofAll(this.queryParameters.getOrElse(name, List.empty())));
-        }
-    }
 }
diff --git a/src/main/java/ch/fritteli/labyrinth/server/handler/ParametersToLabyrinthExtractor.java b/src/main/java/ch/fritteli/labyrinth/server/handler/ParametersToLabyrinthExtractor.java
new file mode 100644
index 0000000..b766b77
--- /dev/null
+++ b/src/main/java/ch/fritteli/labyrinth/server/handler/ParametersToLabyrinthExtractor.java
@@ -0,0 +1,111 @@
+package ch.fritteli.labyrinth.server.handler;
+
+import ch.fritteli.labyrinth.generator.model.Labyrinth;
+import ch.fritteli.labyrinth.server.OutputType;
+import io.vavr.Tuple;
+import io.vavr.Tuple2;
+import io.vavr.collection.HashMap;
+import io.vavr.collection.HashMultimap;
+import io.vavr.collection.HashSet;
+import io.vavr.collection.List;
+import io.vavr.collection.Multimap;
+import io.vavr.collection.Set;
+import io.vavr.collection.Stream;
+import io.vavr.control.Option;
+import io.vavr.control.Try;
+import java.util.Deque;
+import java.util.Map;
+import java.util.Random;
+import java.util.function.Function;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.Nullable;
+
+class ParametersToLabyrinthExtractor {
+
+    @NonNull
+    private final Multimap<RequestParameter, ?> queryParameters;
+
+    ParametersToLabyrinthExtractor(@NonNull final Map<String, Deque<String>> queryParameters) {
+        this.queryParameters = HashMap.ofAll(queryParameters)
+                .foldLeft(
+                        HashMultimap.withSet().empty(),
+                        (map, tuple) -> RequestParameter.parseName(tuple._1()).map(parameter -> Stream.ofAll(tuple._2())
+                                        .flatMap(parameter::extractParameterValue)
+                                        .foldLeft(map, (m, value) -> m.put(parameter, value)))
+                                .getOrElse(map)
+                );
+    }
+
+    @NonNull
+    Try<Tuple2<OutputType, Labyrinth>> createLabyrinth() {
+        final Option<OutputType> output = getParameterValue(RequestParameter.OUTPUT);
+        final Option<Integer> width = getParameterValue(RequestParameter.WIDTH);
+        final Option<Integer> height = getParameterValue(RequestParameter.HEIGHT);
+        final Option<Long> id = getParameterValue(RequestParameter.ID);
+
+        if (output.isEmpty()) {
+            return Try.failure(new IllegalArgumentException("Path parameter %s is required and must be one of: %s".formatted(
+                    RequestParameter.OUTPUT.getNames().mkString("'", " / ", "'"),
+                    Stream.of(OutputType.values())
+                            .flatMap(OutputType::getNames)
+                            .mkString(", ")
+            )));
+        }
+        if (width.isEmpty()) {
+            return Try.failure(new IllegalArgumentException("Query parameter %s is required and must be a positive integer value".formatted(
+                    RequestParameter.WIDTH.getNames().mkString("'", " / ", "'")
+            )));
+        }
+        if (height.isEmpty()) {
+            return Try.failure(new IllegalArgumentException("Query parameter %s is required and must be a positive integer value".formatted(
+                    RequestParameter.HEIGHT.getNames().mkString("'", " / ", "'")
+            )));
+        }
+
+        return Try.of(() -> Tuple.of(output.get(), new Labyrinth(width.get(), height.get(), id.getOrElse(() -> new Random().nextLong()))));
+    }
+
+    @NonNull
+    private <T> Option<T> getParameterValue(@NonNull final RequestParameter parameter) {
+        return (Option<T>) this.queryParameters.getOrElse(parameter, List.empty())
+                .headOption();
+    }
+
+    @Slf4j
+    private enum RequestParameter {
+        WIDTH(p -> Try.of(() -> Integer.parseInt(p))
+                .toOption()
+                .onEmpty(() -> log.debug("Unparseable value for parameter 'width': '{}'", p)), "w", "width"),
+        HEIGHT(p -> Try.of(() -> Integer.parseInt(p))
+                .toOption()
+                .onEmpty(() -> log.debug("Unparseable value for parameter 'height': '{}'", p)), "h", "height"),
+        ID(p -> Try.of(() -> Long.parseLong(p))
+                .toOption()
+                .onEmpty(() -> log.debug("Unparseable value for parameter 'id': '{}'", p)), "i", "id"),
+        OUTPUT(p -> OutputType.ofString(p)
+                .onEmpty(() -> log.debug("Unparseable value for parameter 'output': '{}'", p)), "o", "output");
+        @NonNull
+        private final Function<String, Option<?>> extractor;
+        @Getter
+        @NonNull
+        private final Set<String> names;
+
+        RequestParameter(@NonNull final Function<String, Option<?>> extractor, @NonNull final String... names) {
+            this.extractor = extractor;
+            this.names = HashSet.of(names);
+        }
+
+        static Option<RequestParameter> parseName(@Nullable final String name) {
+            if (name == null) {
+                return Option.none();
+            }
+            return Stream.of(values()).find(param -> param.names.exists(name::equalsIgnoreCase));
+        }
+
+        @NonNull Option<?> extractParameterValue(@NonNull final String parameter) {
+            return this.extractor.apply(parameter);
+        }
+    }
+}