Refactoring.

This commit is contained in:
Manuel Friedli 2023-04-08 09:35:10 +02:00
parent 741894163b
commit a194e5db62
5 changed files with 123 additions and 105 deletions

View file

@ -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));

View file

@ -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);
}
}

View file

@ -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())));
}
}
}

View file

@ -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);
}
}
}