Skip to content

Commit

Permalink
return failed zio instead of throwing exception
Browse files Browse the repository at this point in the history
  • Loading branch information
seakayone committed Feb 27, 2025
1 parent 3040d2a commit 5977fba
Show file tree
Hide file tree
Showing 9 changed files with 72 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -95,24 +95,6 @@ class SearchResponderV2Spec extends CoreSpec {
private def gravsearchV2(query: ConstructQuery, schemaAndOptions: SchemaRendering, user: User) =
ZIO.serviceWithZIO[SearchResponderV2](_.gravsearchV2(query, schemaAndOptions, user))

private def searchResourcesByProjectAndClassV2(
projectIri: SmartIri,
resourceClass: SmartIri,
orderByProperty: Option[SmartIri],
page: Int,
schemaAndOptions: SchemaRendering,
requestingUser: User,
) = ZIO.serviceWithZIO[SearchResponderV2](
_.searchResourcesByProjectAndClassV2(
projectIri,
resourceClass,
orderByProperty,
page,
schemaAndOptions,
requestingUser,
),
)

"The search responder v2" should {

"perform a fulltext search for 'Narr'" in {
Expand Down Expand Up @@ -302,13 +284,15 @@ class SearchResponderV2Spec extends CoreSpec {

"search by project and resource class" in {
val result = UnsafeZioRun.runOrThrow(
searchResourcesByProjectAndClassV2(
projectIri = SharedTestDataADM.incunabulaProject.id.value.toSmartIri,
resourceClass = "http://0.0.0.0:3333/ontology/0803/incunabula/v2#book".toSmartIri,
orderByProperty = Some("http://0.0.0.0:3333/ontology/0803/incunabula/v2#title".toSmartIri),
page = 0,
schemaAndOptions = SchemaRendering.apiV2SchemaWithOption(MarkupRendering.Xml),
requestingUser = SharedTestDataADM.incunabulaProjectAdminUser,
ZIO.serviceWithZIO[SearchResponderV2](
_.searchResourcesByProjectAndClassV2(
projectIri = SharedTestDataADM.incunabulaProject.id,
resourceClass = "http://0.0.0.0:3333/ontology/0803/incunabula/v2#book".toSmartIri,
orderByProperty = Some("http://0.0.0.0:3333/ontology/0803/incunabula/v2#title".toSmartIri),
page = 0,
schemaAndOptions = SchemaRendering.apiV2SchemaWithOption(MarkupRendering.Xml),
requestingUser = SharedTestDataADM.incunabulaProjectAdminUser,
),
),
)
result.resources.size should ===(19)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package org.knora.webapi.messages.v2.responder.resourcemessages

import java.time.Instant
import java.util.UUID

import dsp.errors.*
import dsp.valueobjects.UuidUtil
import org.knora.webapi.*
Expand All @@ -29,6 +28,8 @@ import org.knora.webapi.messages.v2.responder.valuemessages.ValueMessagesV2Optic
import org.knora.webapi.slice.admin.api.model.Project
import org.knora.webapi.slice.admin.domain.model.Permission
import org.knora.webapi.slice.admin.domain.model.User
import zio.Task
import zio.ZIO

/**
* An abstract trait for messages that can be sent to `ResourcesResponderV2`.
Expand Down Expand Up @@ -863,22 +864,27 @@ case class ReadResourcesSequenceV2(
* @throws NotFoundException if the requested resources are not found.
* @throws ForbiddenException if the user does not have permission to see the requested resources.
*/
def checkResourceIris(targetResourceIris: Set[IRI], resourcesSequence: ReadResourcesSequenceV2): Unit = {
def checkResourceIris(targetResourceIris: Set[IRI], resourcesSequence: ReadResourcesSequenceV2): Task[Unit] = {
val hiddenTargetResourceIris: Set[IRI] = targetResourceIris.intersect(resourcesSequence.hiddenResourceIris)

if (hiddenTargetResourceIris.nonEmpty) {
throw ForbiddenException(
s"You do not have permission to see one or more resources: ${hiddenTargetResourceIris.map(iri => s"<$iri>").mkString(", ")}",
return ZIO.fail(

Check warning on line 871 in webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala#L871

Avoid using return.
ForbiddenException(
s"You do not have permission to see one or more resources: ${hiddenTargetResourceIris.map(iri => s"<$iri>").mkString(", ")}",
),
)
}

val missingResourceIris: Set[IRI] = targetResourceIris -- resourcesSequence.resources.map(_.resourceIri).toSet

if (missingResourceIris.nonEmpty) {
throw NotFoundException(
s"One or more resources were not found: ${missingResourceIris.map(iri => s"<$iri>").mkString(", ")}",
return ZIO.fail(
NotFoundException(
s"One or more resources were not found: ${missingResourceIris.map(iri => s"<$iri>").mkString(", ")}",
),
)
}
ZIO.unit
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -620,7 +620,7 @@ final case class ResourcesResponderV2(
requestingUser = requestingUser,
)

_ = apiResponse.checkResourceIris(resourceIris.toSet, apiResponse)
_ <- apiResponse.checkResourceIris(resourceIris.toSet, apiResponse)

_ <- valueUuid match {
case Some(definedValueUuid) =>
Expand Down Expand Up @@ -693,10 +693,7 @@ final case class ResourcesResponderV2(
requestingUser = requestingUser,
)

_ = apiResponse.checkResourceIris(
targetResourceIris = resourceIris.toSet,
resourcesSequence = apiResponse,
)
_ <- apiResponse.checkResourceIris(resourceIris.toSet, apiResponse)

// Check if resources are deleted, if so, replace them with DeletedResource
responseWithDeletedResourcesReplaced = apiResponse.resources match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ trait SearchResponderV2 {
* @return a [[ReadResourcesSequenceV2]].
*/
def searchResourcesByProjectAndClassV2(
projectIri: SmartIri,
projectIri: ProjectIri,
resourceClass: SmartIri,
orderByProperty: Option[SmartIri],
page: Int,
Expand Down Expand Up @@ -682,7 +682,7 @@ final case class SearchResponderV2Live(
* @return a [[ReadResourcesSequenceV2]].
*/
override def searchResourcesByProjectAndClassV2(
projectIri: SmartIri,
projectIri: ProjectIri,
resourceClass: SmartIri,
orderByProperty: Option[SmartIri],
page: Int,
Expand Down Expand Up @@ -776,7 +776,7 @@ final case class SearchResponderV2Live(
// Do a SELECT prequery to get the IRIs of the requested page of resources.
prequery = sparql.v2.txt
.getResourcesByClassInProjectPrequery(
projectIri = projectIri.toString,
projectIri = projectIri.value,
resourceClassIri = internalClassIri,
maybeOrderByProperty = maybeInternalOrderByPropertyIri,
maybeOrderByValuePredicate = maybeOrderByValuePredicate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import org.knora.webapi.routing.RouteUtilZ
import org.knora.webapi.slice.admin.domain.service.ProjectService
import org.knora.webapi.slice.admin.domain.service.UserService
import org.knora.webapi.slice.common.ApiComplexV2JsonLdRequestParser
import org.knora.webapi.slice.common.api.ApiV2.Headers.xKnoraAcceptProject
import org.knora.webapi.slice.resourceinfo.domain.IriConverter
import org.knora.webapi.slice.security.Authenticator
import org.knora.webapi.store.iiif.api.SipiService
Expand Down Expand Up @@ -67,7 +66,6 @@ final case class ResourcesRouteV2(appConfig: AppConfig)(
def makeRoute: Route =
createResource() ~
updateResourceMetadata() ~
getResourcesInProject() ~
getResourcesPreview() ~
getResourcesTei() ~
getResourcesGraph() ~
Expand Down Expand Up @@ -106,74 +104,6 @@ final case class ResourcesRouteV2(appConfig: AppConfig)(
}
}

private def getResourcesInProject(): Route = path(resourcesBasePath) {
get { requestContext =>
val params: Map[String, String] = requestContext.request.uri.query().toMap

val getResourceClass = ZIO
.fromOption(params.get("resourceClass"))
.orElseFail(BadRequestException(s"This route requires the parameter 'resourceClass'"))
.flatMap(iri =>
ZIO
.serviceWithZIO[IriConverter](_.asSmartIri(iri))
.orElseFail(BadRequestException(s"Invalid resource class IRI: $iri")),
)
.filterOrElseWith(it => it.isKnoraApiV2EntityIri && it.isApiV2ComplexSchema)(it =>
ZIO.fail(BadRequestException(s"Invalid resource class IRI: $it")),
)
.flatMap(it => ZIO.serviceWithZIO[IriConverter](_.asInternalSmartIri(it)))

val getOrderByProperty: ZIO[IriConverter, Throwable, Option[SmartIri]] =
ZIO.foreach(params.get("orderByProperty")) { orderByPropertyStr =>
ZIO
.serviceWithZIO[IriConverter](_.asSmartIri(orderByPropertyStr))
.orElseFail(BadRequestException(s"Invalid property IRI: $orderByPropertyStr"))
.filterOrFail(iri => iri.isKnoraApiV2EntityIri && iri.isApiV2ComplexSchema)(
BadRequestException(s"Invalid property IRI: $orderByPropertyStr"),
)
.flatMap(it => ZIO.serviceWithZIO[IriConverter](_.asInternalSmartIri(it)))
}

val getPage = ZIO
.fromOption(params.get("page"))
.orElseFail(BadRequestException(s"This route requires the parameter 'page'"))
.flatMap(pageStr =>
ZIO
.fromOption(ValuesValidator.validateInt(pageStr))
.orElseFail(BadRequestException(s"Invalid page number: $pageStr")),
)

val getProjectIri = RouteUtilV2
.getProjectIri(requestContext)
.some
.orElseFail(BadRequestException(s"This route requires the request header $xKnoraAcceptProject"))

val targetSchemaTask = RouteUtilV2.getOntologySchema(requestContext)
val response = for {
maybeOrderByProperty <- getOrderByProperty
resourceClass <- getResourceClass
projectIri <- getProjectIri
page <- getPage
targetSchema <- targetSchemaTask.zip(RouteUtilV2.getSchemaOptions(requestContext)).map {
case (schema, options) => SchemaRendering(schema, options)
}
requestingUser <- ZIO.serviceWithZIO[Authenticator](_.getUserADM(requestContext))
response <- ZIO.serviceWithZIO[SearchResponderV2](
_.searchResourcesByProjectAndClassV2(
projectIri,
resourceClass,
maybeOrderByProperty,
page,
targetSchema,
requestingUser,
),
)
} yield response

RouteUtilV2.completeResponse(response, requestContext, targetSchemaTask)
}
}

private def getResourceIris(resIris: Seq[IRI]): IO[BadRequestException, Seq[IRI]] =
ZIO
.fail(BadRequestException(s"List of provided resource Iris exceeds limit of $resultsPerPage"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@ import zio.URLayer
import zio.ZLayer

import org.knora.webapi.responders.v2.ResourcesResponderV2
import org.knora.webapi.responders.v2.SearchResponderV2
import org.knora.webapi.responders.v2.ValuesResponderV2
import org.knora.webapi.slice.URModule
import org.knora.webapi.slice.common.ApiComplexV2JsonLdRequestParser
import org.knora.webapi.slice.common.api.BaseEndpoints
import org.knora.webapi.slice.common.api.HandlerMapper
import org.knora.webapi.slice.common.api.KnoraResponseRenderer
import org.knora.webapi.slice.common.api.TapirToPekkoInterpreter
import org.knora.webapi.slice.resourceinfo.domain.IriConverter
import org.knora.webapi.slice.resources.api.service.ResourcesRestService
import org.knora.webapi.slice.resources.api.service.ValuesRestService

object ResourcesApiModule
extends URModule[
ApiComplexV2JsonLdRequestParser & BaseEndpoints & HandlerMapper & KnoraResponseRenderer & ResourcesResponderV2 &
TapirToPekkoInterpreter & ValuesResponderV2,
ApiComplexV2JsonLdRequestParser & BaseEndpoints & HandlerMapper & IriConverter & KnoraResponseRenderer &
ResourcesResponderV2 & SearchResponderV2 & TapirToPekkoInterpreter & ValuesResponderV2,
ResourcesApiRoutes & ValuesEndpoints & ResourcesEndpoints,
] { self =>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,23 @@ final case class ResourcesEndpoints(private val baseEndpoints: BaseEndpoints) {
.out(stringBody)
.out(header[MediaType](HeaderNames.ContentType))

val getResourcesParams = baseEndpoints.withUserEndpoint.get

Check warning on line 86 in webapi/src/main/scala/org/knora/webapi/slice/resources/api/ResourcesEndpoints.scala

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

webapi/src/main/scala/org/knora/webapi/slice/resources/api/ResourcesEndpoints.scala#L86

Usage of get on optional type.
.in(base)
.in(query[IriDto]("resourceClass"))
.in(query[Option[IriDto]]("orderByProperty"))
.in(query[Int]("page").validate(Validator.min(0)))
.in(header[ProjectIri](ApiV2.Headers.xKnoraAcceptProject))
.in(ApiV2.Inputs.formatOptions)
.out(stringBody)
.out(header[MediaType](HeaderNames.ContentType))

val endpoints: Seq[AnyEndpoint] = Seq(
getResourcesIiifManifest,
getResourcesProjectHistoryEvents,
getResourcesHistoryEvents,
getResourcesHistory,
getResources,
getResourcesParams,
).map(_.endpoint.tag("V2 Resources"))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ final class ResourcesEndpointsHandler(
resourcesRestService.getResourcesHistoryEvents,
),
SecuredEndpointHandler(resourcesEndpoints.getResourcesHistory, resourcesRestService.getResourceHistory),
SecuredEndpointHandler(
resourcesEndpoints.getResourcesParams,
resourcesRestService.searchResourcesByProjectAndClass,
),
SecuredEndpointHandler(resourcesEndpoints.getResources, resourcesRestService.getResources),
).map(mapper.mapSecuredEndpointHandler(_))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,24 @@ package org.knora.webapi.slice.resources.api.service
import sttp.model.MediaType
import zio.*

import dsp.errors.BadRequestException
import org.knora.webapi.responders.v2.ResourcesResponderV2
import org.knora.webapi.responders.v2.SearchResponderV2
import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri
import org.knora.webapi.slice.admin.domain.model.User
import org.knora.webapi.slice.common.api.KnoraResponseRenderer
import org.knora.webapi.slice.common.api.KnoraResponseRenderer.FormatOptions
import org.knora.webapi.slice.common.api.KnoraResponseRenderer.RenderedResponse
import org.knora.webapi.slice.resourceinfo.domain.IriConverter
import org.knora.webapi.slice.resources.api.model.IriDto
import org.knora.webapi.slice.resources.api.model.VersionDate

final case class ResourcesRestService(resourcesService: ResourcesResponderV2, renderer: KnoraResponseRenderer) {
final case class ResourcesRestService(
private val resourcesService: ResourcesResponderV2,
private val searchService: SearchResponderV2,
private val iriConverter: IriConverter,
renderer: KnoraResponseRenderer,
) {

def getResourcesIiifManifest(user: User)(
resourceIri: IriDto,
Expand All @@ -40,6 +48,24 @@ final case class ResourcesRestService(resourcesService: ResourcesResponderV2, re
.getResourceHistoryEvents(resourceIri.value, user)
.flatMap(renderer.render(_, formatOptions))

def searchResourcesByProjectAndClass(user: User)(
resourceClass: IriDto,
orderByProperty: Option[IriDto],
page: Int,
projectIri: ProjectIri,
format: FormatOptions,
): Task[(RenderedResponse, MediaType)] = for {
resourceClass <- iriConverter
.asResourceClassIri(resourceClass.value)
.mapBoth(BadRequestException.apply, _.smartIri.toInternalSchema)
order <- ZIO
.foreach(orderByProperty.map(_.value))(iriConverter.asPropertyIri)
.mapBoth(BadRequestException.apply, _.map(_.smartIri.toInternalSchema))
rendering = format.schemaRendering
result <- searchService.searchResourcesByProjectAndClassV2(projectIri, resourceClass, order, page, rendering, user)
response <- renderer.render(result, format)
} yield response

def getResourceHistory(user: User)(
resourceIri: IriDto,
formatOptions: FormatOptions,
Expand Down

0 comments on commit 5977fba

Please sign in to comment.