caio.co/de/cerberus

Drop everything vert.x-related

I'll see you in a future project, buddy.
Id
21e86abbe22d4f0654cf6ccf8ea74c8d2f116c0a
Author
Caio
Commit time
2018-12-02T14:06:16+01:00

Modified pom.xml

@@ -26,17 +26,13
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<start-class>co.caio.cerberus.boot.BootApplication</start-class>
- <cerberus.launcher>co.caio.cerberus.service.CustomLauncher</cerberus.launcher>
- <cerberus.main.verticle>co.caio.cerberus.service.MainVerticle</cerberus.main.verticle>

<os.classifier>linux-x86_64</os.classifier>
<immutables.version>2.7.3</immutables.version>
<jackson.version>2.9.7</jackson.version>
<logback.version>1.2.3</logback.version>
<lucene.version>7.5.0</lucene.version>
- <netty.tcnative.version>2.0.20.Final</netty.tcnative.version>
- <netty.version>4.1.30.Final</netty.version>
- <vertx.version>3.6.0</vertx.version>
+ <junit.jupiter.version>5.3.2</junit.jupiter.version>
</properties>

<dependencyManagement>
@@ -48,98 +44,56
<scope>import</scope>
<type>pom</type>
</dependency>
- <dependency>
- <groupId>io.vertx</groupId>
- <artifactId>vertx-stack-depchain</artifactId>
- <version>${vertx.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
- <groupId>io.vertx</groupId>
- <artifactId>vertx-junit5</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>io.vertx</groupId>
- <artifactId>vertx-web-client</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>${lucene.version}</version>
</dependency>
+
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-facet</artifactId>
<version>${lucene.version}</version>
</dependency>
+
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queries</artifactId>
<version>${lucene.version}</version>
</dependency>
+
<dependency>
<groupId>org.immutables</groupId>
<artifactId>value</artifactId>
<version>${immutables.version}</version>
<scope>provided</scope>
</dependency>
+
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
+
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
+
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
- </dependency>
- <dependency>
- <groupId>io.vertx</groupId>
- <artifactId>vertx-core</artifactId>
- </dependency>
- <dependency>
- <groupId>io.vertx</groupId>
- <artifactId>vertx-web</artifactId>
- </dependency>
- <dependency>
- <groupId>io.vertx</groupId>
- <artifactId>vertx-health-check</artifactId>
- </dependency>
- <dependency>
- <groupId>io.vertx</groupId>
- <artifactId>vertx-config</artifactId>
- </dependency>
- <dependency>
- <groupId>io.vertx</groupId>
- <artifactId>vertx-dropwizard-metrics</artifactId>
- </dependency>
- <dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-tcnative-boringssl-static</artifactId>
- <classifier>${os.classifier}</classifier>
- <version>${netty.tcnative.version}</version>
- </dependency>
- <dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-transport-native-epoll</artifactId>
- <classifier>${os.classifier}</classifier>
- <version>${netty.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
+
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
@@ -150,6 +104,7
</exclusion>
</exclusions>
</dependency>
+
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
@@ -167,6 +122,20
</dependency>

<dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-api</artifactId>
+ <version>${junit.jupiter.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <version>${junit.jupiter.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
@@ -176,31 +145,6

<build>
<plugins>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>exec-maven-plugin</artifactId>
- <version>1.6.0</version>
- <configuration>
- <executable>java</executable>
- <arguments>
- <argument>-classpath</argument>
- <classpath/>
- <argument>-Xmx256m</argument>
- <argument>-Xms256m</argument>
- <argument>-Xlog:gc</argument>
- <argument>-verbose:gc</argument>
- <argument>-Dcerberus.service.port=8080</argument>
- <argument>-Dcerberus.service.data_dir=tmp/hue</argument>
- <argument>-Dcerberus.health.num_docs=200</argument>
- <argument>-Dlogback.configurationFile=src/test/resources/logback-test.xml</argument>
- <argument>${cerberus.launcher}</argument>
- <argument>run</argument>
- <argument>${cerberus.main.verticle}</argument>
- <argument>-instances</argument>
- <argument>4</argument>
- </arguments>
- </configuration>
- </plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>

Modified src/main/java/co/caio/cerberus/Environment.java

@@ -1,16 +1,8
package co.caio.cerberus;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
-import java.io.IOException;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeParseException;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@@ -18,150 +10,14
private static final Logger logger = LoggerFactory.getLogger(Environment.class);

private static final ObjectMapper mapper;
- private static final BuildStatus buildStatus;

static {
mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());
mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
-
- var props = new Properties();
- try {
- props.load(Environment.class.getClassLoader().getResourceAsStream("git.properties"));
- } catch (IOException rethrown) {
- throw new RuntimeException(rethrown);
- }
-
- buildStatus = BuildStatus.fromProperties(props);
}

public static ObjectMapper getObjectMapper() {
return mapper;
- }
-
- public static BuildStatus getBuildStatus() {
- return buildStatus;
- }
-
- public interface BuildStatus {
- String commitId();
-
- String commitTime();
-
- String buildTime();
-
- boolean isDirty();
-
- boolean isValid();
-
- List<String> invalidReasons();
-
- static BuildStatus fromProperties(Properties props) {
- return new BuildStatusImpl(props);
- }
-
- class BuildStatusImpl implements BuildStatus {
-
- private static final DateTimeFormatter formatter =
- DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ");
-
- static final String UNDEFINED = "undefined";
- static final String GIT_COMMIT_ID = "git.commit.id";
- static final String GIT_COMMIT_TIME = "git.commit.time";
- static final String GIT_BUILD_TIME = "git.build.time";
- static final String GIT_DIRTY = "git.dirty";
-
- private final List<String> invalidReasons;
- private final String commitId;
- private final String commitTime;
- private final String buildTime;
- private final boolean isDirty;
-
- BuildStatusImpl(Properties props) {
- List<String> reasons = new LinkedList<>();
-
- // TODO validate commit ids
- commitId = getProperty(props, GIT_COMMIT_ID, reasons);
-
- commitTime = getTime(props, GIT_COMMIT_TIME, reasons);
- buildTime = getTime(props, GIT_BUILD_TIME, reasons);
-
- var gitDirty = getProperty(props, GIT_DIRTY, reasons);
- switch (gitDirty.toLowerCase()) {
- case "true":
- reasons.add("Dirty workdir during build");
- isDirty = true;
- break;
- case "false":
- isDirty = false;
- break;
- case UNDEFINED:
- // reason populated already
- isDirty = true;
- break;
- default:
- reasons.add(String.format("Invalid value for %s: %s", GIT_DIRTY, gitDirty));
- isDirty = true;
- break;
- }
-
- invalidReasons = Collections.unmodifiableList(reasons);
- }
-
- private String getProperty(Properties props, String key, List<String> reason) {
- var result = props.getProperty(key, UNDEFINED);
- if (result.equals(UNDEFINED)) {
- reason.add(String.format("Property %s undefined", key));
- }
- return result;
- }
-
- private String getTime(Properties props, String key, List<String> reason) {
- var result = getProperty(props, key, reason);
- if (!result.equals(UNDEFINED) && isTimeInvalid(result)) {
- reason.add(String.format("Invalid datetime for property %s: %s", key, result));
- }
- return result;
- }
-
- public List<String> invalidReasons() {
- return invalidReasons;
- }
-
- @Override
- public String commitId() {
- return commitId;
- }
-
- @Override
- public String commitTime() {
- return commitTime;
- }
-
- @Override
- public String buildTime() {
- return buildTime;
- }
-
- @Override
- public boolean isDirty() {
- return isDirty;
- }
-
- @Override
- public boolean isValid() {
- return invalidReasons.isEmpty();
- }
-
- private boolean isTimeInvalid(String time) {
- try {
- ZonedDateTime.parse(time, formatter);
- return false;
- } catch (DateTimeParseException logged) {
- logger.debug("Exception parsing time string", logged);
- return true;
- }
- }
- }
}
}

Deleted src/main/java/co/caio/cerberus/service/CustomLauncher.java

@@ -1,19 +1,0
-package co.caio.cerberus.service;
-
-import io.vertx.core.Launcher;
-import io.vertx.core.VertxOptions;
-import io.vertx.core.impl.launcher.VertxLifecycleHooks;
-import io.vertx.ext.dropwizard.DropwizardMetricsOptions;
-
-public class CustomLauncher extends Launcher implements VertxLifecycleHooks {
-
- public static void main(String[] args) {
- new CustomLauncher().dispatch(args);
- }
-
- @Override
- public void beforeStartingVertx(VertxOptions options) {
- options.setPreferNativeTransport(true);
- options.setMetricsOptions(new DropwizardMetricsOptions().setEnabled(true));
- }
-}

Deleted src/main/java/co/caio/cerberus/service/HealthChecks.java

@@ -1,52 +1,0
-package co.caio.cerberus.service;
-
-import co.caio.cerberus.Environment;
-import co.caio.cerberus.service.api.V1SearchHandler;
-import io.vertx.core.Vertx;
-import io.vertx.ext.healthchecks.HealthCheckHandler;
-
-public class HealthChecks {
-
- public static HealthCheckHandler create(
- Vertx vertx, V1SearchHandler v1handler, ServiceConfiguration configuration) {
- var hc = HealthCheckHandler.create(vertx);
-
- hc.register(
- "native-transport",
- future -> {
- if (vertx.isNativeTransportEnabled()) {
- future.complete();
- } else {
- future.fail("native transport not enabled");
- }
- });
-
- hc.register(
- "num-docs",
- future -> {
- var current = v1handler.numDocs();
-
- if (v1handler.numDocs() > configuration.wantedNumDocs()) {
- future.complete();
- } else {
- future.fail(
- String.format(
- "Too few documents in index. Got %d, wanted > %d",
- current, configuration.wantedNumDocs()));
- }
- });
-
- hc.register(
- "build-status",
- future -> {
- var buildStatus = Environment.getBuildStatus();
- if (buildStatus.isValid()) {
- future.complete();
- } else {
- future.fail(String.join("; ", buildStatus.invalidReasons()));
- }
- });
-
- return hc;
- }
-}

Deleted src/main/java/co/caio/cerberus/service/MainVerticle.java

@@ -1,114 +1,0
-package co.caio.cerberus.service;
-
-import co.caio.cerberus.service.api.V1SearchHandler;
-import io.vertx.config.ConfigRetriever;
-import io.vertx.core.AbstractVerticle;
-import io.vertx.core.Future;
-import io.vertx.core.http.HttpServerOptions;
-import io.vertx.core.net.SelfSignedCertificate;
-import io.vertx.ext.dropwizard.MetricsService;
-import io.vertx.ext.web.Router;
-import io.vertx.ext.web.handler.BodyHandler;
-import java.nio.file.Paths;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class MainVerticle extends AbstractVerticle {
-
- private static final Logger logger = LoggerFactory.getLogger(MainVerticle.class);
- private ServiceConfiguration configuration;
-
- public static void main(String[] args) {
- new CustomLauncher().dispatch(new String[] {"run", MainVerticle.class.getCanonicalName()});
- }
-
- @Override
- public void start(Future<Void> startFuture) {
- readConfiguration().compose(this::startWebServer).setHandler(startFuture.completer());
- }
-
- @Override
- public void stop() {
- logger.info("Service stopped");
- }
-
- private Future<Void> startWebServer(ServiceConfiguration config) {
- return Future.future(
- future -> {
- var options =
- new HttpServerOptions()
- .setHandle100ContinueAutomatically(true)
- .setTcpNoDelay(true)
- .setTcpFastOpen(true);
-
- if (config.useSsl()) {
- var certificate = SelfSignedCertificate.create();
- options
- .setSsl(true)
- .setUseAlpn(true)
- .setPemKeyCertOptions(certificate.keyCertOptions())
- .setTrustOptions(certificate.trustOptions());
- }
-
- var router = getRouter(config);
-
- vertx
- .createHttpServer(options)
- .requestHandler(router)
- .listen(
- config.portNumber(),
- ar -> {
- if (ar.succeeded()) {
- logger.info(
- "Service started at {}://localhost:{}",
- config.useSsl() ? "https" : "http",
- config.portNumber());
- future.complete();
- } else {
- future.fail(future.cause());
- }
- });
- });
- }
-
- private Future<ServiceConfiguration> readConfiguration() {
-
- var configRetriever = ConfigRetriever.create(vertx);
-
- return Future.future(
- fut ->
- configRetriever.getConfig(
- ar -> {
- if (ar.succeeded()) {
- configuration = ServiceConfiguration.create(ar.result());
- fut.complete(configuration);
- } else {
- fut.fail(ar.cause());
- }
- }));
- }
-
- private Router getRouter(ServiceConfiguration config) {
- var router = Router.router(vertx);
- var v1handler = new V1SearchHandler(Paths.get(config.dataDirectory()));
-
- router
- .post("/api/v1/search")
- .consumes("application/json")
- .handler(BodyHandler.create())
- .handler(v1handler);
-
- router.get("/health*").handler(HealthChecks.create(vertx, v1handler, configuration));
-
- var metricsService = MetricsService.create(vertx);
- router
- .get("/metrics")
- .handler(
- context -> {
- context.response().putHeader("Content-type", "application/json");
- context.response().end(metricsService.getMetricsSnapshot(vertx).toBuffer());
- });
-
- return router;
- }
-}

Deleted src/main/java/co/caio/cerberus/service/ServiceConfiguration.java

@@ -1,86 +1,0
-package co.caio.cerberus.service;
-
-import io.vertx.core.json.JsonObject;
-import java.io.File;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-interface ServiceConfiguration {
-
- int portNumber();
-
- int wantedNumDocs();
-
- boolean useSsl();
-
- String dataDirectory();
-
- static ServiceConfiguration create(JsonObject config) {
- return new ServiceConfigurationImpl(config);
- }
-
- class ServiceConfigurationImpl implements ServiceConfiguration {
-
- private static final Logger logger = LoggerFactory.getLogger(ServiceConfiguration.class);
-
- static final String CONFIG_HEALTH_NUM_DOCS = "cerberus.health.num_docs";
- static final String CONFIG_SERVICE_PORT = "cerberus.service.port";
- static final String CONFIG_SERVICE_DATA_DIR = "cerberus.service.data_dir";
- static final String CONFIG_SERVICE_SSL = "cerberus.service.ssl";
-
- private final int portNumber;
- private final int wantedNumDocs;
-
- private final boolean useSsl;
- private final String dataDirectory;
-
- ServiceConfigurationImpl(JsonObject config) {
- portNumber = config.getInteger(CONFIG_SERVICE_PORT, 0);
- wantedNumDocs = config.getInteger(CONFIG_HEALTH_NUM_DOCS, -1);
- useSsl = config.getBoolean(CONFIG_SERVICE_SSL, false);
- dataDirectory = config.getString(CONFIG_SERVICE_DATA_DIR);
-
- if (portNumber <= 0 || portNumber > 65535) {
- throw new ConfigurationException(
- String.format("[%s] Invalid port number: %d", CONFIG_SERVICE_PORT, portNumber));
- }
-
- if (wantedNumDocs < 0) {
- logger.error(
- "Configuration {} must be >= 0. Health check effectively disabled",
- CONFIG_HEALTH_NUM_DOCS);
- }
-
- if (dataDirectory == null || !new File(dataDirectory).isDirectory()) {
- throw new ConfigurationException(
- String.format("[%s] Invalid configuration: not a directory", CONFIG_SERVICE_DATA_DIR));
- }
- }
-
- @Override
- public int portNumber() {
- return portNumber;
- }
-
- @Override
- public int wantedNumDocs() {
- return wantedNumDocs;
- }
-
- @Override
- public boolean useSsl() {
- return useSsl;
- }
-
- @Override
- public String dataDirectory() {
- return dataDirectory;
- }
- }
-
- class ConfigurationException extends IllegalStateException {
- ConfigurationException(String message) {
- super(message);
- }
- }
-}

Deleted src/main/java/co/caio/cerberus/service/api/V1SearchHandler.java

@@ -1,146 +1,0
-package co.caio.cerberus.service.api;
-
-import co.caio.cerberus.model.SearchQuery;
-import co.caio.cerberus.model.SearchResult;
-import co.caio.cerberus.search.Searcher;
-import co.caio.cerberus.service.api.V1SearchResponse.ErrorCode;
-import io.vertx.core.AsyncResult;
-import io.vertx.core.Future;
-import io.vertx.core.Handler;
-import io.vertx.ext.web.RoutingContext;
-import java.nio.file.Path;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class V1SearchHandler implements Handler<RoutingContext> {
- private final Searcher searcher;
-
- private static final Logger logger = LoggerFactory.getLogger(V1SearchHandler.class);
-
- private static final String CONTENT_TYPE = "Content-type";
- private static final String APPLICATION_JSON = "application/json";
-
- private static final String unknownFailureResponse;
-
- static {
- unknownFailureResponse =
- V1SearchResponse.toJson(
- V1SearchResponse.failure(
- V1SearchResponse.ErrorCode.UNKNOWN_ERROR, "Unknown/unhandled error"))
- .orElseThrow();
- }
-
- public V1SearchHandler(Path dataDirectory) {
- searcher = new Searcher.Builder().dataDirectory(dataDirectory).build();
- }
-
- // Used for health checks
- public int numDocs() {
- return searcher.numDocs();
- }
-
- @Override
- public void handle(RoutingContext routingContext) {
- readSearchQuery(routingContext)
- .compose(searchQuery -> runSearch(searchQuery, routingContext))
- .setHandler(
- ar -> {
- try {
- writeResponse(ar, routingContext);
- } catch (Exception logged) {
- if (logged.getMessage().endsWith("Response is closed")) {
- logger.debug("Connection closed before sending response. Exception ignored");
- } else {
- logger.error("Failure writing response", logged);
- }
- }
- });
- }
-
- private void writeResponse(AsyncResult<V1SearchResponse> ar, RoutingContext routingContext) {
- // It's pointless to check for the response state here since
- // it may be closed right after the check anyway, so we handle
- // this in the caller
- if (ar.succeeded()) {
- var maybeResult = V1SearchResponse.toJson(ar.result());
- routingContext.response().putHeader(CONTENT_TYPE, APPLICATION_JSON);
- routingContext.response().end(maybeResult.orElse(unknownFailureResponse));
- } else {
- routingContext.response().putHeader(CONTENT_TYPE, APPLICATION_JSON);
- writeError(routingContext, ar.cause());
- }
- }
-
- private Future<V1SearchResponse> runSearch(
- SearchQuery searchQuery, RoutingContext routingContext) {
- Future<V1SearchResponse> future = Future.future();
- routingContext
- .vertx()
- .<SearchResult>executeBlocking(
- fut -> {
- try {
- fut.complete(searcher.search(searchQuery));
- } catch (Exception wrapped) {
- fut.fail(
- new APIErrorMessage(
- ErrorCode.INTERNAL_SEARCH_ERROR,
- "An error occurred during search",
- wrapped));
- }
- },
- // Do not execute every runSearch call serially
- false,
- res -> {
- if (res.succeeded()) {
- future.complete(V1SearchResponse.success(res.result()));
- } else {
- future.fail(res.cause());
- }
- });
- return future;
- }
-
- private Future<SearchQuery> readSearchQuery(RoutingContext routingContext) {
- return Future.future(
- future -> {
- var maybeSq = SearchQuery.fromJson(routingContext.getBodyAsString());
- if (maybeSq.isPresent()) {
- future.complete(maybeSq.get());
- } else {
- future.fail(
- new APIErrorMessage(
- ErrorCode.INPUT_DECODE_ERROR, "Failed to decode input as a SearchQuery", null));
- }
- });
- }
-
- private void writeError(RoutingContext routingContext, Throwable cause) {
- logger.debug("Exception handling V1 /search request", cause);
-
- routingContext.response().setStatusCode(500);
- if (cause instanceof APIErrorMessage) {
- var errorCode = ((APIErrorMessage) cause).code;
- var errorMessage = ((APIErrorMessage) cause).message;
- routingContext
- .response()
- .end(
- V1SearchResponse.toJson(V1SearchResponse.failure(errorCode, errorMessage))
- .orElse(unknownFailureResponse));
-
- } else {
- logger.error("No error code found, rendering unknown failure", cause);
- routingContext.response().end(unknownFailureResponse);
- }
- }
-
- private class APIErrorMessage extends Exception {
- final ErrorCode code;
- final String message;
-
- APIErrorMessage(ErrorCode code, String message, Throwable cause) {
- super(message, cause);
- this.message = message;
- this.code = code;
- }
- }
-}

Deleted src/main/java/co/caio/cerberus/service/api/V1SearchResponse.java

@@ -1,106 +1,0
-package co.caio.cerberus.service.api;
-
-import co.caio.cerberus.Environment;
-import co.caio.cerberus.model.SearchResult;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import java.util.Optional;
-import org.immutables.value.Value;
-import org.slf4j.LoggerFactory;
-
-@Value.Style(
- strictBuilder = true,
- visibility = Value.Style.ImplementationVisibility.PACKAGE,
- overshadowImplementation = true)
-@JsonSerialize(as = ImmutableV1SearchResponse.class)
-@JsonDeserialize(as = ImmutableV1SearchResponse.class)
-@Value.Immutable
-public interface V1SearchResponse {
- Optional<SearchResult> result();
-
- ResponseMetadata metadata();
-
- enum ErrorCode {
- UNKNOWN_ERROR,
- INPUT_DECODE_ERROR,
- INTERNAL_SEARCH_ERROR
- }
-
- @Value.Immutable(builder = false)
- @Value.Style(visibility = Value.Style.ImplementationVisibility.PACKAGE)
- @JsonSerialize(as = ImmutableResponseMetadata.class)
- @JsonDeserialize(as = ImmutableResponseMetadata.class)
- interface ResponseMetadata {
- @Value.Parameter
- boolean success();
-
- @Value.Parameter
- Optional<ErrorMetadata> error();
-
- static ResponseMetadata successMetadata() {
- return ImmutableResponseMetadata.of(true, Optional.empty());
- }
-
- static ResponseMetadata errorMetadata(ErrorMetadata meta) {
- return ImmutableResponseMetadata.of(false, Optional.of(meta));
- }
-
- static ResponseMetadata errorMetadata(ErrorCode code, String cause) {
- return errorMetadata(ErrorMetadata.of(code, cause));
- }
- }
-
- @Value.Immutable(builder = false)
- @Value.Style(visibility = Value.Style.ImplementationVisibility.PACKAGE)
- @JsonSerialize(as = ImmutableErrorMetadata.class)
- @JsonDeserialize(as = ImmutableErrorMetadata.class)
- interface ErrorMetadata {
- @Value.Default
- @Value.Parameter
- default ErrorCode code() {
- return ErrorCode.UNKNOWN_ERROR;
- }
-
- @Value.Parameter
- String message();
-
- static ErrorMetadata of(ErrorCode code, String message) {
- return ImmutableErrorMetadata.of(code, message);
- }
-
- static ErrorMetadata of(String message) {
- return ImmutableErrorMetadata.of(ErrorCode.UNKNOWN_ERROR, message);
- }
- }
-
- static V1SearchResponse success(SearchResult result) {
- return new Builder().result(result).metadata(ResponseMetadata.successMetadata()).build();
- }
-
- static V1SearchResponse failure(ErrorCode code, String cause) {
- return new Builder().metadata(ResponseMetadata.errorMetadata(code, cause)).build();
- }
-
- static Optional<V1SearchResponse> fromJson(String serializedResponse) {
- try {
- return Optional.of(
- Environment.getObjectMapper().readValue(serializedResponse, V1SearchResponse.class));
- } catch (Exception e) {
- LoggerFactory.getLogger(V1SearchResponse.class)
- .error("Failed to read json <{}> as <{}>", serializedResponse, V1SearchResponse.class);
- return Optional.empty();
- }
- }
-
- static Optional<String> toJson(V1SearchResponse searchResponse) {
- try {
- return Optional.of(Environment.getObjectMapper().writeValueAsString(searchResponse));
- } catch (Exception ignored) {
- LoggerFactory.getLogger(V1SearchResponse.class)
- .error("Failed to serialize {} to json", searchResponse);
- return Optional.empty();
- }
- }
-
- class Builder extends ImmutableV1SearchResponse.Builder {}
-}

Deleted src/test/java/co/caio/cerberus/BuildStatusTest.java

@@ -1,101 +1,0
-package co.caio.cerberus;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import java.util.Properties;
-import org.junit.jupiter.api.Test;
-
-class BuildStatusTest {
-
- @Test
- void fromEmptyProperties() {
- var props = new Properties();
-
- var buildStatus = Environment.BuildStatus.fromProperties(props);
- assertFalse(buildStatus.isValid());
- assertEquals(4, buildStatus.invalidReasons().size());
- assertTrue(
- buildStatus
- .invalidReasons()
- .stream()
- .anyMatch(s -> s.contains("git.build.time undefined")));
- assertTrue(
- buildStatus
- .invalidReasons()
- .stream()
- .anyMatch(s -> s.contains("git.commit.time undefined")));
- assertTrue(
- buildStatus.invalidReasons().stream().anyMatch(s -> s.contains("git.commit.id undefined")));
- assertTrue(
- buildStatus.invalidReasons().stream().anyMatch(s -> s.contains("git.dirty undefined")));
- }
-
- @Test
- void isDirty() {
- var props = new Properties();
-
- props.setProperty("git.dirty", "invalid should make isDirty()=true");
- assertTrue(Environment.BuildStatus.fromProperties(props).isDirty());
- props.setProperty("git.dirty", "true");
- assertTrue(Environment.BuildStatus.fromProperties(props).isDirty());
- props.setProperty("git.dirty", "false");
- assertFalse(Environment.BuildStatus.fromProperties(props).isDirty());
- }
-
- @Test
- void commitId() {
- var props = new Properties();
- props.setProperty("git.commit.id", "any value makes this pass");
- var buildStatus = Environment.BuildStatus.fromProperties(props);
- assertTrue(
- buildStatus
- .invalidReasons()
- .stream()
- .noneMatch(s -> s.contains("git.commit.id undefined")));
- }
-
- @Test
- void timeParsing() {
- var props = new Properties();
- var validTimeString = "2018-10-29T20:11:42+0100";
-
- // Both invalid
- props.setProperty("git.build.time", "this is invalid");
- props.setProperty("git.commit.time", "this is also invalid");
- var buildStatus = Environment.BuildStatus.fromProperties(props);
- assertFalse(buildStatus.isValid());
- assertTrue(buildStatus.invalidReasons().stream().anyMatch(s -> s.contains("git.build.time")));
- assertTrue(buildStatus.invalidReasons().stream().anyMatch(s -> s.contains("git.commit.time")));
-
- // Just one invalid
- props.setProperty("git.build.time", validTimeString);
- buildStatus = Environment.BuildStatus.fromProperties(props);
- assertFalse(buildStatus.isValid());
- assertFalse(buildStatus.invalidReasons().stream().anyMatch(s -> s.contains("git.build.time")));
- assertTrue(buildStatus.invalidReasons().stream().anyMatch(s -> s.contains("git.commit.time")));
-
- // Both valid
- props.setProperty("git.commit.time", validTimeString);
- buildStatus = Environment.BuildStatus.fromProperties(props);
- assertFalse(buildStatus.isValid());
- assertFalse(buildStatus.invalidReasons().stream().anyMatch(s -> s.contains("git.build.time")));
- assertFalse(buildStatus.invalidReasons().stream().anyMatch(s -> s.contains("git.commit.time")));
-
- assertEquals(validTimeString, buildStatus.buildTime());
- assertEquals(validTimeString, buildStatus.commitTime());
- }
-
- @Test
- void basicValid() {
- var props = new Properties();
- var validTimeString = "2018-10-29T20:11:42+0100";
- props.setProperty("git.dirty", "false");
- props.setProperty("git.commit.time", validTimeString);
- props.setProperty("git.build.time", validTimeString);
- props.setProperty("git.commit.id", "valid commit id");
-
- var buildStatus = Environment.BuildStatus.fromProperties(props);
- assertEquals("valid commit id", buildStatus.commitId());
- assertTrue(buildStatus.isValid());
- }
-}

Deleted src/test/java/co/caio/cerberus/service/HealthTest.java

@@ -1,74 +1,0
-package co.caio.cerberus.service;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import co.caio.cerberus.Environment;
-import io.vertx.core.json.JsonObject;
-import io.vertx.junit5.VertxTestContext;
-import java.util.function.Consumer;
-import org.junit.jupiter.api.Test;
-
-// Tests here serve mostly to verify that the /health
-// endpoint is not broken
-class HealthTest extends MainVerticleTestCase {
- @Test
- void nativeTransport(VertxTestContext testContext) {
- healthRequest(
- "native-transport",
- testContext,
- result -> {
- // native-transport is only up when started with specific
- // vertx options, which we don't for tests
- assertEquals("DOWN", result.getString("status"));
- testContext.completeNow();
- });
- }
-
- @Test
- void buildStatus(VertxTestContext testContext) {
- healthRequest(
- "build-status",
- testContext,
- result -> {
- var wanted = Environment.getBuildStatus().isValid() ? "UP" : "DOWN";
- assertEquals(wanted, result.getString("status"));
- testContext.completeNow();
- });
- }
-
- @Test
- void numDocs(VertxTestContext testContext) {
- healthRequest(
- "num-docs",
- testContext,
- result -> {
- assertEquals("UP", result.getString("status"));
- testContext.completeNow();
- });
- }
-
- private void healthRequest(
- String checkId, VertxTestContext testContext, Consumer<JsonObject> consumer) {
- client
- .get("/health/" + checkId)
- .send(
- ar -> {
- if (ar.succeeded()) {
- var response = ar.result();
-
- testContext.verify(
- () ->
- assertTrue(
- response.getHeader("Content-type").startsWith("application/json")));
-
- var result = response.bodyAsJsonObject();
- testContext.verify(() -> assertEquals(checkId, result.getString("id")));
-
- consumer.accept(result);
-
- } else {
- testContext.failNow(ar.cause());
- }
- });
- }
-}

Deleted src/test/java/co/caio/cerberus/service/MainVerticleTestCase.java

@@ -1,52 +1,0
-package co.caio.cerberus.service;
-
-import static co.caio.cerberus.service.ServiceConfiguration.ServiceConfigurationImpl.*;
-
-import co.caio.cerberus.Util;
-import io.vertx.core.DeploymentOptions;
-import io.vertx.core.Vertx;
-import io.vertx.core.json.JsonObject;
-import io.vertx.ext.web.client.WebClient;
-import io.vertx.ext.web.client.WebClientOptions;
-import io.vertx.junit5.VertxExtension;
-import io.vertx.junit5.VertxTestContext;
-import java.net.ServerSocket;
-import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-@ExtendWith(VertxExtension.class)
-abstract class MainVerticleTestCase {
-
- static WebClient client;
-
- @BeforeAll
- static void setUp(Vertx vertx, VertxTestContext testContext) throws Exception {
- // record ephemeral port number obtained by opening a socket
- // so that we may use it as the test webserver port
- var serverSocket = new ServerSocket(0);
- var portNumber = serverSocket.getLocalPort();
- serverSocket.close();
-
- client =
- WebClient.create(
- vertx, new WebClientOptions().setDefaultHost("localhost").setDefaultPort(portNumber));
-
- var config =
- new DeploymentOptions()
- .setConfig(
- new JsonObject()
- .put(CONFIG_HEALTH_NUM_DOCS, 100)
- .put(CONFIG_SERVICE_SSL, false)
- .put(CONFIG_SERVICE_PORT, portNumber)
- .put(CONFIG_SERVICE_DATA_DIR, Util.getTestDataDir().toString()));
-
- vertx.deployVerticle(
- new MainVerticle(), config, testContext.succeeding(fut -> testContext.completeNow()));
- }
-
- @AfterAll
- static void tearDown(Vertx vertx, VertxTestContext testContext) {
- vertx.close(testContext.succeeding(fut -> testContext.completeNow()));
- }
-}

Deleted src/test/java/co/caio/cerberus/service/ServiceConfigurationTest.java

@@ -1,40 +1,0
-package co.caio.cerberus.service;
-
-import static co.caio.cerberus.service.ServiceConfiguration.ServiceConfigurationImpl.*;
-import static org.junit.jupiter.api.Assertions.*;
-
-import io.vertx.core.json.JsonObject;
-import org.junit.jupiter.api.Test;
-
-class ServiceConfigurationTest {
- @Test
- void validations() {
- assertThrows(
- ServiceConfiguration.ConfigurationException.class,
- () -> ServiceConfiguration.create(new JsonObject()));
- assertThrows(
- ServiceConfiguration.ConfigurationException.class,
- () -> ServiceConfiguration.create(new JsonObject().put(CONFIG_SERVICE_PORT, -1)));
- assertThrows(
- ServiceConfiguration.ConfigurationException.class,
- () -> ServiceConfiguration.create(new JsonObject().put(CONFIG_SERVICE_PORT, 70000)));
- assertThrows(
- ServiceConfiguration.ConfigurationException.class,
- () ->
- ServiceConfiguration.create(
- new JsonObject().put(CONFIG_SERVICE_DATA_DIR, "/path/doesnt/exist")));
- assertThrows(
- ServiceConfiguration.ConfigurationException.class,
- () ->
- ServiceConfiguration.create(
- new JsonObject().put(CONFIG_SERVICE_DATA_DIR, "/etc/passwd")));
- assertDoesNotThrow(
- () ->
- ServiceConfiguration.create(
- new JsonObject()
- .put(CONFIG_SERVICE_PORT, 80)
- .put(CONFIG_SERVICE_DATA_DIR, "/tmp/")
- .put(CONFIG_SERVICE_SSL, true)
- .put(CONFIG_HEALTH_NUM_DOCS, 1)));
- }
-}

Deleted src/test/java/co/caio/cerberus/service/V1SearchAPITest.java

@@ -1,80 +1,0
-package co.caio.cerberus.service;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import co.caio.cerberus.model.SearchQuery;
-import co.caio.cerberus.service.api.V1SearchResponse;
-import co.caio.cerberus.service.api.V1SearchResponse.ErrorCode;
-import io.vertx.core.buffer.Buffer;
-import io.vertx.junit5.VertxTestContext;
-import java.util.function.Consumer;
-import org.junit.jupiter.api.Test;
-
-class V1SearchAPITest extends MainVerticleTestCase {
-
- @Test
- void simpleSearch(VertxTestContext testContext) {
- var searchQuery = new SearchQuery.Builder().fulltext("keto bacon").build();
- var maybeBody = SearchQuery.toJson(searchQuery);
- assertTrue(maybeBody.isPresent());
- searchRequest(
- testContext,
- maybeBody.get(),
- 200,
- (sr) -> {
- testContext.verify(
- () -> {
- assertTrue(sr.metadata().success());
- assertFalse(sr.metadata().error().isPresent());
- assertTrue(sr.result().isPresent());
- assertTrue(sr.result().get().totalHits() > 0);
- });
- testContext.completeNow();
- });
- }
-
- @Test
- void jsonOnBadInput(VertxTestContext testContext) {
- searchRequest(
- testContext,
- "bad input that isnt even json",
- 500,
- (sr) -> {
- testContext.verify(
- () -> {
- assertFalse(sr.metadata().success());
- assertTrue(sr.metadata().error().isPresent());
- assertEquals(sr.metadata().error().get().code(), ErrorCode.INPUT_DECODE_ERROR);
- });
- testContext.completeNow();
- });
- }
-
- private void searchRequest(
- VertxTestContext testContext,
- String body,
- int wantedStatus,
- Consumer<V1SearchResponse> responseConsumer) {
- client
- .post("/api/v1/search")
- .putHeader("Content-type", "application/json")
- .sendBuffer(
- Buffer.buffer(body),
- ar -> {
- if (ar.succeeded()) {
- var response = ar.result();
- testContext.verify(
- () -> {
- assertEquals(wantedStatus, response.statusCode());
- assertEquals("application/json", response.getHeader("Content-type"));
- });
-
- var maybeSR = V1SearchResponse.fromJson(response.bodyAsString());
- assertTrue(maybeSR.isPresent());
- responseConsumer.accept(maybeSR.get());
- } else {
- testContext.failNow(ar.cause());
- }
- });
- }
-}