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

feat: expose GET /admin/projects/[shortname | shortcode]/{shortname | shortcode} as ZIO HTTP routes (DEV-1587) #2365

Merged
merged 8 commits into from Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
Expand Up @@ -18,8 +18,8 @@ final case class RestProjectsService(bridge: ActorToZioBridge) {
* @return
* '''success''': information about the project as a [[ProjectGetResponseADM]]
*
* '''error''': [[dsp.errors.NotFoundException]] when no project for the given IRI can be found
* [[dsp.errors.ValidationException]] if the given `projectIri` is invalid
* '''failure''': [[dsp.errors.NotFoundException]] when no project for the given IRI can be found
* [[dsp.errors.ValidationException]] if the given `projectIri` is invalid
*/
def getSingleProjectADMRequest(projectIri: IRI): Task[ProjectGetResponseADM] =
ProjectIdentifierADM.IriIdentifier
Expand Down
Expand Up @@ -16,9 +16,9 @@ object RouteUtilZ {
*
* @param value The value in utf-8 to be url decoded
* @param errorMsg Custom error message for the error type
* @return ```success``` the decoded string
* @return '''success''' the decoded string
*
* ```failure``` A [[BadRequestException]] with the `errorMsg`
* '''failure''' A [[BadRequestException]] with the `errorMsg`
*/
def urlDecode(value: String, errorMsg: String = ""): Task[String] =
ZIO
Expand Down
@@ -1,13 +1,16 @@
package org.knora.webapi.routing.admin

import zhttp.http._
import zio.Task
import zio.URLayer
import zio.ZLayer

import dsp.errors.BadRequestException
import dsp.errors.InternalServerException
import dsp.errors.RequestRejectedException
import org.knora.webapi.config.AppConfig
import org.knora.webapi.http.handler.ExceptionHandlerZ
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM._
import org.knora.webapi.responders.admin.RestProjectsService
import org.knora.webapi.routing.RouteUtilZ

Expand All @@ -18,21 +21,34 @@ final case class ProjectsRouteZ(

val route: HttpApp[Any, Nothing] =
Http
.collectZIO[Request] { case Method.GET -> !! / "admin" / "projects" / "iri" / iriUrlEncoded =>
getProjectByIriEncoded(iriUrlEncoded)
.collectZIO[Request] {
case Method.GET -> !! / "admin" / "projects" / "iri" / iriUrlEncoded => getProjectByIriEncoded(iriUrlEncoded)
case Method.GET -> !! / "admin" / "projects" / "shortname" / shortname => getProjectByShortname(shortname)
case Method.GET -> !! / "admin" / "projects" / "shortcode" / shortcode => getProjectByShortcode(shortcode)
}
.catchAll {
case RequestRejectedException(e) =>
ExceptionHandlerZ.exceptionToJsonHttpResponseZ(e, appConfig)
case InternalServerException(e) =>
ExceptionHandlerZ.exceptionToJsonHttpResponseZ(e, appConfig)
case RequestRejectedException(e) => ExceptionHandlerZ.exceptionToJsonHttpResponseZ(e, appConfig)
case InternalServerException(e) => ExceptionHandlerZ.exceptionToJsonHttpResponseZ(e, appConfig)
}

private def getProjectByIriEncoded(iriUrlEncoded: String) =
RouteUtilZ
.urlDecode(iriUrlEncoded, "Failed to url decode IRI parameter.")
.flatMap(projectsService.getSingleProjectADMRequest(_).map(_.toJsValue.toString()))
.map(Response.json(_))
private def getProjectByIriEncoded(iriUrlEncoded: String): Task[Response] =
for {
iriDecoded <- RouteUtilZ.urlDecode(iriUrlEncoded, s"Failed to URL decode IRI parameter $iriUrlEncoded.")
iri <- IriIdentifier.fromString(iriDecoded).toZIO
projectGetResponse <- projectsService.getSingleProjectADMRequest(identifier = iri)
} yield Response.json(projectGetResponse.toJsValue.toString())

private def getProjectByShortname(shortname: String): Task[Response] =
for {
shortnameIdentifier <- ShortnameIdentifier.fromString(shortname).toZIO.mapError(e => BadRequestException(e.msg))
projectGetResponse <- projectsService.getSingleProjectADMRequest(identifier = shortnameIdentifier)
} yield Response.json(projectGetResponse.toJsValue.toString())

private def getProjectByShortcode(shortcode: String): Task[Response] =
for {
shortcodeIdentifier <- ShortcodeIdentifier.fromString(shortcode).toZIO.mapError(e => BadRequestException(e.msg))
projectGetResponse <- projectsService.getSingleProjectADMRequest(identifier = shortcodeIdentifier)
} yield Response.json(projectGetResponse.toJsValue.toString())
}

object ProjectsRouteZ {
Expand Down

This file was deleted.

@@ -0,0 +1,135 @@
package org.knora.webapi.routing.admin

import zhttp.http._
import zio._
import zio.mock.Expectation
import zio.test.ZIOSpecDefault
import zio.test._

import java.net.URLEncoder

import org.knora.webapi.config.AppConfig
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM
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.store.triplestoremessages.StringLiteralV2
import org.knora.webapi.responders.ActorToZioBridge
import org.knora.webapi.responders.ActorToZioBridgeMock
import org.knora.webapi.responders.admin.RestProjectsService

object ProjectRouteZSpec extends ZIOSpecDefault {

private val systemUnderTest: URIO[ProjectsRouteZ, HttpApp[Any, Nothing]] = ZIO.service[ProjectsRouteZ].map(_.route)

// Test data
private val projectIri: ProjectIdentifierADM.IriIdentifier =
ProjectIdentifierADM.IriIdentifier
.fromString("http://rdfh.ch/projects/0001")
.getOrElse(throw new IllegalArgumentException())

private val projectShortname: ProjectIdentifierADM.ShortnameIdentifier =
ProjectIdentifierADM.ShortnameIdentifier
.fromString("shortname")
.getOrElse(throw new IllegalArgumentException())

private val projectShortcode: ProjectIdentifierADM.ShortcodeIdentifier =
ProjectIdentifierADM.ShortcodeIdentifier
.fromString("AB12")
.getOrElse(throw new IllegalArgumentException())

private val basePathIri: Path = !! / "admin" / "projects" / "iri"
private val basePathShortname: Path = !! / "admin" / "projects" / "shortname"
private val basePathShortcode: Path = !! / "admin" / "projects" / "shortcode"
private val validIriUrlEncoded: String = URLEncoder.encode(projectIri.value.value, "utf-8")
private val validShortname: String = "shortname"
private val validShortcode: String = "AB12"
private val expectedRequestSuccessIri: ProjectGetRequestADM = ProjectGetRequestADM(projectIri)
private val expectedRequestSuccessShortname: ProjectGetRequestADM = ProjectGetRequestADM(projectShortname)
private val expectedRequestSuccessShortcode: ProjectGetRequestADM = ProjectGetRequestADM(projectShortcode)
private val expectedResponseSuccess: ProjectGetResponseADM =
ProjectGetResponseADM(
ProjectADM(
id = "id",
shortname = "shortname",
shortcode = "AB12",
longname = None,
description = List(StringLiteralV2("description")),
keywords = List.empty,
logo = None,
ontologies = List.empty,
status = false,
selfjoin = false
)
)

// Expectations and layers for ActorToZioBridge mock
private val commonLayers: URLayer[ActorToZioBridge, ProjectsRouteZ] =
ZLayer.makeSome[ActorToZioBridge, ProjectsRouteZ](AppConfig.test, ProjectsRouteZ.layer, RestProjectsService.layer)

private val expectMessageToProjectResponderIri: ULayer[ActorToZioBridge] =
ActorToZioBridgeMock.AskAppActor
.of[ProjectGetResponseADM]
.apply(Assertion.equalTo(expectedRequestSuccessIri), Expectation.value(expectedResponseSuccess))
.toLayer

private val expectMessageToProjectResponderShortname: ULayer[ActorToZioBridge] =
ActorToZioBridgeMock.AskAppActor
.of[ProjectGetResponseADM]
.apply(Assertion.equalTo(expectedRequestSuccessShortname), Expectation.value(expectedResponseSuccess))
.toLayer

private val expectMessageToProjectResponderShortcode: ULayer[ActorToZioBridge] =
ActorToZioBridgeMock.AskAppActor
.of[ProjectGetResponseADM]
.apply(Assertion.equalTo(expectedRequestSuccessShortcode), Expectation.value(expectedResponseSuccess))
.toLayer

private val expectNoInteractionWithProjectsResponderADM = ActorToZioBridgeMock.empty

val spec: Spec[Any, Serializable] =
suite("ProjectsRouteZSpec")(
test("given valid project iri should respond with success") {
val urlWithValidIri = URL.empty.setPath(basePathIri / validIriUrlEncoded)
for {
route <- systemUnderTest
actual <- route.apply(Request(url = urlWithValidIri)).flatMap(_.body.asString)
} yield assertTrue(actual == expectedResponseSuccess.toJsValue.toString())
}.provide(commonLayers, expectMessageToProjectResponderIri),
test("given valid project shortname should respond with success") {
val urlWithValidShortname = URL.empty.setPath(basePathShortname / validShortname)
for {
route <- systemUnderTest
actual <- route.apply(Request(url = urlWithValidShortname)).flatMap(_.body.asString)
} yield assertTrue(actual == expectedResponseSuccess.toJsValue.toString())
}.provide(commonLayers, expectMessageToProjectResponderShortname),
test("given valid project shortcode should respond with success") {
val urlWithValidShortcode = URL.empty.setPath(basePathShortcode / validShortcode)
for {
route <- systemUnderTest
actual <- route.apply(Request(url = urlWithValidShortcode)).flatMap(_.body.asString)
} yield assertTrue(actual == expectedResponseSuccess.toJsValue.toString())
}.provide(commonLayers, expectMessageToProjectResponderShortcode),
test("given invalid project iri should respond with bad request") {
val urlWithInvalidIri = URL.empty.setPath(basePathIri / "invalid")
for {
route <- systemUnderTest
actual <- route.apply(Request(url = urlWithInvalidIri)).map(_.status)
} yield assertTrue(actual == Status.BadRequest)
}.provide(commonLayers, expectNoInteractionWithProjectsResponderADM),
test("given invalid project shortname should respond with bad request") {
val urlWithInvalidShortname = URL.empty.setPath(basePathShortname / "123")
irinaschubert marked this conversation as resolved.
Show resolved Hide resolved
for {
route <- systemUnderTest
actual <- route.apply(Request(url = urlWithInvalidShortname)).map(_.status)
} yield assertTrue(actual == Status.BadRequest)
}.provide(commonLayers, expectNoInteractionWithProjectsResponderADM),
test("given invalid project shortcode should respond with bad request") {
val urlWithInvalidShortcode = URL.empty.setPath(basePathShortcode / "invalid")
for {
route <- systemUnderTest
actual <- route.apply(Request(url = urlWithInvalidShortcode)).map(_.status)
} yield assertTrue(actual == Status.BadRequest)
}.provide(commonLayers, expectNoInteractionWithProjectsResponderADM)
)
}