Compare commits
	
		
			3 commits
		
	
	
		
			a613a92adb
			...
			1e3e01be23
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1e3e01be23 | |||
| 3c2fca9a74 | |||
| c4d707d64a | 
					 10 changed files with 233 additions and 11 deletions
				
			
		|  | @ -4,4 +4,9 @@ COPY target/maze-server-*.jar /app/ | ||||||
| RUN rm /app/*-sources.jar | RUN rm /app/*-sources.jar | ||||||
| RUN mv /app/*.jar /app/app.jar | RUN mv /app/*.jar /app/app.jar | ||||||
| 
 | 
 | ||||||
| CMD java -Dfritteli.maze.server.host=0.0.0.0 -Dfritteli.maze.server.port=80 -jar /app/app.jar | CMD java \ | ||||||
|  |     -Dfritteli.maze.server.host=0.0.0.0 \ | ||||||
|  |     -Dfritteli.maze.server.port=80 \ | ||||||
|  |     -Dfritteli.maze.maxheight=256 \ | ||||||
|  |     -Dfritteli.maze.maxwidth=256 \ | ||||||
|  |     -jar /app/app.jar | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								pom.xml
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								pom.xml
									
										
									
									
									
								
							|  | @ -56,7 +56,7 @@ | ||||||
|     </distributionManagement> |     </distributionManagement> | ||||||
| 
 | 
 | ||||||
|     <properties> |     <properties> | ||||||
|         <maze-generator.version>0.2.1</maze-generator.version> |         <maze-generator.version>0.3.0</maze-generator.version> | ||||||
|         <maven-site-plugin.version>4.0.0-M8</maven-site-plugin.version> |         <maven-site-plugin.version>4.0.0-M8</maven-site-plugin.version> | ||||||
|         <undertow.version>2.3.18.Final</undertow.version> |         <undertow.version>2.3.18.Final</undertow.version> | ||||||
|     </properties> |     </properties> | ||||||
|  |  | ||||||
							
								
								
									
										45
									
								
								src/main/java/ch/fritteli/maze/server/Algorithm.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/main/java/ch/fritteli/maze/server/Algorithm.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | ||||||
|  | package ch.fritteli.maze.server; | ||||||
|  | 
 | ||||||
|  | import ch.fritteli.maze.generator.algorithm.MazeGeneratorAlgorithm; | ||||||
|  | import ch.fritteli.maze.generator.algorithm.RandomDepthFirst; | ||||||
|  | import ch.fritteli.maze.generator.algorithm.wilson.Wilson; | ||||||
|  | import ch.fritteli.maze.generator.model.Maze; | ||||||
|  | import io.vavr.collection.List; | ||||||
|  | import io.vavr.collection.Stream; | ||||||
|  | import io.vavr.control.Option; | ||||||
|  | import lombok.Getter; | ||||||
|  | import org.jetbrains.annotations.NotNull; | ||||||
|  | import org.jetbrains.annotations.Nullable; | ||||||
|  | 
 | ||||||
|  | import java.util.function.Function; | ||||||
|  | 
 | ||||||
|  | public enum Algorithm { | ||||||
|  |     RANDOM_DEPTH_FIRST(RandomDepthFirst::new, "random", "random-depth-first"), | ||||||
|  |     WILSON(Wilson::new, "wilson"); | ||||||
|  | 
 | ||||||
|  |     @NotNull | ||||||
|  |     private final Function<Maze, MazeGeneratorAlgorithm> creator; | ||||||
|  | 
 | ||||||
|  |     @Getter | ||||||
|  |     @NotNull | ||||||
|  |     private final List<String> names; | ||||||
|  | 
 | ||||||
|  |     Algorithm(@NotNull final Function<Maze, MazeGeneratorAlgorithm> creator, | ||||||
|  |               @NotNull final String... names) { | ||||||
|  |         this.creator = creator; | ||||||
|  |         this.names = List.of(names); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NotNull | ||||||
|  |     public static Option<Algorithm> ofString(@Nullable final String name) { | ||||||
|  |         return Option.of(name) | ||||||
|  |                 .map(String::toLowerCase) | ||||||
|  |                 .flatMap(nameLC -> Stream.of(values()) | ||||||
|  |                         .find(algorithm -> algorithm.getNames().contains(nameLC))); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NotNull | ||||||
|  |     public MazeGeneratorAlgorithm createAlgorithm(@NotNull final Maze maze) { | ||||||
|  |         return this.creator.apply(maze); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -3,6 +3,8 @@ package ch.fritteli.maze.server; | ||||||
| import ch.fritteli.maze.server.handler.CreateHandler; | import ch.fritteli.maze.server.handler.CreateHandler; | ||||||
| import ch.fritteli.maze.server.handler.RenderV1Handler; | import ch.fritteli.maze.server.handler.RenderV1Handler; | ||||||
| import ch.fritteli.maze.server.handler.RenderV2Handler; | import ch.fritteli.maze.server.handler.RenderV2Handler; | ||||||
|  | import ch.fritteli.maze.server.handler.RenderV3Handler; | ||||||
|  | import ch.fritteli.maze.server.handler.RenderVxHandler; | ||||||
| import io.undertow.Undertow; | import io.undertow.Undertow; | ||||||
| import io.undertow.server.RoutingHandler; | import io.undertow.server.RoutingHandler; | ||||||
| import io.vavr.control.Try; | import io.vavr.control.Try; | ||||||
|  | @ -24,7 +26,9 @@ public class MazeServer { | ||||||
|         final RoutingHandler routingHandler = new RoutingHandler() |         final RoutingHandler routingHandler = new RoutingHandler() | ||||||
|                 .get(CreateHandler.PATH_TEMPLATE, new CreateHandler(config.maxMazeHeight(), config.maxMazeWidth())) |                 .get(CreateHandler.PATH_TEMPLATE, new CreateHandler(config.maxMazeHeight(), config.maxMazeWidth())) | ||||||
|                 .post(RenderV1Handler.PATH_TEMPLATE, new RenderV1Handler()) |                 .post(RenderV1Handler.PATH_TEMPLATE, new RenderV1Handler()) | ||||||
|                 .post(RenderV2Handler.PATH_TEMPLATE, new RenderV2Handler()); |                 .post(RenderV2Handler.PATH_TEMPLATE, new RenderV2Handler()) | ||||||
|  |                 .post(RenderV3Handler.PATH_TEMPLATE, new RenderV3Handler()) | ||||||
|  |                 .post(RenderVxHandler.PATH_TEMPLATE, new RenderVxHandler()); | ||||||
| 
 | 
 | ||||||
|         this.undertow = Undertow.builder() |         this.undertow = Undertow.builder() | ||||||
|                 .addHttpListener(port, hostAddress) |                 .addHttpListener(port, hostAddress) | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import ch.fritteli.maze.generator.renderer.pdf.PDFRenderer; | ||||||
| import ch.fritteli.maze.generator.renderer.text.TextRenderer; | import ch.fritteli.maze.generator.renderer.text.TextRenderer; | ||||||
| import ch.fritteli.maze.generator.serialization.v1.SerializerDeserializerV1; | import ch.fritteli.maze.generator.serialization.v1.SerializerDeserializerV1; | ||||||
| import ch.fritteli.maze.generator.serialization.v2.SerializerDeserializerV2; | import ch.fritteli.maze.generator.serialization.v2.SerializerDeserializerV2; | ||||||
|  | import ch.fritteli.maze.generator.serialization.v3.SerializerDeserializerV3; | ||||||
| import io.vavr.collection.List; | import io.vavr.collection.List; | ||||||
| import io.vavr.collection.Stream; | import io.vavr.collection.Stream; | ||||||
| import io.vavr.control.Option; | import io.vavr.control.Option; | ||||||
|  | @ -23,7 +24,8 @@ public enum OutputType { | ||||||
|             maze -> TextRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8), |             maze -> TextRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8), | ||||||
|             false, |             false, | ||||||
|             "t", |             "t", | ||||||
|             "text"), |             "text", | ||||||
|  |             "txt"), | ||||||
|     HTML("text/html", |     HTML("text/html", | ||||||
|             "html", |             "html", | ||||||
|             maze -> HTMLRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8), |             maze -> HTMLRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8), | ||||||
|  | @ -65,7 +67,13 @@ public enum OutputType { | ||||||
|             SerializerDeserializerV2::serialize, |             SerializerDeserializerV2::serialize, | ||||||
|             true, |             true, | ||||||
|             "v", |             "v", | ||||||
|             "binaryv2"); |             "binaryv2"), | ||||||
|  |     BINARY_V3("application/octet-stream", | ||||||
|  |             "maz3", | ||||||
|  |             SerializerDeserializerV3::serialize, | ||||||
|  |             true, | ||||||
|  |             "3", | ||||||
|  |             "binaryv3"); | ||||||
|     @Getter |     @Getter | ||||||
|     @NotNull |     @NotNull | ||||||
|     private final String contentType; |     private final String contentType; | ||||||
|  |  | ||||||
|  | @ -68,7 +68,7 @@ public class CreateHandler extends AbstractHttpHandler { | ||||||
|                             .put(HttpString.tryFromString("X-Maze-ID"), String.valueOf(maze.getRandomSeed())) |                             .put(HttpString.tryFromString("X-Maze-ID"), String.valueOf(maze.getRandomSeed())) | ||||||
|                             .put(HttpString.tryFromString("X-Maze-Width"), String.valueOf(maze.getWidth())) |                             .put(HttpString.tryFromString("X-Maze-Width"), String.valueOf(maze.getWidth())) | ||||||
|                             .put(HttpString.tryFromString("X-Maze-Height"), String.valueOf(maze.getHeight())) |                             .put(HttpString.tryFromString("X-Maze-Height"), String.valueOf(maze.getHeight())) | ||||||
|                             .put(HttpString.tryFromString("X-Maze-Algorithm"), generatedMaze.generatorName()) |                             .put(HttpString.tryFromString("X-Maze-Algorithm"), maze.getAlgorithm()) | ||||||
|                             .put(HttpString.tryFromString("X-Maze-Generation-Duration-millis"), String.valueOf(durationMillis)); |                             .put(HttpString.tryFromString("X-Maze-Generation-Duration-millis"), String.valueOf(durationMillis)); | ||||||
|                     if (outputType.isAttachment()) { |                     if (outputType.isAttachment()) { | ||||||
|                         exchange.getResponseHeaders() |                         exchange.getResponseHeaders() | ||||||
|  |  | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
| package ch.fritteli.maze.server.handler; | package ch.fritteli.maze.server.handler; | ||||||
| 
 | 
 | ||||||
| import ch.fritteli.maze.generator.algorithm.RandomDepthFirst; | import ch.fritteli.maze.generator.algorithm.MazeGeneratorAlgorithm; | ||||||
| import ch.fritteli.maze.generator.model.Maze; | import ch.fritteli.maze.generator.model.Maze; | ||||||
| import ch.fritteli.maze.generator.model.Position; | import ch.fritteli.maze.generator.model.Position; | ||||||
|  | import ch.fritteli.maze.server.Algorithm; | ||||||
| import ch.fritteli.maze.server.InvalidRequestParameterException; | import ch.fritteli.maze.server.InvalidRequestParameterException; | ||||||
| import ch.fritteli.maze.server.OutputType; | import ch.fritteli.maze.server.OutputType; | ||||||
| import io.vavr.collection.Stream; | import io.vavr.collection.Stream; | ||||||
|  | @ -33,6 +34,7 @@ class ParametersToMazeExtractor { | ||||||
|         final Option<Long> id = getParameterValue(RequestParameter.ID); |         final Option<Long> id = getParameterValue(RequestParameter.ID); | ||||||
|         final Option<Position> start = getParameterValue(RequestParameter.START); |         final Option<Position> start = getParameterValue(RequestParameter.START); | ||||||
|         final Option<Position> end = getParameterValue(RequestParameter.END); |         final Option<Position> end = getParameterValue(RequestParameter.END); | ||||||
|  |         final Option<Algorithm> algorithm = getParameterValue(RequestParameter.ALGORITHM); | ||||||
| 
 | 
 | ||||||
|         if (output.isEmpty()) { |         if (output.isEmpty()) { | ||||||
|             return Try.failure(new InvalidRequestParameterException("Path parameter %s is required and must be one of: %s".formatted( |             return Try.failure(new InvalidRequestParameterException("Path parameter %s is required and must be one of: %s".formatted( | ||||||
|  | @ -77,8 +79,11 @@ class ParametersToMazeExtractor { | ||||||
|             } else { |             } else { | ||||||
|                 maze = new Maze(desiredWidth, desiredHeight, id.getOrElse(() -> new Random().nextLong())); |                 maze = new Maze(desiredWidth, desiredHeight, id.getOrElse(() -> new Random().nextLong())); | ||||||
|             } |             } | ||||||
|             new RandomDepthFirst(maze).run(); | 
 | ||||||
|             return new GeneratedMaze(maze, output.get(), RandomDepthFirst.class.getSimpleName()); |             final MazeGeneratorAlgorithm generator = algorithm.getOrElse(Algorithm.WILSON) | ||||||
|  |                     .createAlgorithm(maze); | ||||||
|  |             generator.run(); | ||||||
|  |             return new GeneratedMaze(maze, output.get()); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -87,6 +92,6 @@ class ParametersToMazeExtractor { | ||||||
|         return parameter.getParameterValue(this.queryParameters); |         return parameter.getParameterValue(this.queryParameters); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public record GeneratedMaze(@NotNull Maze maze, @NotNull OutputType outputType, @NotNull String generatorName) { |     public record GeneratedMaze(@NotNull Maze maze, @NotNull OutputType outputType) { | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,61 @@ | ||||||
|  | package ch.fritteli.maze.server.handler; | ||||||
|  | 
 | ||||||
|  | import ch.fritteli.maze.generator.model.Maze; | ||||||
|  | import ch.fritteli.maze.generator.serialization.v3.SerializerDeserializerV3; | ||||||
|  | import ch.fritteli.maze.server.OutputType; | ||||||
|  | import io.undertow.server.HttpServerExchange; | ||||||
|  | import io.undertow.util.HeaderValues; | ||||||
|  | import io.undertow.util.Headers; | ||||||
|  | import io.undertow.util.StatusCodes; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.jetbrains.annotations.NotNull; | ||||||
|  | 
 | ||||||
|  | import java.nio.ByteBuffer; | ||||||
|  | 
 | ||||||
|  | @Slf4j | ||||||
|  | public class RenderV3Handler extends AbstractHttpHandler { | ||||||
|  | 
 | ||||||
|  |     public static final String PATH_TEMPLATE = "/render/v3/{output}"; | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void handle(@NotNull final HttpServerExchange exchange) { | ||||||
|  |         log.debug("Handling render request"); | ||||||
|  | 
 | ||||||
|  |         if (exchange.isInIoThread()) { | ||||||
|  |             exchange.dispatch(this); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         exchange.getRequestReceiver().receiveFullBytes((httpServerExchange, bytes) -> { | ||||||
|  |             final OutputType output = this.getOutputType(httpServerExchange); | ||||||
|  |             final byte[] render; | ||||||
|  |             try { | ||||||
|  |                 final Maze maze = SerializerDeserializerV3.deserialize(bytes); | ||||||
|  |                 render = output.render(maze); | ||||||
|  |             } catch (final Exception e) { | ||||||
|  |                 log.error("Error rendering binary maze data", e); | ||||||
|  |                 httpServerExchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR) | ||||||
|  |                         .getResponseSender() | ||||||
|  |                         .send("Error rendering maze: %s".formatted(e.getMessage())); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             httpServerExchange | ||||||
|  |                     .setStatusCode(StatusCodes.OK) | ||||||
|  |                     .getResponseHeaders() | ||||||
|  |                     .put(Headers.CONTENT_TYPE, output.getContentType()); | ||||||
|  |             httpServerExchange.getResponseSender() | ||||||
|  |                     .send(ByteBuffer.wrap(render)); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NotNull | ||||||
|  |     private OutputType getOutputType(@NotNull final HttpServerExchange httpServerExchange) { | ||||||
|  |         return RequestParameter.OUTPUT.<OutputType>getParameterValue(httpServerExchange.getQueryParameters()) | ||||||
|  |                 .getOrElse(() -> { | ||||||
|  |                     final HeaderValues accept = httpServerExchange.getRequestHeaders().get(Headers.ACCEPT); | ||||||
|  |                     if (accept.contains(OutputType.HTML.getContentType())) { | ||||||
|  |                         return OutputType.HTML; | ||||||
|  |                     } | ||||||
|  |                     return OutputType.TEXT_PLAIN; | ||||||
|  |                 }); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -0,0 +1,91 @@ | ||||||
|  | package ch.fritteli.maze.server.handler; | ||||||
|  | 
 | ||||||
|  | import ch.fritteli.maze.generator.model.Maze; | ||||||
|  | import ch.fritteli.maze.generator.serialization.MazeConstants; | ||||||
|  | import ch.fritteli.maze.generator.serialization.v1.SerializerDeserializerV1; | ||||||
|  | import ch.fritteli.maze.generator.serialization.v2.SerializerDeserializerV2; | ||||||
|  | import ch.fritteli.maze.generator.serialization.v3.SerializerDeserializerV3; | ||||||
|  | import ch.fritteli.maze.server.OutputType; | ||||||
|  | import io.undertow.server.HttpHandler; | ||||||
|  | import io.undertow.server.HttpServerExchange; | ||||||
|  | import io.undertow.util.HeaderValues; | ||||||
|  | import io.undertow.util.Headers; | ||||||
|  | import io.undertow.util.StatusCodes; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | import org.jetbrains.annotations.NotNull; | ||||||
|  | 
 | ||||||
|  | import java.nio.ByteBuffer; | ||||||
|  | 
 | ||||||
|  | @Slf4j | ||||||
|  | public class RenderVxHandler implements HttpHandler { | ||||||
|  |     public static final String PATH_TEMPLATE = "/render/dyn/{output}"; | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void handleRequest(@NotNull final HttpServerExchange exchange) { | ||||||
|  |         log.debug("Handling render request"); | ||||||
|  | 
 | ||||||
|  |         if (exchange.isInIoThread()) { | ||||||
|  |             exchange.dispatch(this); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         exchange.getRequestReceiver().receiveFullBytes((httpServerExchange, bytes) -> { | ||||||
|  |             final OutputType output = this.getOutputType(httpServerExchange); | ||||||
|  |             final byte[] render; | ||||||
|  |             try { | ||||||
|  |                 final Version version = this.getVersion(bytes); | ||||||
|  |                 final Maze maze = switch (version) { | ||||||
|  |                     case V1 -> SerializerDeserializerV1.deserialize(bytes); | ||||||
|  |                     case V2 -> SerializerDeserializerV2.deserialize(bytes); | ||||||
|  |                     case V3 -> SerializerDeserializerV3.deserialize(bytes); | ||||||
|  |                 }; | ||||||
|  |                 render = output.render(maze); | ||||||
|  |             } catch (final Exception e) { | ||||||
|  |                 log.error("Error rendering binary maze data", e); | ||||||
|  |                 httpServerExchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR) | ||||||
|  |                         .getResponseSender() | ||||||
|  |                         .send("Error rendering maze: %s".formatted(e.getMessage())); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             httpServerExchange | ||||||
|  |                     .setStatusCode(StatusCodes.OK) | ||||||
|  |                     .getResponseHeaders() | ||||||
|  |                     .put(Headers.CONTENT_TYPE, output.getContentType()); | ||||||
|  |             httpServerExchange.getResponseSender() | ||||||
|  |                     .send(ByteBuffer.wrap(render)); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NotNull | ||||||
|  |     private Version getVersion(@NotNull final byte[] bytes) throws IllegalArgumentException { | ||||||
|  |         if (bytes.length < 3) { | ||||||
|  |             throw new IllegalArgumentException("Invalid input: too short"); | ||||||
|  |         } | ||||||
|  |         if (bytes[0] == MazeConstants.MAGIC_BYTE_1 && bytes[1] == MazeConstants.MAGIC_BYTE_2) { | ||||||
|  |             final byte version = bytes[2]; | ||||||
|  |             return switch (version) { | ||||||
|  |                 case SerializerDeserializerV1.VERSION_BYTE -> Version.V1; | ||||||
|  |                 case SerializerDeserializerV2.VERSION_BYTE -> Version.V2; | ||||||
|  |                 case SerializerDeserializerV3.VERSION_BYTE -> Version.V3; | ||||||
|  |                 default -> throw new IllegalArgumentException("Invalid version: " + version); | ||||||
|  |             }; | ||||||
|  |         } else { | ||||||
|  |             throw new IllegalArgumentException("Invalid input: not a Maze file"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NotNull | ||||||
|  |     private OutputType getOutputType(@NotNull final HttpServerExchange httpServerExchange) { | ||||||
|  |         return RequestParameter.OUTPUT.<OutputType>getParameterValue(httpServerExchange.getQueryParameters()) | ||||||
|  |                 .getOrElse(() -> { | ||||||
|  |                     final HeaderValues accept = httpServerExchange.getRequestHeaders().get(Headers.ACCEPT); | ||||||
|  |                     if (accept.contains(OutputType.HTML.getContentType())) { | ||||||
|  |                         return OutputType.HTML; | ||||||
|  |                     } | ||||||
|  |                     return OutputType.TEXT_PLAIN; | ||||||
|  |                 }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private enum Version { | ||||||
|  |         V1, V2, V3; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| package ch.fritteli.maze.server.handler; | package ch.fritteli.maze.server.handler; | ||||||
| 
 | 
 | ||||||
| import ch.fritteli.maze.generator.model.Position; | import ch.fritteli.maze.generator.model.Position; | ||||||
|  | import ch.fritteli.maze.server.Algorithm; | ||||||
| import ch.fritteli.maze.server.OutputType; | import ch.fritteli.maze.server.OutputType; | ||||||
| import io.vavr.Tuple2; | import io.vavr.Tuple2; | ||||||
| import io.vavr.collection.HashMap; | import io.vavr.collection.HashMap; | ||||||
|  | @ -44,7 +45,9 @@ enum RequestParameter { | ||||||
|                 return new Position(x, y); |                 return new Position(x, y); | ||||||
|             }) |             }) | ||||||
|             .toOption() |             .toOption() | ||||||
|             .onEmpty(() -> log.debug("Unparseable value for parameter 'end': '{}'", p)), "e", "end"); |             .onEmpty(() -> log.debug("Unparseable value for parameter 'end': '{}'", p)), "e", "end"), | ||||||
|  |     ALGORITHM(p -> Algorithm.ofString(p) | ||||||
|  |             .onEmpty(() -> log.debug("Unparseable value for parameter 'algorithm': '{}'", p)), "a", "algorithm"); | ||||||
|     @NotNull |     @NotNull | ||||||
|     private final Function<String, Option<?>> extractor; |     private final Function<String, Option<?>> extractor; | ||||||
|     @Getter |     @Getter | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue