Skip to content

Commit

Permalink
Merge branch 'master' into specific-aggs
Browse files Browse the repository at this point in the history
  • Loading branch information
olivergrabinski authored Oct 11, 2023
2 parents c32add6 + 2ae9369 commit 7d0d2eb
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package ch.epfl.bluebrain.nexus.delta.sdk

import ch.epfl.bluebrain.nexus.delta.rdf.RdfError
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.{JsonLdApi, JsonLdOptions}
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.{CompactedJsonLd, ExpandedJsonLd}
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, RemoteContextResolution}
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder
import monix.bio.IO

/**
* A definition of a value that can be converted to JSONLD
Expand All @@ -24,4 +29,18 @@ object JsonLdValue {
override val value: A = v
override val encoder: JsonLdEncoder[A] = implicitly[JsonLdEncoder[A]]
}

implicit val jsonLdEncoder: JsonLdEncoder[JsonLdValue] = {
new JsonLdEncoder[JsonLdValue] {
override def context(value: JsonLdValue): ContextValue = value.encoder.context(value.value)
override def expand(
value: JsonLdValue
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, ExpandedJsonLd] =
value.encoder.expand(value.value)
override def compact(
value: JsonLdValue
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, CompactedJsonLd] =
value.encoder.compact(value.value)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,9 @@ object CatsResponseToJsonLd extends FileBytesInstances {
}

onSuccess(flattened.unsafeToFuture()) {
case Left(complete: Complete[E]) => emit(complete)
case Left(reject: Reject[E]) => emit(reject)
case Right(Left(c)) =>
implicit val valueEncoder = c.value.encoder
emit(c.value.value)

case Left(complete: Complete[E]) => emit(complete)
case Left(reject: Reject[E]) => emit(reject)
case Right(Left(c)) => emit(c)
case Right(Right((metadata, content))) =>
headerValueByType(Accept) { accept =>
if (accept.mediaRanges.exists(_.matches(metadata.contentType.mediaType))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,9 @@ object ResponseToJsonLd extends FileBytesInstances {
override def apply(statusOverride: Option[StatusCode]): Route = {
val flattened = io.flatMap { fr => fr.content.attempt.map(_.map { s => fr.metadata -> s }) }.attempt
onSuccess(flattened.runToFuture) {
case Left(complete: Complete[E]) => emit(complete)
case Left(reject: Reject[E]) => emit(reject)
case Right(Left(c)) =>
implicit val valueEncoder = c.value.encoder
emit(c.value.value)

case Left(complete: Complete[E]) => emit(complete)
case Left(reject: Reject[E]) => emit(reject)
case Right(Left(c)) => emit(c)
case Right(Right((metadata, content))) =>
headerValueByType(Accept) { accept =>
if (accept.mediaRanges.exists(_.matches(metadata.contentType.mediaType))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ object AuthTokenError {
final case class AuthTokenNotFoundInResponse(failure: DecodingFailure)
extends AuthTokenError(s"Auth token not found in auth response: ${failure.reason}")

/**
* Signals that the expiry was missing from the authentication response
*/
final case class ExpiryNotFoundInResponse(failure: DecodingFailure)
extends AuthTokenError(s"Expiry not found in auth response: ${failure.reason}")

/**
* Signals that the realm specified for authentication is deprecated
*/
Expand All @@ -45,8 +39,6 @@ object AuthTokenError {
JsonObject(keywords.tpe := "AuthTokenHttpError", "reason" := r.reason)
case AuthTokenNotFoundInResponse(r) =>
JsonObject(keywords.tpe -> "AuthTokenNotFoundInResponse".asJson, "reason" := r.message)
case ExpiryNotFoundInResponse(r) =>
JsonObject(keywords.tpe -> "ExpiryNotFoundInResponse".asJson, "reason" := r.message)
case r: RealmIsDeprecated =>
JsonObject(keywords.tpe := "RealmIsDeprecated", "reason" := r.getMessage)
}
Expand Down
5 changes: 5 additions & 0 deletions delta/sdk/src/test/resources/directives/blank-id.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"@context" : "https://bluebrain.github.io/nexus/contexts/error.json",
"@type" : "BlankResourceId",
"reason" : "Resource identifier cannot be blank."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package ch.epfl.bluebrain.nexus.delta.sdk.ce

import akka.http.scaladsl.model.ContentTypes.`text/plain(UTF-8)`
import akka.http.scaladsl.model.MediaRanges.`*/*`
import akka.http.scaladsl.model.headers.Accept
import akka.http.scaladsl.model.{ContentType, StatusCodes}
import akka.http.scaladsl.server.RouteConcatenation
import akka.stream.scaladsl.Source
import akka.util.ByteString
import ch.epfl.bluebrain.nexus.delta.rdf.RdfMediaTypes.`application/ld+json`
import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.contexts
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.RemoteContextResolution
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder
import ch.epfl.bluebrain.nexus.delta.rdf.syntax.JsonSyntax
import ch.epfl.bluebrain.nexus.delta.rdf.utils.JsonKeyOrdering
import ch.epfl.bluebrain.nexus.delta.sdk.ce.DeltaDirectives._
import ch.epfl.bluebrain.nexus.delta.sdk.directives.FileResponse
import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields
import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection
import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection.BlankResourceId
import ch.epfl.bluebrain.nexus.delta.sdk.utils.RouteHelpers
import ch.epfl.bluebrain.nexus.delta.sdk.{AkkaSource, SimpleRejection, SimpleResource}
import ch.epfl.bluebrain.nexus.testkit.ShouldMatchers.convertToAnyShouldWrapper
import ch.epfl.bluebrain.nexus.testkit.TestHelpers.jsonContentOf
import monix.bio.IO
import cats.effect.{IO => CatsIO}
import monix.execution.Scheduler

