From c624345fb426bdd36f30beafe420feb7fa8b2ebe Mon Sep 17 00:00:00 2001 From: acrusage <139230478+acrusage@users.noreply.github.com> Date: Mon, 22 Apr 2024 12:51:13 +0200 Subject: [PATCH] Add: Finalize compiler --- ...ntlrJsonPathExpressionEvaluationVisitor.kt | 46 +- .../jsonPath/AntlrJsonPathParserExtensions.kt | 2 +- .../AntlrJsonPathTypeCheckerExpressionType.kt | 1 + .../AntlrJsonPathTypeCheckerVisitor.kt | 37 +- .../JsonPathFunctionExtensionManager.kt | 11 +- .../wallet/lib/data/jsonPath/Rfc8259Utils.kt | 9 +- .../wallet/lib/data/jsonPath/Rfc9535Utils.kt | 10 +- .../wallet/lib/agent/AgentSdJwtTest.kt | 8 +- .../lib/data/jsonPath/JsonPathUnitTest.kt | 401 ++++++++++++++---- .../data/jsonPath/JsonStringLiteralTest.kt | 48 +-- 10 files changed, 407 insertions(+), 166 deletions(-) diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathExpressionEvaluationVisitor.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathExpressionEvaluationVisitor.kt index 7971cf096..da1b3c4e9 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathExpressionEvaluationVisitor.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathExpressionEvaluationVisitor.kt @@ -134,8 +134,27 @@ class AntlrJsonPathExpressionEvaluationVisitor( second: JsonPathExpressionValue.ValueTypeValue, ): Boolean = when (first) { is JsonPathExpressionValue.ValueTypeValue.JsonValue -> { - when (first.jsonElement) { - is JsonArray -> if (second is JsonPathExpressionValue.ValueTypeValue.JsonValue) { + if (second !is JsonPathExpressionValue.ValueTypeValue.JsonValue) { + false + } else when (first.jsonElement) { + JsonNull -> { + second.jsonElement == JsonNull + } + + is JsonPrimitive -> { + if (second.jsonElement is JsonPrimitive) { + when { + first.jsonElement.isString != second.jsonElement.isString -> false + first.jsonElement.isString -> first.jsonElement.content == second.jsonElement.content + else -> first.jsonElement.booleanOrNull?.let { it == second.jsonElement.booleanOrNull } + ?: first.jsonElement.longOrNull?.let { it == second.jsonElement.longOrNull } + ?: first.jsonElement.doubleOrNull?.let { it == second.jsonElement.doubleOrNull } + ?: false + } + } else false + } + + is JsonArray -> { if (second.jsonElement is JsonArray) { (first.jsonElement.size == second.jsonElement.size) and first.jsonElement.mapIndexed { index, it -> index to it @@ -146,9 +165,9 @@ class AntlrJsonPathExpressionEvaluationVisitor( ) } } else false - } else false + } - is JsonObject -> if (second is JsonPathExpressionValue.ValueTypeValue.JsonValue) { + is JsonObject -> { if (second.jsonElement is JsonObject) { (first.jsonElement.keys == second.jsonElement.keys) and first.jsonElement.entries.all { this.evaluateComparisonEqualsUnpacked( @@ -163,24 +182,7 @@ class AntlrJsonPathExpressionEvaluationVisitor( ) } } else false - } else false - - is JsonPrimitive -> if (second is JsonPathExpressionValue.ValueTypeValue.JsonValue) { - if (second.jsonElement is JsonPrimitive) { - when { - first.jsonElement.isString != second.jsonElement.isString -> false - first.jsonElement.isString -> first.jsonElement.content == second.jsonElement.content - else -> first.jsonElement.booleanOrNull?.let { it == second.jsonElement.booleanOrNull } - ?: first.jsonElement.longOrNull?.let { it == second.jsonElement.longOrNull } - ?: first.jsonElement.doubleOrNull?.let { it == second.jsonElement.doubleOrNull } - ?: false - } - } else false - } else false - - JsonNull -> if (second is JsonPathExpressionValue.ValueTypeValue.JsonValue) { - second.jsonElement == JsonNull - } else false + } } } diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathParserExtensions.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathParserExtensions.kt index c0308447f..d902a64b0 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathParserExtensions.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathParserExtensions.kt @@ -3,6 +3,6 @@ package at.asitplus.wallet.lib.data.jsonPath import at.asitplus.parser.generated.JsonPathParser fun JsonPathParser.StringLiteralContext.toUnescapedString(): String { - return rfc9535Utils.unpackStringLiteral(this.text) + return Rfc9535Utils.unpackStringLiteral(this.text) } diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathTypeCheckerExpressionType.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathTypeCheckerExpressionType.kt index c7707763f..6a6fa581d 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathTypeCheckerExpressionType.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathTypeCheckerExpressionType.kt @@ -15,5 +15,6 @@ sealed class AntlrJsonPathTypeCheckerExpressionType(val jsonPathExpressionType: data object FunctionNodesType : NodesType() } + data object NoType : AntlrJsonPathTypeCheckerExpressionType(null) data object ErrorType : AntlrJsonPathTypeCheckerExpressionType(null) } \ No newline at end of file diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathTypeCheckerVisitor.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathTypeCheckerVisitor.kt index b21a25320..e3c509e55 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathTypeCheckerVisitor.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/AntlrJsonPathTypeCheckerVisitor.kt @@ -11,6 +11,9 @@ class AntlrJsonPathTypeCheckerVisitor( // see section 2.4.3: Well-Typedness of Function Expressions // - https://datatracker.ietf.org/doc/rfc9535/ + override fun defaultResult(): AntlrJsonPathTypeCheckerExpressionType { + return AntlrJsonPathTypeCheckerExpressionType.NoType + } override fun aggregateResult( aggregate: AntlrJsonPathTypeCheckerExpressionType?, nextResult: AntlrJsonPathTypeCheckerExpressionType @@ -61,6 +64,23 @@ class AntlrJsonPathTypeCheckerVisitor( return AntlrJsonPathTypeCheckerExpressionType.ValueType } + override fun visitTest_expr(ctx: JsonPathParser.Test_exprContext): AntlrJsonPathTypeCheckerExpressionType { + return ctx.function_expr()?.let { functionExpressionContext -> + when (visitFunction_expr(functionExpressionContext)) { + is AntlrJsonPathTypeCheckerExpressionType.ValueType -> { + errorListener + ?.invalidFunctionExtensionForTestExpression( + functionExpressionContext.FUNCTION_NAME().text, + ) + AntlrJsonPathTypeCheckerExpressionType.ErrorType + } + is AntlrJsonPathTypeCheckerExpressionType.ErrorType -> AntlrJsonPathTypeCheckerExpressionType.ErrorType + + else -> AntlrJsonPathTypeCheckerExpressionType.LogicalType + } + } ?: AntlrJsonPathTypeCheckerExpressionType.LogicalType // otherwise this is a filter query + } + override fun visitFunction_expr(ctx: JsonPathParser.Function_exprContext): AntlrJsonPathTypeCheckerExpressionType { val functionArgumentTypes = ctx.function_argument().map { visitFunction_argument(it) @@ -123,23 +143,6 @@ class AntlrJsonPathTypeCheckerVisitor( } } - override fun visitTest_expr(ctx: JsonPathParser.Test_exprContext): AntlrJsonPathTypeCheckerExpressionType { - return ctx.function_expr()?.let { functionExpressionContext -> - when (visitFunction_expr(functionExpressionContext)) { - is AntlrJsonPathTypeCheckerExpressionType.ValueType -> { - errorListener - ?.invalidFunctionExtensionForTestExpression( - functionExpressionContext.FUNCTION_NAME().text, - ) - AntlrJsonPathTypeCheckerExpressionType.ErrorType - } - is AntlrJsonPathTypeCheckerExpressionType.ErrorType -> AntlrJsonPathTypeCheckerExpressionType.ErrorType - - else -> AntlrJsonPathTypeCheckerExpressionType.LogicalType - } - } ?: AntlrJsonPathTypeCheckerExpressionType.LogicalType // otherwise this is a filter query - } - override fun visitComparison_expr(ctx: JsonPathParser.Comparison_exprContext): AntlrJsonPathTypeCheckerExpressionType { // evaluate all comparables in order to find as many errors as possible // otherwise, a comparison always returns a boolean anyway diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/JsonPathFunctionExtensionManager.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/JsonPathFunctionExtensionManager.kt index 51dfd0a79..cae979af3 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/JsonPathFunctionExtensionManager.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/JsonPathFunctionExtensionManager.kt @@ -8,11 +8,12 @@ import at.asitplus.wallet.lib.data.jsonPath.functionExtensions.ValueFunctionExte interface JsonPathFunctionExtensionManager { fun addExtension(functionExtension: JsonPathFunctionExtension<*>) + fun putExtension(functionExtension: JsonPathFunctionExtension<*>) fun removeExtension(functionExtensionName: String) fun getExtension(name: String): JsonPathFunctionExtension<*>? } -val defaultFunctionExtensionManager: JsonPathFunctionExtensionManager by lazy { +val defaultFunctionExtensionManager by lazy { object : JsonPathFunctionExtensionManager { private val extensions: MutableMap> = mutableMapOf() @@ -20,7 +21,11 @@ val defaultFunctionExtensionManager: JsonPathFunctionExtensionManager by lazy { if(extensions.containsKey(functionExtension.name)) { throw FunctionExtensionCollisionException(functionExtension.name) } - extensions.put(functionExtension.name, functionExtension) + extensions[functionExtension.name] = functionExtension + } + + override fun putExtension(functionExtension: JsonPathFunctionExtension<*>) { + extensions[functionExtension.name] = functionExtension } override fun removeExtension(functionExtensionName: String) { @@ -28,7 +33,7 @@ val defaultFunctionExtensionManager: JsonPathFunctionExtensionManager by lazy { } override fun getExtension(name: String): JsonPathFunctionExtension<*>? { - return extensions.get(name) + return extensions[name] } }.apply { addExtension(LengthFunctionExtension) diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/Rfc8259Utils.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/Rfc8259Utils.kt index 4c9cc0d50..2ac64c0d3 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/Rfc8259Utils.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/Rfc8259Utils.kt @@ -6,13 +6,8 @@ import org.antlr.v4.kotlinruntime.CharStreams import org.antlr.v4.kotlinruntime.CommonTokenStream interface Rfc8259Utils { - - fun unpackStringLiteral(string: String): String -} - -val rfc8259Utils by lazy { - object : Rfc8259Utils { - override fun unpackStringLiteral(string: String): String { + companion object { + fun unpackStringLiteral(string: String): String { val lexer = JsonStringLiteralLexer(CharStreams.fromString(string)).apply { addErrorListener(NapierAntlrErrorListener("JSONStringLiteralLexer")) } diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/Rfc9535Utils.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/Rfc9535Utils.kt index 92d0e2306..e61879439 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/Rfc9535Utils.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/data/jsonPath/Rfc9535Utils.kt @@ -1,12 +1,8 @@ package at.asitplus.wallet.lib.data.jsonPath interface Rfc9535Utils { - fun unpackStringLiteral(string: String): String -} - -val rfc9535Utils by lazy { - object : Rfc9535Utils { - override fun unpackStringLiteral(string: String): String { + companion object { + fun unpackStringLiteral(string: String): String { val doubleQuotedString = if (string.startsWith("\"")) { string // treat as normal rfc8259 double quoted string } else { @@ -18,7 +14,7 @@ val rfc9535Utils by lazy { "\"$it\"" } } - return rfc8259Utils.unpackStringLiteral(doubleQuotedString) + return Rfc8259Utils.unpackStringLiteral(doubleQuotedString) } } } \ No newline at end of file diff --git a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/AgentSdJwtTest.kt b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/AgentSdJwtTest.kt index adf2a840b..d67ba9b2c 100644 --- a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/AgentSdJwtTest.kt +++ b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/AgentSdJwtTest.kt @@ -55,7 +55,7 @@ class AgentSdJwtTest : FreeSpec({ constraints = Constraint( fields = listOf( ConstraintField( - path = listOf("$.given-name") + path = listOf("$['given-name']") ) ) ) @@ -89,7 +89,7 @@ class AgentSdJwtTest : FreeSpec({ constraints = Constraint( fields = listOf( ConstraintField( - path = listOf("$.given-name") + path = listOf("$['given-name']") ) ) ) @@ -125,7 +125,7 @@ class AgentSdJwtTest : FreeSpec({ constraints = Constraint( fields = listOf( ConstraintField( - path = listOf("$.given-name") + path = listOf("$['given-name']") ) ) ) @@ -155,7 +155,7 @@ class AgentSdJwtTest : FreeSpec({ constraints = Constraint( fields = listOf( ConstraintField( - path = listOf("$.given-name") + path = listOf("$['given-name']") ) ) ) diff --git a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/data/jsonPath/JsonPathUnitTest.kt b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/data/jsonPath/JsonPathUnitTest.kt index 186baa7a0..db2de116b 100644 --- a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/data/jsonPath/JsonPathUnitTest.kt +++ b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/data/jsonPath/JsonPathUnitTest.kt @@ -17,7 +17,7 @@ import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive class JsonPathUnitTest : FreeSpec({ - "tests from https://datatracker.ietf.org/doc/rfc9535/" - { + "Examples from https://datatracker.ietf.org/doc/rfc9535/" - { "1.5. JSONPath Examples" - { val bookStore = jsonSerializer.decodeFromString( " { \"store\": {\n" + @@ -221,53 +221,50 @@ class JsonPathUnitTest : FreeSpec({ } } - "2.1.3. Example" - { + "2.1. Overview" - { val jsonElement = jsonSerializer.decodeFromString("{\"a\":[{\"b\":0},{\"b\":1},{\"c\":2}]}") "\$" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 1 jsonElement.shouldBeIn(nodeList) } "\$.a" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 1 jsonElement.jsonObject["a"].shouldNotBeNull().shouldBeIn(nodeList) } "\$.a[*]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 3 jsonElement.jsonObject["a"].shouldNotBeNull().jsonArray.forEach { it.shouldBeIn(nodeList) } } "\$.a[*].b" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 2 jsonElement.jsonObject["a"].shouldNotBeNull().jsonArray.forEach { it.shouldBeInstanceOf()["b"]?.jsonPrimitive?.shouldBeIn(nodeList) } } } - "2.2.3. Examples" - { + + "2.2. Root Identifier" - { val jsonElement = jsonSerializer.decodeFromString("{\"k\": \"v\"}") "$" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 1 jsonElement.shouldBeIn(nodeList) } } - "2.3.1.3. Examples" - { + + "2.3.1. Name Selector" - { val jsonElement = jsonSerializer.decodeFromString( "{\n" + " \"o\": {\"j j\": {\"k.k\": 3}},\n" + @@ -275,18 +272,16 @@ class JsonPathUnitTest : FreeSpec({ " }" ) "\$.o['j j']" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 1 jsonElement.jsonObject.get("o") .shouldNotBeNull().jsonObject["j j"] .shouldNotBeNull().shouldBeIn(nodeList) } "\$.o['j j']['k.k']" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 1 jsonElement.jsonObject.get("o") .shouldNotBeNull().jsonObject["j j"] @@ -294,9 +289,8 @@ class JsonPathUnitTest : FreeSpec({ .shouldNotBeNull().shouldBeIn(nodeList) } "\$.o[\"j j\"][\"k.k\"]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 1 jsonElement.jsonObject.get("o") .shouldNotBeNull().jsonObject["j j"] @@ -304,9 +298,8 @@ class JsonPathUnitTest : FreeSpec({ .shouldNotBeNull().shouldBeIn(nodeList) } "\$[\"'\"][\"@\"]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 1 jsonElement.jsonObject.get("'") .shouldNotBeNull().jsonObject["@"] @@ -314,7 +307,7 @@ class JsonPathUnitTest : FreeSpec({ } } - "2.3.2.3. Examples" - { + "2.3.2. Wildcard Selector" - { val jsonElement = jsonSerializer.decodeFromString( " {\n" + " \"o\": {\"j\": 1, \"k\": 2},\n" + @@ -322,9 +315,8 @@ class JsonPathUnitTest : FreeSpec({ " }" ) "\$[*]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 2 jsonElement.jsonObject.get("o") .shouldNotBeNull().shouldBeIn(nodeList) @@ -332,9 +324,8 @@ class JsonPathUnitTest : FreeSpec({ .shouldNotBeNull().shouldBeIn(nodeList) } "\$.o[*]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 2 jsonElement.jsonObject["o"].shouldNotBeNull().let { it.jsonObject["j"] @@ -344,9 +335,8 @@ class JsonPathUnitTest : FreeSpec({ } } "\$.o[*, *]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 4 jsonElement.jsonObject["o"].shouldNotBeNull().let { it.jsonObject["j"] @@ -364,9 +354,8 @@ class JsonPathUnitTest : FreeSpec({ } } "\$.a[*]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 2 jsonElement.jsonObject["a"].shouldNotBeNull().let { it.jsonArray[0] @@ -384,32 +373,31 @@ class JsonPathUnitTest : FreeSpec({ } } } - "2.3.3.3. Examples" - { + + "2.3.3. Index Selector" - { val jsonElement = jsonSerializer.decodeFromString("[\"a\",\"b\"]") "\$[1]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 1 jsonElement.jsonArray[1].shouldNotBeNull().shouldBeIn(nodeList) } "\$[-2]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 1 jsonElement.jsonArray[0].shouldNotBeNull().shouldBeIn(nodeList) } } - "2.3.4.3. Examples" - { + + "2.3.4. Array Slice Selector" - { val jsonElement = jsonSerializer.decodeFromString("[\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\"]") "\$[1:3]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 2 jsonElement.jsonArray[1].shouldNotBeNull().shouldBeIn(nodeList).let { nodeList[0].shouldBe(it) @@ -419,9 +407,8 @@ class JsonPathUnitTest : FreeSpec({ } } "\$[5:]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 2 jsonElement.jsonArray[5].shouldNotBeNull().shouldBeIn(nodeList).let { nodeList[0].shouldBe(it) @@ -431,9 +418,8 @@ class JsonPathUnitTest : FreeSpec({ } } "\$[1:5:2]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 2 jsonElement.jsonArray[1].shouldNotBeNull().shouldBeIn(nodeList).let { nodeList[0].shouldBe(it) @@ -443,9 +429,8 @@ class JsonPathUnitTest : FreeSpec({ } } "\$[5:1:-2]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 2 jsonElement.jsonArray[5].shouldNotBeNull().shouldBeIn(nodeList).let { nodeList[0].shouldBe(it) @@ -455,9 +440,8 @@ class JsonPathUnitTest : FreeSpec({ } } "\$[::-1]" { - val nodeList = - defaultJsonPathCompiler.compile(this.testScope.testCase.name.originalName) - .invoke(jsonElement).map { it.value } + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } nodeList shouldHaveSize 7 for (i in 6.downTo(0)) { jsonElement.jsonArray[i].shouldNotBeNull().shouldBeIn(nodeList).let { @@ -466,7 +450,8 @@ class JsonPathUnitTest : FreeSpec({ } } } - "2.3.5.3. Examples" - { + + "2.3.5. Filter Selector" - { "Comparisons" - { // since this should be compiler agnostic, check whether the evaluation is correct by using // the return list as an indicator for true/false: @@ -782,7 +767,8 @@ class JsonPathUnitTest : FreeSpec({ } } } - "2.4.9. Examples" - { + + "2.4. Function Extensions" - { "\$[?length(@) < 3]" { shouldNotThrowAny { JsonPath(this.testScope.testCase.name.originalName) @@ -835,8 +821,7 @@ class JsonPathUnitTest : FreeSpec({ } "\$[?bar(@.a)]" - { "logical type argument" { - defaultFunctionExtensionManager.removeExtension("bar") - defaultFunctionExtensionManager.addExtension( + defaultFunctionExtensionManager.putExtension( object: JsonPathFunctionExtension.LogicalTypeFunctionExtension( name = "bar", argumentTypes = listOf(JsonPathExpressionType.LogicalType), @@ -851,8 +836,7 @@ class JsonPathUnitTest : FreeSpec({ } } "value type argument" { - defaultFunctionExtensionManager.removeExtension("bar") - defaultFunctionExtensionManager.addExtension( + defaultFunctionExtensionManager.putExtension( object: JsonPathFunctionExtension.LogicalTypeFunctionExtension( name = "bar", argumentTypes = listOf(JsonPathExpressionType.ValueType), @@ -867,8 +851,7 @@ class JsonPathUnitTest : FreeSpec({ } } "nodes type argument" { - defaultFunctionExtensionManager.removeExtension("bar") - defaultFunctionExtensionManager.addExtension( + defaultFunctionExtensionManager.putExtension( object: JsonPathFunctionExtension.LogicalTypeFunctionExtension( name = "bar", argumentTypes = listOf(JsonPathExpressionType.NodesType), @@ -885,8 +868,7 @@ class JsonPathUnitTest : FreeSpec({ } "\$[?bnl(@.*)]" - { "logical type argument" { - defaultFunctionExtensionManager.removeExtension("bnl") - defaultFunctionExtensionManager.addExtension( + defaultFunctionExtensionManager.putExtension( object: JsonPathFunctionExtension.LogicalTypeFunctionExtension( name = "bnl", argumentTypes = listOf(JsonPathExpressionType.LogicalType), @@ -901,8 +883,7 @@ class JsonPathUnitTest : FreeSpec({ } } "value type argument" { - defaultFunctionExtensionManager.removeExtension("bnl") - defaultFunctionExtensionManager.addExtension( + defaultFunctionExtensionManager.putExtension( object: JsonPathFunctionExtension.LogicalTypeFunctionExtension( name = "bnl", argumentTypes = listOf(JsonPathExpressionType.ValueType), @@ -917,8 +898,7 @@ class JsonPathUnitTest : FreeSpec({ } } "nodes type argument" { - defaultFunctionExtensionManager.removeExtension("bnl") - defaultFunctionExtensionManager.addExtension( + defaultFunctionExtensionManager.putExtension( object: JsonPathFunctionExtension.LogicalTypeFunctionExtension( name = "bnl", argumentTypes = listOf(JsonPathExpressionType.NodesType), @@ -933,6 +913,265 @@ class JsonPathUnitTest : FreeSpec({ } } } + "\$[?blt(1==1)]" { + defaultFunctionExtensionManager.putExtension( + object: JsonPathFunctionExtension.LogicalTypeFunctionExtension( + name = "blt", + argumentTypes = listOf(JsonPathExpressionType.LogicalType), + ) { + override fun invoke(arguments: List): JsonPathExpressionValue.LogicalTypeValue { + TODO("Not yet implemented") + } + } + ) + shouldNotThrowAny { + JsonPath(this.testScope.testCase.name.originalName) + } + } + "\$[?blt(1)]" { + defaultFunctionExtensionManager.putExtension( + object: JsonPathFunctionExtension.LogicalTypeFunctionExtension( + name = "blt", + argumentTypes = listOf(JsonPathExpressionType.LogicalType), + ) { + override fun invoke(arguments: List): JsonPathExpressionValue.LogicalTypeValue { + TODO("Not yet implemented") + } + } + ) + shouldThrow { + JsonPath(this.testScope.testCase.name.originalName) + } + } + "\$[?bal(1)]" { + defaultFunctionExtensionManager.putExtension( + object: JsonPathFunctionExtension.LogicalTypeFunctionExtension( + name = "bal", + argumentTypes = listOf(JsonPathExpressionType.ValueType), + ) { + override fun invoke(arguments: List): JsonPathExpressionValue.LogicalTypeValue { + TODO("Not yet implemented") + } + } + ) + shouldNotThrowAny { + JsonPath(this.testScope.testCase.name.originalName) + } + } + } + + "2.5.1. Child Segment" - { + val jsonElement = jsonSerializer.decodeFromString( + "[\"a\", \"b\", \"c\", \"d\", \"e\", \"f\", \"g\"]" + ).jsonArray + "\$[0, 3]" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 2 + nodeList[0].shouldBe(jsonElement[0]) + nodeList[1].shouldBe(jsonElement[3]) + } + "\$[0:2,5]" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 3 + nodeList[0].shouldBe(jsonElement[0]) + nodeList[1].shouldBe(jsonElement[1]) + nodeList[2].shouldBe(jsonElement[5]) + } + "\$[0,0]" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 2 + nodeList[0].shouldBe(jsonElement[0]) + nodeList[1].shouldBe(jsonElement[0]) + } + } + + "2.5.2. Descendant Segment" - { + val jsonElement = jsonSerializer.decodeFromString( + "{\n" + + " \"o\": {\"j\": 1, \"k\": 2},\n" + + " \"a\": [5, 3, [{\"j\": 4}, {\"k\": 6}]]\n" + + " }" + ).jsonObject + "\$..j" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 2 + jsonElement["o"].shouldNotBeNull().jsonObject["j"].shouldBeIn(nodeList) + jsonElement["a"].shouldNotBeNull() + .jsonArray[2].shouldNotBeNull() + .jsonArray[0].shouldNotBeNull() + .jsonObject["j"].shouldNotBeNull().shouldBeIn(nodeList) + } + "\$..[0]" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 2 + jsonElement["a"].shouldNotBeNull().jsonArray.let { a -> + a.jsonArray[0].shouldNotBeNull().shouldBe(nodeList[0]) + a.jsonArray[2].shouldNotBeNull().jsonArray[0].shouldBe(nodeList[1]) + } + } + "\$..[*]" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 11 + jsonElement["a"].shouldNotBeNull().shouldBeIn(nodeList).jsonArray.let { a -> + a.forEach { item -> + item.shouldBeIn(nodeList) + } + a[2].shouldNotBeNull().jsonArray.let { a2 -> + a2.forEach { a2children -> + a2children.shouldBeIn(nodeList) + a2children.jsonObject.forEach { + it.value.shouldBeIn(nodeList) + } + } + } + } + jsonElement["o"].shouldNotBeNull().shouldBeIn(nodeList).jsonObject.let { o -> + o.forEach { item -> + item.value.shouldBeIn(nodeList) + } + } + } + "\$..*" { // same as the one before this + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 11 + jsonElement["a"].shouldNotBeNull().shouldBeIn(nodeList).jsonArray.let { a -> + a.forEach { item -> + item.shouldBeIn(nodeList) + } + a[2].shouldNotBeNull().jsonArray.let { a2 -> + a2.forEach { a2children -> + a2children.shouldBeIn(nodeList) + a2children.jsonObject.forEach { + it.value.shouldBeIn(nodeList) + } + } + } + } + jsonElement["o"].shouldNotBeNull().shouldBeIn(nodeList).jsonObject.let { o -> + o.forEach { item -> + item.value.shouldBeIn(nodeList) + } + } + } + "\$..o" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 1 + jsonElement["o"].shouldNotBeNull().shouldBeIn(nodeList) + } + "\$.o..[*, *]" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 4 + jsonElement["o"].shouldNotBeNull().jsonObject.let { o -> + o.forEach { oDescendant -> + oDescendant.value.shouldBeIn(nodeList) + nodeList.count { + it == oDescendant.value + } shouldBe 2 + } + } + } + "\$.a..[0, 1]" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 4 + jsonElement["a"].shouldNotBeNull().jsonArray.let { a -> + listOf(a, a[2]).forEach { descendant -> + descendant.jsonArray[0].shouldBeIn(nodeList) + descendant.jsonArray[1].shouldBeIn(nodeList) + } + } + } + } + + "2.6. Semantics of null" - { + val jsonElement = jsonSerializer.decodeFromString( + " {\"a\": null, \"b\": [null], \"c\": [{}], \"null\": 1}" + ).jsonObject + "\$.a" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 1 + jsonElement["a"].shouldNotBeNull().shouldBeIn(nodeList) + } + "\$.a[0]" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 0 + } + "\$.a.d" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 0 + } + "\$.b[0]" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 1 + jsonElement["b"].shouldNotBeNull().jsonArray[0].shouldBeIn(nodeList) + } + "\$.b[*]" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 1 + jsonElement["b"].shouldNotBeNull().jsonArray.mapIndexed { index, value -> + nodeList[index].shouldBe(value) + } + } + "\$.b[?@]" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 1 + jsonElement["b"].shouldNotBeNull().jsonArray.mapIndexed { index, value -> + nodeList[index].shouldBe(value) + } + } + "\$.b[?@==null]" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 1 + jsonElement["b"].shouldNotBeNull().jsonArray.mapIndexed { index, value -> + nodeList[index].shouldBe(value) + } + } + "\$.c[?@.d==null]" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 0 + } + "\$.null" { + val nodeList = JsonPath(this.testScope.testCase.name.originalName) + .query(jsonElement).map { it.value } + + nodeList shouldHaveSize 1 + jsonElement["null"].shouldNotBeNull().shouldBeIn(nodeList) + } } } }) diff --git a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/data/jsonPath/JsonStringLiteralTest.kt b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/data/jsonPath/JsonStringLiteralTest.kt index 2420de0b2..1129576a1 100644 --- a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/data/jsonPath/JsonStringLiteralTest.kt +++ b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/data/jsonPath/JsonStringLiteralTest.kt @@ -6,82 +6,82 @@ import io.kotest.matchers.shouldBe class JsonStringLiteralTest : FreeSpec({ "rfc8259" - { "unescapes backslash to backslash" { - rfc8259Utils.unpackStringLiteral("\"\\\\\"") shouldBe "\\" + Rfc8259Utils.unpackStringLiteral("\"\\\\\"") shouldBe "\\" } "unescapes slash to slash" { - rfc8259Utils.unpackStringLiteral("\"\\/\"") shouldBe "/" + Rfc8259Utils.unpackStringLiteral("\"\\/\"") shouldBe "/" } "unescapes quotation mark to quotation mark" { - rfc8259Utils.unpackStringLiteral("\"\\\"\"") shouldBe "\"" + Rfc8259Utils.unpackStringLiteral("\"\\\"\"") shouldBe "\"" } "unescapes b to backspace" { - rfc8259Utils.unpackStringLiteral("\"\\b\"") shouldBe Char(0x0008).toString() + Rfc8259Utils.unpackStringLiteral("\"\\b\"") shouldBe Char(0x0008).toString() } "unescapes f to form feed" { - rfc8259Utils.unpackStringLiteral("\"\\f\"") shouldBe Char(0x000C).toString() + Rfc8259Utils.unpackStringLiteral("\"\\f\"") shouldBe Char(0x000C).toString() } "unescapes n to newline" { - rfc8259Utils.unpackStringLiteral("\"\\n\"") shouldBe "\n" + Rfc8259Utils.unpackStringLiteral("\"\\n\"") shouldBe "\n" } "unescapes r to carriage return" { - rfc8259Utils.unpackStringLiteral("\"\\r\"") shouldBe "\r" + Rfc8259Utils.unpackStringLiteral("\"\\r\"") shouldBe "\r" } "unescapes t to horizontal tab" { - rfc8259Utils.unpackStringLiteral("\"\\t\"") shouldBe "\t" + Rfc8259Utils.unpackStringLiteral("\"\\t\"") shouldBe "\t" } } "rfc9535" - { "rfc8259 conformance" - { "double quoted" - { "unescapes backslash to backslash" { - rfc9535Utils.unpackStringLiteral("\"\\\\\"") shouldBe "\\" + Rfc9535Utils.unpackStringLiteral("\"\\\\\"") shouldBe "\\" } "unescapes slash to slash" { - rfc9535Utils.unpackStringLiteral("\"\\/\"") shouldBe "/" + Rfc9535Utils.unpackStringLiteral("\"\\/\"") shouldBe "/" } "unescapes quotation mark to quotation mark" { - rfc9535Utils.unpackStringLiteral("\"\\\"\"") shouldBe "\"" + Rfc9535Utils.unpackStringLiteral("\"\\\"\"") shouldBe "\"" } "unescapes b to backspace" { - rfc9535Utils.unpackStringLiteral("\"\\b\"") shouldBe Char(0x0008).toString() + Rfc9535Utils.unpackStringLiteral("\"\\b\"") shouldBe Char(0x0008).toString() } "unescapes f to form feed" { - rfc9535Utils.unpackStringLiteral("\"\\f\"") shouldBe Char(0x000C).toString() + Rfc9535Utils.unpackStringLiteral("\"\\f\"") shouldBe Char(0x000C).toString() } "unescapes n to newline" { - rfc9535Utils.unpackStringLiteral("\"\\n\"") shouldBe "\n" + Rfc9535Utils.unpackStringLiteral("\"\\n\"") shouldBe "\n" } "unescapes r to carriage return" { - rfc9535Utils.unpackStringLiteral("\"\\r\"") shouldBe "\r" + Rfc9535Utils.unpackStringLiteral("\"\\r\"") shouldBe "\r" } "unescapes t to horizontal tab" { - rfc9535Utils.unpackStringLiteral("\"\\t\"") shouldBe "\t" + Rfc9535Utils.unpackStringLiteral("\"\\t\"") shouldBe "\t" } } "single quoted" - { "unescapes backslash to backslash" { - rfc9535Utils.unpackStringLiteral("'\\\\'") shouldBe "\\" + Rfc9535Utils.unpackStringLiteral("'\\\\'") shouldBe "\\" } "unescapes slash to slash" { - rfc9535Utils.unpackStringLiteral("'\\/'") shouldBe "/" + Rfc9535Utils.unpackStringLiteral("'\\/'") shouldBe "/" } "unescapes quotation mark to quotation mark" { - rfc9535Utils.unpackStringLiteral("'\\''") shouldBe "'" + Rfc9535Utils.unpackStringLiteral("'\\''") shouldBe "'" } "unescapes b to backspace" { - rfc9535Utils.unpackStringLiteral("'\\b'") shouldBe Char(0x0008).toString() + Rfc9535Utils.unpackStringLiteral("'\\b'") shouldBe Char(0x0008).toString() } "unescapes f to form feed" { - rfc9535Utils.unpackStringLiteral("'\\f'") shouldBe Char(0x000C).toString() + Rfc9535Utils.unpackStringLiteral("'\\f'") shouldBe Char(0x000C).toString() } "unescapes n to newline" { - rfc9535Utils.unpackStringLiteral("'\\n'") shouldBe "\n" + Rfc9535Utils.unpackStringLiteral("'\\n'") shouldBe "\n" } "unescapes r to carriage return" { - rfc9535Utils.unpackStringLiteral("'\\r'") shouldBe "\r" + Rfc9535Utils.unpackStringLiteral("'\\r'") shouldBe "\r" } "unescapes t to horizontal tab" { - rfc9535Utils.unpackStringLiteral("'\\t'") shouldBe "\t" + Rfc9535Utils.unpackStringLiteral("'\\t'") shouldBe "\t" } } }