-
Notifications
You must be signed in to change notification settings - Fork 74
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
Ensure file errors are written correctly #4346
Changes from all commits
62fc300
a5a7be7
0a1d5d5
ec3e9ed
b839875
9ddfd5b
33ef89b
42429fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file is currently duplicated for the Cats migration, we need to keep the two of them in sync There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can I port the FileRoutes across to cats? I don't feel great about this being untested There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps now I'm unit testing this class, I can test both 🤔 |
||
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))) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
*/ | ||
|
@@ -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) | ||
Comment on lines
-48
to
-49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was unused |
||
case r: RealmIsDeprecated => | ||
JsonObject(keywords.tpe := "RealmIsDeprecated", "reason" := r.getMessage) | ||
} | ||
|
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 | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JsonLdValue
is a slightly strange class. I can see why we have it, but I think also this typeclass makes sense for it. The alternative is that we do some wrangling in ResponseToJsonLd, but I think this makes more sense