diff --git a/src/Elasticsearch.Net/Responses/ServerException/Error.cs b/src/Elasticsearch.Net/Responses/ServerException/Error.cs index 68a87d50b4e..e78ec71f2ae 100644 --- a/src/Elasticsearch.Net/Responses/ServerException/Error.cs +++ b/src/Elasticsearch.Net/Responses/ServerException/Error.cs @@ -1,10 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Runtime.Serialization; namespace Elasticsearch.Net { + [DataContract] [JsonFormatter(typeof(ErrorFormatter))] public class Error : ErrorCause { @@ -18,54 +20,61 @@ public class Error : ErrorCause public IReadOnlyCollection RootCause { get; set; } } - internal class ErrorFormatter : IJsonFormatter + internal class ErrorFormatter : ErrorCauseFormatter { - public void Serialize(ref JsonWriter writer, Error value, IJsonFormatterResolver formatterResolver) { } + private static readonly AutomataDictionary Fields = new AutomataDictionary + { + { "headers", 0 }, + { "root_cause", 1 } + }; - public Error Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) + protected override void Serialize(ref JsonWriter writer, ref int count, Error value, IJsonFormatterResolver formatterResolver) { - switch (reader.GetCurrentJsonToken()) + if (value.Headers.Any()) { - case JsonToken.String: - { - var error = new Error { Reason = reader.ReadString() }; - return error; - } - case JsonToken.BeginObject: - { - var formatter = formatterResolver.GetFormatter>(); - var dict = formatter.Deserialize(ref reader, formatterResolver); + if (count > 0) + writer.WriteValueSeparator(); - var error = new Error(); - error.FillValues(dict); + writer.WritePropertyName("headers"); + formatterResolver.GetFormatter>() + .Serialize(ref writer, value.Headers, formatterResolver); - if (dict.TryGetValue("caused_by", out var causedBy)) - error.CausedBy = formatterResolver.ReserializeAndDeserialize(causedBy); + count++; + } - if (dict.TryGetValue("headers", out var headers)) - { - var d = formatterResolver.ReserializeAndDeserialize>(headers); - if (d != null) error.Headers = new ReadOnlyDictionary(d); - } + if (value.RootCause.Any()) + { + if (count > 0) + writer.WriteValueSeparator(); - error.Metadata = ErrorCause.ErrorCauseMetadata.CreateCauseMetadata(dict, formatterResolver); + writer.WritePropertyName("root_cause"); + formatterResolver.GetFormatter>() + .Serialize(ref writer, value.RootCause, formatterResolver); - return ReadRootCause(dict, formatterResolver, error); - } - default: - reader.ReadNextBlock(); - return null; + count++; } } - private static Error ReadRootCause(IDictionary dict, IJsonFormatterResolver formatterResolver, Error error) + protected override bool Deserialize(ref JsonReader reader, ref ArraySegment property, Error value, IJsonFormatterResolver formatterResolver) { - if (!dict.TryGetValue("root_cause", out var rootCause)) return error; + if (Fields.TryGetValue(property, out var fieldValue)) + { + switch (fieldValue) + { + case 0: + value.Headers = formatterResolver.GetFormatter>() + .Deserialize(ref reader, formatterResolver); + break; + case 1: + value.RootCause = formatterResolver.GetFormatter>() + .Deserialize(ref reader, formatterResolver); + break; + } - if (!(rootCause is List os)) return error; + return true; + } - error.RootCause = os.Select(formatterResolver.ReserializeAndDeserialize).ToList().AsReadOnly(); - return error; + return false; } } } diff --git a/src/Elasticsearch.Net/Responses/ServerException/ErrorCause.cs b/src/Elasticsearch.Net/Responses/ServerException/ErrorCause.cs index 53d59de5bff..90a84696200 100644 --- a/src/Elasticsearch.Net/Responses/ServerException/ErrorCause.cs +++ b/src/Elasticsearch.Net/Responses/ServerException/ErrorCause.cs @@ -1,153 +1,439 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using System.Runtime.Serialization; namespace Elasticsearch.Net { [JsonFormatter(typeof(ErrorCauseFormatter))] + [DataContract] public class ErrorCause { - [DataMember(Name = "caused_by")] - public ErrorCause CausedBy { get; set; } + private static readonly IReadOnlyCollection DefaultCollection = + new ReadOnlyCollection(new string[0]); - public ErrorCauseMetadata Metadata { get; set; } + private static readonly IReadOnlyDictionary DefaultDictionary = + new ReadOnlyDictionary(new Dictionary()); - [DataMember(Name = "reason")] - public string Reason { get; set; } + private static readonly IReadOnlyCollection DefaultFailedShards = + new ReadOnlyCollection(new ShardFailure[0]); - [DataMember(Name = "stack_trace")] - public string StackTrace { get; set; } + /// + /// Additional properties related to the error cause. Contains properties that + /// are not explicitly mapped on + /// + public IReadOnlyDictionary AdditionalProperties { get; internal set; } = DefaultDictionary; - [DataMember(Name = "type")] - public string Type { get; set; } + public long? BytesLimit { get; internal set; } + + public long? BytesWanted { get; internal set; } + + public ErrorCause CausedBy { get; internal set; } + + public int? Column { get; internal set; } + + public IReadOnlyCollection FailedShards { get; internal set; } = DefaultFailedShards; + + public bool? Grouped { get; internal set; } + + public string Index { get; internal set; } + + public string IndexUUID { get; internal set; } + + public string Language { get; internal set; } + + public string LicensedExpiredFeature { get; internal set; } + + public int? Line { get; internal set; } + + public string Phase { get; internal set; } + + public string Reason { get; internal set; } + + public IReadOnlyCollection ResourceId { get; internal set; } = DefaultCollection; + + public string ResourceType { get; internal set; } + + public string Script { get; internal set; } + + public IReadOnlyCollection ScriptStack { get; internal set; } = DefaultCollection; + + public int? Shard { get; internal set; } + + public string StackTrace { get; internal set; } + + public string Type { get; internal set; } public override string ToString() => CausedBy == null ? $"Type: {Type} Reason: \"{Reason}\"" : $"Type: {Type} Reason: \"{Reason}\" CausedBy: \"{CausedBy}\""; + } - public class ErrorCauseMetadata + internal class ErrorCauseFormatter : IJsonFormatter + where TErrorCause : ErrorCause, new() + { + private static readonly AutomataDictionary Fields = new AutomataDictionary { - private static readonly IReadOnlyCollection DefaultCollection = - new ReadOnlyCollection(new string[0] { }); + { "bytes_limit", 0 }, + { "bytes_wanted", 1 }, + { "caused_by", 2 }, + { "col", 3 }, + { "failed_shards", 4 }, + { "grouped", 5 }, + { "index", 6 }, + { "index_uuid", 7 }, + { "lang", 8 }, + { "license.expired.feature", 9 }, + { "line", 10 }, + { "phase", 11 }, + { "reason", 12 }, + { "resource.id", 13 }, + { "resource.type", 14 }, + { "script", 15 }, + { "script_stack", 16 }, + { "shard", 17 }, + { "stack_trace", 18 }, + { "type", 19 } + }; + + private static readonly NullableStringIntFormatter ShardFormatter = new NullableStringIntFormatter(); - private static readonly IReadOnlyCollection DefaultFailedShards = - new ReadOnlyCollection(new ShardFailure[0] { }); + private static readonly InterfaceReadOnlyCollectionSingleOrEnumerableFormatter SingleOrEnumerableFormatter = + new InterfaceReadOnlyCollectionSingleOrEnumerableFormatter(); - public long? BytesLimit { get; set; } + private static readonly ErrorCauseFormatter ErrorCausePropertyFormatter = new ErrorCauseFormatter(); - public long? BytesWanted { get; set; } - public int? Column { get; set; } + protected virtual bool Deserialize(ref JsonReader reader, ref ArraySegment property, TErrorCause value, + IJsonFormatterResolver formatterResolver) => false; - public IReadOnlyCollection FailedShards { get; set; } = DefaultFailedShards; - public bool? Grouped { get; set; } - public string Index { get; set; } - public string IndexUUID { get; set; } - public string Language { get; set; } + protected virtual void Serialize(ref JsonWriter writer, ref int count, TErrorCause value, IJsonFormatterResolver formatterResolver) {} - public string LicensedExpiredFeature { get; set; } + public TErrorCause Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) + { + var token = reader.GetCurrentJsonToken(); + switch (token) + { + case JsonToken.String: + return new TErrorCause { Reason = reader.ReadString() }; + case JsonToken.BeginObject: + var count = 0; + var errorCause = new TErrorCause(); + var additionalProperties = new Dictionary(); + errorCause.AdditionalProperties = additionalProperties; + var formatter = formatterResolver.GetFormatter(); + while (reader.ReadIsInObject(ref count)) + { + var property = reader.ReadPropertyNameSegmentRaw(); + if (Fields.TryGetValue(property, out var value)) + { + switch (value) + { + case 0: + errorCause.BytesLimit = reader.ReadInt64(); + break; + case 1: + errorCause.BytesWanted = reader.ReadInt64(); + break; + case 2: + errorCause.CausedBy = ErrorCausePropertyFormatter.Deserialize(ref reader, formatterResolver); + break; + case 3: + errorCause.Column = reader.ReadInt32(); + break; + case 4: + errorCause.FailedShards = formatterResolver.GetFormatter>() + .Deserialize(ref reader, formatterResolver); + break; + case 5: + errorCause.Grouped = reader.ReadBoolean(); + break; + case 6: + errorCause.Index = reader.ReadString(); + break; + case 7: + errorCause.IndexUUID = reader.ReadString(); + break; + case 8: + errorCause.Language = reader.ReadString(); + break; + case 9: + errorCause.LicensedExpiredFeature = reader.ReadString(); + break; + case 10: + errorCause.Line = reader.ReadInt32(); + break; + case 11: + errorCause.Phase = reader.ReadString(); + break; + case 12: + errorCause.Reason = reader.ReadString(); + break; + case 13: + errorCause.ResourceId = SingleOrEnumerableFormatter.Deserialize(ref reader, formatterResolver); + break; + case 14: + errorCause.ResourceType = reader.ReadString(); + break; + case 15: + errorCause.Script = reader.ReadString(); + break; + case 16: + errorCause.ScriptStack = SingleOrEnumerableFormatter.Deserialize(ref reader, formatterResolver); + break; + case 17: + errorCause.Shard = ShardFormatter.Deserialize(ref reader, formatterResolver); + break; + case 18: + errorCause.StackTrace = reader.ReadString(); + break; + case 19: + errorCause.Type = reader.ReadString(); + break; + } + } + else + { + if (!Deserialize(ref reader, ref property, errorCause, formatterResolver)) + additionalProperties.Add(property.Utf8String(), formatter.Deserialize(ref reader, formatterResolver)); + } + } - public int? Line { get; set; } + return errorCause; + default: + reader.ReadNextBlock(); + return null; + } + } - public string Phase { get; set; } - public IReadOnlyCollection ResourceId { get; set; } = DefaultCollection; - public string ResourceType { get; set; } - public string Script { get; set; } + public void Serialize(ref JsonWriter writer, TErrorCause value, IJsonFormatterResolver formatterResolver) + { + if (value == null) + { + writer.WriteNull(); + return; + } - public IReadOnlyCollection ScriptStack { get; set; } = DefaultCollection; - public int? Shard { get; set; } + writer.WriteBeginObject(); + var count = 0; - internal static ErrorCauseMetadata CreateCauseMetadata(IDictionary dict, IJsonFormatterResolver formatterResolver) + if (value.BytesLimit.HasValue) { - var m = new ErrorCauseMetadata(); - if (dict.TryGetValue("license.expired.feature", out var feature)) m.LicensedExpiredFeature = Convert.ToString(feature); - if (dict.TryGetValue("index", out var index)) m.Index = Convert.ToString(index); - if (dict.TryGetValue("index_uuid", out var indexUUID)) m.IndexUUID = Convert.ToString(indexUUID); - if (dict.TryGetValue("resource.type", out var resourceType)) m.ResourceType = Convert.ToString(resourceType); - if (dict.TryGetValue("resource.id", out var resourceId)) m.ResourceId = GetStringArray(resourceId); - if (dict.TryGetValue("shard", out var shard)) m.Shard = Convert.ToInt32(shard); - if (dict.TryGetValue("line", out var line)) m.Line = Convert.ToInt32(line); - if (dict.TryGetValue("col", out var column)) m.Column = Convert.ToInt32(column); - if (dict.TryGetValue("bytes_wanted", out var bytesWanted)) m.BytesWanted = Convert.ToInt64(bytesWanted); - if (dict.TryGetValue("bytes_limit", out var bytesLimit)) m.BytesLimit = Convert.ToInt64(bytesLimit); + writer.WritePropertyName("bytes_limit"); + writer.WriteInt64(value.BytesLimit.Value); + count++; + } - if (dict.TryGetValue("phase", out var phase)) m.Phase = Convert.ToString(phase); - if (dict.TryGetValue("grouped", out var grouped)) m.Grouped = Convert.ToBoolean(grouped); + if (value.BytesWanted.HasValue) + { + if (count > 0) + writer.WriteValueSeparator(); - if (dict.TryGetValue("script_stack", out var scriptStack)) m.ScriptStack = GetStringArray(scriptStack); - if (dict.TryGetValue("script", out var script)) m.Script = Convert.ToString(script); - if (dict.TryGetValue("lang", out var language)) m.Language = Convert.ToString(language); - if (dict.TryGetValue("failed_shards", out var failedShards)) - m.FailedShards = GetShardFailures(failedShards, formatterResolver); - return m; + writer.WritePropertyName("bytes_wanted"); + writer.WriteInt64(value.BytesWanted.Value); + count++; } - private static IReadOnlyCollection GetShardFailures(object value, IJsonFormatterResolver formatterResolver) + if (value.CausedBy != null) { - if (!(value is List objects)) - return DefaultFailedShards; + if (count > 0) + writer.WriteValueSeparator(); - var values = new List(); - foreach (var v in objects) - { - var cause = formatterResolver.ReserializeAndDeserialize(v); - if (cause != null) values.Add(cause); - } - return new ReadOnlyCollection(values.ToArray()); + writer.WritePropertyName("caused_by"); + ErrorCausePropertyFormatter.Serialize(ref writer, value.CausedBy, formatterResolver); + count++; } - private static IReadOnlyCollection GetStringArray(object value) + if (value.Column.HasValue) { - if (value is string s) return new ReadOnlyCollection(new[] { s }); + if (count > 0) + writer.WriteValueSeparator(); - if (value is List objects) - { - var values = new List(); - foreach (var v in objects) - { - if (v is string vs) values.Add(vs); - } - return new ReadOnlyCollection(values.ToArray()); - } - return DefaultCollection; + writer.WritePropertyName("col"); + writer.WriteInt32(value.Column.Value); + count++; } - } - } - internal class ErrorCauseFormatter : IJsonFormatter - { - public ErrorCause Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) - { - var token = reader.GetCurrentJsonToken(); + if (value.FailedShards.Any()) + { + if (count > 0) + writer.WriteValueSeparator(); - switch (token) + writer.WritePropertyName("failed_shards"); + formatterResolver.GetFormatter>() + .Serialize(ref writer, value.FailedShards, formatterResolver); + count++; + } + + if (value.Grouped.HasValue) { - case JsonToken.BeginObject: - { - var formatter = formatterResolver.GetFormatter>(); - var dict = formatter.Deserialize(ref reader, formatterResolver); - var errorCause = new ErrorCause(); - errorCause.FillValues(dict); + if (count > 0) + writer.WriteValueSeparator(); - if (dict.TryGetValue("caused_by", out var causedBy)) - errorCause.CausedBy = formatterResolver.ReserializeAndDeserialize(causedBy); + writer.WritePropertyName("grouped"); + writer.WriteBoolean(value.Grouped.Value); + count++; + } - errorCause.Metadata = ErrorCause.ErrorCauseMetadata.CreateCauseMetadata(dict, formatterResolver); + if (value.Index != null) + { + if (count > 0) + writer.WriteValueSeparator(); - return errorCause; - } - case JsonToken.String: + writer.WritePropertyName("index"); + writer.WriteString(value.Index); + count++; + } + + if (value.IndexUUID != null) + { + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName("index_uuid"); + writer.WriteString(value.IndexUUID); + count++; + } + + if (value.Language != null) + { + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName("lang"); + writer.WriteString(value.Language); + count++; + } + + if (value.LicensedExpiredFeature != null) + { + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName("license.expired.feature"); + writer.WriteString(value.LicensedExpiredFeature); + count++; + } + + if (value.Line.HasValue) + { + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName("line"); + writer.WriteInt32(value.Line.Value); + count++; + } + + if (value.Phase != null) + { + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName("phase"); + writer.WriteString(value.Phase); + count++; + } + + if (value.Reason != null) + { + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName("reason"); + writer.WriteString(value.Reason); + count++; + } + + if (value.ResourceId.Any()) + { + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName("resource.id"); + SingleOrEnumerableFormatter.Serialize(ref writer, value.ResourceId, formatterResolver); + count++; + } + + if (value.ResourceType != null) + { + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName("resource.type"); + writer.WriteString(value.ResourceType); + count++; + } + + if (value.Script != null) + { + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName("script"); + writer.WriteString(value.Script); + count++; + } + + if (value.ScriptStack.Any()) + { + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName("script_stack"); + SingleOrEnumerableFormatter.Serialize(ref writer, value.ScriptStack, formatterResolver); + count++; + } + + if (value.Shard.HasValue) + { + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName("shard"); + writer.WriteInt32(value.Shard.Value); + count++; + } + + if (value.StackTrace != null) + { + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName("stack_trace"); + writer.WriteString(value.StackTrace); + count++; + } + + if (value.Type != null) + { + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName("type"); + writer.WriteString(value.Type); + count++; + } + + Serialize(ref writer, ref count, value, formatterResolver); + + if (value.AdditionalProperties.Any()) + { + var formatter = formatterResolver.GetFormatter(); + foreach (var additionalProperty in value.AdditionalProperties) { - var errorCause = new ErrorCause { Reason = reader.ReadString() }; - return errorCause; + if (count > 0) + writer.WriteValueSeparator(); + + writer.WritePropertyName(additionalProperty.Key); + formatter.Serialize(ref writer, additionalProperty.Value, formatterResolver); + count++; } - default: - reader.ReadNextBlock(); - return null; } - } - public void Serialize(ref JsonWriter writer, ErrorCause value, IJsonFormatterResolver formatterResolver) => - throw new NotSupportedException(); + writer.WriteEndObject(); + } } + + internal class ErrorCauseFormatter : ErrorCauseFormatter {} } diff --git a/src/Elasticsearch.Net/Responses/ServerException/ErrorCauseExtensions.cs b/src/Elasticsearch.Net/Responses/ServerException/ErrorCauseExtensions.cs deleted file mode 100644 index c849d748bd8..00000000000 --- a/src/Elasticsearch.Net/Responses/ServerException/ErrorCauseExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Elasticsearch.Net -{ - internal static class ErrorCauseExtensions - { - public static void FillValues(this ErrorCause rootCause, IDictionary dict) - { - if (dict == null) return; - - if (dict.TryGetValue("reason", out var reason) && reason != null) rootCause.Reason = Convert.ToString(reason); - if (dict.TryGetValue("type", out var type) && type != null) rootCause.Type = Convert.ToString(type); - if (dict.TryGetValue("stack_trace", out var stackTrace) && stackTrace != null) rootCause.StackTrace = Convert.ToString(stackTrace); - -// if (dict.TryGetValue("index", out var index)) rootCause.Index = Convert.ToString(index); -// if (dict.TryGetValue("resource.id", out var resourceId)) rootCause.ResourceId = Convert.ToString(resourceId); -// if (dict.TryGetValue("resource.type", out var resourceType)) rootCause.ResourceType = Convert.ToString(resourceType); - } - } -} diff --git a/src/Elasticsearch.Net/Responses/ServerException/ServerError.cs b/src/Elasticsearch.Net/Responses/ServerException/ServerError.cs index fdc7f7aa21b..03000e9d569 100644 --- a/src/Elasticsearch.Net/Responses/ServerException/ServerError.cs +++ b/src/Elasticsearch.Net/Responses/ServerException/ServerError.cs @@ -8,20 +8,22 @@ namespace Elasticsearch.Net { - [JsonFormatter(typeof(ServerErrorFormatter))] + [DataContract] public class ServerError { + internal ServerError() {} + public ServerError(Error error, int? statusCode) { Error = error; - Status = statusCode.GetValueOrDefault(); + Status = statusCode.GetValueOrDefault(-1); } [DataMember(Name = "error")] - public Error Error { get; } + public Error Error { get; internal set; } [DataMember(Name = "status")] - public int Status { get; } + public int Status { get; internal set; } = -1; public static bool TryCreate(Stream stream, out ServerError serverError) { @@ -40,41 +42,16 @@ public static bool TryCreate(Stream stream, out ServerError serverError) public static ServerError Create(Stream stream) => LowLevelRequestResponseSerializer.Instance.Deserialize(stream); - // TODO: make token default parameter in 7.x - public static Task CreateAsync(Stream stream, CancellationToken token) => + public static Task CreateAsync(Stream stream, CancellationToken token = default) => LowLevelRequestResponseSerializer.Instance.DeserializeAsync(stream, token); public override string ToString() { - var sb = new System.Text.StringBuilder(); + var sb = new StringBuilder(); sb.Append($"ServerError: {Status}"); if (Error != null) sb.Append(Error); return sb.ToString(); } } - - internal class ServerErrorFormatter : IJsonFormatter - { - public void Serialize(ref JsonWriter writer, ServerError value, IJsonFormatterResolver formatterResolver) => - throw new NotSupportedException(); - - // TODO: Optimize this - public ServerError Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) - { - var formatter = formatterResolver.GetFormatter>(); - var dict = formatter.Deserialize(ref reader, formatterResolver); - - var statusCode = -1; - - if (dict.TryGetValue("status", out var status)) - statusCode = Convert.ToInt32(status); - - if (!dict.TryGetValue("error", out var error)) return null; - - var err = formatterResolver.ReserializeAndDeserialize(error); - - return new ServerError(err, statusCode); - } - } } diff --git a/src/Elasticsearch.Net/Responses/ServerException/ShardFailure.cs b/src/Elasticsearch.Net/Responses/ServerException/ShardFailure.cs index 8b91ce8ffad..49ab0a89f57 100644 --- a/src/Elasticsearch.Net/Responses/ServerException/ShardFailure.cs +++ b/src/Elasticsearch.Net/Responses/ServerException/ShardFailure.cs @@ -1,46 +1,23 @@ -using System; -using System.Collections.Generic; +using System.Runtime.Serialization; namespace Elasticsearch.Net { - [JsonFormatter(typeof(ShardFailureFormatter))] + [DataContract] public class ShardFailure { + [DataMember(Name = "index")] public string Index { get; set; } - public string Node { get; set; } - public ErrorCause Reason { get; set; } - public int? Shard { get; set; } - public string Status { get; set; } - } - internal class ShardFailureFormatter : IJsonFormatter - { - public void Serialize(ref JsonWriter writer, ShardFailure value, IJsonFormatterResolver formatterResolver) => - throw new NotSupportedException(); - - public ShardFailure Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) - { - var failure = new ShardFailure(); + [DataMember(Name = "node")] + public string Node { get; set; } - if (reader.GetCurrentJsonToken() != JsonToken.BeginObject) - { - reader.ReadNextBlock(); - return failure; - } + [DataMember(Name = "reason")] + public ErrorCause Reason { get; set; } - var formatter = formatterResolver.GetFormatter>(); - var dict = formatter.Deserialize(ref reader, formatterResolver); + [DataMember(Name = "shard")] + public int? Shard { get; set; } - if (dict.TryGetValue("shard", out var shard)) failure.Shard = Convert.ToInt32(shard); - if (dict.TryGetValue("index", out var index)) failure.Index = Convert.ToString(index); - if (dict.TryGetValue("node", out var node)) failure.Node = Convert.ToString(node); - if (dict.TryGetValue("status", out var status)) failure.Status = Convert.ToString(status); - if (dict.TryGetValue("reason", out var reason)) - { - var cause = formatterResolver.ReserializeAndDeserialize(reason); - failure.Reason = cause; - } - return failure; - } + [DataMember(Name = "status")] + public string Status { get; set; } } } diff --git a/src/Elasticsearch.Net/Serialization/Extensions/ArraySegmentBytesExtensions.cs b/src/Elasticsearch.Net/Serialization/Extensions/ArraySegmentBytesExtensions.cs index 85be9998737..c3abcef1637 100644 --- a/src/Elasticsearch.Net/Serialization/Extensions/ArraySegmentBytesExtensions.cs +++ b/src/Elasticsearch.Net/Serialization/Extensions/ArraySegmentBytesExtensions.cs @@ -74,5 +74,9 @@ public static bool ContainsDateMathSeparator(this ref ArraySegment segment return false; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static string Utf8String(this ref ArraySegment segment) => + StringEncoding.UTF8.GetString(segment.Array, segment.Offset, segment.Count); } } diff --git a/src/Elasticsearch.Net/Serialization/Formatters/InterfaceReadOnlyCollectionSingleOrEnumerableFormatter.cs b/src/Elasticsearch.Net/Serialization/Formatters/InterfaceReadOnlyCollectionSingleOrEnumerableFormatter.cs new file mode 100644 index 00000000000..efad80060b1 --- /dev/null +++ b/src/Elasticsearch.Net/Serialization/Formatters/InterfaceReadOnlyCollectionSingleOrEnumerableFormatter.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Elasticsearch.Net +{ + internal class InterfaceReadOnlyCollectionSingleOrEnumerableFormatter : IJsonFormatter> + { + public IReadOnlyCollection Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) + { + var token = reader.GetCurrentJsonToken(); + return token == JsonToken.BeginArray + ? formatterResolver.GetFormatter>().Deserialize(ref reader, formatterResolver) + : new ReadOnlyCollection(new List(1) { formatterResolver.GetFormatter().Deserialize(ref reader, formatterResolver) }); + } + + public void Serialize(ref JsonWriter writer, IReadOnlyCollection value, IJsonFormatterResolver formatterResolver) + { + if (value == null) + { + writer.WriteNull(); + return; + } + + var formatter = formatterResolver.GetFormatter>(); + formatter.Serialize(ref writer, value, formatterResolver); + } + } +} diff --git a/src/Elasticsearch.Net/Serialization/Formatters/NullableStringIntFormatter.cs b/src/Elasticsearch.Net/Serialization/Formatters/NullableStringIntFormatter.cs new file mode 100644 index 00000000000..ed5673569f5 --- /dev/null +++ b/src/Elasticsearch.Net/Serialization/Formatters/NullableStringIntFormatter.cs @@ -0,0 +1,37 @@ +namespace Elasticsearch.Net +{ + internal class NullableStringIntFormatter : IJsonFormatter + { + public int? Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) + { + var token = reader.GetCurrentJsonToken(); + switch (token) + { + case JsonToken.Null: + reader.ReadNext(); + return null; + case JsonToken.String: + var s = reader.ReadString(); + if (!int.TryParse(s, out var i)) + throw new JsonParsingException($"Cannot parse {typeof(int).FullName} from: {s}"); + + return i; + case JsonToken.Number: + return reader.ReadInt32(); + default: + throw new JsonParsingException($"Cannot parse {typeof(int).FullName} from: {token}"); + } + } + + public void Serialize(ref JsonWriter writer, int? value, IJsonFormatterResolver formatterResolver) + { + if (value == null) + { + writer.WriteNull(); + return; + } + + writer.WriteInt32(value.Value); + } + } +} diff --git a/src/Nest/CommonAbstractions/Extensions/Extensions.cs b/src/Nest/CommonAbstractions/Extensions/Extensions.cs index 2b40a9df7e7..377f4decc71 100644 --- a/src/Nest/CommonAbstractions/Extensions/Extensions.cs +++ b/src/Nest/CommonAbstractions/Extensions/Extensions.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Elasticsearch.Net; namespace Nest { @@ -97,7 +98,7 @@ internal static string ToEnumValue(this T enumValue) where T : struct } internal static string Utf8String(this ref ArraySegment segment) => - Encoding.UTF8.GetString(segment.Array, segment.Offset, segment.Count); + StringEncoding.UTF8.GetString(segment.Array, segment.Offset, segment.Count); internal static string Utf8String(this byte[] bytes) => bytes == null ? null : Encoding.UTF8.GetString(bytes, 0, bytes.Length); diff --git a/src/Nest/CommonAbstractions/SerializationBehavior/JsonFormatters/ErrorCauseFormatter.cs b/src/Nest/CommonAbstractions/SerializationBehavior/JsonFormatters/ErrorCauseFormatter.cs deleted file mode 100644 index 9ced3d8c631..00000000000 --- a/src/Nest/CommonAbstractions/SerializationBehavior/JsonFormatters/ErrorCauseFormatter.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using Elasticsearch.Net; - -namespace Nest -{ - internal class ErrorCauseFormatter : ErrorCauseFormatter { } - - internal class ErrorCauseFormatter : IJsonFormatter - where TErrorCause : ErrorCause, new() - { - public TErrorCause Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) => - ReadError(ref reader, formatterResolver, MaybeSkipProperty); - - public void Serialize(ref JsonWriter writer, TErrorCause value, IJsonFormatterResolver formatterResolver) => - throw new NotSupportedException(); - - private void MaybeSkipProperty(ref JsonReader reader, TErrorCause error, string propertyName, IJsonFormatterResolver formatterResolver) - { - if (!ReadProperty(ref reader, propertyName, error, formatterResolver)) - reader.ReadNextBlock(); - } - - protected virtual bool ReadProperty(ref JsonReader reader, string propertyName, TErrorCause error, IJsonFormatterResolver formatterResolver - ) => - false; - - private static void ReadNextBlock(ref JsonReader reader, TInnerError error, string prop, IJsonFormatterResolver formatterResolver - ) - where TInnerError : ErrorCause, new() => reader.ReadNextBlock(); - - protected ErrorCause ReadCausedBy(ref JsonReader reader, IJsonFormatterResolver formatterResolver) => - ReadError(ref reader, formatterResolver, ReadNextBlock); - - private TInnerError ReadError(ref JsonReader reader, IJsonFormatterResolver formatterResolver, ReadMore readMore) - where TInnerError : ErrorCause, new() - { - var token = reader.GetCurrentJsonToken(); - - if (token == JsonToken.String) - { - var reason = reader.ReadString(); - return new TInnerError { Reason = reason }; - } - if (token != JsonToken.BeginObject) - { - reader.ReadNextBlock(); - return null; - } - - var error = new TInnerError { Metadata = new ErrorCause.ErrorCauseMetadata() }; - - var count = 0; - while (reader.ReadIsInObject(ref count)) - { - var propertyName = reader.ReadPropertyName(); - switch (propertyName) - { - case "type": - error.Type = reader.ReadString(); - break; - case "stack_trace": - error.StackTrace = reader.ReadString(); - break; - case "reason": - error.Reason = reader.ReadString(); - break; - case "caused_by": - error.CausedBy = ReadCausedBy(ref reader, formatterResolver); - break; - default: - if (!ExtractMetadata(ref reader, propertyName, error, formatterResolver)) - readMore(ref reader, error, propertyName, formatterResolver); - break; - } - } - - return error; - } - - protected static bool ExtractMetadata(ref JsonReader reader, string propertyName, ErrorCause error, IJsonFormatterResolver formatterResolver) - { - var m = error.Metadata; - switch (propertyName) - { - case "license.expired.feature": - m.LicensedExpiredFeature = reader.ReadString(); - break; - case "index": - m.Index = reader.ReadString(); - break; - case "index_uuid": - m.IndexUUID = reader.ReadString(); - break; - case "resource.type": - m.ResourceType = reader.ReadString(); - break; - case "resource.id": - m.ResourceId = ReadArray(ref reader, formatterResolver); - break; - case "shard": - m.Shard = reader.GetCurrentJsonToken() == JsonToken.Number - ? reader.ReadInt32() - : int.Parse(reader.ReadString()); - break; - case "line": - m.Line = reader.ReadInt32(); - break; - case "col": - m.Column = reader.ReadInt32(); - break; - case "bytes_wanted": - m.BytesWanted = reader.ReadInt64(); - break; - case "bytes_limit": - m.BytesLimit = reader.ReadInt64(); - break; - case "phase": - m.Phase = reader.ReadString(); - break; - case "grouped": - m.Grouped = reader.ReadBoolean(); - break; - case "script_stack": - m.ScriptStack = ReadArray(ref reader, formatterResolver); - break; - case "script": - m.Script = reader.ReadString(); - break; - case "lang": - m.Language = reader.ReadString(); - break; - case "failed_shards": - m.FailedShards = ExtractFailedShards(ref reader, formatterResolver); - break; - default: return false; - } - return true; - } - - protected static IReadOnlyCollection ExtractFailedShards(ref JsonReader reader, IJsonFormatterResolver formatterResolver) - { - //reader.ReadNext(); - if (reader.GetCurrentJsonToken() != JsonToken.BeginArray) - return EmptyReadOnly.Collection; - - var formatter = formatterResolver.GetFormatter>(); - return formatter.Deserialize(ref reader, formatterResolver); - } - - private static IReadOnlyCollection ReadArray(ref JsonReader reader, IJsonFormatterResolver formatterResolver) - { - var a = Array.Empty(); - var token = reader.GetCurrentJsonToken(); - switch (token) - { - case JsonToken.String: - a = new[] { reader.ReadString() }; - break; - case JsonToken.BeginArray: - a = formatterResolver.GetFormatter().Deserialize(ref reader, formatterResolver); - break; - } - return new ReadOnlyCollection(a); - } - - private delegate void ReadMore(ref JsonReader reader, TInnerError e, string prop, IJsonFormatterResolver formatterResolver) - where TInnerError : ErrorCause, new(); - } -} diff --git a/src/Nest/CommonAbstractions/SerializationBehavior/JsonFormatters/ErrorFormatter.cs b/src/Nest/CommonAbstractions/SerializationBehavior/JsonFormatters/ErrorFormatter.cs deleted file mode 100644 index 4e40df63fd4..00000000000 --- a/src/Nest/CommonAbstractions/SerializationBehavior/JsonFormatters/ErrorFormatter.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Collections.Generic; -using System.Collections.ObjectModel; -using Elasticsearch.Net; - -namespace Nest -{ - internal class ErrorFormatter : ErrorCauseFormatter - { - protected override bool ReadProperty(ref JsonReader reader, string propertyName, Error error, IJsonFormatterResolver formatterResolver) - { - if (propertyName == "root_cause") - return ExtractRootCauses(ref reader, error, formatterResolver); - - if (propertyName == "headers") - return ExtractHeaders(ref reader, error, formatterResolver); - - return ExtractMetadata(ref reader, propertyName, error, formatterResolver); - } - - private static bool ExtractHeaders(ref JsonReader reader, Error error, IJsonFormatterResolver formatterResolver) - { - // reader.ReadNext(); - if (reader.GetCurrentJsonToken() != JsonToken.BeginObject) - return false; - - var headers = formatterResolver.GetFormatter>() - .Deserialize(ref reader, formatterResolver); - - if (headers == null) - return false; - - error.Headers = headers; - return true; - } - - private bool ExtractRootCauses(ref JsonReader reader, Error error, IJsonFormatterResolver formatterResolver) - { - // reader.ReadNext(); - if (reader.GetCurrentJsonToken() != JsonToken.BeginArray) - return false; - - var count = 0; - var rootCauses = new List(); - while (reader.ReadIsInArray(ref count)) - { - var rootCause = ReadCausedBy(ref reader, formatterResolver); - if (rootCause != null) - rootCauses.Add(rootCause); - } - - error.RootCause = rootCauses; - return true; - } - } -} diff --git a/src/Nest/CommonOptions/Failures/BulkError.cs b/src/Nest/CommonOptions/Failures/BulkError.cs deleted file mode 100644 index b55e5bcc394..00000000000 --- a/src/Nest/CommonOptions/Failures/BulkError.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.Serialization; -using Elasticsearch.Net; - -namespace Nest -{ - [DataContract] - [JsonFormatter(typeof(ErrorCauseFormatter))] - public class BulkError : Error - { - public string Index => Metadata?.Index; - - public int Shard => Metadata.Shard.GetValueOrDefault(); - - public override string ToString() - { - var cause = CausedBy != null ? $" CausedBy:\n{CausedBy}" : string.Empty; - - return $"Type: {Type} Reason: \"{Reason}\"{cause}"; - } - } -} diff --git a/src/Nest/Document/Multiple/Bulk/BulkResponseItem/BulkResponseItemBase.cs b/src/Nest/Document/Multiple/Bulk/BulkResponseItem/BulkResponseItemBase.cs index 44880a3d8af..c04a4afb6be 100644 --- a/src/Nest/Document/Multiple/Bulk/BulkResponseItem/BulkResponseItemBase.cs +++ b/src/Nest/Document/Multiple/Bulk/BulkResponseItem/BulkResponseItemBase.cs @@ -3,9 +3,6 @@ namespace Nest { - //TODO: Remove this interface - - /// /// An item within a bulk response /// @@ -16,7 +13,7 @@ public abstract class BulkResponseItemBase /// The error associated with the bulk operation /// [DataMember(Name = "error")] - public BulkError Error { get; internal set; } + public Error Error { get; internal set; } /// /// The id of the document for the bulk operation @@ -84,8 +81,8 @@ public bool IsValid } } } + public override string ToString() => $"{Operation} returned {Status} _index: {Index} _type: {Type} _id: {Id} _version: {Version} error: {Error}"; } - } diff --git a/src/Nest/Document/Multiple/BulkIndexByScrollFailure.cs b/src/Nest/Document/Multiple/BulkIndexByScrollFailure.cs index 1972108e6ab..f0fbe3b0ca2 100644 --- a/src/Nest/Document/Multiple/BulkIndexByScrollFailure.cs +++ b/src/Nest/Document/Multiple/BulkIndexByScrollFailure.cs @@ -7,7 +7,7 @@ namespace Nest public class BulkIndexByScrollFailure { [DataMember(Name = "cause")] - public BulkIndexFailureCause Cause { get; set; } + public Error Cause { get; set; } [DataMember(Name = "id")] public string Id { get; internal set; } @@ -21,13 +21,4 @@ public class BulkIndexByScrollFailure [DataMember(Name = "type")] public string Type { get; internal set; } } - - [DataContract] - [JsonFormatter(typeof(ErrorCauseFormatter))] - public class BulkIndexFailureCause : Error - { - public string Index => Metadata?.Index; - public string IndexUniqueId => Metadata?.IndexUUID; - public int? Shard => Metadata?.Shard; - } } diff --git a/src/Nest/Indices/MappingManagement/GetFieldMapping/FieldMappingJsonConverter.cs b/src/Nest/Indices/MappingManagement/GetFieldMapping/FieldMappingFormatter.cs similarity index 99% rename from src/Nest/Indices/MappingManagement/GetFieldMapping/FieldMappingJsonConverter.cs rename to src/Nest/Indices/MappingManagement/GetFieldMapping/FieldMappingFormatter.cs index 7927b788ddb..c6e59879ad5 100644 --- a/src/Nest/Indices/MappingManagement/GetFieldMapping/FieldMappingJsonConverter.cs +++ b/src/Nest/Indices/MappingManagement/GetFieldMapping/FieldMappingFormatter.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; using Elasticsearch.Net; - - namespace Nest { internal class FieldMappingFormatter : IJsonFormatter> diff --git a/src/Tests/Tests/ClientConcepts/ServerError/ComplexErrorTests.cs b/src/Tests/Tests/ClientConcepts/ServerError/ComplexErrorTests.cs index 198856e843a..4517b2061b7 100644 --- a/src/Tests/Tests/ClientConcepts/ServerError/ComplexErrorTests.cs +++ b/src/Tests/Tests/ClientConcepts/ServerError/ComplexErrorTests.cs @@ -81,13 +81,14 @@ protected override void AssertResponseError(string origin, Error error) error.CausedBy.CausedBy.CausedBy.Reason.Should().Be("x"); error.RootCause.Should().NotBeEmpty(origin); error.Headers.Should().HaveCount(2, origin); - AssertMetadata(origin, error.Metadata); - error.CausedBy.Metadata.Should().NotBeNull(); - error.CausedBy.Metadata.ScriptStack.Should().HaveCount(2); - error.CausedBy.Metadata.ResourceId.Should().HaveCount(2); + AssertMetadata(origin, error); + error.CausedBy.Should().NotBeNull(); + error.CausedBy.ScriptStack.Should().HaveCount(2); + error.CausedBy.ResourceId.Should().HaveCount(2); + error.AdditionalProperties.Should().ContainKeys("unknown_prop", "unknown_prop2"); } - private void AssertMetadata(string origin, ErrorCause.ErrorCauseMetadata errorMetadata) + private void AssertMetadata(string origin, ErrorCause errorMetadata) { errorMetadata.Should().NotBeNull(origin); errorMetadata.Grouped.Should().BeTrue(origin); diff --git a/src/Tests/Tests/ClientConcepts/ServerError/StringErrrorTests.cs b/src/Tests/Tests/ClientConcepts/ServerError/StringErrrorTests.cs index b8ce7b2b65f..3d62805e01c 100644 --- a/src/Tests/Tests/ClientConcepts/ServerError/StringErrrorTests.cs +++ b/src/Tests/Tests/ClientConcepts/ServerError/StringErrrorTests.cs @@ -4,7 +4,7 @@ namespace Tests.ClientConcepts.ServerError { - public class StringErrrorTests : ServerErrorTestsBase + public class StringErrorTests : ServerErrorTestsBase { protected override string Json => @"""alias [x] is missing"""; @@ -17,9 +17,8 @@ protected override void AssertResponseError(string origin, Error error) error.RootCause.Should().BeNull(origin); } } - // - public class TempErrrorTests : ServerErrorTestsBase + public class TempErrorTests : ServerErrorTestsBase { protected override string Json => @"{""root_cause"":[{""type"":""index_not_found_exception"",""reason"":""no such index"",""index_uuid"":""_na_"",""index"":""non-existent-index""}],""type"":""index_not_found_exception"",""reason"":""no such index"",""index_uuid"":""_na_"",""index"":""non-existent-index""}"; diff --git a/src/Tests/Tests/Cluster/TaskManagement/TasksList/TasksListApiTests.cs b/src/Tests/Tests/Cluster/TaskManagement/TasksList/TasksListApiTests.cs index 6181e5cbdbc..38e9ed13f35 100644 --- a/src/Tests/Tests/Cluster/TaskManagement/TasksList/TasksListApiTests.cs +++ b/src/Tests/Tests/Cluster/TaskManagement/TasksList/TasksListApiTests.cs @@ -103,7 +103,7 @@ protected override void IntegrationSetup(IElasticClient client, CallUniqueValues var targetIndex = "tasks-lists-detailed"; var bulkResponse = client.IndexMany(Project.Generator.Generate(10000), sourceIndex); if (!bulkResponse.IsValid) - throw new Exception("failure in setting up integration"); + throw new Exception($"failure in setting up integration {bulkResponse.ServerError}"); client.Refresh(sourceIndex); diff --git a/src/Tests/Tests/Document/Multiple/DeleteByQuery/DeleteByQueryApiTests.cs b/src/Tests/Tests/Document/Multiple/DeleteByQuery/DeleteByQueryApiTests.cs index 255ff84d18e..4509e420cc1 100644 --- a/src/Tests/Tests/Document/Multiple/DeleteByQuery/DeleteByQueryApiTests.cs +++ b/src/Tests/Tests/Document/Multiple/DeleteByQuery/DeleteByQueryApiTests.cs @@ -209,7 +209,7 @@ protected override void ExpectResponse(DeleteByQueryResponse response) failure.Id.Should().NotBeNullOrWhiteSpace(); failure.Cause.Should().NotBeNull(); - failure.Cause.IndexUniqueId.Should().NotBeNullOrWhiteSpace(); + failure.Cause.IndexUUID.Should().NotBeNullOrWhiteSpace(); failure.Cause.Reason.Should().NotBeNullOrWhiteSpace(); failure.Cause.Index.Should().NotBeNullOrWhiteSpace(); failure.Cause.Shard.Should().NotBeNull(); diff --git a/src/Tests/Tests/Document/Multiple/UpdateByQuery/UpdateByQueryApiTests.cs b/src/Tests/Tests/Document/Multiple/UpdateByQuery/UpdateByQueryApiTests.cs index 17b04f65fd1..1180cb3883b 100644 --- a/src/Tests/Tests/Document/Multiple/UpdateByQuery/UpdateByQueryApiTests.cs +++ b/src/Tests/Tests/Document/Multiple/UpdateByQuery/UpdateByQueryApiTests.cs @@ -195,7 +195,7 @@ protected override void ExpectResponse(UpdateByQueryResponse response) failure.Id.Should().NotBeNullOrWhiteSpace(); failure.Cause.Should().NotBeNull(); - failure.Cause.IndexUniqueId.Should().NotBeNullOrWhiteSpace(); + failure.Cause.IndexUUID.Should().NotBeNullOrWhiteSpace(); failure.Cause.Reason.Should().NotBeNullOrWhiteSpace(); failure.Cause.Index.Should().NotBeNullOrWhiteSpace(); failure.Cause.Shard.Should().NotBeNull();