diff --git a/snapi-compiler/src/test/scala/com/rawlabs/snapi/compiler/tests/regressions/RD10971Test.scala b/snapi-compiler/src/test/scala/com/rawlabs/snapi/compiler/tests/regressions/RD10971Test.scala new file mode 100644 index 000000000..c03c12c78 --- /dev/null +++ b/snapi-compiler/src/test/scala/com/rawlabs/snapi/compiler/tests/regressions/RD10971Test.scala @@ -0,0 +1,47 @@ +/* + * Copyright 2024 RAW Labs S.A. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.txt. + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0, included in the file + * licenses/APL.txt. + */ + +package com.rawlabs.snapi.compiler.tests.regressions +import com.rawlabs.snapi.compiler.tests.SnapiTestContext + +class RD10971Test extends SnapiTestContext { + + test("Json.Parse(\" \", type int)")( + // Jackson returns an internal error. + _ should runErrorAs("""Internal error: _parseNumericValue called when parser instance closed + | at [Source: (String)" "; line: 1, column: 2]""".stripMargin) + ) + + test("[Json.Parse(\" \", type int)]")( + _ should run // it doesn't fail because it's in a list. + ) + + test("Json.Parse(\" \", type record(a: int, b: string))")( + // unexpected token. + _ should runErrorAs("expected { but token null found") + ) + + test("[Json.Parse(\" \", type record(a: int, b: string))]")( + // Same as above, the tryable should be left in the list. The query doesn't fail. + _ should run // it doesn't fail because it's in a list. + ) + + test("Json.Parse(\" \", type list(record(a: int, b: string))) // RD-10971")( + // unexpected token. + _ should runErrorAs("expected [ but token null found") + ) + + test("[Json.Parse(\" \", type list(record(a: int, b: string)))] // RD-10971")( + _ should run // it doesn't fail because it's in a list. + ) + +} diff --git a/snapi-compiler/src/test/scala/com/rawlabs/snapi/compiler/tests/regressions/RD14684Test.scala b/snapi-compiler/src/test/scala/com/rawlabs/snapi/compiler/tests/regressions/RD14684Test.scala new file mode 100644 index 000000000..827b4af28 --- /dev/null +++ b/snapi-compiler/src/test/scala/com/rawlabs/snapi/compiler/tests/regressions/RD14684Test.scala @@ -0,0 +1,142 @@ +/* + * Copyright 2024 RAW Labs S.A. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.txt. + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0, included in the file + * licenses/APL.txt. + */ + +package com.rawlabs.snapi.compiler.tests.regressions + +import com.rawlabs.snapi.compiler.tests.SnapiTestContext +import com.rawlabs.snapi.frontend.snapi.SnapiInterpolator + +class RD14684Test extends SnapiTestContext { + + private val missing_field = tempFile("""[ + |{"name": "Benjamin", "birthYear": 1978}, + |{"name": "X"}, + |{"name": "Jane", "birthYear": 200} + |] + """.stripMargin) + + private val missing_field2 = tempFile("""[ + |{"name": "Benjamin", "birthYear": 1978}, + |{"name": "X"}, + |{"name": "Jane", "birthYear": 200}, + |{"name": "Tarzan", "birthYear": 201} + |] + """.stripMargin) + + private val totallyNotRecord = tempFile("""[ + |{"name": "Benjamin", "birthYear": 1978}, + |14, + |{"name": "Jane", "birthYear": 200} + |] + """.stripMargin) + + ignore( + snapi"""Json.InferAndRead("$missing_field", sampleSize = 1, preferNulls = false)""" + ) { it => + // Because we specified a sampleSize of 1, sampling didn't infer a birthyear could miss, + // and we also don't make fields nullable, therefore, the expected behavior is that + // we fail to build the middle record as a whole. + // TODO: RD-14684: the record is indeed parsed as a failed record, but we skip the following one (Jane) + it should evaluateTo( + """[{name: "Benjamin", birthYear: 1978}, + |Error.Build("'birthYear': not found"), + |{name: "Jane", birthYear: 200}]""".stripMargin + ) + } + + ignore( + snapi"""Json.InferAndRead("$missing_field2", sampleSize = 1, preferNulls = false)""" + ) { it => + // TODO: RD-14684: the record is indeed parsed as a failed record, but we skip the following one (Jane) + // Same as in the test above. That test confirms we're skipping the following record, not the remaining one. + it should evaluateTo( + """[{name: "Benjamin", birthYear: 1978}, + |Error.Build("'birthYear': not found"), + |{name: "Jane", birthYear: 200}, + |{name: "Tarzan", birthYear: 201}]""".stripMargin + ) + } + + test( + snapi"""Json.InferAndRead("$missing_field", sampleSize = 1, preferNulls = true)""" + ) { it => + // because sampling didn't infer a birthyear could miss but we make fields + // nullable, we get a null birthYear. + it should evaluateTo( + """[{name: "Benjamin", birthYear: 1978}, + |{name: "X", birthYear: null}, + |{name: "Jane", birthYear: 200}]""".stripMargin + ) + } + + test( + snapi"""Json.InferAndRead("$missing_field2", sampleSize = 1, preferNulls = true)""" + ) { it => + // because sampling didn't infer a birthyear could miss but we make fields + // nullable, we get a null birthYear. + it should evaluateTo( + """[{name: "Benjamin", birthYear: 1978}, + |{name: "X", birthYear: null}, + |{name: "Jane", birthYear: 200}, + |{name: "Tarzan", birthYear: 201}]""".stripMargin + ) + } + + test( + snapi"""Json.InferAndRead("$totallyNotRecord", sampleSize = 1, preferNulls = false)""" + ) { it => + // because sampling didn't infer a birthyear could miss and we don't make fields + // nullable, we fail to build the middle record. This doesn't fail like in the first + // tests likely because the parser doesn't start to read the record, it immediately fails + // when it gets an integer in place of the opening brace. + it should evaluateTo( + """[{name: "Benjamin", birthYear: 1978}, + |Error.Build("expected { but token VALUE_NUMBER_INT found"), + |{name: "Jane", birthYear: 200}]""".stripMargin + ) + } + + test( + snapi"""Json.InferAndRead("$totallyNotRecord", sampleSize = 1, preferNulls = true)""" + ) { it => + // Same as above. Using preferNulls shouldn't change anything. + it should evaluateTo( + """[{name: "Benjamin", birthYear: 1978}, + |Error.Build("expected { but token VALUE_NUMBER_INT found"), + |{name: "Jane", birthYear: 200}]""".stripMargin + ) + } + + test( + snapi"""Json.Read("$totallyNotRecord", type list(record(name: string, birthYear: int)))""" + ) { it => + // Same as above with an explicit type (which means it's tryable/nullable). + // The parser doesn't even get to start to parse a record. The whole record is failed. + it should evaluateTo( + """[{name: "Benjamin", birthYear: 1978}, + |Error.Build("expected { but token VALUE_NUMBER_INT found"), + |{name: "Jane", birthYear: 200}]""".stripMargin + ) + } + + test( + snapi"""Json.Read("$totallyNotRecord", type collection(record(name: string, birthYear: int)))""" + ) { it => + // Same as above with a collection. + it should evaluateTo( + """[{name: "Benjamin", birthYear: 1978}, + |Error.Build("expected { but token VALUE_NUMBER_INT found"), + |{name: "Jane", birthYear: 200}]""".stripMargin + ) + } + +} diff --git a/snapi-compiler/src/test/scala/com/rawlabs/snapi/compiler/tests/regressions/RD14685Test.scala b/snapi-compiler/src/test/scala/com/rawlabs/snapi/compiler/tests/regressions/RD14685Test.scala new file mode 100644 index 000000000..4affe76c7 --- /dev/null +++ b/snapi-compiler/src/test/scala/com/rawlabs/snapi/compiler/tests/regressions/RD14685Test.scala @@ -0,0 +1,47 @@ +/* + * Copyright 2024 RAW Labs S.A. + * + * Use of this software is governed by the Business Source License + * included in the file licenses/BSL.txt. + * + * As of the Change Date specified in that file, in accordance with + * the Business Source License, use of this software will be governed + * by the Apache License, Version 2.0, included in the file + * licenses/APL.txt. + */ + +package com.rawlabs.snapi.compiler.tests.regressions + +import com.rawlabs.snapi.compiler.tests.SnapiTestContext + +class RD14685Test extends SnapiTestContext { + + test("Json.Parse(\"12\", type collection(record(a: int, b: string)))")( + _ should runErrorAs("expected [ but token VALUE_NUMBER_INT found") + ) + + test("Json.Parse(\" \", type collection(record(a: int, b: string)))")( + // Same error as above. The collection is OK but when rendering to JSON we get a failure. + _ should runErrorAs("expected [ but token null found") + ) + + ignore("[Json.Parse(\"12\", type collection(record(a: int, b: string)))]")( + // the collection is consumed when writing JSON, it fails in the middle. + _ should run // TODO if it runs, check the error message in Error.Build + ) + + ignore("[Json.Parse(\" \", type collection(record(a: int, b: string)))]")( + // the collection is consumed when writing JSON, it fails in the middle. + _ should run // TODO if it runs, check the error message in Error.Build + ) + + ignore("[Collection.Count(Json.Parse(\" \", type collection(record(a: int, b: string))))]")( + // the collection is consumed when running Count, it leads to a failed int in the list. + _ should run // TODO if it runs, check the error message in Error.Build + ) + + ignore("[Collection.Count(Json.Parse(\"12\", type collection(record(a: int, b: string))))]")( + // the collection is consumed when running Count, it leads to a failed int in the list. + _ should run // TODO if it runs, check the error message in Error.Build + ) +} diff --git a/snapi-truffle/src/main/java/com/rawlabs/snapi/truffle/ast/io/json/reader/JsonParserNodes.java b/snapi-truffle/src/main/java/com/rawlabs/snapi/truffle/ast/io/json/reader/JsonParserNodes.java index 70f08eed9..f7b82d756 100644 --- a/snapi-truffle/src/main/java/com/rawlabs/snapi/truffle/ast/io/json/reader/JsonParserNodes.java +++ b/snapi-truffle/src/main/java/com/rawlabs/snapi/truffle/ast/io/json/reader/JsonParserNodes.java @@ -609,11 +609,10 @@ protected static TruffleArrayList doParseList( @Cached @Cached.Shared("currentToken") JsonParserNodes.CurrentTokenJsonParserNode currentToken, @Cached @Cached.Shared("nextToken") JsonParserNodes.NextTokenJsonParserNode nextToken) { - if (currentToken.execute(thisNode, parser) != JsonToken.START_ARRAY) { + JsonToken token = currentToken.execute(thisNode, parser); + if (token != JsonToken.START_ARRAY) { throw new JsonUnexpectedTokenException( - JsonToken.START_ARRAY.asString(), - currentToken.execute(thisNode, parser).toString(), - thisNode); + JsonToken.START_ARRAY.asString(), String.valueOf(token), thisNode); } nextToken.execute(thisNode, parser); @@ -640,11 +639,10 @@ protected static Object doParse( JsonParserNodes.CurrentTokenJsonParserNode currentToken, @Cached JsonParserNodes.CurrentFieldJsonParserNode currentField, @Cached RecordNodes.AddPropNode addPropNode) { - if (currentToken.execute(thisNode, parser) != JsonToken.START_OBJECT) { + JsonToken token = currentToken.execute(thisNode, parser); + if (token != JsonToken.START_OBJECT) { throw new JsonUnexpectedTokenException( - JsonToken.START_OBJECT.asString(), - currentToken.execute(thisNode, parser).toString(), - thisNode); + JsonToken.START_OBJECT.asString(), String.valueOf(token), thisNode); } nextToken.execute(thisNode, parser); diff --git a/snapi-truffle/src/main/java/com/rawlabs/snapi/truffle/ast/io/json/reader/parser/ListParseJsonNode.java b/snapi-truffle/src/main/java/com/rawlabs/snapi/truffle/ast/io/json/reader/parser/ListParseJsonNode.java index cde870884..c876acac1 100644 --- a/snapi-truffle/src/main/java/com/rawlabs/snapi/truffle/ast/io/json/reader/parser/ListParseJsonNode.java +++ b/snapi-truffle/src/main/java/com/rawlabs/snapi/truffle/ast/io/json/reader/parser/ListParseJsonNode.java @@ -101,10 +101,10 @@ public ListParseJsonNode( public Object executeGeneric(VirtualFrame frame) { Object[] args = frame.getArguments(); JsonParser parser = (JsonParser) args[0]; - - if (currentToken.execute(this, parser) != JsonToken.START_ARRAY) { + JsonToken token = currentToken.execute(this, parser); + if (token != JsonToken.START_ARRAY) { throw new JsonUnexpectedTokenException( - JsonToken.START_ARRAY.asString(), currentToken.execute(this, parser).toString(), this); + JsonToken.START_ARRAY.asString(), String.valueOf(token), this); } nextToken.execute(this, parser); diff --git a/snapi-truffle/src/main/java/com/rawlabs/snapi/truffle/ast/io/json/reader/parser/RecordParseJsonNode.java b/snapi-truffle/src/main/java/com/rawlabs/snapi/truffle/ast/io/json/reader/parser/RecordParseJsonNode.java index 2823cca72..8fde01c1c 100644 --- a/snapi-truffle/src/main/java/com/rawlabs/snapi/truffle/ast/io/json/reader/parser/RecordParseJsonNode.java +++ b/snapi-truffle/src/main/java/com/rawlabs/snapi/truffle/ast/io/json/reader/parser/RecordParseJsonNode.java @@ -119,11 +119,10 @@ public Object executeGeneric(VirtualFrame frame) { JsonParser parser = (JsonParser) args[0]; BitSet currentBitSet = new BitSet(this.fieldsSize); - if (currentTokenNode.execute(this, parser) != JsonToken.START_OBJECT) { + JsonToken token = currentTokenNode.execute(this, parser); + if (token != JsonToken.START_OBJECT) { throw new JsonUnexpectedTokenException( - JsonToken.START_OBJECT.asString(), - currentTokenNode.execute(this, parser).toString(), - this); + JsonToken.START_OBJECT.asString(), String.valueOf(token), this); } nextTokenNode.execute(this, parser);