Skip to content

Commit 2fb7cf1

Browse files
committed
Fix for #3084, the default internal serializer tried to mimic json.net deserialize() behaviour for empty strings with a bazooka try catch around JToken.LoadAsync which ended up hiding real deserialization errors, **sigh** (#3086)
1 parent 5d08c61 commit 2fb7cf1

File tree

3 files changed

+76
-31
lines changed

3 files changed

+76
-31
lines changed

src/Elasticsearch.Net/Transport/Pipeline/ResponseBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace Elasticsearch.Net
1010
{
1111
public static class ResponseBuilder
1212
{
13-
private const int BufferSize = 81920;
13+
public const int BufferSize = 81920;
1414

1515
public static TResponse ToResponse<TResponse>(
1616
RequestData requestData,

src/Nest/CommonAbstractions/SerializationBehavior/InternalSerializer.cs

Lines changed: 24 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -98,41 +98,25 @@ public object Deserialize(Type type, Stream stream)
9898
public async Task<T> DeserializeAsync<T>(Stream stream, CancellationToken cancellationToken = default(CancellationToken))
9999
{
100100
if (stream == null) return default(T);
101-
using (var streamReader = new StreamReader(stream))
102-
using (var jsonTextReader = new JsonTextReader(streamReader))
103-
{
104-
//JsonSerializerInternalReader that's is used in `Deserialize()` from the synchronous codepath
105-
// has the same try catch
106-
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs#L145
107-
// :/
108-
try
109-
{
110-
var token = await JToken.LoadAsync(jsonTextReader, cancellationToken).ConfigureAwait(false);
111-
return token.ToObject<T>(this.Serializer);
112-
}
113-
catch
114-
{
115-
return default(T);
116-
}
117-
}
101+
var bytes = await ReadToBytesAsync(stream, cancellationToken);
102+
103+
if (bytes == null || bytes.Length == 0) return default(T);
104+
using (var ms = new MemoryStream(bytes))
105+
using (var sr = new StreamReader(ms))
106+
using (var jtr = new JsonTextReader(sr))
107+
return this.Serializer.Deserialize<T>(jtr);
118108
}
119109

120110
public async Task<object> DeserializeAsync(Type type, Stream stream, CancellationToken cancellationToken = default(CancellationToken))
121111
{
122112
if (stream == null) return type.DefaultValue();
123-
using (var streamReader = new StreamReader(stream))
124-
using (var jsonTextReader = new JsonTextReader(streamReader))
125-
{
126-
try
127-
{
128-
var token = await JToken.LoadAsync(jsonTextReader, cancellationToken).ConfigureAwait(false);
129-
return token.ToObject(type, this.Serializer);
130-
}
131-
catch
132-
{
133-
return type.DefaultValue();
134-
}
135-
}
113+
var bytes = await ReadToBytesAsync(stream, cancellationToken);
114+
115+
if (bytes == null || bytes.Length == 0) return type.DefaultValue();
116+
using (var ms = new MemoryStream(bytes))
117+
using (var sr = new StreamReader(ms))
118+
using (var jtr = new JsonTextReader(sr))
119+
return this.Serializer.Deserialize(jtr, type);
136120
}
137121

138122
private JsonSerializerSettings CreateSettings(SerializationFormatting formatting)
@@ -150,5 +134,15 @@ private JsonSerializerSettings CreateSettings(SerializationFormatting formatting
150134

151135
return settings;
152136
}
137+
138+
private static async Task<byte[]> ReadToBytesAsync(Stream stream, CancellationToken cancellationToken)
139+
{
140+
if (stream is MemoryStream o) return o.ToArray();
141+
using (var ms = new MemoryStream())
142+
{
143+
await stream.CopyToAsync(ms, ResponseBuilder.BufferSize, cancellationToken);
144+
return ms.ToArray();
145+
}
146+
}
153147
}
154148
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using Elasticsearch.Net;
5+
using FluentAssertions;
6+
using Nest;
7+
using Tests.Framework;
8+
using Tests.Framework.ManagedElasticsearch.Clusters;
9+
using Tests.Framework.MockData;
10+
using Xunit;
11+
12+
namespace Tests.Reproduce
13+
{
14+
public class GithubIssue3084 : IClusterFixture<WritableCluster>
15+
{
16+
private readonly WritableCluster _cluster;
17+
18+
public GithubIssue3084(WritableCluster cluster) => _cluster = cluster;
19+
20+
protected static string RandomString() => Guid.NewGuid().ToString("N").Substring(0, 8);
21+
22+
public class ObjectVersion1
23+
{
24+
public int Id { get; set; }
25+
public double Numeric { get; set; }
26+
}
27+
public class ObjectVersion2
28+
{
29+
public int Id { get; set; }
30+
public int Numeric { get; set; }
31+
}
32+
33+
34+
[I]
35+
public void DeserializeErrorIsTheSameForAsync()
36+
{
37+
var client = _cluster.Client;
38+
var index = $"gh3084-{RandomString()}";
39+
var document = new ObjectVersion1 {Id = 1, Numeric = 0};
40+
41+
var indexResult = client.Index(document, i => i.Index(index).Type("doc"));
42+
indexResult.ShouldBeValid();
43+
44+
Action getDoc = () =>client.Get<ObjectVersion2>(new GetRequest(index, "doc", 1));
45+
Func<Task<IGetResponse<ObjectVersion2>>> getDocAsync = async () => await client.GetAsync<ObjectVersion2>(new GetRequest(index, "doc", 1));
46+
47+
getDoc.ShouldThrow<Exception>("synchonous code path should throw");
48+
getDocAsync.ShouldThrow<Exception>("async code path should throw");
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)