Skip to content

Commit faf6098

Browse files
authored
Low level client exception structure should match SimpleJson strategy (#3662)
This commit introduces an ExceptionFormatters that serializes exceptions to the same structure as that used in 6.x Fixes #3656
1 parent a2b79eb commit faf6098

File tree

4 files changed

+191
-1
lines changed

4 files changed

+191
-1
lines changed

src/Elasticsearch.Net/Serialization/ElasticsearchNetFormatterResolver.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ internal sealed class InnerResolver : IJsonFormatterResolver
2929
ElasticsearchNetEnumResolver.Instance, // Specialized Enum handling
3030
AttributeFormatterResolver.Instance, // [JsonFormatter]
3131
DynamicGenericResolver.Instance, // T[], List<T>, etc...
32+
ExceptionFormatterResolver.Instance
3233
};
3334

3435
private readonly IJsonFormatterResolver _finalFormatter;
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Globalization;
4+
using System.Reflection;
5+
using System.Runtime.Serialization;
6+
7+
namespace Elasticsearch.Net
8+
{
9+
internal class ExceptionFormatterResolver : IJsonFormatterResolver
10+
{
11+
public static ExceptionFormatterResolver Instance = new ExceptionFormatterResolver();
12+
13+
private ExceptionFormatterResolver() { }
14+
15+
private static readonly ExceptionFormatter ExceptionFormatter = new ExceptionFormatter();
16+
17+
public IJsonFormatter<T> GetFormatter<T>()
18+
{
19+
if (typeof(Exception).IsAssignableFrom(typeof(T)))
20+
return (IJsonFormatter<T>)ExceptionFormatter;
21+
22+
return null;
23+
}
24+
}
25+
26+
internal class ExceptionFormatter : IJsonFormatter<Exception>
27+
{
28+
private List<Dictionary<string, object>> FlattenExceptions(Exception e)
29+
{
30+
var maxExceptions = 20;
31+
var exceptions = new List<Dictionary<string, object>>(maxExceptions);
32+
var depth = 0;
33+
do
34+
{
35+
var o = ToDictionary(e, depth);
36+
exceptions.Add(o);
37+
depth++;
38+
e = e.InnerException;
39+
} while (depth < maxExceptions && e != null);
40+
41+
return exceptions;
42+
}
43+
44+
private static Dictionary<string, object> ToDictionary(Exception e, int depth)
45+
{
46+
var o = new Dictionary<string, object>(10);
47+
var si = new SerializationInfo(e.GetType(), new FormatterConverter());
48+
var sc = new StreamingContext();
49+
e.GetObjectData(si, sc);
50+
51+
var helpUrl = si.GetString("HelpURL");
52+
var stackTrace = si.GetString("StackTraceString");
53+
var remoteStackTrace = si.GetString("RemoteStackTraceString");
54+
var remoteStackIndex = si.GetInt32("RemoteStackIndex");
55+
var exceptionMethod = si.GetString("ExceptionMethod");
56+
var hresult = si.GetInt32("HResult");
57+
var source = si.GetString("Source");
58+
var className = si.GetString("ClassName");
59+
60+
o.Add("Depth", depth);
61+
o.Add("ClassName", className);
62+
o.Add("Message", e.Message);
63+
o.Add("Source", source);
64+
o.Add("StackTraceString", stackTrace);
65+
o.Add("RemoteStackTraceString", remoteStackTrace);
66+
o.Add("RemoteStackIndex", remoteStackIndex);
67+
o.Add("HResult", hresult);
68+
o.Add("HelpURL", helpUrl);
69+
70+
WriteStructuredExceptionMethod(o, exceptionMethod);
71+
return o;
72+
}
73+
74+
private static void WriteStructuredExceptionMethod(Dictionary<string,object> o, string exceptionMethodString)
75+
{
76+
if (string.IsNullOrWhiteSpace(exceptionMethodString)) return;
77+
78+
var args = exceptionMethodString.Split('\0', '\n');
79+
80+
if (args.Length != 5) return;
81+
82+
var memberType = int.Parse(args[0], CultureInfo.InvariantCulture);
83+
var name = args[1];
84+
var assemblyName = args[2];
85+
var className = args[3];
86+
var signature = args[4];
87+
var an = new AssemblyName(assemblyName);
88+
var exceptionMethod = new Dictionary<string, object>(7)
89+
{
90+
{ "Name", name },
91+
{ "AssemblyName", an.Name },
92+
{ "AssemblyVersion", an.Version.ToString() },
93+
{ "AssemblyCulture", an.CultureName },
94+
{ "ClassName", className },
95+
{ "Signature", signature },
96+
{ "MemberType", memberType }
97+
};
98+
99+
o.Add("ExceptionMethod", exceptionMethod);
100+
}
101+
102+
public void Serialize(ref JsonWriter writer, Exception value, IJsonFormatterResolver formatterResolver)
103+
{
104+
if (value == null)
105+
{
106+
writer.WriteNull();
107+
return;
108+
}
109+
110+
var flattenedExceptions = FlattenExceptions(value);
111+
var formatter = formatterResolver.GetFormatter<List<Dictionary<string, object>>>();
112+
formatter.Serialize(ref writer, flattenedExceptions, formatterResolver);
113+
}
114+
115+
public Exception Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) =>
116+
throw new NotSupportedException();
117+
}
118+
}

