From 354662c7fce0aea944412c0967b217bda4ddfb98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Wed, 8 Nov 2023 15:35:41 +0100 Subject: [PATCH] chore: Update tapir to v1.8.4 (#2922) --- .../testcontainers/FusekiTestContainer.scala | 2 +- .../testcontainers/SipiTestContainer.scala | 2 +- project/Dependencies.scala | 2 +- webapi/scripts/healthcheck.sh | 2 +- .../main/scala/org/knora/webapi/Main.scala | 3 +- .../http/handler/ExceptionHandlerZ.scala | 58 ---------- .../instrumentation/health/HealthRouteZ.scala | 107 ------------------ .../instrumentation/index/IndexApp.scala | 33 ------ .../domain/service/DspIngestClient.scala | 2 +- .../domain/service/ProjectImportService.scala | 4 +- .../infrastructure/MetricsServer.scala} | 29 ++--- .../infrastructure/api}/PrometheusApp.scala | 13 +-- 12 files changed, 21 insertions(+), 236 deletions(-) delete mode 100644 webapi/src/main/scala/org/knora/webapi/http/handler/ExceptionHandlerZ.scala delete mode 100644 webapi/src/main/scala/org/knora/webapi/instrumentation/health/HealthRouteZ.scala delete mode 100644 webapi/src/main/scala/org/knora/webapi/instrumentation/index/IndexApp.scala rename webapi/src/main/scala/org/knora/webapi/{core/InstrumentationServer.scala => slice/infrastructure/MetricsServer.scala} (55%) rename webapi/src/main/scala/org/knora/webapi/{instrumentation/prometheus => slice/infrastructure/api}/PrometheusApp.scala (52%) diff --git a/integration/src/test/scala/org/knora/webapi/testcontainers/FusekiTestContainer.scala b/integration/src/test/scala/org/knora/webapi/testcontainers/FusekiTestContainer.scala index 6a96bc55f1..8050ac5881 100644 --- a/integration/src/test/scala/org/knora/webapi/testcontainers/FusekiTestContainer.scala +++ b/integration/src/test/scala/org/knora/webapi/testcontainers/FusekiTestContainer.scala @@ -48,7 +48,7 @@ trait FusekiTestContainer extends GenericContainer[FusekiTestContainer] { .map(_.map(line => line.replace("@REPOSITORY@", repositoryName)).mkString("\n")) request = HttpRequest .newBuilder() - .uri(baseUrl.withPath("/$/datasets").toJavaURI) + .uri(baseUrl.path("/$/datasets").toJavaURI) .POST(BodyPublishers.ofString(fusekiConfig)) .header("Content-Type", "text/turtle; charset=utf-8") .build() diff --git a/integration/src/test/scala/org/knora/webapi/testcontainers/SipiTestContainer.scala b/integration/src/test/scala/org/knora/webapi/testcontainers/SipiTestContainer.scala index 84a3468b4d..292053fecf 100644 --- a/integration/src/test/scala/org/knora/webapi/testcontainers/SipiTestContainer.scala +++ b/integration/src/test/scala/org/knora/webapi/testcontainers/SipiTestContainer.scala @@ -69,7 +69,7 @@ object SipiTestContainer { def portAndHost: ZIO[SipiTestContainer, Nothing, (Int, String)] = port <*> host def resolveUrl(path: http.Path): URIO[SipiTestContainer, URL] = - ZIO.serviceWith[SipiTestContainer](_.sipiBaseUrl.withPath(path)) + ZIO.serviceWith[SipiTestContainer](_.sipiBaseUrl.path(path)) def copyFileToImageFolderInContainer(prefix: String, filename: String): ZIO[SipiTestContainer, Throwable, Unit] = ZIO.serviceWithZIO[SipiTestContainer](_.copyFileToImageFolderInContainer(prefix, filename)) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 72c78a02e6..4e169d9690 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -121,7 +121,7 @@ object Dependencies { // found/added by the plugin but deleted anyway val commonsLang3 = "org.apache.commons" % "commons-lang3" % "3.13.0" - val tapirVersion = "1.7.6" + val tapirVersion = "1.8.4" val tapir = Seq( "com.softwaremill.sttp.tapir" %% "tapir-pekko-http-server" % tapirVersion, diff --git a/webapi/scripts/healthcheck.sh b/webapi/scripts/healthcheck.sh index ae10030fa1..cc942d942c 100755 --- a/webapi/scripts/healthcheck.sh +++ b/webapi/scripts/healthcheck.sh @@ -1,7 +1,7 @@ #!/bin/bash # Fetch health route status json -json=$(curl -sS --max-time 10 'http://localhost:3339/health') +json=$(curl -sS --max-time 10 'http://localhost:3333/health') if [ $? -ne 0 ]; then echo "Health route is not responding" exit 1 diff --git a/webapi/src/main/scala/org/knora/webapi/Main.scala b/webapi/src/main/scala/org/knora/webapi/Main.scala index a1e90c1d7c..194eca7fe5 100644 --- a/webapi/src/main/scala/org/knora/webapi/Main.scala +++ b/webapi/src/main/scala/org/knora/webapi/Main.scala @@ -8,6 +8,7 @@ package org.knora.webapi import zio._ import org.knora.webapi.core._ +import org.knora.webapi.slice.infrastructure.MetricsServer import org.knora.webapi.util.Logger object Main extends ZIOApp { @@ -30,5 +31,5 @@ object Main extends ZIOApp { * Entrypoint of our Application */ override def run: ZIO[Environment with ZIOAppArgs with Scope, Any, Any] = - AppServer.make *> InstrumentationServer.make + AppServer.make *> MetricsServer.make } diff --git a/webapi/src/main/scala/org/knora/webapi/http/handler/ExceptionHandlerZ.scala b/webapi/src/main/scala/org/knora/webapi/http/handler/ExceptionHandlerZ.scala deleted file mode 100644 index 245a32a939..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/http/handler/ExceptionHandlerZ.scala +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.http.handler - -import org.slf4j.LoggerFactory -import spray.json._ -import zio.ZIO -import zio.http._ - -import dsp.errors.RequestRejectedException -import org.knora.webapi.config.AppConfig -import org.knora.webapi.http.status.ApiStatusCodesZ - -/** - * Migrated from [[org.knora.webapi.http.handler.KnoraExceptionHandler]] - */ -object ExceptionHandlerZ { - private val GENERIC_INTERNAL_SERVER_ERROR_MESSAGE = - "The request could not be completed because of an internal server error." - - private val logger = LoggerFactory.getLogger(ExceptionHandlerZ.getClass) - - def exceptionToJsonHttpResponseZ(ex: Throwable, appConfig: AppConfig): ZIO[Any, Nothing, Response] = { - // Get the HTTP status code that corresponds to the exception. - val httpStatus = ApiStatusCodesZ.fromExceptionZ(ex) - if (httpStatus.code == 500) { - logger.error(ex.getMessage, ex) - } - - // Generate an HTTP response containing the error message ... - val responseFields: Map[String, JsValue] = Map( - "error" -> JsString(makeClientErrorMessage(ex, appConfig)) - ) - - val json = JsObject(responseFields).compactPrint - - // ... and the HTTP status code. - ZIO.succeed(Response.json(json).withStatus(httpStatus)) - } - - /** - * Given an exception, returns an error message suitable for clients. - * - * @param throwable the exception. - * @param appConfig the application's configuration. - * @return an error message suitable for clients. - */ - private def makeClientErrorMessage(throwable: Throwable, appConfig: AppConfig): String = - throwable match { - case knownError: RequestRejectedException => knownError.toString - case other => - if (appConfig.showInternalErrors) { other.toString } - else { GENERIC_INTERNAL_SERVER_ERROR_MESSAGE } - } -} diff --git a/webapi/src/main/scala/org/knora/webapi/instrumentation/health/HealthRouteZ.scala b/webapi/src/main/scala/org/knora/webapi/instrumentation/health/HealthRouteZ.scala deleted file mode 100644 index db0696e159..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/instrumentation/health/HealthRouteZ.scala +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.instrumentation.health - -import zio._ -import zio.http._ -import zio.json._ - -import org.knora.webapi.core.State -import org.knora.webapi.core.domain.AppState - -/** - * Provides the '/health' endpoint serving the health status. - */ -final case class HealthRouteZ() { - - val route: HttpApp[State, Nothing] = - Http.collectZIO[Request] { case Method.GET -> Root / "health" => - State.getAppState.map(toHealthCheckResult).flatMap(createResponse) - } - - /** - * Transforms the [[AppState]] into a [[HealthCheckResult]] - * - * @param state the application's state - * @return the result which is either unhealthy or healthy, containing a human readable explanation in case of unhealthy - */ - private def toHealthCheckResult(state: AppState): HealthCheckResult = - state match { - case AppState.Stopped => unhealthy("Stopped. Please retry later.") - case AppState.StartingUp => unhealthy("Starting up. Please retry later.") - case AppState.WaitingForTriplestore => unhealthy("Waiting for triplestore. Please retry later.") - case AppState.TriplestoreReady => unhealthy("Triplestore ready. Please retry later.") - case AppState.UpdatingRepository => unhealthy("Updating repository. Please retry later.") - case AppState.RepositoryUpToDate => unhealthy("Repository up to date. Please retry later.") - case AppState.CreatingCaches => unhealthy("Creating caches. Please retry later.") - case AppState.CachesReady => unhealthy("Caches ready. Please retry later.") - case AppState.UpdatingSearchIndex => unhealthy("Updating search index. Please retry later.") - case AppState.SearchIndexReady => unhealthy("Search index ready. Please retry later.") - case AppState.LoadingOntologies => unhealthy("Loading ontologies. Please retry later.") - case AppState.OntologiesReady => unhealthy("Ontologies ready. Please retry later.") - case AppState.WaitingForIIIFService => unhealthy("Waiting for IIIF service. Please retry later.") - case AppState.IIIFServiceReady => unhealthy("IIIF service ready. Please retry later.") - case AppState.WaitingForCacheService => unhealthy("Waiting for cache service. Please retry later.") - case AppState.CacheServiceReady => unhealthy("Cache service ready. Please retry later.") - case AppState.MaintenanceMode => unhealthy("Application is in maintenance mode. Please retry later.") - case AppState.Running => healthy - } - - /** - * Creates the HTTP response from the health check result (healthy/unhealthy). - * - * @param result the result of the health check - * @return an HTTP response - */ - private def createResponse(result: HealthCheckResult): UIO[Response] = - ZIO.succeed( - Response - .json(result.toJson) - .withStatus(statusCode(result.status)) - ) - - /** - * Returns the HTTP status according to the input boolean. - * - * @param s a boolean from which to derive the HTTP status - * @return the HTTP status (OK or ServiceUnavailable) - */ - private def statusCode(s: Boolean): Status = if (s) Status.Ok else Status.ServiceUnavailable - - /** - * The result of a health check which is either unhealthy or healthy. - * - * @param name always "AppState" - * @param severity severity of the health status. Always "non fatal", otherwise the application would not respond - * @param status the status (either false = unhealthy or true = healthy) - * @param message the message - */ - private case class HealthCheckResult(name: String, severity: String, status: Boolean, message: String) - - private object HealthCheckResult { - implicit val encoder: JsonEncoder[HealthCheckResult] = DeriveJsonEncoder.gen[HealthCheckResult] - } - - private def unhealthy(message: String) = - HealthCheckResult( - name = "AppState", - severity = "non fatal", - status = false, - message = message - ) - - private val healthy = - HealthCheckResult( - name = "AppState", - severity = "non fatal", - status = true, - message = "Application is healthy" - ) -} - -object HealthRouteZ { - val layer = ZLayer.succeed(HealthRouteZ()) -} diff --git a/webapi/src/main/scala/org/knora/webapi/instrumentation/index/IndexApp.scala b/webapi/src/main/scala/org/knora/webapi/instrumentation/index/IndexApp.scala deleted file mode 100644 index 801add37c6..0000000000 --- a/webapi/src/main/scala/org/knora/webapi/instrumentation/index/IndexApp.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright © 2021 - 2023 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.instrumentation.index - -import zio._ -import zio.http._ -import zio.http.html.Html - -/** - * Provides the '/' endpoint serving a small index page. - */ -final case class IndexApp() { - - val route: HttpApp[Any, Nothing] = - Http.collect[Request] { case Method.GET -> Root => Response.html(Html.fromString(indexPage)) } - - private val indexPage = - """ - |Simple Server - | - |

Metrics

- |

Health

- | - |""".stripMargin -} -object IndexApp { - val layer: ULayer[IndexApp] = - ZLayer.succeed(IndexApp()) -} diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DspIngestClient.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DspIngestClient.scala index e114151c13..71088bbeb2 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DspIngestClient.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/DspIngestClient.scala @@ -66,7 +66,7 @@ final case class DspIngestClientLive( importUrl <- ZIO.fromEither(URL.decode(s"${projectsPath(shortcode)}/import")) token <- jwtService.createJwtForDspIngest() request = Request - .post(Body.fromFile(fileToImport.toFile), importUrl) + .post(importUrl, Body.fromFile(fileToImport.toFile)) .addHeaders( Headers( Header.Authorization.Bearer(token.jwtString), diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectImportService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectImportService.scala index df26d76703..c637a2f9d9 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectImportService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectImportService.scala @@ -60,7 +60,7 @@ final case class ProjectImportServiceLive( def acquire(fusekiUrl: URL, httpClient: HttpClient) = ZIO.attempt(RDFConnectionFuseki.service(fusekiUrl.encode).httpClient(httpClient).build()) def release(connection: RDFConnection) = ZIO.attempt(connection.close()).logError.ignore - ZIO.acquireRelease(acquire(fusekiBaseUrl.withPath(s"/${config.fuseki.repositoryName}"), httpClient))(release) + ZIO.acquireRelease(acquire(fusekiBaseUrl.path(s"/${config.fuseki.repositoryName}"), httpClient))(release) } override def importTrigFile(file: Path): Task[Unit] = ZIO.scoped { @@ -126,5 +126,5 @@ final case class ProjectImportServiceLive( } object ProjectImportServiceLive { - val layer = ZLayer.fromFunction(ProjectImportServiceLive.apply _) + val layer = ZLayer.derive[ProjectImportServiceLive] } diff --git a/webapi/src/main/scala/org/knora/webapi/core/InstrumentationServer.scala b/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/MetricsServer.scala similarity index 55% rename from webapi/src/main/scala/org/knora/webapi/core/InstrumentationServer.scala rename to webapi/src/main/scala/org/knora/webapi/slice/infrastructure/MetricsServer.scala index a92ea0ffa2..93e0097dfb 100644 --- a/webapi/src/main/scala/org/knora/webapi/core/InstrumentationServer.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/MetricsServer.scala @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.knora.webapi.core +package org.knora.webapi.slice.infrastructure import zio._ import zio.http._ @@ -12,20 +12,12 @@ import zio.metrics.connectors.prometheus import zio.metrics.jvm.DefaultJvmMetrics import org.knora.webapi.config.AppConfig -import org.knora.webapi.instrumentation.health.HealthRouteZ -import org.knora.webapi.instrumentation.index.IndexApp -import org.knora.webapi.instrumentation.prometheus.PrometheusApp +import org.knora.webapi.core.State +import org.knora.webapi.slice.infrastructure.api.PrometheusApp -object InstrumentationServer { +object MetricsServer { - private val instrumentationServer = - (for { - index <- ZIO.serviceWith[IndexApp](_.route) - health <- ZIO.serviceWith[HealthRouteZ](_.route) - prometheus <- ZIO.serviceWith[PrometheusApp](_.route) - app = index ++ health ++ prometheus - _ <- Server.install(app) - } yield ()) *> ZIO.never + private val metricsServer = ZIO.serviceWithZIO[PrometheusApp](app => Server.install(app.route)) *> ZIO.never val make: ZIO[State with AppConfig, Throwable, Unit] = ZIO.serviceWithZIO[AppConfig] { config => @@ -33,20 +25,15 @@ object InstrumentationServer { val interval = config.instrumentationServerConfig.interval val metricsConfig = MetricsConfig(interval) ZIO.logInfo(s"Starting instrumentation http server on http://localhost:$port") *> - instrumentationServer + metricsServer .provideSome[State]( - // HTTP Server Server.defaultWithPort(port), - // HTTP routes - IndexApp.layer, - HealthRouteZ.layer, - PrometheusApp.layer, - // Metrics dependencies prometheus.publisherLayer, ZLayer.succeed(metricsConfig) >>> prometheus.prometheusLayer, Runtime.enableRuntimeMetrics, Runtime.enableFiberRoots, - DefaultJvmMetrics.live.unit + DefaultJvmMetrics.live.unit, + PrometheusApp.layer ) } } diff --git a/webapi/src/main/scala/org/knora/webapi/instrumentation/prometheus/PrometheusApp.scala b/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/api/PrometheusApp.scala similarity index 52% rename from webapi/src/main/scala/org/knora/webapi/instrumentation/prometheus/PrometheusApp.scala rename to webapi/src/main/scala/org/knora/webapi/slice/infrastructure/api/PrometheusApp.scala index ab75539756..65d052ff74 100644 --- a/webapi/src/main/scala/org/knora/webapi/instrumentation/prometheus/PrometheusApp.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/infrastructure/api/PrometheusApp.scala @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.knora.webapi.instrumentation.prometheus +package org.knora.webapi.slice.infrastructure.api import zio._ import zio.http._ @@ -12,14 +12,9 @@ import zio.metrics.connectors.prometheus.PrometheusPublisher /** * Provides the '/metrics' endpoint serving the metrics in prometheus format. */ -final case class PrometheusApp() { - - val route: HttpApp[PrometheusPublisher, Nothing] = - Http - .collectZIO[Request] { case Method.GET -> Root / "metrics" => - ZIO.serviceWithZIO[PrometheusPublisher](_.get.map(Response.text)) - } +final case class PrometheusApp(prometheus: PrometheusPublisher) { + val route: HttpApp[Any] = Routes(Method.GET / "metrics" -> handler(prometheus.get.map(Response.text(_)))).toHttpApp } object PrometheusApp { - val layer = ZLayer.succeed(PrometheusApp()) + val layer = ZLayer.derive[PrometheusApp] }