class CatsResponseToJsonLdSpec extends RouteHelpers with JsonSyntax with RouteConcatenation {

implicit val s: Scheduler = Scheduler.global
implicit val rcr: RemoteContextResolution =
RemoteContextResolution.fixed(
SimpleResource.contextIri -> SimpleResource.context,
SimpleRejection.contextIri -> SimpleRejection.context,
contexts.error -> jsonContentOf("/contexts/error.json").topContextValueOrEmpty
)
implicit val jo: JsonKeyOrdering = JsonKeyOrdering.default()

private def responseWithSourceError[E: JsonLdEncoder: HttpResponseFields](error: E) = {
responseWith(
`text/plain(UTF-8)`,
IO.raiseError(error)
)
}

private val expectedBlankIdErrorResponse = jsonContentOf(
"/directives/blank-id.json"
)

private val FileContents = "hello"

private def fileSourceOfString(value: String) = {
IO.pure(Source.single(ByteString(value)))
}

private def responseWith[E: JsonLdEncoder: HttpResponseFields](
contentType: ContentType,
contents: IO[E, AkkaSource]
) = {
CatsIO.pure(
Right(
FileResponse(
"file.name",
contentType,
1024,
contents
)
)
)
}

private def request = {
Get() ~> Accept(`*/*`)
}

"ResponseToJsonLd file handling" should {

"Return the contents of a file" in {
request ~> emit(
responseWith(`text/plain(UTF-8)`, fileSourceOfString(FileContents))
) ~> check {
status shouldEqual StatusCodes.OK
contentType shouldEqual `text/plain(UTF-8)`
response.asString shouldEqual FileContents
}
}

"Return an error from a file content IO" in {
request ~> emit(responseWithSourceError[ResourceRejection](BlankResourceId)) ~> check {
status shouldEqual StatusCodes.BadRequest // BlankResourceId is supposed to result in BadRequest
contentType.mediaType shouldEqual `application/ld+json`
response.asJson shouldEqual expectedBlankIdErrorResponse
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package ch.epfl.bluebrain.nexus.delta.sdk.directives

import akka.http.scaladsl.model.ContentTypes.`text/plain(UTF-8)`
import akka.http.scaladsl.model.MediaRanges.`*/*`
import akka.http.scaladsl.model.headers.Accept
import akka.http.scaladsl.model.{ContentType, StatusCodes}
import akka.http.scaladsl.server.RouteConcatenation
import akka.stream.scaladsl.Source
import akka.util.ByteString
import ch.epfl.bluebrain.nexus.delta.rdf.RdfMediaTypes.`application/ld+json`
import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.contexts
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.RemoteContextResolution
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder
import ch.epfl.bluebrain.nexus.delta.rdf.syntax.JsonSyntax
import ch.epfl.bluebrain.nexus.delta.rdf.utils.JsonKeyOrdering
import ch.epfl.bluebrain.nexus.delta.sdk.directives.DeltaDirectives._
import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields
import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection
import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection.BlankResourceId
import ch.epfl.bluebrain.nexus.delta.sdk.utils.RouteHelpers
import ch.epfl.bluebrain.nexus.delta.sdk.{AkkaSource, SimpleRejection, SimpleResource}
import ch.epfl.bluebrain.nexus.testkit.ShouldMatchers.convertToAnyShouldWrapper
import ch.epfl.bluebrain.nexus.testkit.TestHelpers.jsonContentOf
import monix.bio.IO
import monix.execution.Scheduler

class ResponseToJsonLdSpec extends RouteHelpers with JsonSyntax with RouteConcatenation {

implicit val s: Scheduler = Scheduler.global
implicit val rcr: RemoteContextResolution =
RemoteContextResolution.fixed(
SimpleResource.contextIri -> SimpleResource.context,
SimpleRejection.contextIri -> SimpleRejection.context,
contexts.error -> jsonContentOf("/contexts/error.json").topContextValueOrEmpty
)
implicit val jo: JsonKeyOrdering = JsonKeyOrdering.default()

private def responseWithSourceError[E: JsonLdEncoder: HttpResponseFields](error: E) = {
responseWith(
`text/plain(UTF-8)`,
IO.raiseError(error)
)
}

private val expectedBlankIdErrorResponse = jsonContentOf(
"/directives/blank-id.json"
)

private val FileContents = "hello"

private def fileSourceOfString(value: String) = {
IO.pure(Source.single(ByteString(value)))
}

private def responseWith[E: JsonLdEncoder: HttpResponseFields](
contentType: ContentType,
contents: IO[E, AkkaSource]
) = {
IO.pure(
FileResponse(
"file.name",
contentType,
1024,
contents
)
)
}

private def request = {
Get() ~> Accept(`*/*`)
}

"ResponseToJsonLd file handling" should {

"Return the contents of a file" in {
request ~> emit(
responseWith(`text/plain(UTF-8)`, fileSourceOfString(FileContents))
) ~> check {
status shouldEqual StatusCodes.OK
contentType shouldEqual `text/plain(UTF-8)`
response.asString shouldEqual FileContents
}
}

"Return an error from a file content IO" in {
request ~> emit(responseWithSourceError[ResourceRejection](BlankResourceId)) ~> check {
status shouldEqual StatusCodes.BadRequest // BlankResourceId is supposed to result in BadRequest
contentType.mediaType shouldEqual `application/ld+json`
response.asJson shouldEqual expectedBlankIdErrorResponse
}
}
}
}

0 comments on commit 7d0d2eb

Please sign in to comment.