Skip to content

Commit

Permalink
refactor: Remove Spray Json from files refactored during DEV-1627 (#3259
Browse files Browse the repository at this point in the history
)
  • Loading branch information
siers authored May 31, 2024
1 parent 8b2d635 commit 08c4633
Show file tree
Hide file tree
Showing 13 changed files with 102 additions and 133 deletions.
4 changes: 2 additions & 2 deletions integration/src/test/scala/org/knora/webapi/E2ESpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import org.scalatest.BeforeAndAfterAll
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import spray.json.*
import zio.*
import zio.json.*
import zio.json.ast.Json

import java.nio.file.Files
import java.nio.file.Path
Expand Down Expand Up @@ -145,7 +145,7 @@ abstract class E2ESpec
} yield obj,
)

protected def getResponseAsJson(request: HttpRequest): JsObject =
protected def getResponseAsJson(request: HttpRequest): Json.Obj =
UnsafeZioRun.runOrThrow(ZIO.serviceWithZIO[TestClientService](_.getResponseJson(request)))

protected def getResponseAsJsonLD(request: HttpRequest): JsonLDDocument =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import org.apache.pekko.testkit.TestKitBase
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import spray.json.*
import zio.*
import zio.json.ast.Json

import java.util.concurrent.TimeUnit
import scala.concurrent.Await
Expand Down Expand Up @@ -141,7 +141,7 @@ abstract class ITKnoraLiveSpec
.getOrThrowFiberFailure()
}

