diff --git a/webapi/src/main/scala/org/knora/webapi/store/iiif/api/SipiService.scala b/webapi/src/main/scala/org/knora/webapi/store/iiif/api/SipiService.scala index 79072da61c..f7db7e776a 100644 --- a/webapi/src/main/scala/org/knora/webapi/store/iiif/api/SipiService.scala +++ b/webapi/src/main/scala/org/knora/webapi/store/iiif/api/SipiService.scala @@ -5,10 +5,9 @@ package org.knora.webapi.store.iiif.api -import org.apache.pekko.http.scaladsl.marshallers.sprayjson.SprayJsonSupport -import spray.json.DefaultJsonProtocol -import spray.json.RootJsonFormat import zio.* +import zio.json.DeriveJsonDecoder +import zio.json.JsonDecoder import zio.macros.accessible import zio.nio.file.Path @@ -48,10 +47,15 @@ case class FileMetadataSipiResponse( } } -object FileMetadataSipiResponse extends SprayJsonSupport with DefaultJsonProtocol { - implicit val sipiKnoraJsonResponseFormat: RootJsonFormat[FileMetadataSipiResponse] = jsonFormat8( - FileMetadataSipiResponse.apply - ) +object FileMetadataSipiResponse { + // Because Sipi returns JSON Numbers which are whole numbers but not a valid Scala Int, e.g. `width: 1920.0`, we need to + // use a custom decoder for Int. See also https://github.com/zio/zio-json/issues/1049#issuecomment-1814108354 + implicit val anyWholeNumber: JsonDecoder[Int] = JsonDecoder[Double].mapOrFail { d => + val i = d.toInt + if (d == i.toDouble) { Right(i) } + else { Left("32-bit int expected") } + } + implicit val decoder: JsonDecoder[FileMetadataSipiResponse] = DeriveJsonDecoder.gen[FileMetadataSipiResponse] } @accessible diff --git a/webapi/src/main/scala/org/knora/webapi/store/iiif/impl/SipiServiceLive.scala b/webapi/src/main/scala/org/knora/webapi/store/iiif/impl/SipiServiceLive.scala index 934788a438..45064376d9 100644 --- a/webapi/src/main/scala/org/knora/webapi/store/iiif/impl/SipiServiceLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/store/iiif/impl/SipiServiceLive.scala @@ -5,45 +5,34 @@ package org.knora.webapi.store.iiif.impl -import org.apache.http.Consts -import org.apache.http.HttpEntity -import org.apache.http.HttpHost -import org.apache.http.HttpRequest -import org.apache.http.HttpResponse -import org.apache.http.NameValuePair +import dsp.errors.{BadRequestException, NotFoundException} +import org.apache.http.{Consts, HttpEntity, HttpHost, HttpRequest, HttpResponse, NameValuePair} import org.apache.http.client.config.RequestConfig import org.apache.http.client.entity.UrlEncodedFormEntity import org.apache.http.client.methods.* import org.apache.http.client.protocol.HttpClientContext import org.apache.http.config.SocketConfig -import org.apache.http.impl.client.CloseableHttpClient -import org.apache.http.impl.client.HttpClients +import org.apache.http.impl.client.{CloseableHttpClient, HttpClients} import org.apache.http.impl.conn.PoolingHttpClientConnectionManager import org.apache.http.message.BasicNameValuePair import org.apache.http.util.EntityUtils +import org.knora.webapi.config.{AppConfig, Sipi} +import org.knora.webapi.messages.admin.responder.usersmessages.UserADM +import org.knora.webapi.messages.store.sipimessages.* +import org.knora.webapi.messages.v2.responder.SuccessResponseV2 +import org.knora.webapi.routing.{Jwt, JwtService} +import org.knora.webapi.slice.admin.domain.service.Asset +import org.knora.webapi.store.iiif.api.{FileMetadataSipiResponse, SipiService} +import org.knora.webapi.store.iiif.errors.SipiException +import org.knora.webapi.util.{SipiUtil, ZScopedJavaIoStreams} import spray.json.* import zio.* +import zio.json.DecoderOps import zio.nio.file.Path import java.net.URI import java.util -import dsp.errors.BadRequestException -import dsp.errors.NotFoundException -import org.knora.webapi.config.AppConfig -import org.knora.webapi.config.Sipi -import org.knora.webapi.messages.admin.responder.usersmessages.UserADM -import org.knora.webapi.messages.store.sipimessages.* -import org.knora.webapi.messages.v2.responder.SuccessResponseV2 -import org.knora.webapi.routing.Jwt -import org.knora.webapi.routing.JwtService -import org.knora.webapi.slice.admin.domain.service.Asset -import org.knora.webapi.store.iiif.api.FileMetadataSipiResponse -import org.knora.webapi.store.iiif.api.SipiService -import org.knora.webapi.store.iiif.errors.SipiException -import org.knora.webapi.util.SipiUtil -import org.knora.webapi.util.ZScopedJavaIoStreams - /** * Makes requests to Sipi. * @@ -73,7 +62,11 @@ final case class SipiServiceLive( */ override def getFileMetadata(filePath: String): Task[FileMetadataSipiResponse] = doSipiRequest(new HttpGet(sipiConfig.internalBaseUrl + filePath + "/knora.json")) - .mapAttempt(_.parseJson.convertTo[FileMetadataSipiResponse]) + .flatMap(bodyStr => + ZIO + .fromEither(bodyStr.fromJson[FileMetadataSipiResponse]) + .mapError(e => SipiException(s"Invalid response from Sipi: $e, $bodyStr")) + ) /** * Asks Sipi to move a file from temporary storage to permanent storage.