Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: refactor project route for ZIO HTTP #2338

Merged
merged 34 commits into from Dec 15, 2022
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7ea9ede
mob next [ci-skip] [ci skip] [skip ci]
seakayone Dec 12, 2022
e2603f2
mob next [ci-skip] [ci skip] [skip ci]
seakayone Dec 12, 2022
e1a40d4
mob next [ci-skip] [ci skip] [skip ci]
seakayone Dec 13, 2022
a4a1dbb
mob next [ci-skip] [ci skip] [skip ci]
BalduinLandolt Dec 13, 2022
3d17801
mob next [ci-skip] [ci skip] [skip ci]
mpro7 Dec 13, 2022
b13e1c6
mob next [ci-skip] [ci skip] [skip ci]
Dec 13, 2022
2a4f00d
mob next [ci-skip] [ci skip] [skip ci]
seakayone Dec 13, 2022
f0329ee
mob next [ci-skip] [ci skip] [skip ci]
BalduinLandolt Dec 13, 2022
706c133
mob next [ci-skip] [ci skip] [skip ci]
BalduinLandolt Dec 13, 2022
418e69d
mob next [ci-skip] [ci skip] [skip ci]
mpro7 Dec 13, 2022
a7c7b0e
mob next [ci-skip] [ci skip] [skip ci]
Dec 13, 2022
3ff37fa
mob next [ci-skip] [ci skip] [skip ci]
seakayone Dec 13, 2022
bd31537
mob next [ci-skip] [ci skip] [skip ci]
BalduinLandolt Dec 13, 2022
8808b57
mob next [ci-skip] [ci skip] [skip ci]
mpro7 Dec 13, 2022
5203714
mob next [ci-skip] [ci skip] [skip ci]
Dec 13, 2022
b485443
mob next [ci-skip] [ci skip] [skip ci]
seakayone Dec 13, 2022
9b92248
mob next [ci-skip] [ci skip] [skip ci]
seakayone Dec 13, 2022
b70aecf
mob next [ci-skip] [ci skip] [skip ci]
BalduinLandolt Dec 13, 2022
efd0f3b
mob next [ci-skip] [ci skip] [skip ci]
BalduinLandolt Dec 13, 2022
2848391
mob next [ci-skip] [ci skip] [skip ci]
mpro7 Dec 13, 2022
95f3bee
mob next [ci-skip] [ci skip] [skip ci]
Dec 13, 2022
8b48df2
mob next [ci-skip] [ci skip] [skip ci]
seakayone Dec 13, 2022
e726c3e
mob next [ci-skip] [ci skip] [skip ci]
mpro7 Dec 13, 2022
c1bd8ff
mob next [ci-skip] [ci skip] [skip ci]
Dec 13, 2022
811b970
working
seakayone Dec 13, 2022
e99134a
mob next [ci-skip] [ci skip] [skip ci]
seakayone Dec 13, 2022
a2c8b6f
add todo
seakayone Dec 13, 2022
ac5dfe6
rename variable
seakayone Dec 13, 2022
8b62542
rename variable and formatting
seakayone Dec 13, 2022
89765d8
mob next [ci-skip] [ci skip] [skip ci]
BalduinLandolt Dec 13, 2022
768cff4
rename files
Dec 14, 2022
40f21ba
rename health route
Dec 14, 2022
2a0563d
add todo
Dec 14, 2022
7889986
resolve merge conflicts
Dec 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view

This file was deleted.