src/Elasticsearch.Net/Utf8Json/Resolvers/DynamicObjectResolver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,7 @@ public static object BuildAnonymousFormatter(Type type, Func<string, string> nam
715715
{
716716
var ignoreSet = new HashSet<string>(new[]
717717
{
718-
"HelpLink", "TargetSite", "HResult", "Data", "ClassName", "InnerException"
718+
"TargetSite", "ClassName", "InnerException"
719719
}.Select(x => nameMutator(x)));
720720

721721
// special case for exception, modify
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using Elastic.Xunit.XunitPlumbing;
3+
using Elasticsearch.Net;
4+
using FluentAssertions;
5+
using Newtonsoft.Json;
6+
using Newtonsoft.Json.Linq;
7+
8+
namespace Tests.Framework.SerializationTests
9+
{
10+
public class ExceptionSerializationTests
11+
{
12+
private readonly IElasticsearchSerializer _elasticsearchNetSerializer;
13+
14+
private readonly Exception Exception = new Exception("outer_exception",
15+
new InnerException("inner_exception",
16+
new InnerInnerException("inner_inner_exception")));
17+
18+
public ExceptionSerializationTests()
19+
{
20+
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
21+
var connection = new InMemoryConnection();
22+
var values = new ConnectionConfiguration(pool, connection);
23+
var lowlevelClient = new ElasticLowLevelClient(values);
24+
_elasticsearchNetSerializer = lowlevelClient.Serializer;
25+
}
26+
27+
[U]
28+
public void LowLevelExceptionSerializationMatchesJsonNet()
29+
{
30+
var serialized = _elasticsearchNetSerializer.SerializeToString(Exception);
31+
32+
object CreateException(Type exceptionType, string message, int depth)
33+
{
34+
return new
35+
{
36+
Depth = depth,
37+
ClassName = exceptionType.FullName,
38+
Message = message,
39+
Source = (object)null,
40+
StackTraceString = (object)null,
41+
RemoteStackTraceString = (object)null,
42+
RemoteStackIndex = 0,
43+
HResult = -2146233088,
44+
HelpURL = (object)null
45+
};
46+
}
47+
48+
var simpleJsonException = new[]
49+
{
50+
CreateException(typeof(Exception), "outer_exception", 0),
51+
CreateException(typeof(InnerException), "inner_exception", 1),
52+
CreateException(typeof(InnerInnerException), "inner_inner_exception", 2),
53+
};
54+
55+
var jArray = JArray.Parse(serialized);
56+
var jArray2 = JArray.Parse(JsonConvert.SerializeObject(simpleJsonException));
57+
58+
JToken.DeepEquals(jArray, jArray2).Should().BeTrue();
59+
}
60+
61+
public class InnerException : Exception
62+
{
63+
public InnerException(string message, Exception innerException) : base(message, innerException) { }
64+
}
65+
66+
public class InnerInnerException : Exception
67+
{
68+
public InnerInnerException(string message) : base(message) { }
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)