diff --git a/build.sbt b/build.sbt index 3156de5bfb..3c88f5c2c2 100755 --- a/build.sbt +++ b/build.sbt @@ -46,7 +46,6 @@ val kindProjectorVersion = "0.13.3" val log4catsVersion = "2.7.0" val logbackVersion = "1.5.10" val magnoliaVersion = "1.1.10" -val mockitoVersion = "1.17.37" val munitVersion = "1.0.2" val munitCatsEffectVersion = "2.0.0" val nimbusJoseJwtVersion = "9.41.2" @@ -112,7 +111,6 @@ lazy val kindProjector = "org.typelevel" %% "kind lazy val log4cats = "org.typelevel" %% "log4cats-slf4j" % log4catsVersion lazy val logback = "ch.qos.logback" % "logback-classic" % logbackVersion lazy val magnolia = "com.softwaremill.magnolia1_2" %% "magnolia" % magnoliaVersion -lazy val mockito = "org.mockito" %% "mockito-scala" % mockitoVersion lazy val munit = "org.scalameta" %% "munit" % munitVersion lazy val munitCatsEffect = "org.typelevel" %% "munit-cats-effect" % munitCatsEffectVersion lazy val nimbusJoseJwt = "com.nimbusds" % "nimbus-jose-jwt" % nimbusJoseJwtVersion diff --git a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/SchemaJobRoutes.scala b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/SchemaJobRoutes.scala index 24c4b7f064..52903cb601 100644 --- a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/SchemaJobRoutes.scala +++ b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/SchemaJobRoutes.scala @@ -55,7 +55,7 @@ class SchemaJobRoutes( ) } }.map { s => - FileResponse("validation.json", ContentTypes.`application/json`, None, None, None, s) + FileResponse("validation.json", ContentTypes.`application/json`, None, None, s) } def routes: Route = diff --git a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/Files.scala b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/Files.scala index 286970f799..f1a95acc3b 100644 --- a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/Files.scala +++ b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/Files.scala @@ -469,7 +469,6 @@ final class Files( attributes.filename, mediaType, Some(ResourceF.etagValue(file)), - Some(file.updatedAt), Some(attributes.bytes), s.attemptNarrow[FileRejection] ) diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectives.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectives.scala index b6b0c826ca..19297e161e 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectives.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectives.scala @@ -23,7 +23,6 @@ import ch.epfl.bluebrain.nexus.delta.sourcing.model.ProjectRef import ch.epfl.bluebrain.nexus.delta.sourcing.offset.Offset import io.circe.Encoder -import java.time.Instant import scala.reflect.ClassTag object DeltaDirectives extends DeltaDirectives @@ -140,30 +139,23 @@ trait DeltaDirectives extends UriDirectives { .getOrElse(HttpEncodings.identity) } - def conditionalCache( - value: Option[String], - lastModified: Option[Instant], - mediaType: MediaType, - encoding: HttpEncoding - ): Directive0 = - conditionalCache(value, lastModified, mediaType, None, encoding) + def conditionalCache(value: Option[String], mediaType: MediaType, encoding: HttpEncoding): Directive0 = + conditionalCache(value, mediaType, None, encoding) /** * Wraps its inner route with support for Conditional Requests as defined by http://tools.ietf.org/html/rfc7232 * - * Supports `Etag` and `Last-Modified` headers: + * Supports `Etag` header: * https://doc.akka.io/docs/akka-http/10.0/routing-dsl/directives/cache-condition-directives/conditional.html */ def conditionalCache( value: Option[String], - lastModified: Option[Instant], mediaType: MediaType, jsonldFormat: Option[JsonLdFormat], encoding: HttpEncoding ): Directive0 = { - val entityTag = value.map(EtagUtils.compute(_, mediaType, jsonldFormat, encoding)) - val lastModifiedDateTime = lastModified.map { instant => DateTime(instant.toEpochMilli) } - Directives.conditional(entityTag, lastModifiedDateTime) + val entityTag = value.map(EtagUtils.compute(_, mediaType, jsonldFormat, encoding)) + Directives.conditional(entityTag, None) } /** diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/FileResponse.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/FileResponse.scala index 0099f2c8b0..3c8aba1ed7 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/FileResponse.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/FileResponse.scala @@ -10,8 +10,6 @@ import ch.epfl.bluebrain.nexus.delta.sdk.directives.Response.Complete import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields import ch.epfl.bluebrain.nexus.delta.sdk.{AkkaSource, JsonLdValue} -import java.time.Instant - /** * A file response content * @@ -40,7 +38,6 @@ object FileResponse { filename: String, contentType: ContentType, etag: Option[String], - lastModified: Option[Instant], bytes: Option[Long] ) @@ -52,8 +49,6 @@ object FileResponse { value.bytes.map { bytes => `Content-Length`(bytes) }.toSeq override def entityTag(value: Metadata): Option[String] = value.etag - - override def lastModified(value: Metadata): Option[Instant] = value.lastModified } } @@ -61,12 +56,11 @@ object FileResponse { filename: String, contentType: ContentType, etag: Option[String], - lastModified: Option[Instant], bytes: Option[Long], io: IO[Either[E, AkkaSource]] ) = new FileResponse( - Metadata(filename, contentType, etag, lastModified, bytes), + Metadata(filename, contentType, etag, bytes), io.map { r => r.leftMap { e => Complete(e).map(JsonLdValue(_)) @@ -78,13 +72,12 @@ object FileResponse { filename: String, contentType: ContentType, etag: Option[String], - lastModified: Option[Instant], bytes: Option[Long], source: AkkaSource ): FileResponse = - new FileResponse(Metadata(filename, contentType, etag, lastModified, bytes), IO.pure(Right(source))) + new FileResponse(Metadata(filename, contentType, etag, bytes), IO.pure(Right(source))) def noCache(filename: String, contentType: ContentType, bytes: Option[Long], source: AkkaSource): FileResponse = - new FileResponse(Metadata(filename, contentType, None, None, bytes), IO.pure(Right(source))) + new FileResponse(Metadata(filename, contentType, None, bytes), IO.pure(Right(source))) } diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/Response.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/Response.scala index e8e25348bb..d6c488497e 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/Response.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/Response.scala @@ -12,8 +12,6 @@ import ch.epfl.bluebrain.nexus.delta.sdk.syntax._ import io.circe.syntax._ import io.circe.{Encoder, Json} -import java.time.Instant - /** * An enumeration of possible Route responses */ @@ -28,7 +26,6 @@ object Response { status: StatusCode, headers: Seq[HttpHeader], entityTag: Option[String], - lastModified: Option[Instant], value: A ) extends Response[A] { def map[B](f: A => B): Complete[B] = copy(value = f(value)) @@ -40,7 +37,7 @@ object Response { * A constructor helper for when [[HttpResponseFields]] is present */ def apply[A: HttpResponseFields](value: A): Complete[A] = - Complete(value.status, value.headers, value.entityTag, value.lastModified, value) + Complete(value.status, value.headers, value.entityTag, value) } /** diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToJsonLd.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToJsonLd.scala index 97f2cc3b17..209333153d 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToJsonLd.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToJsonLd.scala @@ -68,12 +68,12 @@ object ResponseToJsonLd extends FileBytesInstances { encoding: HttpEncoding ): Route = { val ioRoute = ioFinal.flatMap { - case RouteOutcome.RouteRejected(rej) => IO.pure(reject(rej)) - case RouteOutcome.RouteFailed(Complete(status, headers, _, _, value)) => + case RouteOutcome.RouteRejected(rej) => IO.pure(reject(rej)) + case RouteOutcome.RouteFailed(Complete(status, headers, _, value)) => handle(value).map(complete(status, headers, _)) - case RouteOutcome.RouteCompleted(Complete(status, headers, entityTag, lastModified, value)) => + case RouteOutcome.RouteCompleted(Complete(status, headers, entityTag, value)) => handle(value).map { r => - conditionalCache(entityTag, lastModified, mediaType, jsonldFormat, encoding) { + conditionalCache(entityTag, mediaType, jsonldFormat, encoding) { complete(status, headers, r) } } @@ -154,12 +154,7 @@ object ResponseToJsonLd extends FileBytesInstances { val contentDisposition = RawHeader("Content-Disposition", s"""attachment; filename="$encodedFilename"""") requestEncoding { encoding => - conditionalCache( - metadata.entityTag, - metadata.lastModified, - metadata.contentType.mediaType, - encoding - ) { + conditionalCache(metadata.entityTag, metadata.contentType.mediaType, encoding) { respondWithHeaders(contentDisposition, metadata.headers: _*) { complete(statusOverride.getOrElse(OK), HttpEntity(metadata.contentType, content)) } @@ -225,7 +220,7 @@ sealed trait ValueInstances extends LowPriorityValueInstances { )(implicit cr: RemoteContextResolution, jo: JsonKeyOrdering): ResponseToJsonLd = ResponseToJsonLd(io.map[RouteOutcome[E]] { case Left(e) => RouteOutcome.RouteFailed(Complete(e).map[JsonLdValue](JsonLdValue(_))) - case Right(value) => RouteOutcome.RouteCompleted(Complete(OK, Seq.empty, None, None, value)) + case Right(value) => RouteOutcome.RouteCompleted(Complete(OK, Seq.empty, None, value)) }) implicit def rejectValue[E: JsonLdEncoder]( @@ -248,5 +243,5 @@ sealed trait LowPriorityValueInstances { implicit def valueWithoutHttpResponseFields[A: JsonLdEncoder]( value: A )(implicit cr: RemoteContextResolution, jo: JsonKeyOrdering): ResponseToJsonLd = - ResponseToJsonLd(IO.pure[UseRight[A]](Right(Complete(OK, Seq.empty, None, None, value)))) + ResponseToJsonLd(IO.pure[UseRight[A]](Right(Complete(OK, Seq.empty, None, value)))) } diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToJsonLdDiscardingEntity.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToJsonLdDiscardingEntity.scala index 5cfd5df827..3ae8c1aab9 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToJsonLdDiscardingEntity.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToJsonLdDiscardingEntity.scala @@ -29,9 +29,9 @@ object ResponseToJsonLdDiscardingEntity extends DiscardValueInstances { new ResponseToJsonLdDiscardingEntity { private def fallbackAsPlainJson = - onSuccess(io.unsafeToFuture()) { case Complete(status, headers, entityTag, lastModified, value) => + onSuccess(io.unsafeToFuture()) { case Complete(status, headers, entityTag, value) => requestEncoding { encoding => - conditionalCache(entityTag, lastModified, MediaTypes.`application/json`, encoding) { + conditionalCache(entityTag, MediaTypes.`application/json`, encoding) { complete(status, headers, value.asJson) } } @@ -53,7 +53,7 @@ sealed trait DiscardValueInstances extends DiscardLowPriorityValueInstances { implicit def ioValue[A: JsonLdEncoder: Encoder]( io: IO[A] )(implicit cr: RemoteContextResolution, jo: JsonKeyOrdering): ResponseToJsonLdDiscardingEntity = - ResponseToJsonLdDiscardingEntity(io.map(Complete(OK, Seq.empty, None, None, _))) + ResponseToJsonLdDiscardingEntity(io.map(Complete(OK, Seq.empty, None, _))) implicit def valueWithHttpResponseFields[A: JsonLdEncoder: HttpResponseFields: Encoder]( value: A @@ -66,6 +66,6 @@ sealed trait DiscardLowPriorityValueInstances { implicit def valueWithoutHttpResponseFields[A: JsonLdEncoder: Encoder]( value: A )(implicit cr: RemoteContextResolution, jo: JsonKeyOrdering): ResponseToJsonLdDiscardingEntity = - ResponseToJsonLdDiscardingEntity(IO.pure(Complete(OK, Seq.empty, None, None, value))) + ResponseToJsonLdDiscardingEntity(IO.pure(Complete(OK, Seq.empty, None, value))) } diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToMarshaller.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToMarshaller.scala index 425f1286ea..4baf3e2c2e 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToMarshaller.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToMarshaller.scala @@ -45,7 +45,7 @@ object ResponseToMarshaller extends RdfMarshalling { implicit def ioEntityMarshaller[A: ToEntityMarshaller]( io: IO[A] )(implicit cr: RemoteContextResolution, jo: JsonKeyOrdering): ResponseToMarshaller = - ResponseToMarshaller(io.map[UseRight[A]](v => Right(Complete(OK, Seq.empty, None, None, v)))) + ResponseToMarshaller(io.map[UseRight[A]](v => Right(Complete(OK, Seq.empty, None, v)))) implicit def ioEntityMarshaller[E: JsonLdEncoder: HttpResponseFields, A: ToEntityMarshaller]( io: IO[Either[E, A]] @@ -53,7 +53,7 @@ object ResponseToMarshaller extends RdfMarshalling { val ioComplete = io.map { _.bimap( e => Complete(e), - a => Complete(OK, Seq.empty, None, None, a) + a => Complete(OK, Seq.empty, None, a) ) } ResponseToMarshaller(ioComplete) @@ -63,7 +63,7 @@ object ResponseToMarshaller extends RdfMarshalling { io: IO[Either[Response[E], A]] )(implicit cr: RemoteContextResolution, jo: JsonKeyOrdering): ResponseToMarshaller = { val ioComplete = io.map { - _.map(a => Complete(OK, Seq.empty, None, None, a)) + _.map(a => Complete(OK, Seq.empty, None, a)) } ResponseToMarshaller(ioComplete) } diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToOriginalSource.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToOriginalSource.scala index 4d55990144..5687e6c4d3 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToOriginalSource.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToOriginalSource.scala @@ -44,7 +44,7 @@ object ResponseToOriginalSource extends RdfMarshalling { case Right(v: Complete[OriginalSource]) => IO.pure { requestEncoding { encoding => - conditionalCache(v.entityTag, v.lastModified, MediaTypes.`application/json`, encoding) { + conditionalCache(v.entityTag, MediaTypes.`application/json`, encoding) { complete(v.status, v.headers, v.value) } } diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/marshalling/HttpResponseFields.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/marshalling/HttpResponseFields.scala index e255a495ee..3b4348a632 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/marshalling/HttpResponseFields.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/marshalling/HttpResponseFields.scala @@ -2,8 +2,6 @@ package ch.epfl.bluebrain.nexus.delta.sdk.marshalling import akka.http.scaladsl.model.{HttpHeader, StatusCode, StatusCodes} -import java.time.Instant - /** * Typeclass definition for ''A''s from which the HttpHeaders and StatusCode can be ontained. * @@ -29,8 +27,6 @@ trait HttpResponseFields[A] { def headersFrom(value: A): Seq[HttpHeader] def entityTag(value: A): Option[String] - - def lastModified(value: A): Option[Instant] } // $COVERAGE-OFF$ @@ -46,10 +42,9 @@ object HttpResponseFields { */ def apply[A](f: A => StatusCode): HttpResponseFields[A] = new HttpResponseFields[A] { - override def statusFrom(value: A): StatusCode = f(value) - override def headersFrom(value: A): Seq[HttpHeader] = Seq.empty - override def entityTag(value: A): Option[String] = None - override def lastModified(value: A): Option[Instant] = None + override def statusFrom(value: A): StatusCode = f(value) + override def headersFrom(value: A): Seq[HttpHeader] = Seq.empty + override def entityTag(value: A): Option[String] = None } /** @@ -62,18 +57,16 @@ object HttpResponseFields { */ def fromStatusAndHeaders[A](f: A => (StatusCode, Seq[HttpHeader])): HttpResponseFields[A] = new HttpResponseFields[A] { - override def statusFrom(value: A): StatusCode = f(value)._1 - override def headersFrom(value: A): Seq[HttpHeader] = f(value)._2 - override def entityTag(value: A): Option[String] = None - override def lastModified(value: A): Option[Instant] = None + override def statusFrom(value: A): StatusCode = f(value)._1 + override def headersFrom(value: A): Seq[HttpHeader] = f(value)._2 + override def entityTag(value: A): Option[String] = None } - def fromTagAndLastModified[A](f: A => (String, Instant)): HttpResponseFields[A] = + def fromTag[A](f: A => String): HttpResponseFields[A] = new HttpResponseFields[A] { - override def statusFrom(value: A): StatusCode = StatusCodes.OK - override def headersFrom(value: A): Seq[HttpHeader] = Seq.empty - override def entityTag(value: A): Option[String] = Some(f(value)._1) - override def lastModified(value: A): Option[Instant] = Some(f(value)._2) + override def statusFrom(value: A): StatusCode = StatusCodes.OK + override def headersFrom(value: A): Seq[HttpHeader] = Seq.empty + override def entityTag(value: A): Option[String] = Some(f(value)) } def defaultOk[A]: HttpResponseFields[A] = HttpResponseFields { _ => StatusCodes.OK } diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/marshalling/OriginalSource.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/marshalling/OriginalSource.scala index c78de8ec53..263fa0789f 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/marshalling/OriginalSource.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/marshalling/OriginalSource.scala @@ -8,8 +8,6 @@ import ch.epfl.bluebrain.nexus.delta.sdk.syntax._ import io.circe.syntax.EncoderOps import io.circe.{Encoder, Json} -import java.time.Instant - /** * Defines an original source (what has been provided by clients during the api call) * @@ -69,14 +67,12 @@ object OriginalSource { implicit val originalSourceHttpResponseFields: HttpResponseFields[OriginalSource] = { val resourceFHttpResponseField = ResourceF.resourceFHttpResponseFields[Unit] new HttpResponseFields[OriginalSource] { - override def statusFrom(value: OriginalSource): StatusCode = + override def statusFrom(value: OriginalSource): StatusCode = resourceFHttpResponseField.statusFrom(value.resourceF) - override def headersFrom(value: OriginalSource): Seq[HttpHeader] = + override def headersFrom(value: OriginalSource): Seq[HttpHeader] = resourceFHttpResponseField.headersFrom(value.resourceF) - override def entityTag(value: OriginalSource): Option[String] = + override def entityTag(value: OriginalSource): Option[String] = resourceFHttpResponseField.entityTag(value.resourceF) - override def lastModified(value: OriginalSource): Option[Instant] = - resourceFHttpResponseField.lastModified(value.resourceF) } } diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/model/ResourceF.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/model/ResourceF.scala index 6461948476..0773bd98fa 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/model/ResourceF.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/model/ResourceF.scala @@ -258,6 +258,6 @@ object ResourceF { def etagValue[A](value: ResourceF[A]) = s"${value.uris.relativeAccessUri}_${value.rev}" implicit def resourceFHttpResponseFields[A]: HttpResponseFields[ResourceF[A]] = - HttpResponseFields.fromTagAndLastModified { value => (etagValue(value), value.updatedAt) } + HttpResponseFields.fromTag { value => etagValue(value) } } diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/syntax/HttpResponseFieldsSyntax.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/syntax/HttpResponseFieldsSyntax.scala index 2bc89bf587..8fb1da14ef 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/syntax/HttpResponseFieldsSyntax.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/syntax/HttpResponseFieldsSyntax.scala @@ -3,8 +3,6 @@ package ch.epfl.bluebrain.nexus.delta.sdk.syntax import akka.http.scaladsl.model.{HttpHeader, StatusCode} import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields -import java.time.Instant - trait HttpResponseFieldsSyntax { implicit final def httpResponseFieldsSyntax[A](value: A): HttpResponseFieldsOps[A] = new HttpResponseFieldsOps(value) } @@ -32,10 +30,4 @@ final class HttpResponseFieldsOps[A](private val value: A) extends AnyVal { def entityTag(implicit responseFields: HttpResponseFields[A]): Option[String] = responseFields.entityTag(value) - /** - * @return - * the entity for the last-modified support in conditional requests - */ - def lastModified(implicit responseFields: HttpResponseFields[A]): Option[Instant] = - responseFields.lastModified(value) } diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/SimpleResource.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/SimpleResource.scala index 6fc53f5b87..ba949a008c 100644 --- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/SimpleResource.scala +++ b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/SimpleResource.scala @@ -44,8 +44,6 @@ object SimpleResource extends CirceLiteral { override def headersFrom(value: SimpleResource): Seq[HttpHeader] = Seq(new RawHeader("Test", "Value")) override def entityTag(value: SimpleResource): Option[String] = Some(value.id.toString) - - override def lastModified(value: SimpleResource): Option[Instant] = Some(value.createdAt) } val rawHeader: RawHeader = new RawHeader("Test", "Value") diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectivesSpec.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectivesSpec.scala index 82b35cad97..e323329a1a 100644 --- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectivesSpec.scala +++ b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/DeltaDirectivesSpec.scala @@ -1,6 +1,5 @@ package ch.epfl.bluebrain.nexus.delta.sdk.directives -import akka.http.javadsl.model.headers.LastModified import akka.http.scaladsl.model.HttpMethods.GET import akka.http.scaladsl.model.MediaRange._ import akka.http.scaladsl.model.MediaRanges.{`*/*`, `application/*`, `audio/*`, `text/*`} @@ -291,21 +290,18 @@ class DeltaDirectivesSpec "return the etag and last modified headers" in { Get("/io") ~> Accept(`application/ld+json`) ~> route ~> check { response.header[ETag].value shouldEqual ETag(expectedCompactedJsonLdTag) - response.header[LastModified].value.date().clicks() shouldEqual resource.createdAt.toEpochMilli } } "return the etag and last modified headers accepting gzip" in { Get("/io") ~> Accept(`application/ld+json`) ~> `Accept-Encoding`(gzip) ~> route ~> check { response.header[ETag].value shouldEqual ETag(expectedCompactedJsonLdGzippedTag) - response.header[LastModified].value.date().clicks() shouldEqual resource.createdAt.toEpochMilli } } "return another etag and last modified headers for the expanded format" in { Get("/io?format=expanded") ~> Accept(`application/ld+json`) ~> route ~> check { response.header[ETag].value shouldEqual ETag(expectedExpandedJsonLdTag) - response.header[LastModified].value.date().clicks() shouldEqual resource.createdAt.toEpochMilli } } @@ -332,24 +328,6 @@ class DeltaDirectivesSpec } } - def lastModified(instant: Instant) = DateTime(instant.toEpochMilli) - - "return the resource if if it was modified since" in { - // The provided etag was computed without gzip compression - Get("/io") ~> Accept(`application/ld+json`) ~> - `If-Modified-Since`(lastModified(resource.createdAt.minusSeconds(1L))) ~> route ~> check { - response.status shouldEqual Accepted - } - } - - "return not modified if if it was not modified since" in { - // The provided etag was computed without gzip compression - Get("/io") ~> Accept(`application/ld+json`) ~> - `If-Modified-Since`(lastModified(resource.createdAt.plusSeconds(1L))) ~> route ~> check { - response.status shouldEqual NotModified - } - } - "return bad request rejection in compacted JSON-LD format" in { val badRequestCompacted = badRequestRejection.toCompactedJsonLd.accepted diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToJsonLdSpec.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToJsonLdSpec.scala index 51f46f3548..021ceadb9f 100644 --- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToJsonLdSpec.scala +++ b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/directives/ResponseToJsonLdSpec.scala @@ -23,8 +23,6 @@ import ch.epfl.bluebrain.nexus.delta.sdk.{AkkaSource, SimpleRejection, SimpleRes import ch.epfl.bluebrain.nexus.delta.sourcing.model.ProjectRef import ch.epfl.bluebrain.nexus.testkit.scalatest.ce.CatsEffectSpec -import java.time.Instant - class ResponseToJsonLdSpec extends CatsEffectSpec with RouteHelpers with JsonSyntax with RouteConcatenation { implicit val rcr: RemoteContextResolution = @@ -54,16 +52,10 @@ class ResponseToJsonLdSpec extends CatsEffectSpec with RouteHelpers with JsonSyn contents: IO[Either[E, AkkaSource]], cacheable: Boolean ) = { + val etag = Option.when(cacheable)("test") IO.pure( Right( - FileResponse( - "file.name", - contentType, - Option.when(cacheable)("test"), - Option.when(cacheable)(Instant.EPOCH), - Some(1024L), - contents - ) + FileResponse("file.name", contentType, etag, Some(1024L), contents) ) ) } diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/utils/RouteHelpers.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/utils/RouteHelpers.scala index 3cd259825a..5ec1d5f9b4 100644 --- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/utils/RouteHelpers.scala +++ b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/utils/RouteHelpers.scala @@ -1,10 +1,9 @@ package ch.epfl.bluebrain.nexus.delta.sdk.utils -import akka.http.javadsl.model.headers.LastModified import akka.http.scaladsl.model.HttpEntity.ChunkStreamPart import akka.http.scaladsl.model.MediaTypes.`application/json` -import akka.http.scaladsl.model.headers.ETag import akka.http.scaladsl.model._ +import akka.http.scaladsl.model.headers.ETag import akka.http.scaladsl.testkit.{RouteTestTimeout, ScalatestRouteTest} import akka.stream.Materializer import akka.stream.scaladsl.Source @@ -97,15 +96,11 @@ final class HttpResponseOps(private val http: HttpResponse) extends Consumer { asJsonObject(materializer)("@type") shouldEqual Some(errorType.asJson) } - def expectConditionalCacheHeaders(implicit position: Position): Assertion = { + def expectConditionalCacheHeaders(implicit position: Position): Assertion = http.header[ETag] shouldBe defined - http.header[LastModified] shouldBe defined - } - def expectNoConditionalCacheHeaders(implicit position: Position): Assertion = { + def expectNoConditionalCacheHeaders(implicit position: Position): Assertion = http.header[ETag] shouldBe empty - http.header[LastModified] shouldBe empty - } } diff --git a/docs/src/main/paradox/docs/delta/api/conditional-requests.md b/docs/src/main/paradox/docs/delta/api/conditional-requests.md index d3e5aafa9d..5ea432c6db 100644 --- a/docs/src/main/paradox/docs/delta/api/conditional-requests.md +++ b/docs/src/main/paradox/docs/delta/api/conditional-requests.md @@ -6,9 +6,9 @@ for the different operations: * Fetch original payloads for the different types of resources * Fetching the file contents -The response for those operations are augmented with respective `ETag` and `Last-Modified` response headers. +The response for those operations are augmented with a `ETag` response header. -The client can then use those values to set up caches and save bandwidth by using the +The client can then use this value to set up caches and save bandwidth by using the @link[conditional headers](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-p4-conditional-26#section-3) as Delta can immediately answer with a `304 Not Modified` and not resend the full response. diff --git a/tests/docker/config/delta-postgres.conf b/tests/docker/config/delta-postgres.conf index 9e5db3bf4f..f0beedff51 100644 --- a/tests/docker/config/delta-postgres.conf +++ b/tests/docker/config/delta-postgres.conf @@ -47,15 +47,23 @@ app { # at the same time before pushing the update. max-elements = 100 # the maximum batching duration. - max-interval = 200 millis + max-interval = 100 millis } query { batch-size = 30 - refresh-strategy = 200 millis + refresh-strategy = 100 millis } inactive-interval = 5 seconds } + projections { + batch { + max-elements = 30 + # the maximum batching duration. + max-interval = 1 second + } + } + defaults { database { access { @@ -64,7 +72,7 @@ app { } query { - refresh-strategy = 500 millis + refresh-strategy = 250 millis } } diff --git a/tests/docker/config/search/construct-query.sparql b/tests/docker/config/search/construct-query.sparql index c1c3b7ecf7..5330085a2d 100644 --- a/tests/docker/config/search/construct-query.sparql +++ b/tests/docker/config/search/construct-query.sparql @@ -511,19 +511,21 @@ CONSTRUCT { # Subject OPTIONAL { - ?id nsg:subject / nsg:age ?age . - OPTIONAL { ?age schema:value ?subjectAgeValue . } . - OPTIONAL { ?age schema:minValue ?subjectAgeMinValue . } . - OPTIONAL { ?age schema:maxValue ?subjectAgeMaxValue . } . - ?age schema:unitCode ?subjectAgeUnit . - ?age nsg:period ?subjectAgePeriod . - BIND( - IF( - BOUND(?subjectAgeValue), - CONCAT(STR(?subjectAgeValue), " ", STR(?subjectAgeUnit), " ", STR(?subjectAgePeriod)), - CONCAT(STR(?subjectAgeMinValue), " to ", STR(?subjectAgeMaxValue), " ", STR(?subjectAgeUnit), " ", STR(?subjectAgePeriod)) - ) as ?subjectAgeLabel ) . - } . + GRAPH ?id { + BIND(BNODE(CONCAT(STR(?id), '/age')) as ?age ) . + OPTIONAL { ?id nsg:subject / nsg:age / schema:value ?subjectAgeValue . } . + OPTIONAL { ?id nsg:subject / nsg:age / schema:minValue ?subjectAgeMinValue . } . + OPTIONAL { ?id nsg:subject / nsg:age / schema:maxValue ?subjectAgeMaxValue . } . + ?id nsg:subject / nsg:age / schema:unitCode ?subjectAgeUnit . + ?id nsg:subject / nsg:age / nsg:period ?subjectAgePeriod . + BIND( + IF( + BOUND(?subjectAgeValue), + CONCAT(STR(?subjectAgeValue), " ", STR(?subjectAgeUnit), " ", STR(?subjectAgePeriod)), + CONCAT(STR(?subjectAgeMinValue), " to ", STR(?subjectAgeMaxValue), " ", STR(?subjectAgeUnit), " ", STR(?subjectAgePeriod)) + ) as ?subjectAgeLabel ) . + } + } . OPTIONAL { ?id nsg:subject / schema:weight ?weight . diff --git a/tests/src/test/scala/ch/epfl/bluebrain/nexus/tests/CacheAssertions.scala b/tests/src/test/scala/ch/epfl/bluebrain/nexus/tests/CacheAssertions.scala index 93c8194b03..c969a8713a 100644 --- a/tests/src/test/scala/ch/epfl/bluebrain/nexus/tests/CacheAssertions.scala +++ b/tests/src/test/scala/ch/epfl/bluebrain/nexus/tests/CacheAssertions.scala @@ -1,6 +1,5 @@ package ch.epfl.bluebrain.nexus.tests -import akka.http.javadsl.model.headers.LastModified import akka.http.scaladsl.model.HttpResponse import akka.http.scaladsl.model.headers.ETag import org.scalactic.source.Position @@ -9,14 +8,10 @@ import org.scalatest.matchers.should.Matchers object CacheAssertions extends Matchers { - def expectConditionalCacheHeaders(response: HttpResponse)(implicit position: Position): Assertion = { + def expectConditionalCacheHeaders(response: HttpResponse)(implicit position: Position): Assertion = response.header[ETag] shouldBe defined - response.header[LastModified] shouldBe defined - } - def expectNoConditionalCacheHeaders(response: HttpResponse)(implicit position: Position): Assertion = { + def expectNoConditionalCacheHeaders(response: HttpResponse)(implicit position: Position): Assertion = response.header[ETag] shouldBe empty - response.header[LastModified] shouldBe empty - } }