Skip to content

Add support for MGet #6434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public DefaultRequestResponseSerializer(IElasticsearchClientSettings settings)
new DictionaryConverter(settings),
new PropertyNameConverter(settings),
new IsADictionaryConverter(),
new ResponseItemConverterFactory(),
new UnionConverter()
},
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Elastic.Clients.Elasticsearch
{
/// <summary>
/// A converter factory able to provide a converter to handle (de)serializing <see cref="ResponseItem{TDocument}"/>.
/// </summary>
internal sealed class ResponseItemConverterFactory : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert) => typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(ResponseItem<>);

public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var documentType = typeToConvert.GetGenericArguments()[0];

return (JsonConverter)Activator.CreateInstance(
typeof(ResponseItemConverter<>).MakeGenericType(documentType));
}

private sealed class ResponseItemConverter<TDocument> : JsonConverter<ResponseItem<TDocument>>
{
public override ResponseItem<TDocument>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
const string exceptionMessage = "Unable to deserialize union.";
var readerCopy = reader;

Exception getResultException = null;
Exception errorException = null;

// TODO - Review and optimise performance, possibly read-ahead to check for the error property and then deserialise
// accordingly is better?

try
{
var result = JsonSerializer.Deserialize<GetResult<TDocument>>(ref readerCopy, options);

// If we have a version number, we can be sure this isn't an error
if (result is not null && result.Version is not null)
{
reader = readerCopy; // Ensure we swap the reader to reflect the data we have consumed.
return new ResponseItem<TDocument>(result);
}
}
catch (Exception ex)
{
getResultException = ex;
}

try
{
var result = JsonSerializer.Deserialize<MultiGetError>(ref reader, options);

if (result is not null && result.Error is not null)
{
return new ResponseItem<TDocument>(result);
}
}
catch (Exception ex)
{
errorException = ex;
}

Exception innerException = null;

if (errorException is not null && getResultException is not null)
{
innerException = new AggregateException(errorException, getResultException);
}
else if (errorException is not null)
{
innerException = errorException;
}
else if (getResultException is not null)
{
innerException = getResultException;
}

if (innerException is not null)
{
throw new JsonException(exceptionMessage, innerException);
}

throw new JsonException(exceptionMessage);
}

// Not implemented as this type is read-only on responses.
public override void Write(Utf8JsonWriter writer, ResponseItem<TDocument> value, JsonSerializerOptions options) =>
throw new NotImplementedException("We never expect to serialize an instance of ResponseItem<TDocument> as its a read-only response type.");
}
}
}
82 changes: 78 additions & 4 deletions src/Elastic.Clients.Elasticsearch/Serialization/UnionConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization;
using Elastic.Clients.Elasticsearch.Aggregations;
Expand All @@ -13,7 +12,7 @@ namespace Elastic.Clients.Elasticsearch;

internal sealed class UnionConverter : JsonConverterFactory
{
private static readonly HashSet<Type> TypesToSkip = new HashSet<Type>
private static readonly HashSet<Type> TypesToSkip = new()
{
typeof(SourceConfig)
};
Expand Down Expand Up @@ -47,11 +46,86 @@ public override JsonConverter CreateConverter(
itemTwoType = type.BaseType.GetGenericArguments()[1];
}

var converter = (JsonConverter)Activator.CreateInstance(typeof(UnionConverterInner<,>).MakeGenericType(itemOneType, itemTwoType));
JsonConverter converter;

if (type.Name == typeof(Union<,>).Name)
{
converter = (JsonConverter)Activator.CreateInstance(typeof(UnionConverterInner<,>).MakeGenericType(itemOneType, itemTwoType));
}
else
{
converter = (JsonConverter)Activator.CreateInstance(typeof(DerivedUnionConverterInner<,,>).MakeGenericType(type, itemOneType, itemTwoType));
}

return converter;
}

private class DerivedUnionConverterInner<TType, TItem1, TItem2> : JsonConverter<TType>
{
public override TType? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// TODO - Aggregate Exception if both fail

var readerCopy = reader;

try
{
var itemOne = JsonSerializer.Deserialize<TItem1>(ref readerCopy, options);

if (itemOne is TItem1)
{
reader = readerCopy;
return (TType)Activator.CreateInstance(typeof(TType), itemOne);
}
}
catch
{
// TODO - Store for aggregate exception
}

try
{
var itemTwo = JsonSerializer.Deserialize<TItem2>(ref reader, options);

if (itemTwo is TItem2)
{
return (TType)Activator.CreateInstance(typeof(TType), itemTwo);
}
}
catch
{
// TODO - Store for aggregate exception
}

throw new JsonException("Unable to deserialize union."); // TODO - Add inner aggregate exception.
}

