From b0f494f881f6ac5f09e9a09e583f3e2958886c68 Mon Sep 17 00:00:00 2001 From: Simon Dumas Date: Mon, 4 Mar 2024 18:20:53 +0100 Subject: [PATCH 1/2] Remove wrapped rejections for Json-LD errors --- .../nexus/delta/routes/ResourcesRoutes.scala | 27 ++---- .../bluebrain/nexus/delta/kernel/Mapper.scala | 21 ----- .../delta/plugins/archive/Archives.scala | 9 +- .../archive/model/ArchiveRejection.scala | 52 +---------- .../archive/ArchivesDecodingSpec.scala | 6 +- .../plugins/blazegraph/BlazegraphViews.scala | 4 +- .../model/BlazegraphViewRejection.scala | 80 ++++------------ .../routes/BlazegraphViewsRoutes.scala | 93 +++++++++---------- .../plugins/blazegraph/routes/package.scala | 12 --- .../BlazegraphViewDecodingSpec.scala | 9 +- .../model/CompositeViewRejection.scala | 77 +++------------ .../routes/CompositeViewsRoutes.scala | 6 +- .../compositeviews/routes/package.scala | 11 --- ...mpositeViewFieldsJsonLdSourceDecoder.scala | 6 +- .../CompositeViewDecodingSpec.scala | 4 +- ...ElasticSearchViewJsonLdSourceDecoder.scala | 6 +- .../model/ElasticSearchViewRejection.scala | 78 +++------------- .../routes/ElasticSearchViewsRoutes.scala | 90 +++++++++--------- .../elasticsearch/routes/package.scala | 12 --- .../ElasticSearchViewDecodingSpec.scala | 4 +- .../storage/files/batch/BatchCopy.scala | 2 - .../storage/files/model/FileRejection.scala | 5 - .../storage/storages/FetchStorage.scala | 6 +- .../plugins/storage/storages/Storages.scala | 4 +- .../storages/model/StorageRejection.scala | 43 +-------- .../storage/storages/StoragesSpec.scala | 4 +- .../storages/model/StorageFieldsSpec.scala | 2 +- .../delta/sdk/jsonld/JsonLdRejection.scala | 29 +++++- .../sdk/jsonld/JsonLdSourceProcessor.scala | 46 ++++----- .../sdk/marshalling/RdfExceptionHandler.scala | 2 + .../delta/sdk/resolvers/ResolversImpl.scala | 4 +- .../resolvers/model/ResolverRejection.scala | 42 --------- .../nexus/delta/sdk/resources/Resources.scala | 11 +-- .../delta/sdk/resources/ResourcesImpl.scala | 4 +- .../delta/sdk/resources/ResourcesTrial.scala | 9 +- .../model/ResourceGenerationResult.scala | 14 ++- .../resources/model/ResourceRejection.scala | 33 ------- .../nexus/delta/sdk/schemas/SchemasImpl.scala | 4 +- .../delta/sdk/schemas/ValidateSchema.scala | 2 +- .../sdk/schemas/model/SchemaRejection.scala | 33 ------- .../test/resources/directives/blank-id.json | 5 - .../sdk/directives/ResponseToJsonLdSpec.scala | 17 ++-- .../sdk/resolvers/ResolversImplSpec.scala | 7 +- .../sdk/resources/ResourcesImplSpec.scala | 6 +- .../delta/sdk/schemas/SchemasImplSuite.scala | 3 +- 45 files changed, 277 insertions(+), 667 deletions(-) delete mode 100644 delta/kernel/src/main/scala/ch/epfl/bluebrain/nexus/delta/kernel/Mapper.scala delete mode 100644 delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/routes/package.scala delete mode 100644 delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/routes/package.scala delete mode 100644 delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/package.scala delete mode 100644 delta/sdk/src/test/resources/directives/blank-id.json diff --git a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesRoutes.scala b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesRoutes.scala index bdc045e89e..cd77870dff 100644 --- a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesRoutes.scala +++ b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesRoutes.scala @@ -22,7 +22,7 @@ import ch.epfl.bluebrain.nexus.delta.sdk.model.routes.Tag import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, ResourceF} import ch.epfl.bluebrain.nexus.delta.sdk.permissions.Permissions.resources.{read => Read, write => Write} import ch.epfl.bluebrain.nexus.delta.sdk.resources.NexusSource.DecodingOption -import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection.{InvalidJsonLdFormat, InvalidSchemaRejection, NoSchemaProvided, ResourceNotFound} +import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection._ import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.{Resource, ResourceRejection} import ch.epfl.bluebrain.nexus.delta.sdk.resources.{NexusSource, Resources} import io.circe.{Json, Printer} @@ -93,7 +93,6 @@ final class ResourcesRoutes( .flatTap(index(project, _, mode)) .map(_.void) .attemptNarrow[ResourceRejection] - .rejectWhen(wrongJsonOrNotFound) ) } }, @@ -114,7 +113,6 @@ final class ResourcesRoutes( .flatTap(index(project, _, mode)) .map(_.void) .attemptNarrow[ResourceRejection] - .rejectWhen(wrongJsonOrNotFound) ) case (Some(rev), source, tag) => // Update a resource @@ -124,7 +122,7 @@ final class ResourcesRoutes( .flatTap(index(project, _, mode)) .map(_.void) .attemptNarrow[ResourceRejection] - .rejectWhen(wrongJsonOrNotFound) + .rejectOn[ResourceNotFound] ) } } @@ -138,7 +136,7 @@ final class ResourcesRoutes( .flatTap(index(project, _, mode)) .map(_.void) .attemptNarrow[ResourceRejection] - .rejectWhen(wrongJsonOrNotFound) + .rejectOn[ResourceNotFound] ) } }, @@ -152,7 +150,7 @@ final class ResourcesRoutes( resources .fetch(resourceRef, project, schemaOpt) .attemptNarrow[ResourceRejection] - .rejectWhen(wrongJsonOrNotFound) + .rejectOn[ResourceNotFound] ) } ) @@ -168,7 +166,7 @@ final class ResourcesRoutes( .flatTap(index(project, _, mode)) .map(_.void) .attemptNarrow[ResourceRejection] - .rejectWhen(wrongJsonOrNotFound) + .rejectOn[ResourceNotFound] ) } }, @@ -182,7 +180,7 @@ final class ResourcesRoutes( .flatTap(index(project, _, mode)) } .attemptNarrow[ResourceRejection] - .rejectWhen(wrongJsonOrNotFound) + .rejectOn[ResourceNotFound] ) } }, @@ -195,7 +193,7 @@ final class ResourcesRoutes( .flatTap(index(project, _, mode)) .map(_.void) .attemptNarrow[ResourceRejection] - .rejectWhen(wrongJsonOrNotFound) + .rejectOn[ResourceNotFound] ) } }, @@ -218,7 +216,7 @@ final class ResourcesRoutes( .fetch(resourceRef, project, schemaOpt) .map(_.value.source) .attemptNarrow[ResourceRejection] - .rejectWhen(wrongJsonOrNotFound) + .rejectOn[ResourceNotFound] ) } } @@ -247,7 +245,7 @@ final class ResourcesRoutes( .fetch(resourceRef, project, schemaOpt) .map(_.value.tags) .attemptNarrow[ResourceRejection] - .rejectWhen(wrongJsonOrNotFound) + .rejectOn[ResourceNotFound] ) }, // Tag a resource @@ -261,7 +259,7 @@ final class ResourcesRoutes( .flatTap(index(project, _, mode)) .map(_.void) .attemptNarrow[ResourceRejection] - .rejectWhen(wrongJsonOrNotFound) + .rejectOn[ResourceNotFound] ) } } @@ -291,11 +289,6 @@ final class ResourcesRoutes( } } } - - private val wrongJsonOrNotFound: PartialFunction[ResourceRejection, Boolean] = { - case _: ResourceNotFound | _: InvalidSchemaRejection | _: InvalidJsonLdFormat => true - } - } object ResourcesRoutes { diff --git a/delta/kernel/src/main/scala/ch/epfl/bluebrain/nexus/delta/kernel/Mapper.scala b/delta/kernel/src/main/scala/ch/epfl/bluebrain/nexus/delta/kernel/Mapper.scala deleted file mode 100644 index ed216e35bd..0000000000 --- a/delta/kernel/src/main/scala/ch/epfl/bluebrain/nexus/delta/kernel/Mapper.scala +++ /dev/null @@ -1,21 +0,0 @@ -package ch.epfl.bluebrain.nexus.delta.kernel - -/** - * Transforms a value of type A in a value of type B - */ -trait Mapper[-A, B] { - - /** - * Transforms from A to B - * @param value - * the value to transform - */ - def to(value: A): B - -} - -object Mapper { - implicit def mapperIdentity[A]: Mapper[A, A] = (value: A) => value - - final def apply[A, B](f: A => B): Mapper[A, B] = f(_) -} diff --git a/delta/plugins/archive/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/archive/Archives.scala b/delta/plugins/archive/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/archive/Archives.scala index a212edbf8b..cabbd90c1a 100644 --- a/delta/plugins/archive/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/archive/Archives.scala +++ b/delta/plugins/archive/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/archive/Archives.scala @@ -43,7 +43,7 @@ class Archives( log: ArchiveLog, fetchContext: FetchContext, archiveDownload: ArchiveDownload, - sourceDecoder: JsonLdSourceDecoder[ArchiveRejection, ArchiveValue], + sourceDecoder: JsonLdSourceDecoder[ArchiveValue], config: EphemeralLogConfig )(implicit rcr: RemoteContextResolution) { @@ -204,11 +204,8 @@ object Archives { onUniqueViolation = (id: Iri, c: CreateArchive) => ResourceAlreadyExists(id, c.project) ) - private[archive] def sourceDecoder(implicit - api: JsonLdApi, - uuidF: UUIDF - ): JsonLdSourceDecoder[ArchiveRejection, ArchiveValue] = - new JsonLdSourceDecoder[ArchiveRejection, ArchiveValue](contexts.archives, uuidF) + private[archive] def sourceDecoder(implicit api: JsonLdApi, uuidF: UUIDF): JsonLdSourceDecoder[ArchiveValue] = + new JsonLdSourceDecoder[ArchiveValue](contexts.archives, uuidF) private[archive] def evaluate(clock: Clock[IO])( command: CreateArchive diff --git a/delta/plugins/archive/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/archive/model/ArchiveRejection.scala b/delta/plugins/archive/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/archive/model/ArchiveRejection.scala index 9bddf118fd..c35a238a66 100644 --- a/delta/plugins/archive/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/archive/model/ArchiveRejection.scala +++ b/delta/plugins/archive/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/archive/model/ArchiveRejection.scala @@ -1,19 +1,16 @@ package ch.epfl.bluebrain.nexus.delta.plugins.archive.model import akka.http.scaladsl.model.StatusCodes -import ch.epfl.bluebrain.nexus.delta.kernel.Mapper import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClassUtils import ch.epfl.bluebrain.nexus.delta.plugins.archive.FileSelf import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.FileRejection import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.AbsolutePath import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri +import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.ContextValue import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords -import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.JsonLdDecoderError import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder -import ch.epfl.bluebrain.nexus.delta.rdf.{RdfError, Vocabulary} -import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields import ch.epfl.bluebrain.nexus.delta.sdk.syntax._ import ch.epfl.bluebrain.nexus.delta.sourcing.model.{ProjectRef, ResourceRef} @@ -93,41 +90,6 @@ object ArchiveRejection { final case class InvalidArchiveId(id: String) extends ArchiveRejection(s"Archive identifier '$id' cannot be expanded to an Iri.") - /** - * Rejection returned when attempting to create an Archive while providing an id that is blank. - */ - final case object BlankArchiveId extends ArchiveRejection(s"Archive identifier cannot be blank.") - - /** - * Rejection returned when attempting to create an Archive where the passed id does not match the id on the source - * json document. - * - * @param id - * the archive identifier - * @param sourceId - * the archive identifier in the source json document - */ - final case class UnexpectedArchiveId(id: Iri, sourceId: Iri) - extends ArchiveRejection( - s"The provided Archive '$id' does not match the id '$sourceId' in the source document." - ) - - /** - * Rejection returned when attempting to decode an expanded JsonLD as an Archive. - * - * @param error - * the decoder error - */ - final case class DecodingFailed(error: JsonLdDecoderError) extends ArchiveRejection(error.getMessage) - - /** - * Rejection returned when converting the source Json document to a JsonLD document. - */ - final case class InvalidJsonLdFormat(id: Option[Iri], rdfError: RdfError) - extends ArchiveRejection( - s"The provided Archive JSON document ${id.fold("")(id => s"with id '$id'")} cannot be interpreted as a JSON-LD document." - ) - /** * Rejection returned when a referenced resource could not be found. * @@ -147,13 +109,6 @@ object ArchiveRejection { */ final case class WrappedFileRejection(rejection: FileRejection) extends ArchiveRejection(rejection.reason) - implicit final val jsonLdRejectionMapper: Mapper[JsonLdRejection, ArchiveRejection] = { - case JsonLdRejection.UnexpectedId(id, sourceId) => UnexpectedArchiveId(id, sourceId) - case JsonLdRejection.InvalidJsonLdFormat(id, rdfError) => InvalidJsonLdFormat(id, rdfError) - case JsonLdRejection.DecodingFailed(error) => DecodingFailed(error) - case JsonLdRejection.BlankId => BlankArchiveId - } - implicit final val archiveRejectionEncoder: Encoder.AsObject[ArchiveRejection] = Encoder.AsObject.instance { r => val tpe = ClassUtils.simpleName(r) @@ -161,7 +116,6 @@ object ArchiveRejection { r match { case InvalidResourceCollection(duplicates, invalids, longIds) => obj.add("duplicates", duplicates.asJson).add("invalids", invalids.asJson).add("longIds", longIds.asJson) - case InvalidJsonLdFormat(_, rdf) => obj.add("rdf", rdf.asJson) case _: ArchiveNotFound => obj.add(keywords.tpe, "ResourceNotFound".asJson) case _ => obj } @@ -176,11 +130,7 @@ object ArchiveRejection { case InvalidResourceCollection(_, _, _) => StatusCodes.BadRequest case ArchiveNotFound(_, _) => StatusCodes.NotFound case InvalidArchiveId(_) => StatusCodes.BadRequest - case UnexpectedArchiveId(_, _) => StatusCodes.BadRequest - case DecodingFailed(_) => StatusCodes.BadRequest - case InvalidJsonLdFormat(_, _) => StatusCodes.BadRequest case ResourceNotFound(_, _) => StatusCodes.NotFound - case BlankArchiveId => StatusCodes.BadRequest case InvalidFileSelf(_) => StatusCodes.BadRequest case WrappedFileRejection(rejection) => rejection.status } diff --git a/delta/plugins/archive/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/archive/ArchivesDecodingSpec.scala b/delta/plugins/archive/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/archive/ArchivesDecodingSpec.scala index ab81b12af0..4f4c39f991 100644 --- a/delta/plugins/archive/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/archive/ArchivesDecodingSpec.scala +++ b/delta/plugins/archive/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/archive/ArchivesDecodingSpec.scala @@ -3,20 +3,20 @@ package ch.epfl.bluebrain.nexus.delta.plugins.archive import cats.data.NonEmptySet import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF import ch.epfl.bluebrain.nexus.delta.plugins.archive.model.ArchiveReference.{FileReference, ResourceReference} -import ch.epfl.bluebrain.nexus.delta.plugins.archive.model.ArchiveRejection.{DecodingFailed, InvalidJsonLdFormat, UnexpectedArchiveId} import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.AbsolutePath import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.nxv import ch.epfl.bluebrain.nexus.delta.rdf.implicits._ import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.{JsonLdApi, JsonLdJavaApi} +import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.{DecodingFailed, InvalidJsonLdFormat, UnexpectedId} import ch.epfl.bluebrain.nexus.delta.sdk.model.ResourceRepresentation.{AnnotatedSourceJson, CompactedJsonLd, Dot, ExpandedJsonLd, NTriples, SourceJson} import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.{ApiMappings, ProjectContext} import ch.epfl.bluebrain.nexus.delta.sourcing.model.ProjectRef import ch.epfl.bluebrain.nexus.delta.sourcing.model.ResourceRef.{Latest, Revision, Tag} import ch.epfl.bluebrain.nexus.delta.sourcing.model.Tag.UserTag import ch.epfl.bluebrain.nexus.testkit.scalatest.ce.CatsEffectSpec +import io.circe.literal._ import java.nio.file.Paths -import io.circe.literal._ class ArchivesDecodingSpec extends CatsEffectSpec with RemoteContextResolutionFixture { @@ -347,7 +347,7 @@ class ArchivesDecodingSpec extends CatsEffectSpec with RemoteContextResolutionFi } ] }""" - decoder(context, providedId, source).rejectedWith[UnexpectedArchiveId] + decoder(context, providedId, source).rejectedWith[UnexpectedId] } "parsing a source as an ExpandedJsonLd" in { diff --git a/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/BlazegraphViews.scala b/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/BlazegraphViews.scala index 9d0e5a5a97..b28b2bbeda 100644 --- a/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/BlazegraphViews.scala +++ b/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/BlazegraphViews.scala @@ -44,7 +44,7 @@ import java.util.UUID final class BlazegraphViews( log: BlazegraphLog, fetchContext: FetchContext, - sourceDecoder: JsonLdSourceResolvingDecoder[BlazegraphViewRejection, BlazegraphViewValue], + sourceDecoder: JsonLdSourceResolvingDecoder[BlazegraphViewValue], createNamespace: ViewResource => IO[Unit], prefix: String ) { @@ -549,7 +549,7 @@ object BlazegraphViews { BlazegraphDecoderConfiguration.apply .map { implicit config => - new JsonLdSourceResolvingDecoder[BlazegraphViewRejection, BlazegraphViewValue]( + new JsonLdSourceResolvingDecoder[BlazegraphViewValue]( contexts.blazegraph, contextResolution, uuidF diff --git a/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/model/BlazegraphViewRejection.scala b/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/model/BlazegraphViewRejection.scala index bd7ea15732..47b45c73c0 100644 --- a/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/model/BlazegraphViewRejection.scala +++ b/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/model/BlazegraphViewRejection.scala @@ -1,21 +1,18 @@ package ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model import akka.http.scaladsl.model.StatusCodes -import ch.epfl.bluebrain.nexus.delta.kernel.Mapper import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClassUtils import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.BlazegraphErrorParser import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.client.SparqlClientError import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri -import ch.epfl.bluebrain.nexus.delta.rdf.RdfError.ConversionError +import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.ContextValue import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords -import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.JsonLdDecoderError import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder -import ch.epfl.bluebrain.nexus.delta.rdf.{RdfError, Vocabulary} import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection -import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.{BlankId, UnexpectedId} import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields +import ch.epfl.bluebrain.nexus.delta.sdk.syntax._ import ch.epfl.bluebrain.nexus.delta.sdk.model.IdSegmentRef import ch.epfl.bluebrain.nexus.delta.sdk.permissions.model.Permission import ch.epfl.bluebrain.nexus.delta.sdk.views.ViewRef @@ -118,34 +115,10 @@ object BlazegraphViewRejection { ) /** - * Rejection when attempting to decode an expanded JsonLD as an BlazegraphViewValue. - * - * @param error - * the decoder error - */ - final case class DecodingFailed(error: JsonLdDecoderError) extends BlazegraphViewRejection(error.getMessage) - - /** - * Signals an error converting the source Json document to a JsonLD document. - */ - final case class InvalidJsonLdFormat(id: Option[Iri], rdfError: RdfError) - extends BlazegraphViewRejection( - s"The provided Blazegraph view JSON document ${id.fold("")(id => s"with id '$id' ")}cannot be interpreted as a JSON-LD document." - ) - - /** - * Rejection returned when attempting to create an BlazegraphView where the passed id does not match the id on the - * source json document. - * - * @param id - * the view identifier - * @param sourceId - * the view identifier in the source json document + * Rejection returned when attempting to decode an expanded JsonLD as an BlazegraphViewValue. */ - final case class UnexpectedBlazegraphViewId(id: Iri, sourceId: Iri) - extends BlazegraphViewRejection( - s"The provided Blazegraph view '$id' does not match the id '$sourceId' in the source document." - ) + // TODO Remove when the rejection workflow gets refactored / when view endpoints get separated + final case class BlazegraphDecodingRejection(error: JsonLdRejection) extends BlazegraphViewRejection(error.reason) /** * Signals a rejection caused by an attempt to create or update a Blazegraph view with a permission that is not @@ -195,12 +168,6 @@ object BlazegraphViewRejection { final case class InvalidBlazegraphViewId(id: String) extends BlazegraphViewRejection(s"Blazegraph view identifier '$id' cannot be expanded to an Iri.") - /** - * Rejection returned when attempting to create a Blazegraph view while providing an id that is blank. - */ - final case object BlankBlazegraphViewId - extends BlazegraphViewRejection(s"Blazegraph view identifier cannot be blank.") - /** * Rejection returned when a resource id cannot be expanded to [[Iri]]. * @@ -226,28 +193,20 @@ object BlazegraphViewRejection { final case class TooManyViewReferences(provided: Int, max: Int) extends BlazegraphViewRejection(s"$provided exceeds the maximum allowed number of view references ($max).") - implicit val jsonLdRejectionMapper: Mapper[JsonLdRejection, BlazegraphViewRejection] = { - case UnexpectedId(id, payloadIri) => UnexpectedBlazegraphViewId(id, payloadIri) - case JsonLdRejection.InvalidJsonLdFormat(id, rdfError) => InvalidJsonLdFormat(id, rdfError) - case JsonLdRejection.DecodingFailed(error) => DecodingFailed(error) - case BlankId => BlankBlazegraphViewId - } - implicit private[plugins] val blazegraphViewRejectionEncoder: Encoder.AsObject[BlazegraphViewRejection] = Encoder.AsObject.instance { r => val tpe = ClassUtils.simpleName(r) val obj = JsonObject(keywords.tpe -> tpe.asJson, "reason" -> r.reason.asJson) r match { - case WrappedBlazegraphClientError(rejection) => + case WrappedBlazegraphClientError(rejection) => obj .add(keywords.tpe, "SparqlClientError".asJson) .add("details", BlazegraphErrorParser.details(rejection).asJson) - case IncorrectRev(provided, expected) => obj.add("provided", provided.asJson).add("expected", expected.asJson) - case InvalidViewReferences(views) => obj.add("views", views.asJson) - case InvalidJsonLdFormat(_, ConversionError(details, _)) => obj.add("details", details.asJson) - case InvalidJsonLdFormat(_, rdf) => obj.add("rdf", rdf.asJson) - case _: ViewNotFound => obj.add(keywords.tpe, "ResourceNotFound".asJson) - case _ => obj + case IncorrectRev(provided, expected) => obj.add("provided", provided.asJson).add("expected", expected.asJson) + case InvalidViewReferences(views) => obj.add("views", views.asJson) + case BlazegraphDecodingRejection(error) => error.asJsonObject + case _: ViewNotFound => obj.add(keywords.tpe, "ResourceNotFound".asJson) + case _ => obj } } @@ -256,13 +215,14 @@ object BlazegraphViewRejection { implicit val blazegraphViewHttpResponseFields: HttpResponseFields[BlazegraphViewRejection] = HttpResponseFields { - case RevisionNotFound(_, _) => StatusCodes.NotFound - case TagNotFound(_) => StatusCodes.NotFound - case ViewNotFound(_, _) => StatusCodes.NotFound - case ResourceAlreadyExists(_, _) => StatusCodes.Conflict - case ViewIsDefaultView => StatusCodes.Forbidden - case IncorrectRev(_, _) => StatusCodes.Conflict - case _: FetchByTagNotSupported => StatusCodes.BadRequest - case _ => StatusCodes.BadRequest + case RevisionNotFound(_, _) => StatusCodes.NotFound + case TagNotFound(_) => StatusCodes.NotFound + case ViewNotFound(_, _) => StatusCodes.NotFound + case ResourceAlreadyExists(_, _) => StatusCodes.Conflict + case ViewIsDefaultView => StatusCodes.Forbidden + case IncorrectRev(_, _) => StatusCodes.Conflict + case BlazegraphDecodingRejection(error) => error.status + case _: FetchByTagNotSupported => StatusCodes.BadRequest + case _ => StatusCodes.BadRequest } } diff --git a/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/routes/BlazegraphViewsRoutes.scala b/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/routes/BlazegraphViewsRoutes.scala index 0a84855aa5..e7fabe9872 100644 --- a/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/routes/BlazegraphViewsRoutes.scala +++ b/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/routes/BlazegraphViewsRoutes.scala @@ -1,7 +1,9 @@ package ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.routes import akka.http.scaladsl.model.StatusCodes.Created +import akka.http.scaladsl.model.{StatusCode, StatusCodes} import akka.http.scaladsl.server.{Directive0, Route} +import cats.effect.IO import cats.implicits._ import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.BlazegraphViewRejection._ import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model._ @@ -20,12 +22,13 @@ import ch.epfl.bluebrain.nexus.delta.sdk.fusion.FusionConfig import ch.epfl.bluebrain.nexus.delta.sdk.identities.Identities import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller import ch.epfl.bluebrain.nexus.delta.sdk.implicits._ +import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.{DecodingFailed, InvalidJsonLdFormat} import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.RdfMarshalling import ch.epfl.bluebrain.nexus.delta.sdk.model.search.SearchResults._ import ch.epfl.bluebrain.nexus.delta.sdk.model.search.{PaginationConfig, SearchResults} import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, IdSegment} import ch.epfl.bluebrain.nexus.delta.sourcing.model.ProjectRef -import io.circe.Json +import io.circe.{Json, Printer} /** * The Blazegraph views routes @@ -57,6 +60,34 @@ class BlazegraphViewsRoutes( with RdfMarshalling with BlazegraphViewsDirectives { + private val rejectPredicateOnWrite: PartialFunction[BlazegraphViewRejection, Boolean] = { + case _: ViewNotFound | _: BlazegraphDecodingRejection => true + } + + private def emitMetadataOrReject(statusCode: StatusCode, io: IO[ViewResource]): Route = { + emit( + statusCode, + io.mapValue(_.metadata) + .adaptError { + case d: DecodingFailed => BlazegraphDecodingRejection(d) + case i: InvalidJsonLdFormat => BlazegraphDecodingRejection(i) + case other => other + } + .attemptNarrow[BlazegraphViewRejection] + .rejectWhen(rejectPredicateOnWrite) + ) + } + + private def emitMetadataOrReject(io: IO[ViewResource]): Route = emitMetadataOrReject(StatusCodes.OK, io) + + private def emitFetch(io: IO[ViewResource]): Route = + emit(io.attemptNarrow[BlazegraphViewRejection].rejectOn[ViewNotFound]) + + private def emitSource(io: IO[ViewResource]): Route = { + implicit val source: Printer = sourcePrinter + emit(io.map(_.value.source).attemptNarrow[BlazegraphViewRejection].rejectOn[ViewNotFound]) + } + def routes: Route = concat( pathPrefix("views") { @@ -66,14 +97,9 @@ class BlazegraphViewsRoutes( concat( (pathEndOrSingleSlash & post & entity(as[Json]) & noParameter("rev") & indexingMode) { (source, mode) => authorizeFor(project, Write).apply { - emit( + emitMetadataOrReject( Created, - views - .create(project, source) - .flatTap(index(project, _, mode)) - .mapValue(_.metadata) - .attemptNarrow[BlazegraphViewRejection] - .rejectWhen(decodingFailedOrViewNotFound) + views.create(project, source).flatTap(index(project, _, mode)) ) } }, @@ -86,24 +112,14 @@ class BlazegraphViewsRoutes( (parameter("rev".as[Int].?) & pathEndOrSingleSlash & entity(as[Json])) { case (None, source) => // Create a view with id segment - emit( + emitMetadataOrReject( Created, - views - .create(id, project, source) - .flatTap(index(project, _, mode)) - .mapValue(_.metadata) - .attemptNarrow[BlazegraphViewRejection] - .rejectWhen(decodingFailedOrViewNotFound) + views.create(id, project, source).flatTap(index(project, _, mode)) ) case (Some(rev), source) => // Update a view - emit( - views - .update(id, project, rev, source) - .flatTap(index(project, _, mode)) - .mapValue(_.metadata) - .attemptNarrow[BlazegraphViewRejection] - .rejectWhen(decodingFailedOrViewNotFound) + emitMetadataOrReject( + views.update(id, project, rev, source) ) } } @@ -111,13 +127,8 @@ class BlazegraphViewsRoutes( (delete & parameter("rev".as[Int])) { rev => // Deprecate a view authorizeFor(project, Write).apply { - emit( - views - .deprecate(id, project, rev) - .flatTap(index(project, _, mode)) - .mapValue(_.metadata) - .attemptNarrow[BlazegraphViewRejection] - .rejectOn[ViewNotFound] + emitMetadataOrReject( + views.deprecate(id, project, rev).flatTap(index(project, _, mode)) ) } }, @@ -127,12 +138,7 @@ class BlazegraphViewsRoutes( project, id, authorizeFor(project, Read).apply { - emit( - views - .fetch(id, project) - .attemptNarrow[BlazegraphViewRejection] - .rejectOn[ViewNotFound] - ) + emitFetch(views.fetch(id, project)) } ) } @@ -141,13 +147,8 @@ class BlazegraphViewsRoutes( // Undeprecate a blazegraph view (pathPrefix("undeprecate") & put & parameter("rev".as[Int]) & authorizeFor(project, Write) & pathEndOrSingleSlash) { rev => - emit( - views - .undeprecate(id, project, rev) - .flatTap(index(project, _, mode)) - .mapValue(_.metadata) - .attemptNarrow[BlazegraphViewRejection] - .rejectOn[ViewNotFound] + emitMetadataOrReject( + views.undeprecate(id, project, rev).flatTap(index(project, _, mode)) ) }, // Query a blazegraph view @@ -169,13 +170,7 @@ class BlazegraphViewsRoutes( // Fetch a view original source (pathPrefix("source") & get & pathEndOrSingleSlash & idSegmentRef(id)) { id => authorizeFor(project, Read).apply { - emit( - views - .fetch(id, project) - .map(_.value.source) - .attemptNarrow[BlazegraphViewRejection] - .rejectOn[ViewNotFound] - ) + emitSource(views.fetch(id, project)) } }, //Incoming/outgoing links for views diff --git a/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/routes/package.scala b/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/routes/package.scala deleted file mode 100644 index 4ce0b3fd2d..0000000000 --- a/delta/plugins/blazegraph/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/routes/package.scala +++ /dev/null @@ -1,12 +0,0 @@ -package ch.epfl.bluebrain.nexus.delta.plugins.blazegraph - -import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.BlazegraphViewRejection -import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.BlazegraphViewRejection.{DecodingFailed, InvalidJsonLdFormat, ViewNotFound} - -package object routes { - - val decodingFailedOrViewNotFound: PartialFunction[BlazegraphViewRejection, Boolean] = { - case _: DecodingFailed | _: ViewNotFound | _: InvalidJsonLdFormat => true - } - -} diff --git a/delta/plugins/blazegraph/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/BlazegraphViewDecodingSpec.scala b/delta/plugins/blazegraph/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/BlazegraphViewDecodingSpec.scala index 588868f783..74880857ca 100644 --- a/delta/plugins/blazegraph/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/BlazegraphViewDecodingSpec.scala +++ b/delta/plugins/blazegraph/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/blazegraph/BlazegraphViewDecodingSpec.scala @@ -2,12 +2,12 @@ package ch.epfl.bluebrain.nexus.delta.plugins.blazegraph import cats.data.NonEmptySet import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF -import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.BlazegraphViewRejection.{DecodingFailed, InvalidJsonLdFormat, UnexpectedBlazegraphViewId} import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.BlazegraphViewValue.{AggregateBlazegraphViewValue, IndexingBlazegraphViewValue} -import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.{contexts, BlazegraphViewRejection, BlazegraphViewValue} +import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.{contexts, BlazegraphViewValue} import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.nxv import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.Configuration import ch.epfl.bluebrain.nexus.delta.rdf.syntax.iriStringContextSyntax +import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.{DecodingFailed, InvalidJsonLdFormat, UnexpectedId} import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdSourceProcessor.JsonLdSourceDecoder import ch.epfl.bluebrain.nexus.delta.sdk.permissions.model.Permission import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.{ApiMappings, ProjectContext} @@ -31,8 +31,7 @@ class BlazegraphViewDecodingSpec extends CatsEffectSpec with Fixtures { implicit private val uuidF: UUIDF = UUIDF.fixed(UUID.randomUUID()) implicit val config: Configuration = BlazegraphDecoderConfiguration.apply.accepted - private val decoder = - new JsonLdSourceDecoder[BlazegraphViewRejection, BlazegraphViewValue](contexts.blazegraph, uuidF) + private val decoder = new JsonLdSourceDecoder[BlazegraphViewValue](contexts.blazegraph, uuidF) "An IndexingBlazegraphValue" should { @@ -82,7 +81,7 @@ class BlazegraphViewDecodingSpec extends CatsEffectSpec with Fixtures { "the provided id did not match the expected one" in { val id = iri"http://localhost/expected" val source = json"""{"@id": "http://localhost/provided", "@type": "SparqlView"}""" - decoder(context, id, source).rejectedWith[UnexpectedBlazegraphViewId] + decoder(context, id, source).rejectedWith[UnexpectedId] } "there's no known type discriminator" in { val sources = List( diff --git a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/model/CompositeViewRejection.scala b/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/model/CompositeViewRejection.scala index 8f66157322..671a310846 100644 --- a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/model/CompositeViewRejection.scala +++ b/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/model/CompositeViewRejection.scala @@ -1,28 +1,23 @@ package ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model import akka.http.scaladsl.model.StatusCodes -import ch.epfl.bluebrain.nexus.delta.kernel.Mapper import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClassUtils import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.client.SparqlClientError import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.CompositeViewSource._ import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri -import ch.epfl.bluebrain.nexus.delta.rdf.RdfError.ConversionError +import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.ContextValue import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords -import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.JsonLdDecoderError import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder -import ch.epfl.bluebrain.nexus.delta.rdf.{RdfError, Vocabulary} import ch.epfl.bluebrain.nexus.delta.sdk.http.HttpClientError import ch.epfl.bluebrain.nexus.delta.sdk.implicits._ import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection -import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.UnexpectedId import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, IdSegmentRef} import ch.epfl.bluebrain.nexus.delta.sdk.permissions.model.Permission import ch.epfl.bluebrain.nexus.delta.sdk.views.ViewRef import ch.epfl.bluebrain.nexus.delta.sourcing.model.ProjectRef -import ch.epfl.bluebrain.nexus.delta.sourcing.model.Tag.UserTag import io.circe.syntax._ import io.circe.{Encoder, Json, JsonObject} @@ -253,48 +248,10 @@ object CompositeViewRejection { extends CompositeViewRejection(s"Composite view identifier '$id' cannot be expanded to an Iri.") /** - * Rejection returned when attempting to create a composite view while providing an id that is blank. + * Rejection returned when attempting to decode an expanded JsonLD as an CompositeViewValue. */ - final case object BlankCompositeViewId extends CompositeViewRejection(s"Composite view identifier cannot be blank.") - - /** - * Rejection returned when a subject intends to retrieve a view at a specific tag, but the provided tag does not - * exist. - * - * @param tag - * the provided tag - */ - final case class TagNotFound(tag: UserTag) extends CompositeViewRejection(s"Tag requested '$tag' not found.") - - /** - * Rejection returned when attempting to create a composite view where the passed id does not match the id on the - * source json document. - * - * @param id - * the view identifier - * @param sourceId - * the view identifier in the source json document - */ - final case class UnexpectedCompositeViewId(id: Iri, sourceId: Iri) - extends CompositeViewRejection( - s"The provided composite view '$id' does not match the id '$sourceId' in the source document." - ) - - /** - * Signals an error converting the source Json document to a JsonLD document. - */ - final case class InvalidJsonLdFormat(id: Option[Iri], rdfError: RdfError) - extends CompositeViewRejection( - s"The provided composite view JSON document${id.fold("")(id => s" with id '$id'")} cannot be interpreted as a JSON-LD document." - ) - - /** - * Rejection when attempting to decode an expanded JsonLD as a [[CompositeViewValue]]. - * - * @param error - * the decoder error - */ - final case class DecodingFailed(error: JsonLdDecoderError) extends CompositeViewRejection(error.getMessage) + // TODO Remove when the rejection workflow gets refactored / when view endpoints get separated + final case class CompositeVieDecodingRejection(error: JsonLdRejection) extends CompositeViewRejection(error.reason) /** * Signals a rejection caused when interacting with the blazegraph client @@ -307,29 +264,21 @@ object CompositeViewRejection { final case class WrappedElasticSearchClientError(error: HttpClientError) extends CompositeViewProjectionRejection("Error while interacting with the underlying ElasticSearch index") - implicit val jsonLdRejectionMapper: Mapper[JsonLdRejection, CompositeViewRejection] = { - case UnexpectedId(id, payloadIri) => UnexpectedCompositeViewId(id, payloadIri) - case JsonLdRejection.InvalidJsonLdFormat(id, rdfError) => InvalidJsonLdFormat(id, rdfError) - case JsonLdRejection.DecodingFailed(error) => DecodingFailed(error) - case JsonLdRejection.BlankId => BlankCompositeViewId - } - implicit private[plugins] val compositeViewRejectionEncoder: Encoder.AsObject[CompositeViewRejection] = Encoder.AsObject.instance { r => val tpe = ClassUtils.simpleName(r) val obj = JsonObject(keywords.tpe -> tpe.asJson, "reason" -> r.reason.asJson) r match { - case WrappedBlazegraphClientError(rejection) => + case WrappedBlazegraphClientError(rejection) => obj.add(keywords.tpe, "SparqlClientError".asJson).add("details", rejection.toString().asJson) - case WrappedElasticSearchClientError(rejection) => + case WrappedElasticSearchClientError(rejection) => rejection.jsonBody.flatMap(_.asObject).getOrElse(obj.add(keywords.tpe, "ElasticSearchClientError".asJson)) - case IncorrectRev(provided, expected) => obj.add("provided", provided.asJson).add("expected", expected.asJson) - case InvalidJsonLdFormat(_, ConversionError(details, _)) => obj.add("details", details.asJson) - case InvalidJsonLdFormat(_, rdf) => obj.add("rdf", rdf.asJson) - case InvalidElasticSearchProjectionPayload(details) => obj.addIfExists("details", details) - case InvalidRemoteProjectSource(_, httpError) => obj.add("details", httpError.reason.asJson) - case _: ViewNotFound => obj.add(keywords.tpe, "ResourceNotFound".asJson) - case _ => obj + case IncorrectRev(provided, expected) => obj.add("provided", provided.asJson).add("expected", expected.asJson) + case CompositeVieDecodingRejection(error) => error.asJsonObject + case InvalidElasticSearchProjectionPayload(details) => obj.addIfExists("details", details) + case InvalidRemoteProjectSource(_, httpError) => obj.add("details", httpError.reason.asJson) + case _: ViewNotFound => obj.add(keywords.tpe, "ResourceNotFound".asJson) + case _ => obj } } @@ -339,13 +288,13 @@ object CompositeViewRejection { implicit val compositeViewHttpResponseFields: HttpResponseFields[CompositeViewRejection] = HttpResponseFields { case RevisionNotFound(_, _) => StatusCodes.NotFound - case TagNotFound(_) => StatusCodes.NotFound case ViewNotFound(_, _) => StatusCodes.NotFound case ProjectionNotFound(_) => StatusCodes.NotFound case SourceNotFound(_, _, _) => StatusCodes.NotFound case ViewAlreadyExists(_, _) => StatusCodes.Conflict case ResourceAlreadyExists(_, _) => StatusCodes.Conflict case IncorrectRev(_, _) => StatusCodes.Conflict + case CompositeVieDecodingRejection(error) => error.status case WrappedElasticSearchClientError(error) => error.errorCode.getOrElse(StatusCodes.InternalServerError) case _ => StatusCodes.BadRequest } diff --git a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/routes/CompositeViewsRoutes.scala b/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/routes/CompositeViewsRoutes.scala index 798753ae59..2b4cc52fad 100644 --- a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/routes/CompositeViewsRoutes.scala +++ b/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/routes/CompositeViewsRoutes.scala @@ -46,10 +46,14 @@ class CompositeViewsRoutes( with ElasticSearchViewsDirectives with BlazegraphViewsDirectives { + private val rejectPredicateOnWrite: PartialFunction[CompositeViewRejection, Boolean] = { + case _: ViewNotFound | _: CompositeVieDecodingRejection => true + } + private def emitMetadata(statusCode: StatusCode, io: IO[ViewResource]): Route = emit( statusCode, - io.mapValue(_.metadata).attemptNarrow[CompositeViewRejection].rejectWhen(decodingFailedOrViewNotFound) + io.mapValue(_.metadata).attemptNarrow[CompositeViewRejection].rejectWhen(rejectPredicateOnWrite) ) private def emitMetadata(io: IO[ViewResource]): Route = emitMetadata(OK, io) diff --git a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/routes/package.scala b/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/routes/package.scala deleted file mode 100644 index eff2280230..0000000000 --- a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/routes/package.scala +++ /dev/null @@ -1,11 +0,0 @@ -package ch.epfl.bluebrain.nexus.delta.plugins.compositeviews - -import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.CompositeViewRejection -import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.CompositeViewRejection.{DecodingFailed, InvalidJsonLdFormat, ViewNotFound} - -package object routes { - - val decodingFailedOrViewNotFound: PartialFunction[CompositeViewRejection, Boolean] = { - case _: DecodingFailed | _: ViewNotFound | _: InvalidJsonLdFormat => true - } -} diff --git a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/serialization/CompositeViewFieldsJsonLdSourceDecoder.scala b/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/serialization/CompositeViewFieldsJsonLdSourceDecoder.scala index 7bdde5fc55..24fbc2d627 100644 --- a/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/serialization/CompositeViewFieldsJsonLdSourceDecoder.scala +++ b/delta/plugins/composite-views/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/serialization/CompositeViewFieldsJsonLdSourceDecoder.scala @@ -2,7 +2,7 @@ package ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.serialization import cats.effect.IO import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF -import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.{contexts, CompositeViewFields, CompositeViewRejection} +import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.{contexts, CompositeViewFields} import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.JsonLdApi import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.JsonLdDecoder @@ -23,7 +23,7 @@ import scala.concurrent.duration.FiniteDuration */ //TODO remove when support for @json is added in json-ld library final class CompositeViewFieldsJsonLdSourceDecoder private ( - decoder: JsonLdSourceResolvingDecoder[CompositeViewRejection, CompositeViewFields] + decoder: JsonLdSourceResolvingDecoder[CompositeViewFields] ) { def apply(ref: ProjectRef, context: ProjectContext, source: Json)(implicit caller: Caller @@ -49,7 +49,7 @@ object CompositeViewFieldsJsonLdSourceDecoder { implicit val compositeViewFieldsJsonLdDecoder: JsonLdDecoder[CompositeViewFields] = CompositeViewFields.jsonLdDecoder(minIntervalRebuild) new CompositeViewFieldsJsonLdSourceDecoder( - new JsonLdSourceResolvingDecoder[CompositeViewRejection, CompositeViewFields]( + new JsonLdSourceResolvingDecoder[CompositeViewFields]( contexts.compositeViews, contextResolution, uuidF diff --git a/delta/plugins/composite-views/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViewDecodingSpec.scala b/delta/plugins/composite-views/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViewDecodingSpec.scala index fed62171e0..d2799fc8be 100644 --- a/delta/plugins/composite-views/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViewDecodingSpec.scala +++ b/delta/plugins/composite-views/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/compositeviews/CompositeViewDecodingSpec.scala @@ -5,7 +5,6 @@ import cats.data.NonEmptyList import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.CompositeView.Interval import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.CompositeViewProjectionFields.{ElasticSearchProjectionFields, SparqlProjectionFields} -import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.CompositeViewRejection.{DecodingFailed, UnexpectedCompositeViewId} import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.CompositeViewSourceFields._ import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.{CompositeViewFields, TemplateSparqlConstructQuery} import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.serialization.CompositeViewFieldsJsonLdSourceDecoder @@ -15,6 +14,7 @@ import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.ContextValue.ContextObje import ch.epfl.bluebrain.nexus.delta.rdf.syntax._ import ch.epfl.bluebrain.nexus.delta.sdk.generators.ProjectGen import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller +import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.{DecodingFailed, UnexpectedId} import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.{ApiMappings, ProjectContext} import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolverContextResolution import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.{Group, User} @@ -201,7 +201,7 @@ class CompositeViewDecodingSpec extends CatsEffectSpec with CirceLiteral with Fi pc, iri"http://example.com/wrong.id", source - ).rejectedWith[UnexpectedCompositeViewId] + ).rejectedWith[UnexpectedId] } "the resource_id template does not exist" in { diff --git a/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/ElasticSearchViewJsonLdSourceDecoder.scala b/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/ElasticSearchViewJsonLdSourceDecoder.scala index 01a8a1dc92..3aff678466 100644 --- a/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/ElasticSearchViewJsonLdSourceDecoder.scala +++ b/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/ElasticSearchViewJsonLdSourceDecoder.scala @@ -6,7 +6,7 @@ import cats.implicits._ import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.ElasticSearchViewJsonLdSourceDecoder.{toValue, ElasticSearchViewFields} import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.ElasticSearchViewValue.{AggregateElasticSearchViewValue, IndexingElasticSearchViewValue} -import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.{contexts, permissions, ElasticSearchViewRejection, ElasticSearchViewType, ElasticSearchViewValue} +import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.{contexts, permissions, ElasticSearchViewType, ElasticSearchViewValue} import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.nxv import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.ExpandedJsonLdCursor @@ -37,7 +37,7 @@ import scala.annotation.nowarn */ //TODO remove when support for @json is added in json-ld library class ElasticSearchViewJsonLdSourceDecoder private ( - decoder: JsonLdSourceResolvingDecoder[ElasticSearchViewRejection, ElasticSearchViewFields] + decoder: JsonLdSourceResolvingDecoder[ElasticSearchViewFields] ) { def apply(ref: ProjectRef, context: ProjectContext, source: Json)(implicit @@ -202,7 +202,7 @@ object ElasticSearchViewJsonLdSourceDecoder { ElasticSearchDecoderConfiguration.apply.map { implicit config => new ElasticSearchViewJsonLdSourceDecoder( - new JsonLdSourceResolvingDecoder[ElasticSearchViewRejection, ElasticSearchViewFields]( + new JsonLdSourceResolvingDecoder[ElasticSearchViewFields]( contexts.elasticsearch, contextResolution, uuidF diff --git a/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/model/ElasticSearchViewRejection.scala b/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/model/ElasticSearchViewRejection.scala index 7ffea86403..2c0482d2f6 100644 --- a/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/model/ElasticSearchViewRejection.scala +++ b/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/model/ElasticSearchViewRejection.scala @@ -1,16 +1,13 @@ package ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model import akka.http.scaladsl.model.StatusCodes -import ch.epfl.bluebrain.nexus.delta.kernel.Mapper import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClassUtils import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri -import ch.epfl.bluebrain.nexus.delta.rdf.RdfError.ConversionError +import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.ContextValue import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords -import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.JsonLdDecoderError import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder -import ch.epfl.bluebrain.nexus.delta.rdf.{RdfError, Vocabulary} import ch.epfl.bluebrain.nexus.delta.sdk.http.HttpClientError import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields @@ -19,7 +16,6 @@ import ch.epfl.bluebrain.nexus.delta.sdk.permissions.model.Permission import ch.epfl.bluebrain.nexus.delta.sdk.syntax._ import ch.epfl.bluebrain.nexus.delta.sdk.views.ViewRef import ch.epfl.bluebrain.nexus.delta.sourcing.model.ProjectRef -import ch.epfl.bluebrain.nexus.delta.sourcing.model.Tag.UserTag import ch.epfl.bluebrain.nexus.delta.sourcing.stream.ProjectionErr import io.circe.syntax._ import io.circe.{Encoder, Json, JsonObject} @@ -48,15 +44,6 @@ object ElasticSearchViewRejection { s"Revision requested '$provided' not found, last known revision is '$current'." ) - /** - * Rejection returned when a subject intends to retrieve a view at a specific tag, but the provided tag does not - * exist. - * - * @param tag - * the provided tag - */ - final case class TagNotFound(tag: UserTag) extends ElasticSearchViewRejection(s"Tag requested '$tag' not found.") - /** * Rejection returned when attempting to create an elastic search view but the id already exists. * @@ -169,20 +156,6 @@ object ElasticSearchViewRejection { s"At least one view reference does not exist or is deprecated." ) - /** - * Rejection returned when attempting to create an ElasticSearchView where the passed id does not match the id on the - * source json document. - * - * @param id - * the view identifier - * @param sourceId - * the view identifier in the source json document - */ - final case class UnexpectedElasticSearchViewId(id: Iri, sourceId: Iri) - extends ElasticSearchViewRejection( - s"The provided ElasticSearch view '$id' does not match the id '$sourceId' in the source document." - ) - /** * Rejection returned when attempting to interact with an ElasticSearchView while providing an id that cannot be * resolved to an Iri. @@ -194,26 +167,11 @@ object ElasticSearchViewRejection { extends ElasticSearchViewRejection(s"ElasticSearch view identifier '$id' cannot be expanded to an Iri.") /** - * Rejection returned when attempting to create an ElasticSearchView while providing an id that is blank. - */ - final case object BlankElasticSearchViewId - extends ElasticSearchViewRejection(s"Elastic search view identifier cannot be blank.") - - /** - * Rejection when attempting to decode an expanded JsonLD as an ElasticSearchViewValue. - * - * @param error - * the decoder error + * Rejection returned when attempting to decode an expanded JsonLD as an ElasticSearchViewValue. */ - final case class DecodingFailed(error: JsonLdDecoderError) extends ElasticSearchViewRejection(error.getMessage) - - /** - * Signals an error converting the source Json document to a JsonLD document. - */ - final case class InvalidJsonLdFormat(id: Option[Iri], rdfError: RdfError) - extends ElasticSearchViewRejection( - s"The provided ElasticSearch view JSON document${id.fold("")(id => s" with id '$id'")} cannot be interpreted as a JSON-LD document." - ) + // TODO Remove when the rejection workflow gets refactored / when view endpoints get separated + final case class ElasticSearchDecodingRejection(error: JsonLdRejection) + extends ElasticSearchViewRejection(error.reason) /** * Signals a rejection caused when interacting with the elasticserch client @@ -241,28 +199,20 @@ object ElasticSearchViewRejection { final case class TooManyViewReferences(provided: Int, max: Int) extends ElasticSearchViewRejection(s"$provided exceeds the maximum allowed number of view references ($max).") - implicit final val jsonLdRejectionMapper: Mapper[JsonLdRejection, ElasticSearchViewRejection] = { - case JsonLdRejection.UnexpectedId(id, sourceId) => UnexpectedElasticSearchViewId(id, sourceId) - case JsonLdRejection.InvalidJsonLdFormat(id, rdfError) => InvalidJsonLdFormat(id, rdfError) - case JsonLdRejection.DecodingFailed(error) => DecodingFailed(error) - case JsonLdRejection.BlankId => BlankElasticSearchViewId - } - implicit val elasticSearchRejectionEncoder: Encoder.AsObject[ElasticSearchViewRejection] = Encoder.AsObject.instance { r => val tpe = ClassUtils.simpleName(r) val obj = JsonObject.empty.add(keywords.tpe, tpe.asJson).add("reason", r.reason.asJson) r match { - case WrappedElasticSearchClientError(rejection) => + case WrappedElasticSearchClientError(rejection) => rejection.jsonBody.flatMap(_.asObject).getOrElse(obj.add(keywords.tpe, "ElasticSearchClientError".asJson)) - case InvalidJsonLdFormat(_, ConversionError(details, _)) => obj.add("details", details.asJson) - case InvalidJsonLdFormat(_, rdf) => obj.add("rdf", rdf.asJson) - case IncorrectRev(provided, expected) => obj.add("provided", provided.asJson).add("expected", expected.asJson) - case InvalidElasticSearchIndexPayload(details) => obj.addIfExists("details", details) - case InvalidViewReferences(views) => obj.add("views", views.asJson) - case InvalidPipeline(error) => obj.add("details", error.reason.asJson) - case _: ViewNotFound => obj.add(keywords.tpe, "ResourceNotFound".asJson) - case _ => obj + case ElasticSearchDecodingRejection(error) => error.asJsonObject + case IncorrectRev(provided, expected) => obj.add("provided", provided.asJson).add("expected", expected.asJson) + case InvalidElasticSearchIndexPayload(details) => obj.addIfExists("details", details) + case InvalidViewReferences(views) => obj.add("views", views.asJson) + case InvalidPipeline(error) => obj.add("details", error.reason.asJson) + case _: ViewNotFound => obj.add(keywords.tpe, "ResourceNotFound".asJson) + case _ => obj } } @@ -272,12 +222,12 @@ object ElasticSearchViewRejection { implicit val elasticSearchViewRejectionHttpResponseFields: HttpResponseFields[ElasticSearchViewRejection] = HttpResponseFields { case RevisionNotFound(_, _) => StatusCodes.NotFound - case TagNotFound(_) => StatusCodes.NotFound case ViewNotFound(_, _) => StatusCodes.NotFound case ResourceAlreadyExists(_, _) => StatusCodes.Conflict case IncorrectRev(_, _) => StatusCodes.Conflict case ViewIsDefaultView => StatusCodes.Forbidden case WrappedElasticSearchClientError(error) => error.errorCode.getOrElse(StatusCodes.InternalServerError) + case ElasticSearchDecodingRejection(error) => error.status case _ => StatusCodes.BadRequest } diff --git a/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/ElasticSearchViewsRoutes.scala b/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/ElasticSearchViewsRoutes.scala index 06645b5c91..09bb708c57 100644 --- a/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/ElasticSearchViewsRoutes.scala +++ b/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/ElasticSearchViewsRoutes.scala @@ -1,7 +1,9 @@ package ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.routes import akka.http.scaladsl.model.StatusCodes.Created +import akka.http.scaladsl.model.{StatusCode, StatusCodes} import akka.http.scaladsl.server._ +import cats.effect.IO import cats.implicits._ import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.ElasticSearchViewRejection._ import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model._ @@ -17,9 +19,10 @@ import ch.epfl.bluebrain.nexus.delta.sdk.directives.DeltaDirectives._ import ch.epfl.bluebrain.nexus.delta.sdk.fusion.FusionConfig import ch.epfl.bluebrain.nexus.delta.sdk.identities.Identities import ch.epfl.bluebrain.nexus.delta.sdk.implicits._ +import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.{DecodingFailed, InvalidJsonLdFormat} import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.RdfMarshalling import ch.epfl.bluebrain.nexus.delta.sdk.model._ -import io.circe.{Json, JsonObject} +import io.circe.{Json, JsonObject, Printer} /** * The elasticsearch views routes @@ -51,6 +54,34 @@ final class ElasticSearchViewsRoutes( with ElasticSearchViewsDirectives with RdfMarshalling { + private val rejectPredicateOnWrite: PartialFunction[ElasticSearchViewRejection, Boolean] = { + case _: ViewNotFound | _: ElasticSearchDecodingRejection => true + } + + private def emitMetadataOrReject(statusCode: StatusCode, io: IO[ViewResource]): Route = { + emit( + statusCode, + io.mapValue(_.metadata) + .adaptError { + case d: DecodingFailed => ElasticSearchDecodingRejection(d) + case i: InvalidJsonLdFormat => ElasticSearchDecodingRejection(i) + case other => other + } + .attemptNarrow[ElasticSearchViewRejection] + .rejectWhen(rejectPredicateOnWrite) + ) + } + + private def emitMetadataOrReject(io: IO[ViewResource]): Route = emitMetadataOrReject(StatusCodes.OK, io) + + private def emitFetch(io: IO[ViewResource]): Route = + emit(io.attemptNarrow[ElasticSearchViewRejection].rejectOn[ViewNotFound]) + + private def emitSource(io: IO[ViewResource]): Route = { + implicit val source: Printer = sourcePrinter + emit(io.map(_.value.source).attemptNarrow[ElasticSearchViewRejection].rejectOn[ViewNotFound]) + } + def routes: Route = pathPrefix("views") { extractCaller { implicit caller => @@ -60,14 +91,9 @@ final class ElasticSearchViewsRoutes( // Create an elasticsearch view without id segment (post & pathEndOrSingleSlash & noParameter("rev") & entity(as[Json]) & indexingMode) { (source, mode) => authorizeFor(project, Write).apply { - emit( + emitMetadataOrReject( Created, - views - .create(project, source) - .flatTap(index(project, _, mode)) - .mapValue(_.metadata) - .attemptNarrow[ElasticSearchViewRejection] - .rejectWhen(decodingFailedOrViewNotFound) + views.create(project, source).flatTap(index(project, _, mode)) ) } } @@ -82,24 +108,14 @@ final class ElasticSearchViewsRoutes( (parameter("rev".as[Int].?) & pathEndOrSingleSlash & entity(as[Json])) { case (None, source) => // Create an elasticsearch view with id segment - emit( + emitMetadataOrReject( Created, - views - .create(id, project, source) - .flatTap(index(project, _, mode)) - .mapValue(_.metadata) - .attemptNarrow[ElasticSearchViewRejection] - .rejectWhen(decodingFailedOrViewNotFound) + views.create(id, project, source).flatTap(index(project, _, mode)) ) case (Some(rev), source) => // Update a view - emit( - views - .update(id, project, rev, source) - .flatTap(index(project, _, mode)) - .mapValue(_.metadata) - .attemptNarrow[ElasticSearchViewRejection] - .rejectWhen(decodingFailedOrViewNotFound) + emitMetadataOrReject( + views.update(id, project, rev, source).flatTap(index(project, _, mode)) ) } } @@ -107,13 +123,8 @@ final class ElasticSearchViewsRoutes( // Deprecate an elasticsearch view (delete & parameter("rev".as[Int])) { rev => authorizeFor(project, Write).apply { - emit( - views - .deprecate(id, project, rev) - .flatTap(index(project, _, mode)) - .mapValue(_.metadata) - .attemptNarrow[ElasticSearchViewRejection] - .rejectWhen(decodingFailedOrViewNotFound) + emitMetadataOrReject( + views.deprecate(id, project, rev).flatTap(index(project, _, mode)) ) } }, @@ -123,9 +134,7 @@ final class ElasticSearchViewsRoutes( project, id, authorizeFor(project, Read).apply { - emit( - views.fetch(id, project).attemptNarrow[ElasticSearchViewRejection].rejectOn[ViewNotFound] - ) + emitFetch(views.fetch(id, project)) } ) } @@ -134,13 +143,8 @@ final class ElasticSearchViewsRoutes( // Undeprecate an elasticsearch view (pathPrefix("undeprecate") & put & pathEndOrSingleSlash & parameter("rev".as[Int])) { rev => authorizeFor(project, Write).apply { - emit( - views - .undeprecate(id, project, rev) - .flatTap(index(project, _, mode)) - .mapValue(_.metadata) - .attemptNarrow[ElasticSearchViewRejection] - .rejectWhen(decodingFailedOrViewNotFound) + emitMetadataOrReject( + views.undeprecate(id, project, rev).flatTap(index(project, _, mode)) ) } }, @@ -153,13 +157,7 @@ final class ElasticSearchViewsRoutes( // Fetch an elasticsearch view original source (pathPrefix("source") & get & pathEndOrSingleSlash & idSegmentRef(id)) { id => authorizeFor(project, Read).apply { - emit( - views - .fetch(id, project) - .map(_.value.source) - .attemptNarrow[ElasticSearchViewRejection] - .rejectOn[ViewNotFound] - ) + emitSource(views.fetch(id, project)) } } ) diff --git a/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/package.scala b/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/package.scala deleted file mode 100644 index 37f0133571..0000000000 --- a/delta/plugins/elasticsearch/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/routes/package.scala +++ /dev/null @@ -1,12 +0,0 @@ -package ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch - -import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.ElasticSearchViewRejection -import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.ElasticSearchViewRejection.{DecodingFailed, InvalidJsonLdFormat, ViewNotFound} - -package object routes { - - val decodingFailedOrViewNotFound: PartialFunction[ElasticSearchViewRejection, Boolean] = { - case _: DecodingFailed | _: ViewNotFound | _: InvalidJsonLdFormat => true - } - -} diff --git a/delta/plugins/elasticsearch/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/ElasticSearchViewDecodingSpec.scala b/delta/plugins/elasticsearch/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/ElasticSearchViewDecodingSpec.scala index 97711c8405..8f2cb30b1e 100644 --- a/delta/plugins/elasticsearch/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/ElasticSearchViewDecodingSpec.scala +++ b/delta/plugins/elasticsearch/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/elasticsearch/ElasticSearchViewDecodingSpec.scala @@ -3,13 +3,13 @@ package ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch import cats.data.NonEmptySet import cats.effect.unsafe.implicits._ import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF -import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.ElasticSearchViewRejection.{DecodingFailed, InvalidJsonLdFormat, UnexpectedElasticSearchViewId} import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.ElasticSearchViewValue.{AggregateElasticSearchViewValue, IndexingElasticSearchViewValue} import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.permissions import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.schemas import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.ContextValue.ContextObject import ch.epfl.bluebrain.nexus.delta.rdf.syntax.iriStringContextSyntax import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller +import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.{DecodingFailed, InvalidJsonLdFormat, UnexpectedId} import ch.epfl.bluebrain.nexus.delta.sdk.permissions.model.Permission import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.{ApiMappings, ProjectContext} import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolverContextResolution @@ -242,7 +242,7 @@ class ElasticSearchViewDecodingSpec extends CatsEffectSpec with Fixtures { "the provided id did not match the expected one" in { val id = iri"http://localhost/expected" val source = json"""{"@id": "http://localhost/provided", "@type": "ElasticSearchView", "mapping": $mapping}""" - decoder(ref, context, id, source).rejectedWith[UnexpectedElasticSearchViewId] + decoder(ref, context, id, source).rejectedWith[UnexpectedId] } "there's no known type discriminator or both are present" in { val sources = List( diff --git a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/batch/BatchCopy.scala b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/batch/BatchCopy.scala index 524f29c091..8159dc7e6b 100644 --- a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/batch/BatchCopy.scala +++ b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/batch/BatchCopy.scala @@ -5,8 +5,6 @@ import cats.effect.IO import cats.implicits.toFunctorOps import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.FetchFileResource -import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.FileRejection._ -import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.{FileDescription, FileMetadata} import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model._ import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.routes.CopyFileSource import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.Storage.{DiskStorage, RemoteDiskStorage} diff --git a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/model/FileRejection.scala b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/model/FileRejection.scala index 20a37d5a40..cecd63333d 100644 --- a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/model/FileRejection.scala +++ b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/files/model/FileRejection.scala @@ -2,11 +2,9 @@ package ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.server.{Rejection => AkkaRejection} -import ch.epfl.bluebrain.nexus.delta.kernel.Mapper import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClassUtils import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.StorageRejection -import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.StorageRejection.StorageFetchRejection import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.StorageFileRejection import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.operations.StorageFileRejection.{CopyFileRejection, FetchFileRejection, SaveFileRejection} import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri @@ -246,9 +244,6 @@ object FileRejection { Some(rejection.loggedDetails) ) - implicit val fileStorageFetchRejectionMapper: Mapper[StorageFetchRejection, WrappedStorageRejection] = - WrappedStorageRejection.apply - implicit val fileRejectionEncoder: Encoder.AsObject[FileRejection] = Encoder.AsObject.instance { r => val tpe = ClassUtils.simpleName(r) diff --git a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/FetchStorage.scala b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/FetchStorage.scala index d66b9009c6..d42160c0cc 100644 --- a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/FetchStorage.scala +++ b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/FetchStorage.scala @@ -2,7 +2,7 @@ package ch.epfl.bluebrain.nexus.delta.plugins.storage.storages import cats.effect.IO import cats.implicits.catsSyntaxMonadError -import ch.epfl.bluebrain.nexus.delta.kernel.Mapper +import ch.epfl.bluebrain.nexus.delta.plugins.storage.files.model.FileRejection.WrappedStorageRejection import ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model.StorageRejection.StorageFetchRejection import ch.epfl.bluebrain.nexus.delta.sdk.model.IdSegmentRef import ch.epfl.bluebrain.nexus.delta.sourcing.model.{ProjectRef, ResourceRef} @@ -20,9 +20,9 @@ trait FetchStorage { final def fetch[R <: Throwable]( resourceRef: ResourceRef, project: ProjectRef - )(implicit rejectionMapper: Mapper[StorageFetchRejection, R]): IO[StorageResource] = + ): IO[StorageResource] = fetch(IdSegmentRef(resourceRef), project).adaptError { case err: StorageFetchRejection => - rejectionMapper.to(err) + WrappedStorageRejection(err) } /** diff --git a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/Storages.scala b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/Storages.scala index 1da09396e4..1c80ce16f1 100644 --- a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/Storages.scala +++ b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/Storages.scala @@ -42,7 +42,7 @@ import java.time.Instant final class Storages private ( log: StorageLog, fetchContext: FetchContext, - sourceDecoder: JsonLdSourceResolvingDecoder[StorageRejection, StorageFields], + sourceDecoder: JsonLdSourceResolvingDecoder[StorageFields], serviceAccount: ServiceAccount ) extends FetchStorage { @@ -494,7 +494,7 @@ object Storages { StorageDecoderConfiguration.apply .map { implicit config => - new JsonLdSourceResolvingDecoder[StorageRejection, StorageFields](contexts.storages, contextResolution, uuidF) + new JsonLdSourceResolvingDecoder[StorageFields](contexts.storages, contextResolution, uuidF) } .map { sourceDecoder => new Storages( diff --git a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/model/StorageRejection.scala b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/model/StorageRejection.scala index 9ed5f23940..eb2a075566 100644 --- a/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/model/StorageRejection.scala +++ b/delta/plugins/storage/src/main/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/model/StorageRejection.scala @@ -1,17 +1,13 @@ package ch.epfl.bluebrain.nexus.delta.plugins.storage.storages.model import akka.http.scaladsl.model.StatusCodes -import ch.epfl.bluebrain.nexus.delta.kernel.Mapper import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClassUtils import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri +import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.ContextValue import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords -import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.JsonLdDecoderError import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder -import ch.epfl.bluebrain.nexus.delta.rdf.{RdfError, Vocabulary} -import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection -import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.UnexpectedId import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields import ch.epfl.bluebrain.nexus.delta.sdk.model.IdSegmentRef import ch.epfl.bluebrain.nexus.delta.sdk.permissions.model.Permission @@ -71,11 +67,6 @@ object StorageRejection { final case class InvalidStorageId(id: String) extends StorageFetchRejection(s"Storage identifier '$id' cannot be expanded to an Iri.") - /** - * Rejection returned when attempting to create a storage while providing an id that is blank. - */ - final case object BlankStorageId extends StorageRejection(s"Storage identifier cannot be blank.") - /** * Rejection returned when attempting to create a storage but the id already exists. * @@ -105,24 +96,6 @@ object StorageRejection { final case class StorageNotAccessible(id: Iri, details: String) extends StorageRejection(s"Storage '$id' not accessible.") - /** - * Rejection returned when attempting to create a storage where the passed id does not match the id on the payload. - * - * @param id - * the storage identifier - * @param payloadId - * the storage identifier on the payload - */ - final case class UnexpectedStorageId(id: Iri, payloadId: Iri) - extends StorageRejection(s"Storage '$id' does not match storage id on payload '$payloadId'.") - - /** - * Rejection when attempting to decode an expanded JsonLD as a case class - * @param error - * the decoder error - */ - final case class DecodingFailed(error: JsonLdDecoderError) extends StorageRejection(error.getMessage) - /** * Signals an error creating/updating a storage with a wrong maxFileSize */ @@ -131,12 +104,6 @@ object StorageRejection { s"'maxFileSize' field on storage '$id' has wrong range. Found '$value'. Allowed range [1,$maxAllowed]." ) - /** - * Signals an error converting the source Json to JsonLD - */ - final case class InvalidJsonLdFormat(id: Option[Iri], rdfError: RdfError) - extends StorageRejection(s"Storage ${id.fold("")(id => s"'$id'")} has invalid JSON-LD payload.") - /** * Signals an attempt to update a storage to a different storage type */ @@ -193,20 +160,12 @@ object StorageRejection { s"The provided permissions '${permissions.mkString(",")}' are not defined in the collection of allowed permissions." ) - implicit val storageJsonLdRejectionMapper: Mapper[JsonLdRejection, StorageRejection] = { - case UnexpectedId(id, payloadIri) => UnexpectedStorageId(id, payloadIri) - case JsonLdRejection.InvalidJsonLdFormat(id, rdfError) => InvalidJsonLdFormat(id, rdfError) - case JsonLdRejection.DecodingFailed(error) => DecodingFailed(error) - case JsonLdRejection.BlankId => BlankStorageId - } - implicit private[plugins] val storageRejectionEncoder: Encoder.AsObject[StorageRejection] = Encoder.AsObject.instance { r => val tpe = ClassUtils.simpleName(r) val obj = JsonObject(keywords.tpe -> tpe.asJson, "reason" -> r.reason.asJson) r match { case StorageNotAccessible(_, details) => obj.add("details", details.asJson) - case InvalidJsonLdFormat(_, rdf) => obj.add("rdf", rdf.asJson) case IncorrectRev(provided, expected) => obj.add("provided", provided.asJson).add("expected", expected.asJson) case _: StorageNotFound => obj.add(keywords.tpe, "ResourceNotFound".asJson) case _ => obj diff --git a/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/StoragesSpec.scala b/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/StoragesSpec.scala index ac55686a6d..332cb9b423 100644 --- a/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/StoragesSpec.scala +++ b/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/StoragesSpec.scala @@ -11,6 +11,7 @@ import ch.epfl.bluebrain.nexus.delta.sdk.ConfigFixtures import ch.epfl.bluebrain.nexus.delta.sdk.generators.ProjectGen import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.{Caller, ServiceAccount} import ch.epfl.bluebrain.nexus.delta.sdk.implicits._ +import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.UnexpectedId import ch.epfl.bluebrain.nexus.delta.sdk.model.IdSegmentRef import ch.epfl.bluebrain.nexus.delta.sdk.projects.FetchContextDummy import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectRejection.{ProjectIsDeprecated, ProjectNotFound} @@ -91,8 +92,7 @@ private class StoragesSpec "reject with different ids on the payload and passed" in { val otherId = nxv + "other" val payload = s3FieldsJson deepMerge Json.obj(keywords.id -> s3Id.asJson) - storages.create(otherId, projectRef, payload).rejected shouldEqual - UnexpectedStorageId(id = otherId, payloadId = s3Id) + storages.create(otherId, projectRef, payload).rejected shouldEqual UnexpectedId(id = otherId, payloadId = s3Id) } "reject if it already exists" in { diff --git a/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/model/StorageFieldsSpec.scala b/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/model/StorageFieldsSpec.scala index 4e2d359d89..21af6f81d3 100644 --- a/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/model/StorageFieldsSpec.scala +++ b/delta/plugins/storage/src/test/scala/ch/epfl/bluebrain/nexus/delta/plugins/storage/storages/model/StorageFieldsSpec.scala @@ -15,7 +15,7 @@ import ch.epfl.bluebrain.nexus.testkit.scalatest.ce.CatsEffectSpec class StorageFieldsSpec extends CatsEffectSpec with RemoteContextResolutionFixture with StorageFixtures { implicit private val cfg: Configuration = StorageDecoderConfiguration.apply.accepted - val sourceDecoder = new JsonLdSourceDecoder[StorageRejection, StorageFields](contexts.storages, UUIDF.random) + val sourceDecoder = new JsonLdSourceDecoder[StorageFields](contexts.storages, UUIDF.random) "StorageFields" when { val pc = ProjectContext.unsafe(ApiMappings.empty, nxv.base, nxv.base, enforceSchema = false) diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/jsonld/JsonLdRejection.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/jsonld/JsonLdRejection.scala index db4e1e6de9..5efcc379f9 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/jsonld/JsonLdRejection.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/jsonld/JsonLdRejection.scala @@ -1,9 +1,19 @@ package ch.epfl.bluebrain.nexus.delta.sdk.jsonld +import akka.http.scaladsl.model.StatusCodes import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection +import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClassUtils import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri import ch.epfl.bluebrain.nexus.delta.rdf.RdfError +import ch.epfl.bluebrain.nexus.delta.rdf.RdfError.ConversionError +import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.contexts +import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.ContextValue +import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.JsonLdDecoderError +import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder +import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields +import io.circe.syntax.EncoderOps +import io.circe.{Encoder, JsonObject} sealed abstract class JsonLdRejection(val reason: String) extends Rejection @@ -37,7 +47,7 @@ object JsonLdRejection { */ final case class InvalidJsonLdFormat(id: Option[Iri], rdfError: RdfError) extends InvalidJsonLdRejection( - s"Storage ${id.fold("")(id => s"'$id'")} has invalid JSON-LD payload. Error: '${rdfError.reason}'" + s"Resource ${id.fold("")(id => s"'$id'")} has invalid JSON-LD payload. Error: '${rdfError.reason}'" ) /** @@ -46,4 +56,21 @@ object JsonLdRejection { * the decoder error */ final case class DecodingFailed(error: JsonLdDecoderError) extends JsonLdRejection(error.getMessage) + + implicit val jsonLdRejectionEncoder: Encoder.AsObject[JsonLdRejection] = + Encoder.AsObject.instance { r => + val tpe = ClassUtils.simpleName(r) + val obj = JsonObject.empty.add(keywords.tpe, tpe.asJson).add("reason", r.reason.asJson) + r match { + case InvalidJsonLdFormat(_, ConversionError(details, _)) => obj.add("details", details.asJson) + case InvalidJsonLdFormat(_, rdf) => obj.add("rdf", rdf.asJson) + case _ => obj + } + } + + implicit val resourceRejectionJsonLdEncoder: JsonLdEncoder[JsonLdRejection] = + JsonLdEncoder.computeFromCirce(ContextValue(contexts.error)) + + implicit val responseFieldsJsonLd: HttpResponseFields[JsonLdRejection] = + HttpResponseFields(_ => StatusCodes.BadRequest) } diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/jsonld/JsonLdSourceProcessor.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/jsonld/JsonLdSourceProcessor.scala index bd574121d3..853304f716 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/jsonld/JsonLdSourceProcessor.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/jsonld/JsonLdSourceProcessor.scala @@ -2,7 +2,6 @@ package ch.epfl.bluebrain.nexus.delta.sdk.jsonld import cats.effect.IO import cats.syntax.all._ -import ch.epfl.bluebrain.nexus.delta.kernel.Mapper import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.{BNode, Iri} import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.ExpandedJsonLd @@ -69,13 +68,11 @@ object JsonLdSourceProcessor { /** * Allows to parse the given json source to JsonLD compacted and expanded using static contexts */ - final class JsonLdSourceParser[R <: Throwable]( + final private class JsonLdSourceParser( contextIri: Seq[Iri], override val uuidF: UUIDF - )(implicit - api: JsonLdApi, - rejectionMapper: Mapper[InvalidJsonLdRejection, R] - ) extends JsonLdSourceProcessor { + )(implicit api: JsonLdApi) + extends JsonLdSourceProcessor { /** * Converts the passed ''source'' to JsonLD compacted and expanded. The @id value is extracted from the payload. @@ -101,7 +98,7 @@ object JsonLdSourceProcessor { expanded = originalExpanded.replaceId(iri) assembly <- JsonLdAssembly(iri, source, expanded, ctx, result.remoteContexts) } yield assembly - }.adaptError { case r: InvalidJsonLdRejection => rejectionMapper.to(r) } + } /** * Converts the passed ''source'' to JsonLD compacted and expanded. The @id value is extracted from the payload if @@ -120,7 +117,7 @@ object JsonLdSourceProcessor { source: Json )(implicit rcr: RemoteContextResolution - ): IO[JsonLdAssembly] = { + ): IO[JsonLdAssembly] = { for { _ <- validateIdNotBlank(source) (ctx, result) <- expandSource(context, source.addContext(contextIri: _*)) @@ -128,21 +125,21 @@ object JsonLdSourceProcessor { expanded <- checkAndSetSameId(iri, originalExpanded) assembly <- JsonLdAssembly(iri, source, expanded, ctx, result.remoteContexts) } yield assembly - }.adaptError { case r: InvalidJsonLdRejection => rejectionMapper.to(r) } + } } /** * Allows to parse the given json source to JsonLD compacted and expanded using static and resolver-based contexts */ - final class JsonLdSourceResolvingParser[R <: Throwable]( + final class JsonLdSourceResolvingParser( contextIri: Seq[Iri], contextResolution: ResolverContextResolution, override val uuidF: UUIDF - )(implicit api: JsonLdApi, rejectionMapper: Mapper[InvalidJsonLdRejection, R]) + )(implicit api: JsonLdApi) extends JsonLdSourceProcessor { - private val underlying = new JsonLdSourceParser[R](contextIri, uuidF) + private val underlying = new JsonLdSourceParser(contextIri, uuidF) /** * Converts the passed ''source'' to JsonLD compacted and expanded. The @id value is extracted from the payload. @@ -215,19 +212,17 @@ object JsonLdSourceProcessor { } object JsonLdSourceResolvingParser { - def apply[R <: Throwable](contextResolution: ResolverContextResolution, uuidF: UUIDF)(implicit - api: JsonLdApi, - rejectionMapper: Mapper[InvalidJsonLdRejection, R] - ): JsonLdSourceResolvingParser[R] = + def apply(contextResolution: ResolverContextResolution, uuidF: UUIDF)(implicit + api: JsonLdApi + ): JsonLdSourceResolvingParser = new JsonLdSourceResolvingParser(Seq.empty, contextResolution, uuidF) } /** * Allows to parse the given json source and decode it into an ''A'' using static contexts */ - final class JsonLdSourceDecoder[R <: Throwable, A: JsonLdDecoder](contextIri: Iri, override val uuidF: UUIDF)(implicit - api: JsonLdApi, - rejectionMapper: Mapper[JsonLdRejection, R] + final class JsonLdSourceDecoder[A: JsonLdDecoder](contextIri: Iri, override val uuidF: UUIDF)(implicit + api: JsonLdApi ) extends JsonLdSourceProcessor { /** @@ -250,7 +245,7 @@ object JsonLdSourceProcessor { iri <- getOrGenerateId(expanded.rootId.asIri, context) decodedValue <- IO.fromEither(expanded.to[A].leftMap(DecodingFailed)) } yield (iri, decodedValue) - }.adaptError { case r: JsonLdRejection => rejectionMapper.to(r) } + } /** * Expands the passed ''source'' and attempt to decode it into an ''A'' The @id value is extracted from the payload @@ -266,28 +261,27 @@ object JsonLdSourceProcessor { */ def apply(context: ProjectContext, iri: Iri, source: Json)(implicit rcr: RemoteContextResolution - ): IO[A] = { + ): IO[A] = { for { (_, result) <- expandSource(context, source.addContext(contextIri)) originalExpanded = result.value expanded <- checkAndSetSameId(iri, originalExpanded) decodedValue <- IO.fromEither(expanded.to[A].leftMap(DecodingFailed)) } yield decodedValue - }.adaptError { case r: JsonLdRejection => rejectionMapper.to(r) } - + } } /** * Allows to parse the given json source and decode it into an ''A'' using static and resolver-based contexts */ - final class JsonLdSourceResolvingDecoder[R <: Throwable, A: JsonLdDecoder]( + final class JsonLdSourceResolvingDecoder[A: JsonLdDecoder]( contextIri: Iri, contextResolution: ResolverContextResolution, override val uuidF: UUIDF - )(implicit api: JsonLdApi, rejectionMapper: Mapper[JsonLdRejection, R]) + )(implicit api: JsonLdApi) extends JsonLdSourceProcessor { - private val underlying = new JsonLdSourceDecoder[R, A](contextIri, uuidF) + private val underlying = new JsonLdSourceDecoder[A](contextIri, uuidF) /** * Expands the passed ''source'' and attempt to decode it into an ''A'' The @id value is extracted from the diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/marshalling/RdfExceptionHandler.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/marshalling/RdfExceptionHandler.scala index b0591b7eb8..e16e035c24 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/marshalling/RdfExceptionHandler.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/marshalling/RdfExceptionHandler.scala @@ -15,6 +15,7 @@ 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.error.ServiceError.AuthorizationFailed import ch.epfl.bluebrain.nexus.delta.sdk.error.{AuthTokenError, IdentityError, ServiceError} +import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection import ch.epfl.bluebrain.nexus.delta.sdk.model.BaseUri import ch.epfl.bluebrain.nexus.delta.sdk.organizations.model.OrganizationRejection import ch.epfl.bluebrain.nexus.delta.sdk.permissions.model.PermissionsRejection @@ -41,6 +42,7 @@ object RdfExceptionHandler { case err: OrganizationRejection => discardEntityAndForceEmit(err) case err: ProjectRejection => discardEntityAndForceEmit(err) case err: QuotaRejection => discardEntityAndForceEmit(err) + case err: JsonLdRejection => discardEntityAndForceEmit(err) case err: AuthTokenError => discardEntityAndForceEmit(err) case err: AuthorizationFailed => discardEntityAndForceEmit(err: ServiceError) case err: RdfError => discardEntityAndForceEmit(err) diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resolvers/ResolversImpl.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resolvers/ResolversImpl.scala index d201021b2d..3329efceaf 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resolvers/ResolversImpl.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resolvers/ResolversImpl.scala @@ -31,7 +31,7 @@ import io.circe.Json final class ResolversImpl private ( log: ResolversLog, fetchContext: FetchContext, - sourceDecoder: JsonLdSourceResolvingDecoder[ResolverRejection, ResolverValue] + sourceDecoder: JsonLdSourceResolvingDecoder[ResolverValue] ) extends Resolvers { implicit private val kamonComponent: KamonMetricComponent = KamonMetricComponent(entityType.value) @@ -178,7 +178,7 @@ object ResolversImpl { new ResolversImpl( ScopedEventLog(Resolvers.definition(priorityAlreadyExists, clock), config, xas), fetchContext, - new JsonLdSourceResolvingDecoder[ResolverRejection, ResolverValue]( + new JsonLdSourceResolvingDecoder[ResolverValue]( contexts.resolvers, contextResolution, uuidF diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resolvers/model/ResolverRejection.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resolvers/model/ResolverRejection.scala index 9f34928741..c7683a803a 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resolvers/model/ResolverRejection.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resolvers/model/ResolverRejection.scala @@ -1,18 +1,13 @@ package ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model import akka.http.scaladsl.model.StatusCodes -import ch.epfl.bluebrain.nexus.delta.kernel.Mapper import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClassUtils import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri -import ch.epfl.bluebrain.nexus.delta.rdf.RdfError import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.contexts import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.ContextValue import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords -import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.JsonLdDecoderError import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder -import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection -import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.{BlankId, UnexpectedId} import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.ResourceResolutionReport.ResolverReport import ch.epfl.bluebrain.nexus.delta.sdk.syntax._ @@ -74,17 +69,6 @@ object ResolverRejection { final case class ResolverNotFound(id: Iri, project: ProjectRef) extends ResolverRejection(s"Resolver '$id' not found in project '$project'.") - /** - * Rejection returned when attempting to create a resolver where the passed id does not match the id on the payload. - * - * @param id - * the resolver identifier - * @param payloadId - * the resolver identifier on the payload - */ - final case class UnexpectedResolverId(id: Iri, payloadId: Iri) - extends ResolverRejection(s"Resolver '$id' does not match resolver id on payload '$payloadId'.") - /** * Rejection returned when attempting to interact with a resolver providing an id that cannot be resolved to an Iri. * @@ -94,11 +78,6 @@ object ResolverRejection { final case class InvalidResolverId(id: String) extends ResolverRejection(s"Resolver identifier '$id' cannot be expanded to an Iri.") - /** - * Rejection returned when attempting to create a resolver while providing an id that is blank. - */ - final case object BlankResolverId extends ResolverRejection(s"Resolver identifier cannot be blank.") - /** * Rejection returned when attempting to resolve a resource providing an id that cannot be resolved to an Iri. * @@ -108,19 +87,6 @@ object ResolverRejection { final case class InvalidResolvedResourceId(id: String) extends ResolverRejection(s"Resource identifier '$id' cannot be expanded to an Iri.") - /** - * Rejection when attempting to decode an expanded JsonLD as a case class - * @param error - * the decoder error - */ - final case class DecodingFailed(error: JsonLdDecoderError) extends ResolverRejection(error.getMessage) - - /** - * Signals an error converting the source Json to JsonLD - */ - final case class InvalidJsonLdFormat(id: Option[Iri], rdfError: RdfError) - extends ResolverRejection(s"Resolver${id.fold("")(id => s" '$id'")} has invalid JSON-LD payload.") - /** * Signals an error when there is another resolver with the provided priority already existing */ @@ -217,19 +183,11 @@ object ResolverRejection { */ final case class ResolverIsDeprecated(id: Iri) extends ResolverRejection(s"Resolver '$id' is deprecated.") - implicit val jsonLdRejectionMapper: Mapper[JsonLdRejection, ResolverRejection] = { - case UnexpectedId(id, payloadIri) => UnexpectedResolverId(id, payloadIri) - case JsonLdRejection.InvalidJsonLdFormat(id, rdfError) => InvalidJsonLdFormat(id, rdfError) - case JsonLdRejection.DecodingFailed(error) => DecodingFailed(error) - case BlankId => BlankResolverId - } - implicit val resolverRejectionEncoder: Encoder.AsObject[ResolverRejection] = Encoder.AsObject.instance { r => val tpe = ClassUtils.simpleName(r) val obj = JsonObject.empty.add(keywords.tpe, tpe.asJson).add("reason", r.reason.asJson) r match { - case InvalidJsonLdFormat(_, rdf) => obj.add("details", rdf.asJson) case IncorrectRev(provided, expected) => obj.add("provided", provided.asJson).add("expected", expected.asJson) case InvalidResolution(_, _, report) => obj.addContext(contexts.resolvers).add("report", report.asJson) case InvalidResolverResolution(_, _, _, report) => diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/Resources.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/Resources.scala index cafa90a9d9..7d3ea4e277 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/Resources.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/Resources.scala @@ -2,7 +2,6 @@ package ch.epfl.bluebrain.nexus.delta.sdk.resources import cats.effect.{Clock, IO} import cats.syntax.all._ -import ch.epfl.bluebrain.nexus.delta.kernel.Mapper import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.{nxv, schemas} @@ -14,7 +13,7 @@ import ch.epfl.bluebrain.nexus.delta.sdk.model._ import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.{ApiMappings, ProjectBase, ProjectContext} import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceCommand._ import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceEvent._ -import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection.{IncorrectRev, InvalidResourceId, NoChangeDetected, ResourceAlreadyExists, ResourceFetchRejection, ResourceIsDeprecated, ResourceIsNotDeprecated, ResourceNotFound, RevisionNotFound, TagNotFound, UnexpectedResourceSchema} +import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection._ import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.{ResourceCommand, ResourceEvent, ResourceRejection, ResourceState} import ch.epfl.bluebrain.nexus.delta.sourcing.ScopedEntityDefinition.Tagger import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.Subject @@ -255,13 +254,11 @@ trait Resources { * @param projectRef * the project reference where the schema belongs */ - def fetch[R <: Throwable]( + def fetch( resourceRef: ResourceRef, projectRef: ProjectRef - )(implicit rejectionMapper: Mapper[ResourceFetchRejection, R]): IO[DataResource] = - fetch(IdSegmentRef(resourceRef), projectRef, None).adaptError { case e: ResourceFetchRejection => - rejectionMapper.to(e) - } + ): IO[DataResource] = + fetch(IdSegmentRef(resourceRef), projectRef, None) } object Resources { diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesImpl.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesImpl.scala index 45bf06cc43..563a320f6f 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesImpl.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesImpl.scala @@ -29,7 +29,7 @@ import io.circe.Json final class ResourcesImpl private ( log: ResourcesLog, fetchContext: FetchContext, - sourceParser: JsonLdSourceResolvingParser[ResourceRejection] + sourceParser: JsonLdSourceResolvingParser ) extends Resources { implicit private val kamonComponent: KamonMetricComponent = KamonMetricComponent(entityType.value) @@ -236,7 +236,7 @@ object ResourcesImpl { new ResourcesImpl( ScopedEventLog(Resources.definition(validateResource, detectChange, clock), config.eventLog, xas), fetchContext, - JsonLdSourceResolvingParser[ResourceRejection](contextResolution, uuidF) + JsonLdSourceResolvingParser(contextResolution, uuidF) ) } diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesTrial.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesTrial.scala index ed95f1433e..11b66fbbb4 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesTrial.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesTrial.scala @@ -2,6 +2,7 @@ package ch.epfl.bluebrain.nexus.delta.sdk.resources import cats.effect.{Clock, IO} import cats.implicits._ +import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.JsonLdApi import ch.epfl.bluebrain.nexus.delta.sdk.DataResource @@ -12,7 +13,7 @@ import ch.epfl.bluebrain.nexus.delta.sdk.model.{IdSegment, IdSegmentRef, Resourc import ch.epfl.bluebrain.nexus.delta.sdk.projects.FetchContext import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolverContextResolution import ch.epfl.bluebrain.nexus.delta.sdk.resources.Resources.expandResourceRef -import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.{ResourceGenerationResult, ResourceRejection, ResourceState} +import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.{ResourceGenerationResult, ResourceState} import ch.epfl.bluebrain.nexus.delta.sdk.schemas.model.Schema import ch.epfl.bluebrain.nexus.delta.sourcing.model.ProjectRef @@ -77,7 +78,7 @@ object ResourcesTrial { clock: Clock[IO] )(implicit api: JsonLdApi, uuidF: UUIDF): ResourcesTrial = new ResourcesTrial { - private val sourceParser = JsonLdSourceResolvingParser[ResourceRejection](contextResolution, uuidF) + private val sourceParser = JsonLdSourceResolvingParser(contextResolution, uuidF) override def generate(project: ProjectRef, schema: IdSegment, source: NexusSource)(implicit caller: Caller @@ -90,7 +91,7 @@ object ResourcesTrial { validation <- validateResource(jsonld, schemaClaim, projectContext.enforceSchema) result <- toResourceF(project, jsonld, source, validation) } yield result - }.attemptNarrow[ResourceRejection].map { attempt => + }.attemptNarrow[Rejection].map { attempt => ResourceGenerationResult(None, attempt) } @@ -103,7 +104,7 @@ object ResourcesTrial { validation <- validateResource(jsonld, schema) result <- toResourceF(project, jsonld, source, validation) } yield result - }.attemptNarrow[ResourceRejection].map { attempt => + }.attemptNarrow[Rejection].map { attempt => ResourceGenerationResult(Some(schema), attempt) } diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceGenerationResult.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceGenerationResult.scala index c55ef41b37..84621a210e 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceGenerationResult.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceGenerationResult.scala @@ -1,14 +1,18 @@ package ch.epfl.bluebrain.nexus.delta.sdk.resources.model import cats.effect.IO +import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection +import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClassUtils import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.{JsonLdApi, JsonLdJavaApi} 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.sdk.jsonld.JsonLdRejection import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, ResourceF} import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceGenerationResult._ import ch.epfl.bluebrain.nexus.delta.sdk.syntax._ import ch.epfl.bluebrain.nexus.delta.sdk.{DataResource, SchemaResource} import io.circe.Json +import io.circe.syntax.KeyOps /** * Result of the generation of a resource @@ -19,19 +23,25 @@ import io.circe.Json */ final case class ResourceGenerationResult( schema: Option[SchemaResource], - attempt: Either[ResourceRejection, DataResource] + attempt: Either[Rejection, DataResource] ) { def asJson(implicit base: BaseUri, rcr: RemoteContextResolution): IO[Json] = { for { schema <- schema.fold(emptySchema)(toJsonField("schema", _)) resourceOrError <- attempt.fold( - toJsonField("error", _), + errorField, toJsonField("result", _) ) } yield schema deepMerge resourceOrError } + private def errorField(rejection: Rejection)(implicit rcr: RemoteContextResolution) = rejection match { + case rejection: ResourceRejection => toJsonField("error": String, rejection) + case rejection: JsonLdRejection => toJsonField("error": String, rejection) + case other: Rejection => IO.pure(Json.obj("@type" := ClassUtils.simpleName(other), "reason" := other.reason)) + } + private def toJsonField[A](fieldName: String, value: A)(implicit encoder: JsonLdEncoder[A], rcr: RemoteContextResolution diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceRejection.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceRejection.scala index 7097d952fb..9412957c02 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceRejection.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceRejection.scala @@ -1,19 +1,15 @@ package ch.epfl.bluebrain.nexus.delta.sdk.resources.model import akka.http.scaladsl.model.StatusCodes -import ch.epfl.bluebrain.nexus.delta.kernel.Mapper import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClassUtils import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri -import ch.epfl.bluebrain.nexus.delta.rdf.RdfError import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.contexts import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.ExpandedJsonLd import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.ContextValue import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder import ch.epfl.bluebrain.nexus.delta.rdf.shacl.ValidationReport -import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection -import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.{BlankId, InvalidJsonLdRejection, UnexpectedId} import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.ResourceResolutionReport import ch.epfl.bluebrain.nexus.delta.sdk.syntax._ @@ -78,11 +74,6 @@ object ResourceRejection { final case class InvalidResourceId(id: String) extends ResourceFetchRejection(s"Resource identifier '$id' cannot be expanded to an Iri.") - /** - * Rejection returned when attempting to create a resource while providing an id that is blank. - */ - final case object BlankResourceId extends ResourceRejection(s"Resource identifier cannot be blank.") - /** * Rejection returned when attempting to create/update a resource with a reserved id. */ @@ -113,17 +104,6 @@ object ResourceRejection { s"No changes were detected when attempting to update resource '${currentState.id}' in project '${currentState.project}'." ) - /** - * Rejection returned when attempting to create a resource where the passed id does not match the id on the payload. - * - * @param id - * the resource identifier - * @param payloadId - * the resource identifier on the payload - */ - final case class UnexpectedResourceId(id: Iri, payloadId: Iri) - extends ResourceRejection(s"Resource '$id' does not match resource id on payload '$payloadId'.") - /** * Rejection returned when attempting to create/update a resource where the payload does not satisfy the SHACL schema * constrains. @@ -226,25 +206,12 @@ object ResourceRejection { */ final case object NoSchemaProvided extends ResourceRejection(s"A schema is required but was not provided.") - /** - * Signals an error converting the source Json to JsonLD - */ - final case class InvalidJsonLdFormat(idOpt: Option[Iri], rdfError: RdfError) - extends ResourceRejection(s"Resource${idOpt.fold("")(id => s" '$id'")} has invalid JSON-LD payload.") - - implicit val jsonLdRejectionMapper: Mapper[InvalidJsonLdRejection, ResourceRejection] = { - case UnexpectedId(id, payloadIri) => UnexpectedResourceId(id, payloadIri) - case JsonLdRejection.InvalidJsonLdFormat(id, rdfError) => InvalidJsonLdFormat(id, rdfError) - case BlankId => BlankResourceId - } - implicit val resourceRejectionEncoder: Encoder.AsObject[ResourceRejection] = Encoder.AsObject.instance { r => val tpe = ClassUtils.simpleName(r) val obj = JsonObject.empty.add(keywords.tpe, tpe.asJson).add("reason", r.reason.asJson) r match { case ResourceShaclEngineRejection(_, _, details) => obj.add("details", details.asJson) - case InvalidJsonLdFormat(_, rdf) => obj.add("rdf", rdf.asJson) case InvalidResource(_, _, report, expanded) => obj.addContext(contexts.shacl).add("details", report.json).add("expanded", expanded.json) case InvalidSchemaRejection(_, _, report) => diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/SchemasImpl.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/SchemasImpl.scala index 826e28dd51..70553aed35 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/SchemasImpl.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/SchemasImpl.scala @@ -31,7 +31,7 @@ final class SchemasImpl private ( log: SchemasLog, fetchContext: FetchContext, schemaImports: SchemaImports, - sourceParser: JsonLdSourceResolvingParser[SchemaRejection] + sourceParser: JsonLdSourceResolvingParser ) extends Schemas { implicit private val kamonComponent: KamonMetricComponent = KamonMetricComponent(entityType.value) @@ -185,7 +185,7 @@ object SchemasImpl { uuidF: UUIDF ): Schemas = { val parser = - new JsonLdSourceResolvingParser[SchemaRejection]( + new JsonLdSourceResolvingParser( List(contexts.shacl, contexts.schemasMetadata), contextResolution, uuidF diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/ValidateSchema.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/ValidateSchema.scala index f82dea4c52..5dc0b88a77 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/ValidateSchema.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/ValidateSchema.scala @@ -7,9 +7,9 @@ import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri import ch.epfl.bluebrain.nexus.delta.rdf.graph.Graph import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.ExpandedJsonLd import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.JsonLdApi +import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.InvalidJsonLdFormat import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.RemoteContextResolution import ch.epfl.bluebrain.nexus.delta.rdf.shacl.{ShaclEngine, ShaclShapesGraph, ValidationReport} -import ch.epfl.bluebrain.nexus.delta.sdk.schemas.model.SchemaRejection.InvalidJsonLdFormat trait ValidateSchema { diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/model/SchemaRejection.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/model/SchemaRejection.scala index ff05a76d6a..20bcd2e3cc 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/model/SchemaRejection.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/model/SchemaRejection.scala @@ -1,18 +1,14 @@ package ch.epfl.bluebrain.nexus.delta.sdk.schemas.model import akka.http.scaladsl.model.StatusCodes -import ch.epfl.bluebrain.nexus.delta.kernel.Mapper import ch.epfl.bluebrain.nexus.delta.kernel.error.Rejection import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClassUtils import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri -import ch.epfl.bluebrain.nexus.delta.rdf.RdfError import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.contexts import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.ContextValue import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder import ch.epfl.bluebrain.nexus.delta.rdf.shacl.ValidationReport -import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection -import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.{BlankId, InvalidJsonLdRejection, UnexpectedId} import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.HttpResponseFields import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.ResourceResolutionReport import ch.epfl.bluebrain.nexus.delta.sdk.syntax._ @@ -77,11 +73,6 @@ object SchemaRejection { final case class InvalidSchemaId(id: String) extends SchemaFetchRejection(s"Schema identifier '$id' cannot be expanded to an Iri.") - /** - * Rejection returned when attempting to create a schema while providing an id that is blank. - */ - final case object BlankSchemaId extends SchemaRejection(s"Schema identifier cannot be blank.") - /** * Rejection returned when attempting to create a schema but the id already exists. * @@ -93,17 +84,6 @@ object SchemaRejection { final case class ResourceAlreadyExists(id: Iri, project: ProjectRef) extends SchemaRejection(s"Resource '$id' already exists in project '$project'.") - /** - * Rejection returned when attempting to create a schema where the passed id does not match the id on the payload. - * - * @param id - * the schema identifier - * @param payloadId - * the schema identifier on the payload - */ - final case class UnexpectedSchemaId(id: Iri, payloadId: Iri) - extends SchemaRejection(s"Schema '$id' does not match schema id on payload '$payloadId'.") - /** * Rejection returned when attempting to create/update a schema with a reserved id. */ @@ -185,12 +165,6 @@ object SchemaRejection { s"Incorrect revision '$provided' provided, expected '$expected', the schema may have been updated since last seen." ) - /** - * Signals an error converting the source Json to JsonLD - */ - final case class InvalidJsonLdFormat(idOpt: Option[Iri], rdfError: RdfError) - extends SchemaRejection(s"Schema${idOpt.fold("")(id => s" '$id'")} has invalid JSON-LD payload.") - implicit val schemasRejectionEncoder: Encoder.AsObject[SchemaRejection] = { def importsAsJson(imports: Map[ResourceRef, ResourceResolutionReport]) = Json.fromValues( @@ -204,7 +178,6 @@ object SchemaRejection { val obj = JsonObject.empty.add(keywords.tpe, tpe.asJson).add("reason", r.reason.asJson) r match { case SchemaShaclEngineRejection(_, details) => obj.add("details", details.asJson) - case InvalidJsonLdFormat(_, rdf) => obj.add("rdf", rdf.asJson) case InvalidSchema(_, report) => obj.addContext(contexts.shacl).add("details", report.json) case InvalidSchemaResolution(_, schemaImports, resourceImports, nonOntologyResources) => obj @@ -221,12 +194,6 @@ object SchemaRejection { implicit final val schemasRejectionJsonLdEncoder: JsonLdEncoder[SchemaRejection] = JsonLdEncoder.computeFromCirce(ContextValue(contexts.error)) - implicit val schemaJsonLdRejectionMapper: Mapper[InvalidJsonLdRejection, SchemaRejection] = { - case UnexpectedId(id, payloadIri) => UnexpectedSchemaId(id, payloadIri) - case JsonLdRejection.InvalidJsonLdFormat(id, rdfError) => InvalidJsonLdFormat(id, rdfError) - case BlankId => BlankSchemaId - } - implicit val responseFieldsSchemas: HttpResponseFields[SchemaRejection] = HttpResponseFields { case RevisionNotFound(_, _) => StatusCodes.NotFound diff --git a/delta/sdk/src/test/resources/directives/blank-id.json b/delta/sdk/src/test/resources/directives/blank-id.json deleted file mode 100644 index 66a095e9d3..0000000000 --- a/delta/sdk/src/test/resources/directives/blank-id.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "@context" : "https://bluebrain.github.io/nexus/contexts/error.json", - "@type" : "BlankResourceId", - "reason" : "Resource identifier cannot be blank." -} 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 d9c42069c5..2b886f546f 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 @@ -9,7 +9,7 @@ import akka.stream.scaladsl.Source import akka.util.ByteString import cats.effect.IO 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.Vocabulary.{contexts, nxv} 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 @@ -17,9 +17,10 @@ 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.resources.model.ResourceRejection.ResourceNotFound import ch.epfl.bluebrain.nexus.delta.sdk.utils.RouteHelpers import ch.epfl.bluebrain.nexus.delta.sdk.{AkkaSource, SimpleRejection, SimpleResource} +import ch.epfl.bluebrain.nexus.delta.sourcing.model.ProjectRef class ResponseToJsonLdSpec extends RouteHelpers with JsonSyntax with RouteConcatenation { @@ -38,10 +39,6 @@ class ResponseToJsonLdSpec extends RouteHelpers with JsonSyntax with RouteConcat ) } - private val expectedBlankIdErrorResponse = jsonContentOf( - "directives/blank-id.json" - ) - private val FileContents = "hello" private def fileSourceOfString(value: String) = { @@ -81,10 +78,12 @@ class ResponseToJsonLdSpec extends RouteHelpers with JsonSyntax with RouteConcat } "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 + val error = ResourceNotFound(nxv + "xxx", ProjectRef.unsafe("org", "proj")) + request ~> emit(responseWithSourceError[ResourceRejection](error)) ~> check { + status shouldEqual StatusCodes.NotFound contentType.mediaType shouldEqual `application/ld+json` - response.asJson shouldEqual expectedBlankIdErrorResponse + response.asJsonObject.apply("@type").flatMap(_.asString).value shouldEqual "ResourceNotFound" + } } } diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resolvers/ResolversImplSpec.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resolvers/ResolversImplSpec.scala index cd053e2f4d..7c14789283 100644 --- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resolvers/ResolversImplSpec.scala +++ b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resolvers/ResolversImplSpec.scala @@ -13,13 +13,14 @@ import ch.epfl.bluebrain.nexus.delta.sdk.generators.ProjectGen import ch.epfl.bluebrain.nexus.delta.sdk.generators.ResolverGen.{resolverResourceFor, sourceFrom, sourceWithoutId} import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller import ch.epfl.bluebrain.nexus.delta.sdk.implicits._ +import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.{DecodingFailed, UnexpectedId} import ch.epfl.bluebrain.nexus.delta.sdk.model._ import ch.epfl.bluebrain.nexus.delta.sdk.model.search.SearchParams.ResolverSearchParams import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ApiMappings import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ProjectRejection.{ProjectIsDeprecated, ProjectNotFound} import ch.epfl.bluebrain.nexus.delta.sdk.projects.{FetchContextDummy, Projects} import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.IdentityResolution.{ProvidedIdentities, UseCurrentCaller} -import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.ResolverRejection.{DecodingFailed, IncorrectRev, InvalidIdentities, InvalidResolverId, NoIdentities, PriorityAlreadyExists, ResolverIsDeprecated, ResolverNotFound, ResourceAlreadyExists, RevisionNotFound, UnexpectedResolverId} +import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.ResolverRejection.{IncorrectRev, InvalidIdentities, InvalidResolverId, NoIdentities, PriorityAlreadyExists, ResolverIsDeprecated, ResolverNotFound, ResourceAlreadyExists, RevisionNotFound} import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model.ResolverValue.{CrossProjectValue, InProjectValue} import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.model._ import ch.epfl.bluebrain.nexus.delta.sdk.resources.Resources @@ -195,7 +196,7 @@ class ResolversImplSpec extends CatsEffectSpec with DoobieScalaTestFixture with ) { case (id, value) => val payloadId = nxv + "resolver-fail" val payload = sourceFrom(payloadId, value) - resolvers.create(id, projectRef, payload).assertRejectedEquals(UnexpectedResolverId(id, payloadId)) + resolvers.create(id, projectRef, payload).assertRejectedEquals(UnexpectedId(id, payloadId)) } } @@ -383,7 +384,7 @@ class ResolversImplSpec extends CatsEffectSpec with DoobieScalaTestFixture with val payload = sourceFrom(payloadId, value) resolvers .update(id, projectRef, 2, payload) - .assertRejectedEquals(UnexpectedResolverId(id = id, payloadId = payloadId)) + .assertRejectedEquals(UnexpectedId(id = id, payloadId = payloadId)) } } diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesImplSpec.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesImplSpec.scala index d8b80d905c..96c26af3a9 100644 --- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesImplSpec.scala +++ b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesImplSpec.scala @@ -8,6 +8,7 @@ import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, RemoteContextResolution} import ch.epfl.bluebrain.nexus.delta.sdk.generators.{ProjectGen, ResourceGen, ResourceResolutionGen, SchemaGen} import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller +import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection._ import ch.epfl.bluebrain.nexus.delta.sdk.model.{IdSegment, IdSegmentRef, Tags} import ch.epfl.bluebrain.nexus.delta.sdk.projects.FetchContextDummy import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ApiMappings @@ -246,12 +247,11 @@ class ResourcesImplSpec "reject with different ids on the payload and passed" in { val otherId = nxv + "other" resources.create(otherId, projectRef, schemas.resources, source, None).rejected shouldEqual - UnexpectedResourceId(id = otherId, payloadId = myId) + UnexpectedId(id = otherId, payloadId = myId) } "reject if the id is blank" in { - resources.create(projectRef, schemas.resources, sourceWithBlankId, None).rejected shouldEqual - BlankResourceId + resources.create(projectRef, schemas.resources, sourceWithBlankId, None).rejected shouldEqual BlankId } "reject with ReservedResourceId" in { diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/SchemasImplSuite.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/SchemasImplSuite.scala index 5b2bae5ba2..0e6403bd06 100644 --- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/SchemasImplSuite.scala +++ b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/schemas/SchemasImplSuite.scala @@ -10,6 +10,7 @@ import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, RemoteCon import ch.epfl.bluebrain.nexus.delta.rdf.shacl.ShaclShapesGraph import ch.epfl.bluebrain.nexus.delta.sdk.ConfigFixtures import ch.epfl.bluebrain.nexus.delta.sdk.generators.{ProjectGen, SchemaGen} +import ch.epfl.bluebrain.nexus.delta.sdk.jsonld.JsonLdRejection.UnexpectedId import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller import ch.epfl.bluebrain.nexus.delta.sdk.model.{IdSegmentRef, Tags} import ch.epfl.bluebrain.nexus.delta.sdk.projects.FetchContextDummy @@ -113,7 +114,7 @@ class SchemasImplSuite extends NexusSuite with Doobie.Fixture with ConfigFixture test("Creating a schema fails with different ids on the payload and passed") { val otherId = nxv + "other" - schemas.create(otherId, projectRef, source).interceptEquals(UnexpectedSchemaId(id = otherId, payloadId = mySchema)) + schemas.create(otherId, projectRef, source).interceptEquals(UnexpectedId(id = otherId, payloadId = mySchema)) } test("Creating a schema fails if it already exists") { From 778b6c33917d9f99d42438e45eb795cc09db44eb Mon Sep 17 00:00:00 2001 From: Simon Dumas Date: Mon, 4 Mar 2024 18:42:29 +0100 Subject: [PATCH 2/2] Fix test --- delta/app/src/test/resources/resources/errors/blank-id.json | 5 ----- .../bluebrain/nexus/delta/routes/ResourcesRoutesSpec.scala | 4 +--- 2 files changed, 1 insertion(+), 8 deletions(-) delete mode 100644 delta/app/src/test/resources/resources/errors/blank-id.json diff --git a/delta/app/src/test/resources/resources/errors/blank-id.json b/delta/app/src/test/resources/resources/errors/blank-id.json deleted file mode 100644 index 66a095e9d3..0000000000 --- a/delta/app/src/test/resources/resources/errors/blank-id.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "@context" : "https://bluebrain.github.io/nexus/contexts/error.json", - "@type" : "BlankResourceId", - "reason" : "Resource identifier cannot be blank." -} diff --git a/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesRoutesSpec.scala b/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesRoutesSpec.scala index f7d83cfc4f..1a34cd9b27 100644 --- a/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesRoutesSpec.scala +++ b/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesRoutesSpec.scala @@ -245,9 +245,7 @@ class ResourcesRoutesSpec extends BaseRouteSpec with CatsIOValues { "fail if the id is blank" in { Post("/v1/resources/myorg/myproject/_/", payloadWithBlankId.toEntity) ~> asWriter ~> routes ~> check { response.status shouldEqual StatusCodes.BadRequest - response.asJson shouldEqual jsonContentOf( - "resources/errors/blank-id.json" - ) + response.asJson.hcursor.get[String]("@type").toOption should contain("BlankId") } }