Skip to content

Commit 909ecb8

Browse files
committed
Custom path-based validation error output
Implements custom JSON-path validation error string construction for v0.9 payloads in Kotlin, matching Python exactly. Validates messages individually via sub-validators and iterates component arrays to construct precise path prefixes. Port of Python SDK commit 15ee789 � Conflicts: � agent_sdks/kotlin/src/main/kotlin/com/google/a2ui/core/parser/StreamingParser.kt � agent_sdks/kotlin/src/main/kotlin/com/google/a2ui/core/schema/Validator.kt
1 parent c6bae76 commit 909ecb8

5 files changed

Lines changed: 526 additions & 104 deletions

File tree

agent_sdks/kotlin/src/main/kotlin/com/google/a2ui/core/parser/StreamingParser.kt

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -402,13 +402,14 @@ abstract class StreamingParser(
402402

403403
if (isSu && sid != null) {
404404
if (!seenSu.contains(sid)) {
405-
dedupedMsgs.add(0, m)
405+
dedupedMsgs.add(m)
406406
seenSu.add(sid)
407407
}
408408
} else {
409-
dedupedMsgs.add(0, m)
409+
dedupedMsgs.add(m)
410410
}
411411
}
412+
dedupedMsgs.reverse()
412413
messages[i] = part.copy(a2uiJson = dedupedMsgs)
413414
}
414415

@@ -480,16 +481,17 @@ abstract class StreamingParser(
480481
if (braceCount >= 0) {
481482
val objBuffer = jsonBuffer.substring(startIdx)
482483
if (objBuffer.startsWith("{") && objBuffer.endsWith("}")) {
484+
val isTopLevel =
485+
braceStack.isEmpty() ||
486+
(inTopLevelList && braceStack.size == 1 && braceStack[0].first == "[")
487+
483488
try {
484489
val obj = Json.parseToJsonElement(objBuffer) as? JsonObject
485490
if (obj != null) {
486491
foundValidJsonInBlock = true
487492

488493
val isProtocol = inTopLevelList && isProtocolMsg(obj)
489494
val isComp = obj.containsKey("id") && obj.containsKey("component")
490-
val isTopLevel =
491-
braceStack.isEmpty() ||
492-
(inTopLevelList && braceStack.size == 1 && braceStack[0].first == "[")
493495

494496
if (isComp) {
495497
handlePartialComponent(obj, messages)
@@ -516,11 +518,8 @@ abstract class StreamingParser(
516518
}
517519
} catch (e: Exception) {
518520
if (
519-
(e is IllegalArgumentException &&
520-
e !is kotlinx.serialization.SerializationException) ||
521-
e.message?.contains("Circular reference") == true ||
522-
e.message?.contains("Self-reference") == true ||
523-
e.message?.contains("Validation failed") == true
521+
e is IllegalArgumentException &&
522+
e !is kotlinx.serialization.SerializationException
524523
) {
525524
throw e
526525
}
@@ -588,7 +587,9 @@ abstract class StreamingParser(
588587
"root" -> ROOT_ID_REGEX.find(jsonBuffer, idx)
589588
else -> {
590589
val fragment = jsonBuffer.substring(idx)
591-
Regex("\"$key\"\\s*:\\s*\"([^\"]+)\"").find(fragment)
590+
val regex =
591+
LATEST_VALUE_REGEX_CACHE.getOrPut(key) { Regex("\"$key\"\\s*:\\s*\"([^\"]+)\"") }
592+
regex.find(fragment)
592593
}
593594
}
594595
if (match != null) {
@@ -630,6 +631,7 @@ abstract class StreamingParser(
630631

631632
protected fun sniffPartialDataModel(messages: MutableList<ResponsePart>) {
632633
val msgType = dataModelMsgType
634+
633635
if (jsonBuffer.indexOf("\"$msgType\"") == -1) return
634636

635637
for (i in braceStack.indices.reversed()) {
@@ -996,6 +998,9 @@ abstract class StreamingParser(
996998
if (pathElem != null) {
997999
val currentPath = pathElem.jsonPrimitive.content
9981000
if (!currentPath.startsWith("/")) {
1001+
if (!map.containsKey("componentId")) {
1002+
map.clear()
1003+
}
9991004
map["path"] = JsonPrimitive("/$currentPath")
10001005
}
10011006
}
@@ -1093,6 +1098,7 @@ abstract class StreamingParser(
10931098
private val PREV_KEY_MATCHES_REGEX = Regex("\"key\"\\s*:\\s*\"([^\"]+)\"")
10941099
private val SURFACE_ID_REGEX = Regex("\"surfaceId\"\\s*:\\s*\"([^\"]+)\"")
10951100
private val ROOT_ID_REGEX = Regex("\"root\"\\s*:\\s*\"([^\"]+)\"")
1101+
private val LATEST_VALUE_REGEX_CACHE = mutableMapOf<String, Regex>()
10961102
private const val MAX_JSON_BUFFER_SIZE = 5 * 1024 * 1024
10971103

10981104
/** Factory method returning a version-specific parser instance. */

0 commit comments

Comments
 (0)