Description
Source
https://forum.exercism.org/t/restapi-and-system-text-json-issue/15946
Description
The issue has been initially identified as part of the work on rest-api
exercise for F# language. All tests are green locally, however exercism
platform test runner considers all tests as failed with the following error:
System.Text.Json.JsonException : '\' is an invalid start of a property name. Expected a '"'. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
---- System.Text.Json.JsonReaderException : '\' is an invalid start of a property name. Expected a '"'. LineNumber: 0 | BytePositionInLine: 1.
The local debug showed that the issue is produced by the Rewrite.fs
file. After the rewrite, the output of the test code has every "
inside the string escaped with additional \
, that makes the json inside the triple-quoted string invalid.
Here is the full error output of the test in JetBrains Rider:
System.Text.Json.JsonException: '\' is an invalid start of a property name. Expected a '"'. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
System.Text.Json.JsonException
'\' is an invalid start of a property name. Expected a '"'. Path: $ | LineNumber: 0 | BytePositionInLine: 1.
at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& state, JsonReaderException ex)
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 utf8Json, JsonTypeInfo`1 jsonTypeInfo, Nullable`1 actualByteCount)
at System.Text.Json.JsonSerializer.ReadFromSpan[TValue](ReadOnlySpan`1 json, JsonTypeInfo`1 jsonTypeInfo)
at RestApi.RestApi..ctor(String database) in /Users/Rakkatakka/Exercism/fsharp/rest-api/RestApi.fs:line 37
at RestApiTests.Add user() in /Users/Rakkatakka/Exercism/fsharp/rest-api/RestApiTests.fs:line 24
at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
System.Text.Json.JsonReaderException
'\' is an invalid start of a property name. Expected a '"'. LineNumber: 0 | BytePositionInLine: 1.
at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes)
at System.Text.Json.Utf8JsonReader.ReadSingleSegment()
at System.Text.Json.Utf8JsonReader.Read()
at System.Text.Json.Serialization.Converters.ObjectWithParameterizedConstructorConverter`1.OnTryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value)
at System.Text.Json.Serialization.JsonConverter`1.TryRead(Utf8JsonReader& reader, Type typeToConvert, JsonSerializerOptions options, ReadStack& state, T& value, Boolean& isPopulatedValue)
at System.Text.Json.Serialization.JsonConverter`1.ReadCore(Utf8JsonReader& reader, JsonSerializerOptions options, ReadStack& state)
The entire content of the malformed file is:
module RestApiTests
open FsUnit.Xunit
open Xunit
open RestApi
[<Fact>]
let ``No users`` () =
let database = """{\\"users\\":[]}"""
let url = "/users"
let expected = """{\\"users\\":[]}"""
let api = RestApi(database)
api.Get url |> should equal expected
[<Fact>]
let ``Add user`` () =
let database = """{\\"users\\":[]}"""
let payload = """{\\"user\\":\\"Adam\\"}"""
let url = "/add"
let expected =
"""{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0}"""
let api = RestApi(database)
api.Post(url, payload) |> should equal expected
[<Fact>]
let ``Get single user`` () =
let database =
"""{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0}]}"""
let payload = """{\\"users\\":[\\"Bob\\"]}"""
let url = "/users"
let expected =
"""{\\"users\\":[{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0}]}"""
let api = RestApi(database)
api.Get(url, payload) |> should equal expected
[<Fact>]
let ``Both users have 0 balance`` () =
let database =
"""{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0}]}"""
let payload =
"""{\\"lender\\":\\"Adam\\",\\"borrower\\":\\"Bob\\",\\"amount\\":3.0}"""
let url = "/iou"
let expected =
"""{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{\\"Bob\\":3.0},\\"balance\\":3.0},{\\"name\\":\\"Bob\\",\\"owes\\":{\\"Adam\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0}]}"""
let api = RestApi(database)
api.Post(url, payload) |> should equal expected
[<Fact>]
let ``Borrower has negative balance`` () =
let database =
"""{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0},{\\"name\\":\\"Bob\\",\\"owes\\":{\\"Chuck\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0},{\\"name\\":\\"Chuck\\",\\"owes\\":{},\\"owed_by\\":{\\"Bob\\":3.0},\\"balance\\":3.0}]}"""
let payload =
"""{\\"lender\\":\\"Adam\\",\\"borrower\\":\\"Bob\\",\\"amount\\":3.0}"""
let url = "/iou"
let expected =
"""{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{\\"Bob\\":3.0},\\"balance\\":3.0},{\\"name\\":\\"Bob\\",\\"owes\\":{\\"Adam\\":3.0,\\"Chuck\\":3.0},\\"owed_by\\":{},\\"balance\\":-6.0}]}"""
let api = RestApi(database)
api.Post(url, payload) |> should equal expected
[<Fact>]
let ``Lender has negative balance`` () =
let database =
"""{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0},{\\"name\\":\\"Bob\\",\\"owes\\":{\\"Chuck\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0},{\\"name\\":\\"Chuck\\",\\"owes\\":{},\\"owed_by\\":{\\"Bob\\":3.0},\\"balance\\":3.0}]}"""
let payload =
"""{\\"lender\\":\\"Bob\\",\\"borrower\\":\\"Adam\\",\\"amount\\":3.0}"""
let url = "/iou"
let expected =
"""{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{\\"Bob\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0},{\\"name\\":\\"Bob\\",\\"owes\\":{\\"Chuck\\":3.0},\\"owed_by\\":{\\"Adam\\":3.0},\\"balance\\":0.0}]}"""
let api = RestApi(database)
api.Post(url, payload) |> should equal expected
[<Fact>]
let ``Lender owes borrower`` () =
let database =
"""{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{\\"Bob\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{\\"Adam\\":3.0},\\"balance\\":3.0}]}"""
let payload =
"""{\\"lender\\":\\"Adam\\",\\"borrower\\":\\"Bob\\",\\"amount\\":2.0}"""
let url = "/iou"
let expected =
"""{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{\\"Bob\\":1.0},\\"owed_by\\":{},\\"balance\\":-1.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{\\"Adam\\":1.0},\\"balance\\":1.0}]}"""
let api = RestApi(database)
api.Post(url, payload) |> should equal expected
[<Fact>]
let ``Lender owes borrower less than new loan`` () =
let database =
"""{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{\\"Bob\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{\\"Adam\\":3.0},\\"balance\\":3.0}]}"""
let payload =
"""{\\"lender\\":\\"Adam\\",\\"borrower\\":\\"Bob\\",\\"amount\\":4.0}"""
let url = "/iou"
let expected =
"""{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{\\"Bob\\":1.0},\\"balance\\":1.0},{\\"name\\":\\"Bob\\",\\"owes\\":{\\"Adam\\":1.0},\\"owed_by\\":{},\\"balance\\":-1.0}]}"""
let api = RestApi(database)
api.Post(url, payload) |> should equal expected
[<Fact>]
let ``Lender owes borrower same as new loan`` () =
let database =
"""{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{\\"Bob\\":3.0},\\"owed_by\\":{},\\"balance\\":-3.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{\\"Adam\\":3.0},\\"balance\\":3.0}]}"""
let payload =
"""{\\"lender\\":\\"Adam\\",\\"borrower\\":\\"Bob\\",\\"amount\\":3.0}"""
let url = "/iou"
let expected =
"""{\\"users\\":[{\\"name\\":\\"Adam\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0},{\\"name\\":\\"Bob\\",\\"owes\\":{},\\"owed_by\\":{},\\"balance\\":0.0}]}"""
let api = RestApi(database)
api.Post(url, payload) |> should equal expected
Quick Fix
The replacement of triple-quoted strings """
with a single quote ones "
in the test file helps to address the issue. Here is the example of a test file that successfully passes the rewrite:
module RestApiTests
open FsUnit.Xunit
open Xunit
open RestApi
[<Fact>]
let ``No users`` () =
let database = "{\"users\":[]}"
let url = "/users"
let expected = "{\"users\":[]}"
let api = RestApi(database)
api.Get url |> should equal expected
[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Add user`` () =
let database = "{\"users\":[]}"
let payload = "{\"user\":\"Adam\"}"
let url = "/add"
let expected = "{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{},\"balance\":0.0}"
let api = RestApi(database)
api.Post(url, payload) |> should equal expected
[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Get single user`` () =
let database =
"{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{},\"balance\":0.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{},\"balance\":0.0}]}"
let payload = "{\"users\":[\"Bob\"]}"
let url = "/users"
let expected =
"{\"users\":[{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{},\"balance\":0.0}]}"
let api = RestApi(database)
api.Get(url, payload) |> should equal expected
[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Both users have 0 balance`` () =
let database =
"{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{},\"balance\":0.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{},\"balance\":0.0}]}"
let payload = "{\"lender\":\"Adam\",\"borrower\":\"Bob\",\"amount\":3.0}"
let url = "/iou"
let expected =
"{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{\"Bob\":3.0},\"balance\":3.0},{\"name\":\"Bob\",\"owes\":{\"Adam\":3.0},\"owed_by\":{},\"balance\":-3.0}]}"
let api = RestApi(database)
api.Post(url, payload) |> should equal expected
[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Borrower has negative balance`` () =
let database =
"{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{},\"balance\":0.0},{\"name\":\"Bob\",\"owes\":{\"Chuck\":3.0},\"owed_by\":{},\"balance\":-3.0},{\"name\":\"Chuck\",\"owes\":{},\"owed_by\":{\"Bob\":3.0},\"balance\":3.0}]}"
let payload = "{\"lender\":\"Adam\",\"borrower\":\"Bob\",\"amount\":3.0}"
let url = "/iou"
let expected =
"{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{\"Bob\":3.0},\"balance\":3.0},{\"name\":\"Bob\",\"owes\":{\"Adam\":3.0,\"Chuck\":3.0},\"owed_by\":{},\"balance\":-6.0}]}"
let api = RestApi(database)
api.Post(url, payload) |> should equal expected
[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Lender has negative balance`` () =
let database =
"{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{},\"balance\":0.0},{\"name\":\"Bob\",\"owes\":{\"Chuck\":3.0},\"owed_by\":{},\"balance\":-3.0},{\"name\":\"Chuck\",\"owes\":{},\"owed_by\":{\"Bob\":3.0},\"balance\":3.0}]}"
let payload = "{\"lender\":\"Bob\",\"borrower\":\"Adam\",\"amount\":3.0}"
let url = "/iou"
let expected =
"{\"users\":[{\"name\":\"Adam\",\"owes\":{\"Bob\":3.0},\"owed_by\":{},\"balance\":-3.0},{\"name\":\"Bob\",\"owes\":{\"Chuck\":3.0},\"owed_by\":{\"Adam\":3.0},\"balance\":0.0}]}"
let api = RestApi(database)
api.Post(url, payload) |> should equal expected
[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Lender owes borrower`` () =
let database =
"{\"users\":[{\"name\":\"Adam\",\"owes\":{\"Bob\":3.0},\"owed_by\":{},\"balance\":-3.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{\"Adam\":3.0},\"balance\":3.0}]}"
let payload = "{\"lender\":\"Adam\",\"borrower\":\"Bob\",\"amount\":2.0}"
let url = "/iou"
let expected =
"{\"users\":[{\"name\":\"Adam\",\"owes\":{\"Bob\":1.0},\"owed_by\":{},\"balance\":-1.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{\"Adam\":1.0},\"balance\":1.0}]}"
let api = RestApi(database)
api.Post(url, payload) |> should equal expected
[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Lender owes borrower less than new loan`` () =
let database =
"{\"users\":[{\"name\":\"Adam\",\"owes\":{\"Bob\":3.0},\"owed_by\":{},\"balance\":-3.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{\"Adam\":3.0},\"balance\":3.0}]}"
let payload = "{\"lender\":\"Adam\",\"borrower\":\"Bob\",\"amount\":4.0}"
let url = "/iou"
let expected =
"{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{\"Bob\":1.0},\"balance\":1.0},{\"name\":\"Bob\",\"owes\":{\"Adam\":1.0},\"owed_by\":{},\"balance\":-1.0}]}"
let api = RestApi(database)
api.Post(url, payload) |> should equal expected
[<Fact(Skip = "Remove this Skip property to run this test")>]
let ``Lender owes borrower same as new loan`` () =
let database =
"{\"users\":[{\"name\":\"Adam\",\"owes\":{\"Bob\":3.0},\"owed_by\":{},\"balance\":-3.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{\"Adam\":3.0},\"balance\":3.0}]}"
let payload = "{\"lender\":\"Adam\",\"borrower\":\"Bob\",\"amount\":3.0}"
let url = "/iou"
let expected =
"{\"users\":[{\"name\":\"Adam\",\"owes\":{},\"owed_by\":{},\"balance\":0.0},{\"name\":\"Bob\",\"owes\":{},\"owed_by\":{},\"balance\":0.0}]}"
let api = RestApi(database)
api.Post(url, payload) |> should equal expected