From 1f634005094bd6447334b734ca9007d6109cb77f Mon Sep 17 00:00:00 2001 From: Ian Streeter Date: Mon, 3 Jun 2024 20:02:45 +0100 Subject: [PATCH] Fix listSchemasLike when multiple Iglu repositories host the schemas We had a problem with listSchemasLike when using the mustIncludeKey option, which was introduced in #215 For a particular schema... - Iglu Server 1 hosted versions `1-0-0` and `1-0-1` - Iglu Server 2 hosted versions `1-0-0`, `1-0-1`, `1-0-2` and `1-0-3` and `mustIncludeKey` was set to `1-0-3`. But `listSchemasLike` returned only `1-0-0` and `1-0-1` because Iglu Server 1 had higher priority. After this change, `listSchemasLike` will return the list from Iglu Server 2 under these circumstances. Even though it has lower priority, it is the server that contains the `mustIncludeKey`. --- .../client/resolver/Resolver.scala | 12 ++++- .../client_session/jsonschema/1-0-0 | 38 +++++++++++++ .../client_session/jsonschema/1-0-1 | 53 +++++++++++++++++++ .../resolver/ResolverSpec.scala | 28 ++++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 modules/core/src/test/resources/iglu-test-embedded/schemas/com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-0 create mode 100644 modules/core/src/test/resources/iglu-test-embedded/schemas/com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-1 diff --git a/modules/core/src/main/scala/com.snowplowanalytics.iglu/client/resolver/Resolver.scala b/modules/core/src/main/scala/com.snowplowanalytics.iglu/client/resolver/Resolver.scala index a94d05c5..771b71f0 100644 --- a/modules/core/src/main/scala/com.snowplowanalytics.iglu/client/resolver/Resolver.scala +++ b/modules/core/src/main/scala/com.snowplowanalytics.iglu/client/resolver/Resolver.scala @@ -220,7 +220,17 @@ final case class Resolver[F[_]](repos: List[Registry], cache: Option[ResolverCac L: RegistryLookup[F], C: Clock[F] ): F[Either[ResolutionError, SchemaListLookupResult]] = { - val get: Registry => F[Either[RegistryError, SchemaList]] = r => L.list(r, vendor, name, model) + val get: Registry => F[Either[RegistryError, SchemaList]] = { r => + L.list(r, vendor, name, model) + .map { either => + either.flatMap { schemaList => + if (mustIncludeKey.forall(schemaList.schemas.contains)) + Right(schemaList) + else + Left(RegistryError.NotFound) + } + } + } def handleAfterFetch( result: Either[LookupFailureMap, SchemaList] diff --git a/modules/core/src/test/resources/iglu-test-embedded/schemas/com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-0 b/modules/core/src/test/resources/iglu-test-embedded/schemas/com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-0 new file mode 100644 index 00000000..06607d43 --- /dev/null +++ b/modules/core/src/test/resources/iglu-test-embedded/schemas/com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-0 @@ -0,0 +1,38 @@ +{ + "$schema": "http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#", + "description": "Schema for a client-generated user session", + "self": { + "vendor": "com.snowplowanalytics.snowplow", + "name": "client_session", + "format": "jsonschema", + "version": "1-0-0" + }, + "type": "object", + "properties": { + "userId": { + "type": "string", + "pattern": "^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$|^[0-9a-f]{16}$" + }, + "sessionId": { + "type": "string", + "format": "uuid" + }, + "sessionIndex": { + "type": "integer", + "minimum": 0, + "maximum": 2147483647 + }, + "previousSessionId": { + "type": [ + "null", + "string" + ], + "format": "uuid" + }, + "storageMechanism": { + "enum": [ "SQLITE", "COOKIE_1", "COOKIE_3", "LOCAL_STORAGE", "FLASH_LSO" ] + } + }, + "required": [ "userId", "sessionId", "sessionIndex", "previousSessionId", "storageMechanism" ], + "additionalProperties": false +} diff --git a/modules/core/src/test/resources/iglu-test-embedded/schemas/com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-1 b/modules/core/src/test/resources/iglu-test-embedded/schemas/com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-1 new file mode 100644 index 00000000..2a130b36 --- /dev/null +++ b/modules/core/src/test/resources/iglu-test-embedded/schemas/com.snowplowanalytics.snowplow/client_session/jsonschema/1-0-1 @@ -0,0 +1,53 @@ +{ + "$schema": "http://iglucentral.com/schemas/com.snowplowanalytics.self-desc/schema/jsonschema/1-0-0#", + "description": "Schema for a client-generated user session", + "self": { + "vendor": "com.snowplowanalytics.snowplow", + "name": "client_session", + "format": "jsonschema", + "version": "1-0-1" + }, + "type": "object", + "properties": { + "userId": { + "type": "string", + "pattern": "^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$|^[0-9a-f]{16}$", + "maxLength": 36, + "description": "An identifier for the user of the session" + }, + "sessionId": { + "type": "string", + "format": "uuid", + "description": "An identifier for the session" + }, + "sessionIndex": { + "type": "integer", + "minimum": 0, + "maximum": 2147483647, + "description": "The index of the current session for this user" + }, + "previousSessionId": { + "type": [ + "null", + "string" + ], + "format": "uuid", + "description": "The previous session identifier for this user" + }, + "storageMechanism": { + "type": "string", + "enum": [ "SQLITE", "COOKIE_1", "COOKIE_3", "LOCAL_STORAGE", "FLASH_LSO" ], + "description": "The mechanism that the session information has been stored on the device" + }, + "firstEventId": { + "type": [ + "null", + "string" + ], + "format": "uuid", + "description": "The optional identifier of the first event id for this session" + } + }, + "required": [ "userId", "sessionId", "sessionIndex", "previousSessionId", "storageMechanism" ], + "additionalProperties": false +} diff --git a/modules/core/src/test/scala/com.snowplowanalytics.iglu.client/resolver/ResolverSpec.scala b/modules/core/src/test/scala/com.snowplowanalytics.iglu.client/resolver/ResolverSpec.scala index 636ddd10..ff4296b6 100644 --- a/modules/core/src/test/scala/com.snowplowanalytics.iglu.client/resolver/ResolverSpec.scala +++ b/modules/core/src/test/scala/com.snowplowanalytics.iglu.client/resolver/ResolverSpec.scala @@ -75,6 +75,7 @@ class ResolverSpec extends Specification with CatsEffect { we can construct a Resolver from a valid resolver 1-0-2 configuration JSON $e10 a Resolver should cache SchemaLists with different models separately $e11 a Resolver should use schemaKey provided in SchemaListLike for result validation $e12 + result from SchemaListLike should contain the exact schemaKey provided $e13 """ import ResolverSpec._ @@ -452,4 +453,31 @@ class ResolverSpec extends Specification with CatsEffect { SchemaList.parseUnsafe(List(schema100, schema101)) ) } + + def e13 = { + val IgluCentralServer = Registry.Http( + Registry.Config("Iglu Central EU1", 10, List("com.snowplowanalytics")), + Registry + .HttpConnection(URI.create("https://com-iglucentral-eu1-prod.iglu.snplow.net/api"), None) + ) + + val schema100 = SchemaKey( + "com.snowplowanalytics.snowplow", + "client_session", + "jsonschema", + SchemaVer.Full(1, 0, 0) + ) + val schema101 = schema100.copy(version = SchemaVer.Full(1, 0, 1)) + val schema102 = schema100.copy(version = SchemaVer.Full(1, 0, 2)) + + val resolver: Resolver[Id] = + Resolver.init[Id](10, None, IgluCentralServer, SpecHelpers.EmbeddedTest) + + // Embedded registry only contains 1-0-0 and 1-0-1 versions of client_session schema, while Iglu Central + // contains 1-0-0, 1-0-1 and 1-0-2 versions. When listSchemasLike is called with 1-0-2, even though Iglu Central + // has lower priority than embedded registry, we expect 1-0-2 to be returned as well in the result list. + val result = resolver.listSchemasLike(schema102) + + result must beRight(SchemaList.parseUnsafe(List(schema100, schema101, schema102))) + } }