30 changes: 30 additions & 0 deletions webapi/src/main/scala/org/knora/webapi/core/HttpServerZ.scala
@@ -0,0 +1,30 @@
/*
* Copyright © 2021 - 2022 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.core
import zhttp.service.Server
import zio.ZLayer
import zio._

import org.knora.webapi.config.AppConfig
import org.knora.webapi.routing.HealthRouteZ
import org.knora.webapi.routing.admin.ProjectsRouteZ

object HttpServerZ {

val routes = HealthRouteZ()

val layer: ZLayer[AppRouter & AppConfig & State & ProjectsRouteZ, Nothing, Unit] =
ZLayer {
for {
appConfig <- ZIO.service[AppConfig]
projectsRoute <- ZIO.service[ProjectsRouteZ]
r = routes ++ projectsRoute.route
port = appConfig.knoraApi.externalZioPort
_ <- Server.start(port, r).forkDaemon
_ <- ZIO.logInfo(">>> Acquire ZIO HTTP Server <<<")
} yield ()
}
}
Expand Up @@ -11,6 +11,7 @@ import zio.ZLayer
import org.knora.webapi.auth.JWTService
import org.knora.webapi.config.AppConfig
import org.knora.webapi.routing.ApiRoutes
import org.knora.webapi.routing.admin.ProjectsRouteZ
import org.knora.webapi.store.cache.CacheServiceManager
import org.knora.webapi.store.cache.api.CacheService
import org.knora.webapi.store.cache.impl.CacheServiceInMemImpl
Expand Down Expand Up @@ -55,13 +56,14 @@ object LayersLive {
CacheServiceManager.layer,
CacheServiceInMemImpl.layer,
HttpServer.layer,
HttpServerWithZIOHttp.layer, // this is the new ZIO HTTP server layer
HttpServerZ.layer, // this is the new ZIO HTTP server layer
IIIFServiceManager.layer,
IIIFServiceSipiImpl.layer,
JWTService.layer,
RepositoryUpdater.layer,
State.layer,
TriplestoreServiceManager.layer,
TriplestoreServiceHttpConnectorImpl.layer
TriplestoreServiceHttpConnectorImpl.layer,
ProjectsRouteZ.layer
)
}
@@ -0,0 +1,58 @@
/*
* Copyright © 2021 - 2022 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 spray.json._
import zhttp.http.Http
import zhttp.http.Response

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."

def exceptionToJsonHttpResponseZ(ex: Throwable, appConfig: AppConfig): Http[Any, Nothing, Any, Response] = {

// Get the HTTP status code that corresponds to the exception.
val httpStatus = ApiStatusCodesZ.fromExceptionZ(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.
Http.response(Response.json(json).setStatus(httpStatus))

}

/**
* Given an exception, returns an error message suitable for clients.
*
* @param ex the exception.
* @param appConfig the application's configuration.
* @return an error message suitable for clients.
*/
private def makeClientErrorMessage(ex: Throwable, appConfig: AppConfig): String =
ex match {
case rre: RequestRejectedException => rre.toString

case other =>
if (appConfig.showInternalErrors) {
other.toString
} else {
GENERIC_INTERNAL_SERVER_ERROR_MESSAGE
}
}
}
@@ -0,0 +1,50 @@
/*
* Copyright © 2021 - 2022 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.status

import zhttp.http.Status

import dsp.errors._
import org.knora.webapi.store.triplestore.errors.TriplestoreTimeoutException

/**
* Migrated from [[org.knora.webapi.http.status.ApiStatusCodes]]
*
* The possible values for the HTTP status code that is returned as part of each Knora API v2 response.
*/
object ApiStatusCodesZ {

/**
* Converts an exception to a suitable HTTP status code.
*
* @param ex an exception.
* @return an HTTP status code.
*/

def fromExceptionZ(ex: Throwable): Status =
ex match {
// Subclasses of RequestRejectedException
case NotFoundException(_) => Status.NotFound
case ForbiddenException(_) => Status.Forbidden
case BadCredentialsException(_) => Status.Unauthorized
case DuplicateValueException(_) => Status.BadRequest
case OntologyConstraintException(_) => Status.BadRequest
case EditConflictException(_) => Status.Conflict
case BadRequestException(_) => Status.BadRequest
case ValidationException(_, _) => Status.BadRequest
case RequestRejectedException(_) => Status.BadRequest
// RequestRejectedException must be the last one in this group

// Subclasses of InternalServerException
case UpdateNotPerformedException(_) => Status.Conflict
case TriplestoreTimeoutException(_, _) => Status.GatewayTimeout
case InternalServerException(_) => Status.InternalServerError
// InternalServerException must be the last one in this group

case _ => Status.InternalServerError
}

}
Expand Up @@ -16,7 +16,7 @@ import org.knora.webapi.core.domain.AppState
/**
* Provides the '/healthZ' endpoint serving the health status.
*/
object HealthRouteWithZIOHttp {
object HealthRouteZ {

def apply(): HttpApp[State, Nothing] =
Http.collectZIO[Request] { case Method.GET -> !! / "healthZ" =>
Expand Down
@@ -0,0 +1,62 @@
package org.knora.webapi.routing.admin

import akka.actor.ActorRef
import akka.pattern.ask
import akka.util.Timeout
import zhttp.http._
import zio.ZIO
import zio.ZLayer

import java.net.URLDecoder

import dsp.errors.BadRequestException
import dsp.errors.InternalServerException
import dsp.errors.KnoraException
import dsp.errors.RequestRejectedException
import org.knora.webapi.config.AppConfig
import org.knora.webapi.core.AppRouter
import org.knora.webapi.http.handler.ExceptionHandlerZ
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectGetRequestADM
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectGetResponseADM
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM
import org.knora.webapi.messages.util.KnoraSystemInstances

final case class ProjectsRouteZ(router: AppRouter, appConfig: AppConfig) {
implicit val sender: ActorRef = router.ref
implicit val timeout: Timeout = appConfig.defaultTimeoutAsDuration

def getProjectByIri(iri: String): ZIO[Any, KnoraException, Response] =
for {
user <- ZIO.succeed(KnoraSystemInstances.Users.SystemUser)
iriDecoded <-
ZIO
.attempt(URLDecoder.decode(iri, "utf-8"))
.orElseFail(BadRequestException(s"Failed to decode IRI $iri"))
iriValue <- ProjectIdentifierADM.IriIdentifier
.fromString(iriDecoded)
.toZIO
message = ProjectGetRequestADM(identifier = iriValue, requestingUser = user)
response <- ZIO.fromFuture(_ => router.ref.ask(message)).map(_.asInstanceOf[ProjectGetResponseADM]).orDie
} yield Response.json(response.toJsValue.toString())

val route: HttpApp[Any, Nothing] =
(Http
.collectZIO[Request] {
// TODO : Add user authentication, make tests run with the new route
// Returns a single project identified through the IRI.
case Method.GET -> !! / "admin" / "projects" / "iri" / iri =>
getProjectByIri(iri)
})
.catchAll {
case RequestRejectedException(e) =>
ExceptionHandlerZ.exceptionToJsonHttpResponseZ(e, appConfig)
case InternalServerException(e) =>
ExceptionHandlerZ.exceptionToJsonHttpResponseZ(e, appConfig)
}
}

object ProjectsRouteZ {
val layer: ZLayer[AppRouter with AppConfig, Nothing, ProjectsRouteZ] = ZLayer.fromFunction { (router, config) =>
ProjectsRouteZ(router, config)
}
}