Skip to content

Commit 0c69b0d

Browse files
feat(client): make union deserialization more robust (#104)
feat(client): add enum validation method chore(client): remove unnecessary json state from some query param classes chore(internal): add json roundtripping tests chore(internal): add invalid json deserialization tests
1 parent c1cde72 commit 0c69b0d

40 files changed

+4236
-115
lines changed

openlayer-java-core/src/main/kotlin/com/openlayer/api/core/BaseDeserializer.kt

+5-28
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@ import com.fasterxml.jackson.databind.BeanProperty
77
import com.fasterxml.jackson.databind.DeserializationContext
88
import com.fasterxml.jackson.databind.JavaType
99
import com.fasterxml.jackson.databind.JsonDeserializer
10-
import com.fasterxml.jackson.databind.JsonMappingException
1110
import com.fasterxml.jackson.databind.JsonNode
1211
import com.fasterxml.jackson.databind.deser.ContextualDeserializer
1312
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
14-
import com.openlayer.api.errors.OpenlayerInvalidDataException
1513
import kotlin.reflect.KClass
1614

1715
abstract class BaseDeserializer<T : Any>(type: KClass<T>) :
@@ -30,38 +28,17 @@ abstract class BaseDeserializer<T : Any>(type: KClass<T>) :
3028

3129
protected abstract fun ObjectCodec.deserialize(node: JsonNode): T
3230

33-
protected fun <T> ObjectCodec.deserialize(node: JsonNode, type: TypeReference<T>): T =
31+
protected fun <T> ObjectCodec.tryDeserialize(node: JsonNode, type: TypeReference<T>): T? =
3432
try {
3533
readValue(treeAsTokens(node), type)
3634
} catch (e: Exception) {
37-
throw OpenlayerInvalidDataException("Error deserializing", e)
38-
}
39-
40-
protected fun <T> ObjectCodec.tryDeserialize(
41-
node: JsonNode,
42-
type: TypeReference<T>,
43-
validate: (T) -> Unit = {},
44-
): T? {
45-
return try {
46-
readValue(treeAsTokens(node), type).apply(validate)
47-
} catch (e: JsonMappingException) {
48-
null
49-
} catch (e: RuntimeException) {
5035
null
5136
}
52-
}
5337

54-
protected fun <T> ObjectCodec.tryDeserialize(
55-
node: JsonNode,
56-
type: JavaType,
57-
validate: (T) -> Unit = {},
58-
): T? {
59-
return try {
60-
readValue<T>(treeAsTokens(node), type).apply(validate)
61-
} catch (e: JsonMappingException) {
62-
null
63-
} catch (e: RuntimeException) {
38+
protected fun <T> ObjectCodec.tryDeserialize(node: JsonNode, type: JavaType): T? =
39+
try {
40+
readValue(treeAsTokens(node), type)
41+
} catch (e: Exception) {
6442
null
6543
}
66-
}
6744
}

openlayer-java-core/src/main/kotlin/com/openlayer/api/core/Utils.kt

+28
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,34 @@ internal fun <K : Comparable<K>, V> SortedMap<K, V>.toImmutable(): SortedMap<K,
2525
if (isEmpty()) Collections.emptySortedMap()
2626
else Collections.unmodifiableSortedMap(toSortedMap(comparator()))
2727

28+
/**
29+
* Returns all elements that yield the largest value for the given function, or an empty list if
30+
* there are zero elements.
31+
*
32+
* This is similar to [Sequence.maxByOrNull] except it returns _all_ elements that yield the largest
33+
* value; not just the first one.
34+
*/
35+
@JvmSynthetic
36+
internal fun <T, R : Comparable<R>> Sequence<T>.allMaxBy(selector: (T) -> R): List<T> {
37+
var maxValue: R? = null
38+
val maxElements = mutableListOf<T>()
39+
40+
val iterator = iterator()
41+
while (iterator.hasNext()) {
42+
val element = iterator.next()
43+
val value = selector(element)
44+
if (maxValue == null || value > maxValue) {
45+
maxValue = value
46+
maxElements.clear()
47+
maxElements.add(element)
48+
} else if (value == maxValue) {
49+
maxElements.add(element)
50+
}
51+
}
52+
53+
return maxElements
54+
}
55+
2856
/**
2957
* Returns whether [this] is equal to [other].
3058
*

openlayer-java-core/src/main/kotlin/com/openlayer/api/models/commits/CommitRetrieveResponse.kt

+106-1
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,7 @@ private constructor(
805805
mlModelId()
806806
passingGoalCount()
807807
projectId()
808-
status()
808+
status().validate()
809809
statusMessage()
810810
storageUri()
811811
totalGoalCount()
@@ -817,6 +817,39 @@ private constructor(
817817
validated = true
818818
}
819819

820+
fun isValid(): Boolean =
821+
try {
822+
validate()
823+
true
824+
} catch (e: OpenlayerInvalidDataException) {
825+
false
826+
}
827+
828+
/**
829+
* Returns a score indicating how many valid values are contained in this object recursively.
830+
*
831+
* Used for best match union deserialization.
832+
*/
833+
@JvmSynthetic
834+
internal fun validity(): Int =
835+
(if (id.asKnown().isPresent) 1 else 0) +
836+
(commit.asKnown().getOrNull()?.validity() ?: 0) +
837+
(if (dateArchived.asKnown().isPresent) 1 else 0) +
838+
(if (dateCreated.asKnown().isPresent) 1 else 0) +
839+
(if (failingGoalCount.asKnown().isPresent) 1 else 0) +
840+
(if (mlModelId.asKnown().isPresent) 1 else 0) +
841+
(if (passingGoalCount.asKnown().isPresent) 1 else 0) +
842+
(if (projectId.asKnown().isPresent) 1 else 0) +
843+
(status.asKnown().getOrNull()?.validity() ?: 0) +
844+
(if (statusMessage.asKnown().isPresent) 1 else 0) +
845+
(if (storageUri.asKnown().isPresent) 1 else 0) +
846+
(if (totalGoalCount.asKnown().isPresent) 1 else 0) +
847+
(if (trainingDatasetId.asKnown().isPresent) 1 else 0) +
848+
(if (validationDatasetId.asKnown().isPresent) 1 else 0) +
849+
(if (archived.asKnown().isPresent) 1 else 0) +
850+
(if (deploymentStatus.asKnown().isPresent) 1 else 0) +
851+
(links.asKnown().getOrNull()?.validity() ?: 0)
852+
820853
/** The details of a commit (project version). */
821854
class Commit
822855
private constructor(
@@ -1416,6 +1449,35 @@ private constructor(
14161449
validated = true
14171450
}
14181451

1452+
fun isValid(): Boolean =
1453+
try {
1454+
validate()
1455+
true
1456+
} catch (e: OpenlayerInvalidDataException) {
1457+
false
1458+
}
1459+
1460+
/**
1461+
* Returns a score indicating how many valid values are contained in this object
1462+
* recursively.
1463+
*
1464+
* Used for best match union deserialization.
1465+
*/
1466+
@JvmSynthetic
1467+
internal fun validity(): Int =
1468+
(if (id.asKnown().isPresent) 1 else 0) +
1469+
(if (authorId.asKnown().isPresent) 1 else 0) +
1470+
(if (fileSize.asKnown().isPresent) 1 else 0) +
1471+
(if (message.asKnown().isPresent) 1 else 0) +
1472+
(if (mlModelId.asKnown().isPresent) 1 else 0) +
1473+
(if (storageUri.asKnown().isPresent) 1 else 0) +
1474+
(if (trainingDatasetId.asKnown().isPresent) 1 else 0) +
1475+
(if (validationDatasetId.asKnown().isPresent) 1 else 0) +
1476+
(if (dateCreated.asKnown().isPresent) 1 else 0) +
1477+
(if (gitCommitRef.asKnown().isPresent) 1 else 0) +
1478+
(if (gitCommitSha.asKnown().isPresent) 1 else 0) +
1479+
(if (gitCommitUrl.asKnown().isPresent) 1 else 0)
1480+
14191481
override fun equals(other: Any?): Boolean {
14201482
if (this === other) {
14211483
return true
@@ -1549,6 +1611,33 @@ private constructor(
15491611
OpenlayerInvalidDataException("Value is not a String")
15501612
}
15511613

1614+
private var validated: Boolean = false
1615+
1616+
fun validate(): Status = apply {
1617+
if (validated) {
1618+
return@apply
1619+
}
1620+
1621+
known()
1622+
validated = true
1623+
}
1624+
1625+
fun isValid(): Boolean =
1626+
try {
1627+
validate()
1628+
true
1629+
} catch (e: OpenlayerInvalidDataException) {
1630+
false
1631+
}
1632+
1633+
/**
1634+
* Returns a score indicating how many valid values are contained in this object
1635+
* recursively.
1636+
*
1637+
* Used for best match union deserialization.
1638+
*/
1639+
@JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1
1640+
15521641
override fun equals(other: Any?): Boolean {
15531642
if (this === other) {
15541643
return true
@@ -1680,6 +1769,22 @@ private constructor(
16801769
validated = true
16811770
}
16821771

1772+
fun isValid(): Boolean =
1773+
try {
1774+
validate()
1775+
true
1776+
} catch (e: OpenlayerInvalidDataException) {
1777+
false
1778+
}
1779+
1780+
/**
1781+
* Returns a score indicating how many valid values are contained in this object
1782+
* recursively.
1783+
*
1784+
* Used for best match union deserialization.
1785+
*/
1786+
@JvmSynthetic internal fun validity(): Int = (if (app.asKnown().isPresent) 1 else 0)
1787+
16831788
override fun equals(other: Any?): Boolean {
16841789
if (this === other) {
16851790
return true

openlayer-java-core/src/main/kotlin/com/openlayer/api/models/commits/testresults/TestResultListParams.kt

+54
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,33 @@ private constructor(
409409
OpenlayerInvalidDataException("Value is not a String")
410410
}
411411

412+
private var validated: Boolean = false
413+
414+
fun validate(): Status = apply {
415+
if (validated) {
416+
return@apply
417+
}
418+
419+
known()
420+
validated = true
421+
}
422+
423+
fun isValid(): Boolean =
424+
try {
425+
validate()
426+
true
427+
} catch (e: OpenlayerInvalidDataException) {
428+
false
429+
}
430+
431+
/**
432+
* Returns a score indicating how many valid values are contained in this object
433+
* recursively.
434+
*
435+
* Used for best match union deserialization.
436+
*/
437+
@JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1
438+
412439
override fun equals(other: Any?): Boolean {
413440
if (this === other) {
414441
return true
@@ -531,6 +558,33 @@ private constructor(
531558
OpenlayerInvalidDataException("Value is not a String")
532559
}
533560

561+
private var validated: Boolean = false
562+
563+
fun validate(): Type = apply {
564+
if (validated) {
565+
return@apply
566+
}
567+
568+
known()
569+
validated = true
570+
}
571+
572+
fun isValid(): Boolean =
573+
try {
574+
validate()
575+
true
576+
} catch (e: OpenlayerInvalidDataException) {
577+
false
578+
}
579+
580+
/**
581+
* Returns a score indicating how many valid values are contained in this object
582+
* recursively.
583+
*
584+
* Used for best match union deserialization.
585+
*/
586+
@JvmSynthetic internal fun validity(): Int = if (value() == Value._UNKNOWN) 0 else 1
587+
534588
override fun equals(other: Any?): Boolean {
535589
if (this === other) {
536590
return true

0 commit comments

Comments
 (0)