public override void Write(Utf8JsonWriter writer, TType value,
JsonSerializerOptions options)
{
if (value is null)
{
writer.WriteNullValue();
return;
}

//if (value.Item1 is not null)
//{
// JsonSerializer.Serialize(writer, value.Item1, value.Item1.GetType(), options);
// return;
//}

//if (value.Item2 is not null)
//{
// JsonSerializer.Serialize(writer, value.Item2, value.Item2.GetType(), options);
// return;
//}

throw new JsonException("TODO");
//throw new JsonException("Invalid union type.");
}
}

private class UnionConverterInner<TItem1, TItem2> : JsonConverter<Union<TItem1, TItem2>>
{
public override Union<TItem1, TItem2>? Read(ref Utf8JsonReader reader, Type typeToConvert,
Expand All @@ -72,7 +146,7 @@ public override void Write(Utf8JsonWriter writer, Union<TItem1, TItem2> value,
return;
}

throw new SerializationException("Invalid union type");
throw new JsonException("Invalid union type");
}
}

Expand Down
139 changes: 69 additions & 70 deletions src/Elastic.Clients.Elasticsearch/Types/TermsOrder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,98 +7,97 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Elastic.Clients.Elasticsearch
namespace Elastic.Clients.Elasticsearch;

[JsonConverter(typeof(TermsOrderConverter))]
public readonly struct TermsOrder : IEquatable<TermsOrder>
{
[JsonConverter(typeof(TermsOrderConverter))]
public readonly struct TermsOrder : IEquatable<TermsOrder>
{
public TermsOrder(string key, SortOrder order) => (Key, Order) = (key, order);
public TermsOrder(string key, SortOrder order) => (Key, Order) = (key, order);

public static TermsOrder CountAscending => new() { Key = "_count", Order = SortOrder.Asc };
public static TermsOrder CountDescending => new() { Key = "_count", Order = SortOrder.Desc };
public static TermsOrder KeyAscending => new() { Key = "_key", Order = SortOrder.Asc };
public static TermsOrder KeyDescending => new() { Key = "_key", Order = SortOrder.Desc };
public static TermsOrder CountAscending => new() { Key = "_count", Order = SortOrder.Asc };
public static TermsOrder CountDescending => new() { Key = "_count", Order = SortOrder.Desc };
public static TermsOrder KeyAscending => new() { Key = "_key", Order = SortOrder.Asc };
public static TermsOrder KeyDescending => new() { Key = "_key", Order = SortOrder.Desc };

public string Key { get; init; }
public SortOrder Order { get; init; }
public string Key { get; init; }
public SortOrder Order { get; init; }

public bool Equals(TermsOrder other) => Key == other.Key && Order == other.Order;
public override bool Equals(object obj) => obj is TermsOrder other && Equals(other);
public override int GetHashCode() => (Key, Order).GetHashCode();
public static bool operator ==(TermsOrder lhs, TermsOrder rhs) => lhs.Equals(rhs);
public static bool operator !=(TermsOrder lhs, TermsOrder rhs) => !(lhs == rhs);
}
public bool Equals(TermsOrder other) => Key == other.Key && Order == other.Order;
public override bool Equals(object obj) => obj is TermsOrder other && Equals(other);
public override int GetHashCode() => (Key, Order).GetHashCode();
public static bool operator ==(TermsOrder lhs, TermsOrder rhs) => lhs.Equals(rhs);
public static bool operator !=(TermsOrder lhs, TermsOrder rhs) => !(lhs == rhs);
}

internal sealed class TermsOrderConverter : JsonConverter<TermsOrder>
internal sealed class TermsOrderConverter : JsonConverter<TermsOrder>
{
public override TermsOrder Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
public override TermsOrder Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
return default;
if (reader.TokenType != JsonTokenType.StartObject)
return default;

reader.Read();
var key = reader.GetString();
reader.Read();
var key = reader.GetString();

reader.Read();
var valueString = reader.GetString();
var value = valueString switch
{
"asc" => SortOrder.Asc,
"desc" => SortOrder.Desc,
_ => throw new JsonException("Unexpected sort order in JSON"),
};
reader.Read();
var valueString = reader.GetString();
var value = valueString switch
{
"asc" => SortOrder.Asc,
"desc" => SortOrder.Desc,
_ => throw new JsonException("Unexpected sort order in JSON"),
};

reader.Read();
reader.Read();

if (reader.TokenType != JsonTokenType.EndObject)
throw new JsonException("JSON did not conform to expected shape");
if (reader.TokenType != JsonTokenType.EndObject)
throw new JsonException("JSON did not conform to expected shape");

return new TermsOrder(key, value);
return new TermsOrder(key, value);
}

