diff --git a/src/EventStore.Projections.Core.Tests/Services/Jint/Serialization/when_serializing_state.cs b/src/EventStore.Projections.Core.Tests/Services/Jint/Serialization/when_serializing_state.cs index c56cc8ff49..692fae07f4 100644 --- a/src/EventStore.Projections.Core.Tests/Services/Jint/Serialization/when_serializing_state.cs +++ b/src/EventStore.Projections.Core.Tests/Services/Jint/Serialization/when_serializing_state.cs @@ -136,6 +136,15 @@ public void undefined() Assert.AreEqual(@"null", serialized); } + [TestCase("NaN")] + [TestCase("Infinity")] + [TestCase("-Infinity")] + public void non_finite_numbers_are_serialized_as_null(string expression) + { + var serialized = _sut.Serialize(_engine.Evaluate(expression)); + Assert.AreEqual(@"null", serialized); + } + [Test] public void undefined_property() { diff --git a/src/EventStore.Projections.Core.Tests/Services/Jint/when_accessing_event_body_with_non_object_data.cs b/src/EventStore.Projections.Core.Tests/Services/Jint/when_accessing_event_body_with_non_object_data.cs new file mode 100644 index 0000000000..d8a909b220 --- /dev/null +++ b/src/EventStore.Projections.Core.Tests/Services/Jint/when_accessing_event_body_with_non_object_data.cs @@ -0,0 +1,35 @@ +using System; +using EventStore.Projections.Core.Services; +using EventStore.Projections.Core.Services.Processing.Checkpointing; +using NUnit.Framework; + +namespace EventStore.Projections.Core.Tests.Services.Jint; + +[TestFixture] +public class when_accessing_event_body_with_non_object_data : TestFixtureWithInterpretedProjection +{ + protected override void Given() + { + _projection = @" + fromAll().when({$any: + function(state, event) { + return event.body; + } + }); + "; + } + + [TestCase("null", null)] + [TestCase("42", "42")] + [TestCase(@"""hello""", @"""hello""")] + [TestCase("true", "true")] + [Category(_projectionType)] + public void process_event_returns_json_primitive_body(string data, string expectedState) + { + _stateHandler.ProcessEvent( + "", CheckpointTag.FromPosition(0, 20, 10), "stream1", "type1", "category", Guid.NewGuid(), 0, + "metadata", data, out var state, out _); + + Assert.AreEqual(expectedState, state); + } +} diff --git a/src/EventStore.Projections.Core.Tests/Services/Jint/when_round_tripping_js_projection_with_string_state.cs b/src/EventStore.Projections.Core.Tests/Services/Jint/when_round_tripping_js_projection_with_string_state.cs new file mode 100644 index 0000000000..4e5ad0330d --- /dev/null +++ b/src/EventStore.Projections.Core.Tests/Services/Jint/when_round_tripping_js_projection_with_string_state.cs @@ -0,0 +1,63 @@ +using System; +using EventStore.Projections.Core.Services; +using EventStore.Projections.Core.Services.Processing.Checkpointing; +using NUnit.Framework; + +namespace EventStore.Projections.Core.Tests.Services.Jint; + +[TestFixture] +public class when_round_tripping_js_projection_with_string_state : TestFixtureWithInterpretedProjection +{ + protected override void Given() + { + _projection = @" + fromAll().when({ + type1: function(state, event) { + return 'hello'; + } + }); + "; + } + + [Test, Category(_projectionType)] + public void process_event_returns_json_encoded_string_state() + { + _stateHandler.ProcessEvent( + "", CheckpointTag.FromPosition(0, 20, 10), "stream1", "type1", "category", Guid.NewGuid(), 0, + "metadata", @"{""a"":""b""}", out var state, out _); + + Assert.AreEqual(@"""hello""", state); + Assert.DoesNotThrow(() => _stateHandler.Load(state)); + } +} + +[TestFixture] +public class when_round_tripping_bi_state_js_projection_with_string_state : TestFixtureWithInterpretedProjection +{ + protected override void Given() + { + _projection = @" + options({ + biState: true, + }); + fromAll().foreachStream().when({ + type1: function(state, event) { + state[0] = 'hello'; + return state; + } + }); + "; + } + + [Test, Category(_projectionType)] + public void process_event_returns_json_encoded_string_state() + { + _stateHandler.ProcessEvent( + "", CheckpointTag.FromPosition(0, 20, 10), "stream1", "type1", "category", Guid.NewGuid(), 0, + "metadata", @"{""a"":""b""}", out var state, out var sharedState, out _); + + Assert.AreEqual(@"""hello""", state); + Assert.IsNull(sharedState); + Assert.DoesNotThrow(() => _stateHandler.Load(state)); + } +} diff --git a/src/EventStore.Projections.Core/Services/Interpreted/JintProjectionStateHandler.cs b/src/EventStore.Projections.Core/Services/Interpreted/JintProjectionStateHandler.cs index 7c42f602e5..64b4e566c9 100644 --- a/src/EventStore.Projections.Core/Services/Interpreted/JintProjectionStateHandler.cs +++ b/src/EventStore.Projections.Core/Services/Interpreted/JintProjectionStateHandler.cs @@ -243,14 +243,7 @@ private void PrepareOutput(out string? newState, out string? newSharedState, out var arr = _state.AsArray(); if (arr.TryGetValue(0, out var state)) { - if (_state.IsString()) - { - newState = _state.AsString(); - } - else - { - newState = ConvertToStringHandlingNulls(state); - } + newState = ConvertToStringHandlingNulls(state); } else { @@ -267,11 +260,6 @@ private void PrepareOutput(out string? newState, out string? newSharedState, out } } - else if (_state.IsString()) - { - newState = _state.AsString(); - newSharedState = null; - } else { newState = ConvertToStringHandlingNulls(_state); @@ -921,7 +909,7 @@ public JsValue Handle(JsValue state, EventEnvelope eventEnvelope) } else { - newState = eventEnvelope.BodyRaw; + newState = eventEnvelope.IsJson ? eventEnvelope.Body : eventEnvelope.BodyRaw; } return newState == Undefined ? state : newState; } @@ -1096,33 +1084,32 @@ public JsValue Body { get { - if (TryGetValue("body", out var value) && value is ObjectInstance oi) + if (TryGetValue("body", out var value) && value is not JsUndefined) { - return oi; + return value; } - if (EnsureBody(out JsValue objectInstance)) + if (EnsureBody(out var body)) { - return objectInstance; + return body; } return Undefined; } } - private bool EnsureBody(out JsValue objectInstance) + private bool EnsureBody(out JsValue body) { if (IsJson && TryGetValue("bodyRaw", out var raw) && raw is not JsUndefined) { - var body = raw.IsNull() ? raw : _parser.Parse(raw.AsString()); + body = raw.IsNull() ? raw : _parser.Parse(raw.AsString()); var pd = new PropertyDescriptor(body, false, true, false); SetOwnProperty("body", pd); SetOwnProperty("data", pd); - objectInstance = (ObjectInstance)body; return true; } - objectInstance = Undefined; + body = Undefined; return false; } @@ -1555,7 +1542,15 @@ static void SerializePrimitive(JsValue value, Utf8JsonWriter writer) break; case Types.Number: - writer.WriteNumberValue(value.AsNumber()); + var number = value.AsNumber(); + if (double.IsFinite(number)) + { + writer.WriteNumberValue(number); + } + else + { + writer.WriteNullValue(); + } break; case Types.BigInt: writer.WriteStringValue(value.ToString());