Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.google.a2ui.core.schema.A2uiConstants
import com.google.a2ui.core.schema.A2uiVersion
import com.google.a2ui.core.schema.CatalogConfig
import com.google.a2ui.core.schema.SchemaResourceLoader
import com.google.a2ui.core.schema.resolveExamplesPath
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive

Expand Down Expand Up @@ -91,6 +92,6 @@ object BasicCatalog {
CatalogConfig(
name = BASIC_CATALOG_NAME,
provider = BundledCatalogProvider(version),
examplesPath = examplesPath,
examplesPath = resolveExamplesPath(examplesPath),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -402,13 +402,14 @@ abstract class StreamingParser(

if (isSu && sid != null) {
if (!seenSu.contains(sid)) {
dedupedMsgs.add(0, m)
dedupedMsgs.add(m)
seenSu.add(sid)
}
} else {
dedupedMsgs.add(0, m)
dedupedMsgs.add(m)
}
}
dedupedMsgs.reverse()
messages[i] = part.copy(a2uiJson = dedupedMsgs)
}

Expand Down Expand Up @@ -480,16 +481,17 @@ abstract class StreamingParser(
if (braceCount >= 0) {
val objBuffer = jsonBuffer.substring(startIdx)
if (objBuffer.startsWith("{") && objBuffer.endsWith("}")) {
val isTopLevel =
braceStack.isEmpty() ||
(inTopLevelList && braceStack.size == 1 && braceStack[0].first == "[")

try {
val obj = Json.parseToJsonElement(objBuffer) as? JsonObject
if (obj != null) {
foundValidJsonInBlock = true

val isProtocol = inTopLevelList && isProtocolMsg(obj)
val isComp = obj.containsKey("id") && obj.containsKey("component")
val isTopLevel =
braceStack.isEmpty() ||
(inTopLevelList && braceStack.size == 1 && braceStack[0].first == "[")

if (isComp) {
handlePartialComponent(obj, messages)
Expand All @@ -516,11 +518,8 @@ abstract class StreamingParser(
}
} catch (e: Exception) {
if (
(e is IllegalArgumentException &&
e !is kotlinx.serialization.SerializationException) ||
e.message?.contains("Circular reference") == true ||
e.message?.contains("Self-reference") == true ||
e.message?.contains("Validation failed") == true
e is IllegalArgumentException &&
e !is kotlinx.serialization.SerializationException
) {
throw e
}
Expand Down Expand Up @@ -588,7 +587,9 @@ abstract class StreamingParser(
"root" -> ROOT_ID_REGEX.find(jsonBuffer, idx)
else -> {
val fragment = jsonBuffer.substring(idx)
Regex("\"$key\"\\s*:\\s*\"([^\"]+)\"").find(fragment)
val regex =
LATEST_VALUE_REGEX_CACHE.getOrPut(key) { Regex("\"$key\"\\s*:\\s*\"([^\"]+)\"") }
regex.find(fragment)
}
}
if (match != null) {
Expand Down Expand Up @@ -630,6 +631,7 @@ abstract class StreamingParser(

protected fun sniffPartialDataModel(messages: MutableList<ResponsePart>) {
val msgType = dataModelMsgType

if (jsonBuffer.indexOf("\"$msgType\"") == -1) return

for (i in braceStack.indices.reversed()) {
Expand Down Expand Up @@ -996,6 +998,9 @@ abstract class StreamingParser(
if (pathElem != null) {
val currentPath = pathElem.jsonPrimitive.content
if (!currentPath.startsWith("/")) {
if (!map.containsKey("componentId")) {
map.clear()
}
map["path"] = JsonPrimitive("/$currentPath")
}
}
Expand Down Expand Up @@ -1093,6 +1098,7 @@ abstract class StreamingParser(
private val PREV_KEY_MATCHES_REGEX = Regex("\"key\"\\s*:\\s*\"([^\"]+)\"")
private val SURFACE_ID_REGEX = Regex("\"surfaceId\"\\s*:\\s*\"([^\"]+)\"")
private val ROOT_ID_REGEX = Regex("\"root\"\\s*:\\s*\"([^\"]+)\"")
private val LATEST_VALUE_REGEX_CACHE = mutableMapOf<String, Regex>()
private const val MAX_JSON_BUFFER_SIZE = 5 * 1024 * 1024

/** Factory method returning a version-specific parser instance. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,47 @@ data class CatalogConfig(
/** Create a [CatalogConfig] using a [FileSystemCatalogProvider]. */
@JvmStatic
@JvmOverloads
fun fromPath(name: String, catalogPath: String, examplesPath: String? = null): CatalogConfig =
CatalogConfig(name, FileSystemCatalogProvider(catalogPath), examplesPath)
fun fromPath(name: String, catalogPath: String, examplesPath: String? = null): CatalogConfig {
val uri =
try {
java.net.URI(catalogPath)
} catch (e: Exception) {
null
}
Comment on lines +48 to +51
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The java.net.URI constructor is strict and may throw URISyntaxException for valid local file paths that contain characters like spaces or colons (common on Windows). While the try-catch handles the exception, it's safer to check if the string looks like a URI before attempting to parse it, or use File(catalogPath).toURI() if a local path is intended.

val scheme = uri?.scheme?.lowercase()

val provider =
when {
scheme == null || scheme == "file" -> {
val path =
if (scheme == "file") java.nio.file.Paths.get(uri).toString() else catalogPath
FileSystemCatalogProvider(path)
}
scheme == "http" || scheme == "https" ->
throw NotImplementedError("HTTP support is coming soon.")
else -> throw IllegalArgumentException("Unsupported catalog URL scheme: $catalogPath")
}

return CatalogConfig(name, provider, resolveExamplesPath(examplesPath))
}
}
}

internal fun resolveExamplesPath(path: String?): String? {
if (path != null) {
val uri =
try {
java.net.URI(path)
} catch (e: Exception) {
null
}
val scheme = uri?.scheme?.lowercase()
if (scheme == null || scheme == "file") {
return if (scheme == "file") java.nio.file.Paths.get(uri).toString() else path
}
throw IllegalArgumentException("Unsupported examples URL scheme: $path")
}
return null
}

/** Represents a processed component catalog with its schema. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ interface A2uiCatalogProvider {
}

/** Loads catalog definition from the local filesystem. */
class FileSystemCatalogProvider(private val path: String) : A2uiCatalogProvider {
class FileSystemCatalogProvider(val path: String) : A2uiCatalogProvider {
override fun load(): JsonObject {
try {
val file = File(path)
Expand Down
Loading
Loading