public override void Write(Utf8JsonWriter writer, TermsOrder value, JsonSerializerOptions options)
{
if (string.IsNullOrEmpty(value.Key))
{
writer.WriteNullValue();
return;
}

public override void Write(Utf8JsonWriter writer, TermsOrder value, JsonSerializerOptions options)
writer.WriteStartObject();
writer.WritePropertyName(value.Key);
switch (value.Order)
{
if (string.IsNullOrEmpty(value.Key))
{
writer.WriteNullValue();
return;
}

writer.WriteStartObject();
writer.WritePropertyName(value.Key);
switch (value.Order)
{
case SortOrder.Asc:
writer.WriteStringValue("asc");
break;
case SortOrder.Desc:
writer.WriteStringValue("desc");
break;
default:
throw new JsonException("Unknown sort order specified.");
}
writer.WriteEndObject();
case SortOrder.Asc:
writer.WriteStringValue("asc");
break;
case SortOrder.Desc:
writer.WriteStringValue("desc");
break;
default:
throw new JsonException("Unknown sort order specified.");
}
writer.WriteEndObject();
}
}

public sealed class TermsOrderDescriptor : PromiseDescriptor<TermsOrderDescriptor, IList<TermsOrder>>
{
public TermsOrderDescriptor() : base(new List<TermsOrder>()) { }
public sealed class TermsOrderDescriptor : PromiseDescriptor<TermsOrderDescriptor, IList<TermsOrder>>
{
public TermsOrderDescriptor() : base(new List<TermsOrder>()) { }

internal TermsOrderDescriptor(Action<TermsOrderDescriptor> configure) : this() => configure?.Invoke(this);
internal TermsOrderDescriptor(Action<TermsOrderDescriptor> configure) : this() => configure?.Invoke(this);

public TermsOrderDescriptor CountAscending() => Assign(a => a.Add(TermsOrder.CountAscending));
public TermsOrderDescriptor CountAscending() => Assign(a => a.Add(TermsOrder.CountAscending));

public TermsOrderDescriptor CountDescending() => Assign(a => a.Add(TermsOrder.CountDescending));
public TermsOrderDescriptor CountDescending() => Assign(a => a.Add(TermsOrder.CountDescending));

public TermsOrderDescriptor KeyAscending() => Assign(a => a.Add(TermsOrder.KeyAscending));
public TermsOrderDescriptor KeyAscending() => Assign(a => a.Add(TermsOrder.KeyAscending));

public TermsOrderDescriptor KeyDescending() => Assign(a => a.Add(TermsOrder.KeyDescending));
public TermsOrderDescriptor KeyDescending() => Assign(a => a.Add(TermsOrder.KeyDescending));

public TermsOrderDescriptor Ascending(string key) =>
string.IsNullOrWhiteSpace(key) ? this : Assign(key, (a, v) => a.Add(new TermsOrder { Key = v, Order = SortOrder.Asc }));
public TermsOrderDescriptor Ascending(string key) =>
string.IsNullOrWhiteSpace(key) ? this : Assign(key, (a, v) => a.Add(new TermsOrder { Key = v, Order = SortOrder.Asc }));

public TermsOrderDescriptor Descending(string key) =>
string.IsNullOrWhiteSpace(key) ? this : Assign(key, (a, v) => a.Add(new TermsOrder { Key = v, Order = SortOrder.Desc }));
}
public TermsOrderDescriptor Descending(string key) =>
string.IsNullOrWhiteSpace(key) ? this : Assign(key, (a, v) => a.Add(new TermsOrder { Key = v, Order = SortOrder.Desc }));
}
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ internal static class ApiUrlsLookups
internal static ApiUrls MachineLearningUpgradeJobSnapshot = new ApiUrls(new[] { "/_ml/anomaly_detectors/{job_id}/model_snapshots/{snapshot_id}/_upgrade" });
internal static ApiUrls MachineLearningValidateDetector = new ApiUrls(new[] { "/_ml/anomaly_detectors/_validate/detector" });
internal static ApiUrls MachineLearningValidate = new ApiUrls(new[] { "/_ml/anomaly_detectors/_validate" });
internal static ApiUrls NoNamespaceMget = new ApiUrls(new[] { "/_mget", "/{index}/_mget" });
internal static ApiUrls NodesHotThreads = new ApiUrls(new[] { "/_nodes/hot_threads", "/_nodes/{node_id}/hot_threads" });
internal static ApiUrls NodesInfo = new ApiUrls(new[] { "/_nodes", "/_nodes/{node_id}", "/_nodes/{metric}", "/_nodes/{node_id}/{metric}" });
internal static ApiUrls NodesReloadSecureSettings = new ApiUrls(new[] { "/_nodes/reload_secure_settings", "/_nodes/{node_id}/reload_secure_settings" });
Expand Down
Loading