protected def getResponseJson(request: HttpRequest): JsObject =
protected def getResponseJson(request: HttpRequest): Json.Obj =
Unsafe.unsafe { implicit u =>
runtime.unsafe
.run(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,10 @@ class StandoffRouteV2ITSpec extends ITKnoraLiveSpec with AuthenticationV2JsonPro
)
val sipiRequest = Post(s"${baseInternalSipiUrl}/upload?token=$loginToken", sipiFormData)
val sipiResponse = singleAwaitingRequest(sipiRequest)
val uploadedFile = responseToString(sipiResponse).parseJson.asJsObject
.convertTo[SipiUploadResponse]
.uploadedFiles
.head
val uploadedFile = {
import zio.json.*
responseToString(sipiResponse).fromJson[SipiUploadResponse].toOption.get.uploadedFiles.head
}

// create FileRepresentation in API
val uploadFileJson = UploadFileRequest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import org.apache.http
import org.apache.http.entity.ContentType
import org.apache.pekko
import org.apache.pekko.actor.ActorSystem
import spray.json.*
import spray.json.JsObject
import sttp.capabilities.zio.ZioStreams
import sttp.client3
import sttp.client3.*
import sttp.client3.SttpBackend
import sttp.client3.httpclient.zio.HttpClientZioBackend
import zio.*
import zio.json.*
import zio.json.ast.Json

import java.nio.file.Path
import java.util.concurrent.TimeUnit
Expand Down Expand Up @@ -139,10 +139,10 @@ final case class TestClientService(
/**
* Performs a http request and tries to parse the response body as Json.
*/
def getResponseJson(request: pekko.http.scaladsl.model.HttpRequest): Task[JsObject] =
def getResponseJson(request: pekko.http.scaladsl.model.HttpRequest): Task[Json.Obj] =
for {
body <- getResponseString(request)
json <- ZIO.succeed(body.parseJson.asJsObject)
json <- ZIO.fromEither(body.fromJson[Json.Obj]).mapError(Throwable(_))
} yield json

/**
Expand Down Expand Up @@ -171,7 +171,8 @@ final case class TestClientService(
.contentType(file.mimeType.toString)
}
response <- doSipiRequest(quickRequest.post(url).multipartBody(multiparts))
} yield response.parseJson.asJsObject.convertTo[SipiUploadResponse]
json <- ZIO.fromEither(response.fromJson[SipiUploadResponse]).mapError(Throwable(_))
} yield json

/**
* Uploads a file to the IIIF Service's "upload_without_processing" route and returns the information in Sipi's response.
Expand All @@ -193,7 +194,8 @@ final case class TestClientService(
.contentType(file.mimeType.toString)
}
response <- doSipiRequest(quickRequest.post(url).multipartBody(multiparts))
} yield response.parseJson.asJsObject.convertTo[SipiUploadWithoutProcessingResponse]
json <- ZIO.fromEither(response.fromJson[SipiUploadWithoutProcessingResponse]).mapError(Throwable(_))
} yield json

private def doSipiRequest[T](request: Request[String, Any]): Task[String] =
sttp.send(request).flatMap { response =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,13 @@

package org.knora.webapi.messages.store.sipimessages

import org.apache.pekko
import spray.json.*
import zio.json.*

import org.knora.webapi.core.RelayedMessage
import org.knora.webapi.messages.store.StoreRequest
import org.knora.webapi.messages.traits.RequestWithSender
import org.knora.webapi.slice.admin.domain.model.User

import pekko.http.scaladsl.marshallers.sprayjson.SprayJsonSupport

/**
* An abstract trait for messages that can be sent to the [[org.knora.webapi.store.iiif.api.SipiService]]
*/
Expand Down Expand Up @@ -90,26 +87,23 @@ case class SipiUploadWithoutProcessingResponseEntry(
*
* @param uploadedFiles the information about each file that was uploaded.
*/
case class SipiUploadResponse(uploadedFiles: Seq[SipiUploadResponseEntry])
case class SipiUploadResponse(uploadedFiles: List[SipiUploadResponseEntry])

object SipiUploadResponseJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol {
implicit val sipiUploadResponseEntryFormat: RootJsonFormat[SipiUploadResponseEntry] =
jsonFormat4(SipiUploadResponseEntry.apply)
implicit val sipiUploadResponseFormat: RootJsonFormat[SipiUploadResponse] =
jsonFormat1(SipiUploadResponse.apply)
object SipiUploadResponseJsonProtocol {
implicit val entry: JsonCodec[SipiUploadResponseEntry] = DeriveJsonCodec.gen[SipiUploadResponseEntry]
implicit val response: JsonCodec[SipiUploadResponse] = DeriveJsonCodec.gen[SipiUploadResponse]
}

/**
* Represents Sipi's response to a file upload without processing request.
*
* @param uploadedFiles the information about each file that was uploaded.
*/
case class SipiUploadWithoutProcessingResponse(uploadedFiles: Seq[SipiUploadWithoutProcessingResponseEntry])
case class SipiUploadWithoutProcessingResponse(uploadedFiles: List[SipiUploadWithoutProcessingResponseEntry])

object SipiUploadWithoutProcessingResponseJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol {
implicit val sipiUploadWithoutProcessingResponseEntryFormat
: RootJsonFormat[SipiUploadWithoutProcessingResponseEntry] =
jsonFormat3(SipiUploadWithoutProcessingResponseEntry.apply)
implicit val sipiUploadWithoutProcessingResponseFormat: RootJsonFormat[SipiUploadWithoutProcessingResponse] =
jsonFormat1(SipiUploadWithoutProcessingResponse.apply)
object SipiUploadWithoutProcessingResponseJsonProtocol {
implicit val withoutResponseEntryCodec: JsonCodec[SipiUploadWithoutProcessingResponseEntry] =
DeriveJsonCodec.gen[SipiUploadWithoutProcessingResponseEntry]
implicit val withoutResponseCodec: JsonCodec[SipiUploadWithoutProcessingResponse] =
DeriveJsonCodec.gen[SipiUploadWithoutProcessingResponse]
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@

package org.knora.webapi.messages.store.triplestoremessages

import org.apache.pekko
import spray.json.DefaultJsonProtocol
import spray.json.JsonFormat
import spray.json.RootJsonFormat

import pekko.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import zio.json.*

/**
* Represents a response from Fuseki about the state of the Fuseki server.
Expand All @@ -35,7 +30,11 @@ case class FusekiServer(
* @param dsState the state of the dataset.
* @param dsServices the services available for the dataset.
*/
case class FusekiDataset(dsName: String, dsState: Boolean, dsServices: Seq[FusekiService])
case class FusekiDataset(
@jsonField("ds.name") dsName: String,
@jsonField("ds.state") dsState: Boolean,
@jsonField("ds.services") dsServices: Seq[FusekiService],
)

/**
* Represets a service available for a Fuseki dataset.
Expand All @@ -44,16 +43,17 @@ case class FusekiDataset(dsName: String, dsState: Boolean, dsServices: Seq[Fusek
* @param srvDescription a description of the service.
* @param srvEndpoints the endpoints provided by the service.
*/
case class FusekiService(srvType: String, srvDescription: String, srvEndpoints: Seq[String])
case class FusekiService(
@jsonField("srv.type") srvType: String,
@jsonField("srv.description") srvDescription: String,
@jsonField("srv.endpoints") srvEndpoints: Seq[String],
)

/**
* Parses server status responses from Fuseki.
*/
object FusekiJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol {
implicit val fusekiServiceFormat: JsonFormat[FusekiService] =
jsonFormat(FusekiService.apply, "srv.type", "srv.description", "srv.endpoints")
implicit val fusekiDatasetFormat: JsonFormat[FusekiDataset] =
jsonFormat(FusekiDataset.apply, "ds.name", "ds.state", "ds.services")
implicit val fusekiServerFormat: RootJsonFormat[FusekiServer] =
jsonFormat4(FusekiServer.apply)
object FusekiJsonProtocol {
implicit val fusekiServiceFormat: JsonCodec[FusekiService] = DeriveJsonCodec.gen[FusekiService]
implicit val fusekiDatasetFormat: JsonCodec[FusekiDataset] = DeriveJsonCodec.gen[FusekiDataset]
implicit val fusekiServerFormat: JsonCodec[FusekiServer] = DeriveJsonCodec.gen[FusekiServer]
}
Original file line number Diff line number Diff line change
Expand Up @@ -451,58 +451,30 @@ case class DateTimeLiteralV2(value: Instant) extends LiteralV2 {
// JSON formatting

/**
* A spray-json protocol that parses JSON returned by a SPARQL endpoint. Empty values and empty rows are
* A ZIO json protocol that parses JSON returned by a SPARQL endpoint. Empty values and empty rows are
* ignored.
*/
object SparqlResultProtocol extends DefaultJsonProtocol {

/**
* Converts a [[JsValue]] to a [[VariableResultsRow]].
*/
implicit object VariableResultsJsonFormat extends JsonFormat[VariableResultsRow] {
def read(jsonVal: JsValue): VariableResultsRow = {

// Collapse the JSON structure into a simpler Map of SPARQL variable names to values.
val mapToWrap: Map[String, String] = jsonVal.asJsObject.fields.foldLeft(Map.empty[String, String]) {
case (acc, (key, value)) =>
value.asJsObject.getFields("value") match {
case Seq(JsString(valueStr)) if valueStr.nonEmpty => // Ignore empty strings.
acc + (key -> valueStr)
case _ => acc
}
import zio.json._
import zio.json.ast.Json
import cats.implicits._

implicit val VariableResultsZioJsonFormat: JsonDecoder[VariableResultsRow] =
JsonDecoder[Json.Obj].map { obj =>
val mapToWrap: Map[String, String] = obj.fields.toList.foldMap { case (key, value) =>
value.asObject.foldMap(_.get("value").flatMap(_.asString).filter(_.nonEmpty).foldMap(s => Map(key -> s)))
}

// Wrap that Map in an ErrorHandlingMap that will gracefully report errors about missing values when they
// are accessed later.
VariableResultsRow(
new ErrorHandlingMap(
mapToWrap,
{ (key: String) => s"No value found for SPARQL query variable '$key' in query result row" },
),
)
// Wrapped in an ErrorHandlingMap which gracefully reports errors about accessing missing values.
val keyMissing = (key: String) => s"No value found for SPARQL query variable '$key' in query result row"
VariableResultsRow(new ErrorHandlingMap(mapToWrap, keyMissing))
}

def write(variableResultsRow: VariableResultsRow): JsValue = ???
}

/**
* Converts a [[JsValue]] to a [[SparqlSelectResultBody]].
*/
implicit object SparqlSelectResponseBodyFormat extends JsonFormat[SparqlSelectResultBody] {
def read(jsonVal: JsValue): SparqlSelectResultBody =
jsonVal.asJsObject.fields.get("bindings") match {
case Some(bindingsJson: JsArray) =>
// Filter out empty rows.
SparqlSelectResultBody(bindingsJson.convertTo[Seq[VariableResultsRow]].filter(_.rowMap.keySet.nonEmpty))

case _ => SparqlSelectResultBody(Nil)
}

def write(sparqlSelectResponseBody: SparqlSelectResultBody): JsValue = ???
}
implicit val SparqlSelectResponseBodyFormatZ: JsonDecoder[SparqlSelectResultBody] =
DeriveJsonDecoder.gen[SparqlSelectResultBodyUnchecked].map(_.asChecked)

implicit val headerFormat: JsonFormat[SparqlSelectResultHeader] = jsonFormat1(SparqlSelectResultHeader.apply)
implicit val responseFormat: JsonFormat[SparqlSelectResult] = jsonFormat2(SparqlSelectResult.apply)
implicit val headerDecoder: JsonDecoder[SparqlSelectResultHeader] = DeriveJsonDecoder.gen[SparqlSelectResultHeader]
implicit val responseDecoder: JsonDecoder[SparqlSelectResult] = DeriveJsonDecoder.gen[SparqlSelectResult]
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ case class SparqlSelectResultBody(bindings: Seq[VariableResultsRow]) {
require(bindings.forall(_.rowMap.nonEmpty), "Empty rows are not allowed in a SparqlSelectResponseBody")
}

case class SparqlSelectResultBodyUnchecked(bindings: Seq[VariableResultsRow]) {
def asChecked = SparqlSelectResultBody(bindings.filter(_.rowMap.keySet.nonEmpty))
}

/**
* Represents a row of results in the result of a SPARQL SELECT query.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import pdi.jwt.JwtAlgorithm
import pdi.jwt.JwtClaim
import pdi.jwt.JwtHeader
import pdi.jwt.JwtZIOJson
import spray.json.JsObject
import spray.json.JsValue
import zio.Clock
import zio.Duration
import zio.Random
Expand All @@ -21,6 +19,7 @@ import zio.UIO
import zio.ZIO
import zio.ZLayer
import zio.durationInt
import zio.json.ast.Json

import scala.util.Failure
import scala.util.Success
Expand Down Expand Up @@ -56,7 +55,7 @@ trait JwtService {
* @param content any other content to be included in the token.
* @return a [[String]] containing the JWT.
*/
def createJwt(user: User, content: Map[String, JsValue] = Map.empty): UIO[Jwt]
def createJwt(user: User, content: Map[String, Json] = Map.empty): UIO[Jwt]

def createJwtForDspIngest(): UIO[Jwt]

Expand Down Expand Up @@ -90,9 +89,11 @@ final case class JwtServiceLive(
private val logger = Logger(LoggerFactory.getLogger(this.getClass))
private val audience = Set("Knora", "Sipi", dspIngestConfig.audience)

override def createJwt(user: User, content: Map[String, JsValue] = Map.empty): UIO[Jwt] =
override def createJwt(user: User, content: Map[String, Json] = Map.empty): UIO[Jwt] =
calculateScope(user)
.flatMap(scope => createJwtToken(jwtConfig.issuerAsString(), user.id, audience, scope, Some(JsObject(content))))
.flatMap(scope =>
createJwtToken(jwtConfig.issuerAsString(), user.id, audience, scope, Some(Json.Obj(content.toSeq: _*))),
)

private def calculateScope(user: User) =
if (user.isSystemAdmin || user.isSystemUser) { ZIO.succeed(Scope.admin) }
Expand Down Expand Up @@ -131,15 +132,15 @@ final case class JwtServiceLive(
subject: IRI,
audience: Set[IRI],
scope: Scope,
content: Option[JsObject] = None,
content: Option[Json.Obj] = None,
expiration: Option[Duration] = None,
) =
for {
now <- Clock.instant
uuid <- Random.nextUUID
exp = now.plus(expiration.getOrElse(jwtConfig.expiration))
claim = JwtClaim(
content = content.getOrElse(JsObject.empty).compactPrint,
content = content.getOrElse(Json.Obj()).toString,
issuer = Some(issuer),
subject = Some(subject),
audience = Some(audience),
Expand Down
Loading

0 comments on commit 08c4633

Please sign in to comment.