From 5f5989bcf55ae33a985ac4cd3f055430dfea4782 Mon Sep 17 00:00:00 2001 From: Yaroslav Lepenkin Date: Mon, 15 May 2017 16:03:24 +0300 Subject: [PATCH] [stats-collector] introduced DeserializationResult to fail early --- .../events/completion/EventStreamValidator.kt | 8 +-- .../events/completion/LogEventSerializer.kt | 52 +++++++++++++++---- .../EventSerializeDeserializeTest.kt | 31 ++++++++++- 3 files changed, 76 insertions(+), 15 deletions(-) diff --git a/plugins/stats-collector/log-events/src/com/intellij/stats/events/completion/EventStreamValidator.kt b/plugins/stats-collector/log-events/src/com/intellij/stats/events/completion/EventStreamValidator.kt index 7d653a18a0f4..80a000f19384 100644 --- a/plugins/stats-collector/log-events/src/com/intellij/stats/events/completion/EventStreamValidator.kt +++ b/plugins/stats-collector/log-events/src/com/intellij/stats/events/completion/EventStreamValidator.kt @@ -34,7 +34,9 @@ open class SessionsInputSeparator(input: InputStream, var line: String? = inputReader.readLine() while (line != null) { - val event: LogEvent? = LogEventSerializer.fromString(line) + val event: LogEvent? = LogEventSerializer.fromString(line)?.event + //if there is any unknown or absent fields we should fail here + if (event == null) { handleNullEvent(line) continue @@ -46,7 +48,7 @@ open class SessionsInputSeparator(input: InputStream, currentSessionUid = event.sessionUid } - session.add(com.intellij.stats.events.completion.EventLine(event, line)) + session.add(EventLine(event, line)) line = inputReader.readLine() } @@ -63,7 +65,7 @@ open class SessionsInputSeparator(input: InputStream, val initial = session.first() if (initial.event is CompletionStartedEvent) { - val state = com.intellij.stats.events.completion.CompletionValidationState(initial.event) + val state = CompletionValidationState(initial.event) session.drop(1).forEach { state.accept(it.event) } isValidSession = state.isFinished && state.isValid } diff --git a/plugins/stats-collector/log-events/src/com/intellij/stats/events/completion/LogEventSerializer.kt b/plugins/stats-collector/log-events/src/com/intellij/stats/events/completion/LogEventSerializer.kt index 1e917feb6079..401d06aeeae5 100644 --- a/plugins/stats-collector/log-events/src/com/intellij/stats/events/completion/LogEventSerializer.kt +++ b/plugins/stats-collector/log-events/src/com/intellij/stats/events/completion/LogEventSerializer.kt @@ -16,16 +16,32 @@ package com.intellij.stats.events.completion import com.google.gson.Gson +import com.google.gson.internal.LinkedTreeMap object JsonSerializer { private val gson = Gson() - fun toJson(obj: Any) = gson.toJson(obj) - fun fromJson(json: String, clazz: Class) = gson.fromJson(json, clazz) + + fun toJson(obj: Any): String = gson.toJson(obj) + + fun fromJson(json: String, clazz: Class): DeserializationResult { + val declaredFields = clazz.declaredFields.map { it.name }.toSet() + val jsonFields = gson.fromJson(json, LinkedTreeMap::class.java).keys.map { it.toString() }.toSet() + val value = gson.fromJson(json, clazz) + + val unknownFields = jsonFields.subtract(declaredFields) + val absentFields = declaredFields.subtract(jsonFields) + + return DeserializationResult(value, unknownFields, absentFields) + } } +class DeserializationResult(val value: T, val unknownFields: Set, val absentFields: Set) + + object LogEventSerializer { + private val actionClassMap: Map> = mapOf( Action.COMPLETION_STARTED to CompletionStartedEvent::class.java, Action.TYPE to TypeEvent::class.java, @@ -38,11 +54,13 @@ object LogEventSerializer { Action.CUSTOM to CustomMessageEvent::class.java ) + fun toString(event: LogEvent): String { return "${event.timestamp}\t${event.recorderId}\t${event.userUid}\t${event.sessionUid}\t${event.actionType}\t${JsonSerializer.toJson(event)}" } - fun fromString(line: String): LogEvent? { + + fun fromString(line: String): DeserializedLogEvent? { val items = mutableListOf() var start = -1 @@ -62,15 +80,29 @@ object LogEventSerializer { val clazz = actionClassMap[actionType] ?: return null val json = line.substring(start + 1) - val obj = JsonSerializer.fromJson(json, clazz) + val result = JsonSerializer.fromJson(json, clazz) - obj.userUid = userUid - obj.timestamp = timestamp - obj.recorderId = recorderId - obj.sessionUid = sessionUid - obj.actionType = actionType + val event = result.value - return obj + event.userUid = userUid + event.timestamp = timestamp + event.recorderId = recorderId + event.sessionUid = sessionUid + event.actionType = actionType + + return DeserializedLogEvent(event, result.unknownFields, result.absentFields) } +} + + +class DeserializedLogEvent( + val event: LogEvent, + val unknownEventFields: Set, + val absentEventFields: Set +) { + + val isOk: Boolean + get() = unknownEventFields.isEmpty() || absentEventFields.isEmpty() + } \ No newline at end of file diff --git a/plugins/stats-collector/log-events/test/com/intellij/stats/events/completion/EventSerializeDeserializeTest.kt b/plugins/stats-collector/log-events/test/com/intellij/stats/events/completion/EventSerializeDeserializeTest.kt index b0db1172788e..6115666a2adb 100644 --- a/plugins/stats-collector/log-events/test/com/intellij/stats/events/completion/EventSerializeDeserializeTest.kt +++ b/plugins/stats-collector/log-events/test/com/intellij/stats/events/completion/EventSerializeDeserializeTest.kt @@ -1,6 +1,7 @@ package com.intellij.stats.events.completion import junit.framework.Assert.assertEquals +import org.assertj.core.api.Assertions.assertThat import org.junit.Test import java.util.* @@ -23,8 +24,8 @@ class EventSerializeDeserializeTest { private fun serializeDeserializeAndCheck(event: LogEvent) { val logLine = LogEventSerializer.toString(event) - val eventFromString = LogEventSerializer.fromString(logLine) - assertEquals(logLine, LogEventSerializer.toString(eventFromString!!)) + val eventFromString = LogEventSerializer.fromString(logLine)!!.event + assertEquals(logLine, LogEventSerializer.toString(eventFromString)) } @Test @@ -84,5 +85,31 @@ class EventSerializeDeserializeTest { val event = TypeEvent(Fixtures.userId, "xx", listOf(1,2,3), Fixtures.lookupList, 1) serializeDeserializeAndCheck(event) } + + @Test + fun `deserialization with info`() { + val json = JsonSerializer.toJson(First()) + val obj: DeserializationResult = JsonSerializer.fromJson(json, Second::class.java) + + assertThat(obj.absentFields).hasSize(2) + assertThat(obj.absentFields).contains("absent_field0").contains("absent_field1") + + assertThat(obj.unknownFields).hasSize(1) + assertThat(obj.unknownFields).contains("unknown_field") + } } + + +private class First { + val just_field: String = "" + val unknown_field: Int = 0 +} + + +class Second { + val just_field: String = "" + val absent_field0: Double = 1.0 + val absent_field1: Double = 1.0 +} +