Skip to content

Commit eb28d9a

Browse files
committed
Fix for _schema_version overriding non-object entity data
We recommend that Snowplow entities should be JSON *objects* only. But technically it is possible for them to be something else, e.g. a JSON array. Since version 3.2.0 we started adding the `_schema_version` field to context entities, see #132. But this accidentally clobbered the original data if the entity was not an object. This commit fixes the problem by only adding `_schema_version` if the entity is an object.
1 parent 5599caf commit eb28d9a

File tree

4 files changed

+105
-5
lines changed

4 files changed

+105
-5
lines changed

build.sbt

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ lazy val root = project
4646
// Scala (test only)
4747
Dependencies.specs2,
4848
Dependencies.specs2Scalacheck,
49-
Dependencies.scalacheck
49+
Dependencies.scalacheck,
50+
Dependencies.circeLiteral
5051
)
5152
)
5253

project/Dependencies.scala

+1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ object Dependencies {
3131
val specs2 = "org.specs2" %% "specs2-core" % V.specs2 % Test
3232
val specs2Scalacheck = "org.specs2" %% "specs2-scalacheck" % V.specs2 % Test
3333
val scalacheck = "org.scalacheck" %% "scalacheck" % V.scalaCheck % Test
34+
val circeLiteral = "io.circe" %% "circe-literal" % V.circe % Test
3435
}

src/main/scala/com.snowplowanalytics.snowplow.analytics.scalasdk/SnowplowEvent.scala

+5-4
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,11 @@ object SnowplowEvent {
6868
}
6969
}
7070

71-
private def addSchemaVersionToData(contextSdd: SelfDescribingData[Json]): Json = {
72-
val version = Json.obj("_schema_version" -> contextSdd.schema.version.asString.asJson)
73-
contextSdd.data.deepMerge(version)
74-
}
71+
private def addSchemaVersionToData(contextSdd: SelfDescribingData[Json]): Json =
72+
if (contextSdd.data.isObject) {
73+
val version = Json.obj("_schema_version" -> contextSdd.schema.version.asString.asJson)
74+
contextSdd.data.deepMerge(version)
75+
} else contextSdd.data
7576

7677
implicit final val contextsCirceEncoder: Encoder[Contexts] =
7778
Encoder.instance { contexts =>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2016-2020 Snowplow Analytics Ltd. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0.
7+
*
8+
* Unless required by applicable law or agreed to in writing,
9+
* software distributed under the Apache License Version 2.0 is distributed on an
10+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
12+
*/
13+
14+
package com.snowplowanalytics.snowplow.analytics.scalasdk
15+
16+
// java
17+
import java.time.Instant
18+
import java.util.UUID
19+
import java.nio.ByteBuffer
20+
import java.nio.charset.StandardCharsets
21+
22+
// cats
23+
import cats.data.Validated.{Invalid, Valid}
24+
import cats.data.NonEmptyList
25+
import cats.syntax.either._
26+
27+
// circe
28+
import io.circe.{Decoder, Encoder, Json, JsonObject}
29+
import io.circe.syntax._
30+
import io.circe.parser._
31+
import io.circe.generic.semiauto._
32+
import io.circe.literal._
33+
34+
// Specs2
35+
import org.specs2.mutable.Specification
36+
37+
// Iglu
38+
import com.snowplowanalytics.iglu.core.{SchemaKey, SelfDescribingData}
39+
40+
/**
41+
* Tests Event case class
42+
*/
43+
class SnowplowEventSpec extends Specification {
44+
import EventSpec._
45+
46+
"Contexts toShreddedJson" should {
47+
"return a map of JSON entities, keyed by column name" in {
48+
49+
val sdd1 = SelfDescribingData[Json](SchemaKey.fromUri("iglu:myvendor1/myname1/jsonschema/1-2-3").toOption.get, json"""{"xyz": 42}""")
50+
val sdd2 =
51+
SelfDescribingData[Json](SchemaKey.fromUri("iglu:myvendor2/myname2/jsonschema/2-3-4").toOption.get, json"""{"abc": true}""")
52+
53+
val input = SnowplowEvent.Contexts(List(sdd1, sdd2))
54+
55+
val result = input.toShreddedJson
56+
57+
val expected = Map(
58+
"contexts_myvendor1_myname1_1" -> json"""[{"_schema_version": "1-2-3", "xyz": 42}]""",
59+
"contexts_myvendor2_myname2_2" -> json"""[{"_schema_version": "2-3-4", "abc": true}]"""
60+
)
61+
62+
result must beEqualTo(expected)
63+
64+
}
65+
66+
"return a map of JSON entities for all types of JSON value (object, array, string, number, boolean, null)" in {
67+
68+
def sdd(version: Int, v: Json): SelfDescribingData[Json] =
69+
SelfDescribingData[Json](SchemaKey.fromUri(s"iglu:myvendor/myname/jsonschema/$version-0-0").toOption.get, v)
70+
71+
val input = SnowplowEvent.Contexts(
72+
List(
73+
sdd(1, json"""{"xyz": 123}"""),
74+
sdd(2, json"""[1, 2, 3]"""),
75+
sdd(3, json""""foo""""),
76+
sdd(4, json"""42"""),
77+
sdd(5, json"""true"""),
78+
sdd(6, json"""null""")
79+
)
80+
)
81+
82+
val result = input.toShreddedJson
83+
84+
val expected = Map(
85+
"contexts_myvendor_myname_1" -> json"""[{"_schema_version": "1-0-0", "xyz": 123}]""",
86+
"contexts_myvendor_myname_2" -> json"""[[1, 2, 3]]""",
87+
"contexts_myvendor_myname_3" -> json"""["foo"]""",
88+
"contexts_myvendor_myname_4" -> json"""[42]""",
89+
"contexts_myvendor_myname_5" -> json"""[true]""",
90+
"contexts_myvendor_myname_6" -> json"""[null]"""
91+
)
92+
93+
result must beEqualTo(expected)
94+
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)