Skip to content

Commit 39c4f57

Browse files
committed
fix(projections): harden js state handling
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
1 parent 35e09cd commit 39c4f57

4 files changed

Lines changed: 125 additions & 23 deletions

File tree

src/EventStore.Projections.Core.Tests/Services/Jint/Serialization/when_serializing_state.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,15 @@ public void undefined()
136136
Assert.AreEqual(@"null", serialized);
137137
}
138138

139+
[TestCase("NaN")]
140+
[TestCase("Infinity")]
141+
[TestCase("-Infinity")]
142+
public void non_finite_numbers_are_serialized_as_null(string expression)
143+
{
144+
var serialized = _sut.Serialize(_engine.Evaluate(expression));
145+
Assert.AreEqual(@"null", serialized);
146+
}
147+
139148
[Test]
140149
public void undefined_property()
141150
{
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using EventStore.Projections.Core.Services;
3+
using EventStore.Projections.Core.Services.Processing.Checkpointing;
4+
using NUnit.Framework;
5+
6+
namespace EventStore.Projections.Core.Tests.Services.Jint;
7+
8+
[TestFixture]
9+
public class when_accessing_event_body_with_non_object_data : TestFixtureWithInterpretedProjection
10+
{
11+
protected override void Given()
12+
{
13+
_projection = @"
14+
fromAll().when({$any:
15+
function(state, event) {
16+
return event.body;
17+
}
18+
});
19+
";
20+
}
21+
22+
[TestCase("null", null)]
23+
[TestCase("42", "42")]
24+
[TestCase(@"""hello""", @"""hello""")]
25+
[TestCase("true", "true")]
26+
[Category(_projectionType)]
27+
public void process_event_returns_json_primitive_body(string data, string expectedState)
28+
{
29+
_stateHandler.ProcessEvent(
30+
"", CheckpointTag.FromPosition(0, 20, 10), "stream1", "type1", "category", Guid.NewGuid(), 0,
31+
"metadata", data, out var state, out _);
32+
33+
Assert.AreEqual(expectedState, state);
34+
}
35+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System;
2+
using EventStore.Projections.Core.Services;
3+
using EventStore.Projections.Core.Services.Processing.Checkpointing;
4+
using NUnit.Framework;
5+
6+
namespace EventStore.Projections.Core.Tests.Services.Jint;
7+
8+
[TestFixture]
9+
public class when_round_tripping_js_projection_with_string_state : TestFixtureWithInterpretedProjection
10+
{
11+
protected override void Given()
12+
{
13+
_projection = @"
14+
fromAll().when({
15+
type1: function(state, event) {
16+
return 'hello';
17+
}
18+
});
19+
";
20+
}
21+
22+
[Test, Category(_projectionType)]
23+
public void process_event_returns_json_encoded_string_state()
24+
{
25+
_stateHandler.ProcessEvent(
26+
"", CheckpointTag.FromPosition(0, 20, 10), "stream1", "type1", "category", Guid.NewGuid(), 0,
27+
"metadata", @"{""a"":""b""}", out var state, out _);
28+
29+
Assert.AreEqual(@"""hello""", state);
30+
Assert.DoesNotThrow(() => _stateHandler.Load(state));
31+
}
32+
}
33+
34+
[TestFixture]
35+
public class when_round_tripping_bi_state_js_projection_with_string_state : TestFixtureWithInterpretedProjection
36+
{
37+
protected override void Given()
38+
{
39+
_projection = @"
40+
options({
41+
biState: true,
42+
});
43+
fromAll().foreachStream().when({
44+
type1: function(state, event) {
45+
state[0] = 'hello';
46+
return state;
47+
}
48+
});
49+
";
50+
}
51+
52+
[Test, Category(_projectionType)]
53+
public void process_event_returns_json_encoded_string_state()
54+
{
55+
_stateHandler.ProcessEvent(
56+
"", CheckpointTag.FromPosition(0, 20, 10), "stream1", "type1", "category", Guid.NewGuid(), 0,
57+
"metadata", @"{""a"":""b""}", out var state, out var sharedState, out _);
58+
59+
Assert.AreEqual(@"""hello""", state);
60+
Assert.IsNull(sharedState);
61+
Assert.DoesNotThrow(() => _stateHandler.Load(state));
62+
}
63+
}

src/EventStore.Projections.Core/Services/Interpreted/JintProjectionStateHandler.cs

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,7 @@ private void PrepareOutput(out string? newState, out string? newSharedState, out
243243
var arr = _state.AsArray();
244244
if (arr.TryGetValue(0, out var state))
245245
{
246-
if (_state.IsString())
247-
{
248-
newState = _state.AsString();
249-
}
250-
else
251-
{
252-
newState = ConvertToStringHandlingNulls(state);
253-
}
246+
newState = ConvertToStringHandlingNulls(state);
254247
}
255248
else
256249
{
@@ -267,11 +260,6 @@ private void PrepareOutput(out string? newState, out string? newSharedState, out
267260
}
268261

269262
}
270-
else if (_state.IsString())
271-
{
272-
newState = _state.AsString();
273-
newSharedState = null;
274-
}
275263
else
276264
{
277265
newState = ConvertToStringHandlingNulls(_state);
@@ -921,7 +909,7 @@ public JsValue Handle(JsValue state, EventEnvelope eventEnvelope)
921909
}
922910
else
923911
{
924-
newState = eventEnvelope.BodyRaw;
912+
newState = eventEnvelope.IsJson ? eventEnvelope.Body : eventEnvelope.BodyRaw;
925913
}
926914
return newState == Undefined ? state : newState;
927915
}
@@ -1096,33 +1084,32 @@ public JsValue Body
10961084
{
10971085
get
10981086
{
1099-
if (TryGetValue("body", out var value) && value is ObjectInstance oi)
1087+
if (TryGetValue("body", out var value) && value is not JsUndefined)
11001088
{
1101-
return oi;
1089+
return value;
11021090
}
11031091

1104-
if (EnsureBody(out JsValue objectInstance))
1092+
if (EnsureBody(out var body))
11051093
{
1106-
return objectInstance;
1094+
return body;
11071095
}
11081096

11091097
return Undefined;
11101098
}
11111099
}
11121100

1113-
private bool EnsureBody(out JsValue objectInstance)
1101+
private bool EnsureBody(out JsValue body)
11141102
{
11151103
if (IsJson && TryGetValue("bodyRaw", out var raw) && raw is not JsUndefined)
11161104
{
1117-
var body = raw.IsNull() ? raw : _parser.Parse(raw.AsString());
1105+
body = raw.IsNull() ? raw : _parser.Parse(raw.AsString());
11181106
var pd = new PropertyDescriptor(body, false, true, false);
11191107
SetOwnProperty("body", pd);
11201108
SetOwnProperty("data", pd);
1121-
objectInstance = (ObjectInstance)body;
11221109
return true;
11231110
}
11241111

1125-
objectInstance = Undefined;
1112+
body = Undefined;
11261113
return false;
11271114
}
11281115

@@ -1555,7 +1542,15 @@ static void SerializePrimitive(JsValue value, Utf8JsonWriter writer)
15551542

15561543
break;
15571544
case Types.Number:
1558-
writer.WriteNumberValue(value.AsNumber());
1545+
var number = value.AsNumber();
1546+
if (double.IsFinite(number))
1547+
{
1548+
writer.WriteNumberValue(number);
1549+
}
1550+
else
1551+
{
1552+
writer.WriteNullValue();
1553+
}
15591554
break;
15601555
case Types.BigInt:
15611556
writer.WriteStringValue(value.ToString());

0 commit comments

Comments
 (0)