From 523ff528b761eb4333333e497602d1940634b60d Mon Sep 17 00:00:00 2001 From: Oliver <20188437+olivergrabinski@users.noreply.github.com> Date: Tue, 3 Oct 2023 14:24:42 +0200 Subject: [PATCH] Handle deserialization of empty remote contexts in `ResourceEvent`s and `ResourceState` (#4317) --- .../sdk/resources/model/ResourceEvent.scala | 16 +++-- .../sdk/resources/model/ResourceState.scala | 8 ++- .../resource-created-no-remote-contexts.json | 54 ++++++++++++++++ ...resource-refreshed-no-remote-contexts.json | 42 +++++++++++++ .../resource-updated-no-remote-contexts.json | 54 ++++++++++++++++ .../resource-state-no-remote-contexts.json | 63 +++++++++++++++++++ .../model/ResourceSerializationSuite.scala | 41 ++++++++++-- .../init/V1_09_M05_002__remote_contexts.ddl | 14 ----- 8 files changed, 265 insertions(+), 27 deletions(-) create mode 100644 delta/sdk/src/test/resources/resources/database/resource-created-no-remote-contexts.json create mode 100644 delta/sdk/src/test/resources/resources/database/resource-refreshed-no-remote-contexts.json create mode 100644 delta/sdk/src/test/resources/resources/database/resource-updated-no-remote-contexts.json create mode 100644 delta/sdk/src/test/resources/resources/resource-state-no-remote-contexts.json delete mode 100644 delta/sourcing-psql/src/main/resources/scripts/postgres/init/V1_09_M05_002__remote_contexts.ddl diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceEvent.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceEvent.scala index dfd2f8a826..97d596de23 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceEvent.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceEvent.scala @@ -21,7 +21,7 @@ import ch.epfl.bluebrain.nexus.delta.sourcing.model.{EntityType, Label, ProjectR import io.circe.generic.extras.Configuration import io.circe.generic.extras.semiauto.{deriveConfiguredCodec, deriveConfiguredEncoder} import io.circe.syntax._ -import io.circe.{Codec, Decoder, Encoder, Json, JsonObject} +import io.circe._ import java.time.Instant import scala.annotation.nowarn @@ -90,7 +90,8 @@ object ResourceEvent { source: Json, compacted: CompactedJsonLd, expanded: ExpandedJsonLd, - remoteContexts: Set[RemoteContextRef], + // TODO: Remove default after 1.10 migration + remoteContexts: Set[RemoteContextRef] = Set.empty, rev: Int, instant: Instant, subject: Subject @@ -133,7 +134,8 @@ object ResourceEvent { source: Json, compacted: CompactedJsonLd, expanded: ExpandedJsonLd, - remoteContexts: Set[RemoteContextRef], + // TODO: Remove default after 1.10 migration + remoteContexts: Set[RemoteContextRef] = Set.empty, rev: Int, instant: Instant, subject: Subject @@ -173,7 +175,8 @@ object ResourceEvent { types: Set[Iri], compacted: CompactedJsonLd, expanded: ExpandedJsonLd, - remoteContexts: Set[RemoteContextRef], + // TODO: Remove default after 1.10 migration + remoteContexts: Set[RemoteContextRef] = Set.empty, rev: Int, instant: Instant, subject: Subject @@ -269,8 +272,9 @@ object ResourceEvent { import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.ExpandedJsonLd.Database._ import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.Database._ - //TODO remove after migration of events - implicit val configuration: Configuration = Serializer.circeConfiguration + // TODO: The `.withDefaults` method is used in order to inject the default empty remoteContexts + // when deserializing an event that has none. Remove it after 1.10 migration. + implicit val configuration: Configuration = Serializer.circeConfiguration.withDefaults implicit val coder: Codec.AsObject[ResourceEvent] = deriveConfiguredCodec[ResourceEvent] Serializer() diff --git a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceState.scala b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceState.scala index f8e99955ed..5e40514e42 100644 --- a/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceState.scala +++ b/delta/sdk/src/main/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceState.scala @@ -59,7 +59,8 @@ final case class ResourceState( source: Json, compacted: CompactedJsonLd, expanded: ExpandedJsonLd, - remoteContexts: Set[RemoteContextRef], + // TODO: Remove default after 1.10 migration + remoteContexts: Set[RemoteContextRef] = Set.empty, rev: Int, deprecated: Boolean, schema: ResourceRef, @@ -94,7 +95,10 @@ object ResourceState { import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.CompactedJsonLd.Database._ import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.ExpandedJsonLd.Database._ import ch.epfl.bluebrain.nexus.delta.sourcing.model.Identity.Database._ - implicit val configuration: Configuration = Serializer.circeConfiguration + + // TODO: The `.withDefaults` method is used in order to inject the default empty remoteContexts + // when deserializing an event that has none. Remove it after 1.10 migration. + implicit val configuration: Configuration = Serializer.circeConfiguration.withDefaults implicit val codec: Codec.AsObject[ResourceState] = deriveConfiguredCodec[ResourceState] Serializer() } diff --git a/delta/sdk/src/test/resources/resources/database/resource-created-no-remote-contexts.json b/delta/sdk/src/test/resources/resources/database/resource-created-no-remote-contexts.json new file mode 100644 index 0000000000..19326052c2 --- /dev/null +++ b/delta/sdk/src/test/resources/resources/database/resource-created-no-remote-contexts.json @@ -0,0 +1,54 @@ +{ + "id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "project": "myorg/myproj", + "schema": "https://bluebrain.github.io/nexus/schemas/unconstrained.json?rev=1", + "schemaProject": "myorg/myproj", + "types": [ + "https://neuroshapes.org/Morphology" + ], + "source": { + "@context": [ + "https://neuroshapes.org", + "https://bluebrain.github.io/nexus/contexts/metadata.json", + { + "@vocab": "https://bluebrain.github.io/nexus/vocabulary/" + } + ], + "@id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "@type": "Morphology", + "name": "Morphology 001" + }, + "compacted": { + "@context": [ + "https://neuroshapes.org", + "https://bluebrain.github.io/nexus/contexts/metadata.json", + { + "@vocab": "https://bluebrain.github.io/nexus/vocabulary/" + } + ], + "@id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "@type": "Morphology", + "name": "Morphology 001" + }, + "expanded": [ + { + "@id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "@type": [ + "https://neuroshapes.org/Morphology" + ], + "https://bluebrain.github.io/nexus/vocabulary/name": [ + { + "@value": "Morphology 001" + } + ] + } + ], + "rev": 1, + "instant": "1970-01-01T00:00:00Z", + "subject": { + "subject": "username", + "realm": "myrealm", + "@type": "User" + }, + "@type": "ResourceCreated" +} \ No newline at end of file diff --git a/delta/sdk/src/test/resources/resources/database/resource-refreshed-no-remote-contexts.json b/delta/sdk/src/test/resources/resources/database/resource-refreshed-no-remote-contexts.json new file mode 100644 index 0000000000..beb1843cf0 --- /dev/null +++ b/delta/sdk/src/test/resources/resources/database/resource-refreshed-no-remote-contexts.json @@ -0,0 +1,42 @@ +{ + "id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "project": "myorg/myproj", + "schema": "https://bluebrain.github.io/nexus/schemas/unconstrained.json?rev=1", + "schemaProject": "myorg/myproj", + "types" : [ + "https://neuroshapes.org/Morphology" + ], + "compacted": { + "@context": [ + "https://neuroshapes.org", + "https://bluebrain.github.io/nexus/contexts/metadata.json", + { + "@vocab": "https://bluebrain.github.io/nexus/vocabulary/" + } + ], + "@id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "@type": "Morphology", + "name": "Morphology 001" + }, + "expanded": [ + { + "@id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "@type": [ + "https://neuroshapes.org/Morphology" + ], + "https://bluebrain.github.io/nexus/vocabulary/name": [ + { + "@value": "Morphology 001" + } + ] + } + ], + "rev": 2, + "instant": "1970-01-01T00:00:00Z", + "subject": { + "subject": "username", + "realm": "myrealm", + "@type": "User" + }, + "@type": "ResourceRefreshed" +} \ No newline at end of file diff --git a/delta/sdk/src/test/resources/resources/database/resource-updated-no-remote-contexts.json b/delta/sdk/src/test/resources/resources/database/resource-updated-no-remote-contexts.json new file mode 100644 index 0000000000..82cda3addd --- /dev/null +++ b/delta/sdk/src/test/resources/resources/database/resource-updated-no-remote-contexts.json @@ -0,0 +1,54 @@ +{ + "id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "project": "myorg/myproj", + "schema": "https://bluebrain.github.io/nexus/schemas/unconstrained.json?rev=1", + "schemaProject": "myorg/myproj", + "types": [ + "https://neuroshapes.org/Morphology" + ], + "source": { + "@context": [ + "https://neuroshapes.org", + "https://bluebrain.github.io/nexus/contexts/metadata.json", + { + "@vocab": "https://bluebrain.github.io/nexus/vocabulary/" + } + ], + "@id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "@type": "Morphology", + "name": "Morphology 001" + }, + "compacted": { + "@context": [ + "https://neuroshapes.org", + "https://bluebrain.github.io/nexus/contexts/metadata.json", + { + "@vocab": "https://bluebrain.github.io/nexus/vocabulary/" + } + ], + "@id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "@type": "Morphology", + "name": "Morphology 001" + }, + "expanded": [ + { + "@id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "@type": [ + "https://neuroshapes.org/Morphology" + ], + "https://bluebrain.github.io/nexus/vocabulary/name": [ + { + "@value": "Morphology 001" + } + ] + } + ], + "rev": 2, + "instant": "1970-01-01T00:00:00Z", + "subject": { + "subject": "username", + "realm": "myrealm", + "@type": "User" + }, + "@type": "ResourceUpdated" +} \ No newline at end of file diff --git a/delta/sdk/src/test/resources/resources/resource-state-no-remote-contexts.json b/delta/sdk/src/test/resources/resources/resource-state-no-remote-contexts.json new file mode 100644 index 0000000000..0eb22794b8 --- /dev/null +++ b/delta/sdk/src/test/resources/resources/resource-state-no-remote-contexts.json @@ -0,0 +1,63 @@ +{ + "id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "project": "myorg/myproj", + "schemaProject": "myorg/myproj", + "source": { + "@context": [ + "https://neuroshapes.org", + "https://bluebrain.github.io/nexus/contexts/metadata.json", + { + "@vocab": "https://bluebrain.github.io/nexus/vocabulary/" + } + ], + "@id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "@type": "Morphology", + "name": "Morphology 001" + }, + "compacted": { + "@context": [ + "https://neuroshapes.org", + "https://bluebrain.github.io/nexus/contexts/metadata.json", + { + "@vocab": "https://bluebrain.github.io/nexus/vocabulary/" + } + ], + "@id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "@type": "Morphology", + "name": "Morphology 001" + }, + "expanded": [ + { + "@id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "@type": [ + "https://neuroshapes.org/Morphology" + ], + "https://bluebrain.github.io/nexus/vocabulary/name": [ + { + "@value": "Morphology 001" + } + ] + } + ], + "rev": 2, + "deprecated": false, + "schema": "https://bluebrain.github.io/nexus/schemas/unconstrained.json?rev=1", + "types": [ + "https://neuroshapes.org/Morphology" + ], + "tags": { + "mytag": 3 + }, + "createdAt": "1970-01-01T00:00:00Z", + "createdBy": { + "subject": "username", + "realm": "myrealm", + "@type": "User" + }, + "updatedAt": "1970-01-01T00:00:00Z", + "updatedBy": { + "subject": "username", + "realm": "myrealm", + "@type": "User" + } +} \ No newline at end of file diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceSerializationSuite.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceSerializationSuite.scala index d01a6dbced..395328cab2 100644 --- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceSerializationSuite.scala +++ b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/model/ResourceSerializationSuite.scala @@ -108,21 +108,21 @@ class ResourceSerializationSuite extends SerializationSuite with ResourceInstanc ) resourcesMapping.foreach { case (event, (database, sse), action) => - test(s"Correctly serialize ${event.getClass.getName}") { + test(s"Correctly serialize ${event.getClass.getSimpleName}") { assertOutput(ResourceEvent.serializer, event, database) } - test(s"Correctly deserialize ${event.getClass.getName}") { + test(s"Correctly deserialize ${event.getClass.getSimpleName}") { assertEquals(ResourceEvent.serializer.codec.decodeJson(database), Right(event)) } - test(s"Correctly serialize ${event.getClass.getName} as an SSE") { + test(s"Correctly serialize ${event.getClass.getSimpleName} as an SSE") { sseEncoder.toSse .decodeJson(database) .assertRight(SseData(ClassUtils.simpleName(event), Some(ProjectRef(org, proj)), sse)) } - test(s"Correctly encode ${event.getClass.getName} to metric") { + test(s"Correctly encode ${event.getClass.getSimpleName} to metric") { ResourceEvent.resourceEventMetricEncoder.toMetric.decodeJson(database).assertRight { ProjectScopedMetric( instant, @@ -139,6 +139,19 @@ class ResourceSerializationSuite extends SerializationSuite with ResourceInstanc } } + private val resourcesMappingNoRemoteContexts = List( + (created.noRemoteContext, jsonContentOf("resources/database/resource-created-no-remote-contexts.json")), + (updated.noRemoteContext, jsonContentOf("resources/database/resource-updated-no-remote-contexts.json")), + (refreshed.noRemoteContext, jsonContentOf("resources/database/resource-refreshed-no-remote-contexts.json")) + ) + + // TODO: Remove test after 1.10 migration. + resourcesMappingNoRemoteContexts.foreach { case (event, database) => + test(s"Correctly deserialize a ${event.getClass.getSimpleName} with no RemoteContext") { + assertEquals(ResourceEvent.serializer.codec.decodeJson(database), Right(event)) + } + } + private val state = ResourceState( myId, projectRef, @@ -158,7 +171,8 @@ class ResourceSerializationSuite extends SerializationSuite with ResourceInstanc updatedBy = subject ) - private val jsonState = jsonContentOf("/resources/resource-state.json") + private val jsonState = jsonContentOf("/resources/resource-state.json") + private val jsonStateNoRemoteContext = jsonContentOf("/resources/resource-state-no-remote-contexts.json") test(s"Correctly serialize a ResourceState") { assertOutput(ResourceState.serializer, state, jsonState) @@ -168,4 +182,21 @@ class ResourceSerializationSuite extends SerializationSuite with ResourceInstanc assertEquals(ResourceState.serializer.codec.decodeJson(jsonState), Right(state)) } + // TODO: Remove test after 1.10 migration. + test("Correctly deserialize a ResourceState with no remote contexts") { + assertEquals( + ResourceState.serializer.codec.decodeJson(jsonStateNoRemoteContext), + Right(state.copy(remoteContexts = Set.empty)) + ) + } + + implicit class ResourceEventTestOps(event: ResourceEvent) { + def noRemoteContext: ResourceEvent = event match { + case r: ResourceCreated => r.copy(remoteContexts = Set.empty) + case r: ResourceUpdated => r.copy(remoteContexts = Set.empty) + case r: ResourceRefreshed => r.copy(remoteContexts = Set.empty) + case r => r + } + } + } diff --git a/delta/sourcing-psql/src/main/resources/scripts/postgres/init/V1_09_M05_002__remote_contexts.ddl b/delta/sourcing-psql/src/main/resources/scripts/postgres/init/V1_09_M05_002__remote_contexts.ddl deleted file mode 100644 index 858030423e..0000000000 --- a/delta/sourcing-psql/src/main/resources/scripts/postgres/init/V1_09_M05_002__remote_contexts.ddl +++ /dev/null @@ -1,14 +0,0 @@ -------------------------------------------------- --- Add empty remoteContexts field to resources -- -------------------------------------------------- -UPDATE public.scoped_events -SET value = value || '{"remoteContexts": []}' -WHERE type = 'resource' -AND value ->> 'remoteContexts' is null -AND value ->> '@type' in ('ResourceCreated', 'ResourceUpdated', 'ResourceRefreshed'); - -UPDATE public.scoped_states -SET value = value || '{"remoteContexts": []}' -WHERE type = 'resource' -AND value ->> 'remoteContexts' is null -