diff --git a/src/Elastic.Clients.Elasticsearch/Types/Aggregations/TermsAggregationDescriptor.cs b/src/Elastic.Clients.Elasticsearch/Types/Aggregations/TermsAggregationDescriptor.cs new file mode 100644 index 00000000000..dd7f5965325 --- /dev/null +++ b/src/Elastic.Clients.Elasticsearch/Types/Aggregations/TermsAggregationDescriptor.cs @@ -0,0 +1,40 @@ +// 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.Collections.Generic; + +namespace Elastic.Clients.Elasticsearch.Aggregations; + +public partial class TermsAggregationDescriptor +{ + public TermsAggregationDescriptor Include(long partition, long numberOfPartitions) + { + IncludeValue = new TermsInclude(partition, numberOfPartitions); + return Self; + } + + public TermsAggregationDescriptor Include(string includeRegexPattern) + { + IncludeValue = new TermsInclude(includeRegexPattern); + return Self; + } + + public TermsAggregationDescriptor Include(IEnumerable values) + { + IncludeValue = new TermsInclude(values); + return Self; + } + + public TermsAggregationDescriptor Exclude(string excludeRegexPattern) + { + ExcludeValue = new TermsExclude(excludeRegexPattern); + return Self; + } + + public TermsAggregationDescriptor Exclude(IEnumerable values) + { + ExcludeValue = new TermsExclude(values); + return Self; + } +} diff --git a/src/Elastic.Clients.Elasticsearch/Types/Aggregations/TermsExclude.cs b/src/Elastic.Clients.Elasticsearch/Types/Aggregations/TermsExclude.cs index cc23cd076cc..d15316c8562 100644 --- a/src/Elastic.Clients.Elasticsearch/Types/Aggregations/TermsExclude.cs +++ b/src/Elastic.Clients.Elasticsearch/Types/Aggregations/TermsExclude.cs @@ -7,7 +7,8 @@ using System.Text.Json; using System.Text.Json.Serialization; -namespace Elastic.Clients.Elasticsearch.Types.Aggregations; +#nullable enable +namespace Elastic.Clients.Elasticsearch.Aggregations; /// /// Filters which terms to exclude from the response. @@ -20,14 +21,26 @@ public sealed class TermsExclude /// to determine the terms to exclude from the response. /// /// The regular expression pattern. - public TermsExclude(string regexPattern) => RegexPattern = regexPattern; + public TermsExclude(string regexPattern) + { + if (regexPattern is null) + throw new ArgumentNullException(nameof(regexPattern)); + + RegexPattern = regexPattern; + } /// /// Creates an instance of that uses a collection of terms /// to exclude from the response. /// /// The exact terms to exclude. - public TermsExclude(ICollection values) => Values = values; + public TermsExclude(IEnumerable values) + { + if (values is null) + throw new ArgumentNullException(nameof(values)); + + Values = values; + } /// /// The regular expression pattern to determine terms to exclude from the response. @@ -37,7 +50,7 @@ public sealed class TermsExclude /// /// Collection of terms to exclude from the response. /// - public ICollection? Values { get; } + public IEnumerable? Values { get; } } internal sealed class TermsExcludeConverter : JsonConverter @@ -55,12 +68,12 @@ internal sealed class TermsExcludeConverter : JsonConverter switch (reader.TokenType) { case JsonTokenType.StartArray: - var terms = JsonSerializer.Deserialize>(ref reader, options); + var terms = JsonSerializer.Deserialize(ref reader, options) ?? Array.Empty(); termsExclude = new TermsExclude(terms); break; case JsonTokenType.String: var regex = reader.GetString(); - termsExclude = new TermsExclude(regex); + termsExclude = new TermsExclude(regex!); break; default: throw new JsonException($"Unexpected token {reader.TokenType} when deserializing {nameof(TermsExclude)}"); @@ -79,7 +92,7 @@ public override void Write(Utf8JsonWriter writer, TermsExclude value, JsonSerial if (value.Values is not null) { - JsonSerializer.Serialize>(writer, value.Values, options); + JsonSerializer.Serialize(writer, value.Values, options); return; } diff --git a/src/Elastic.Clients.Elasticsearch/Types/Aggregations/TermsInclude.cs b/src/Elastic.Clients.Elasticsearch/Types/Aggregations/TermsInclude.cs new file mode 100644 index 00000000000..2861d59703c --- /dev/null +++ b/src/Elastic.Clients.Elasticsearch/Types/Aggregations/TermsInclude.cs @@ -0,0 +1,158 @@ +// 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.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +#nullable enable +namespace Elastic.Clients.Elasticsearch.Aggregations; + +/// +/// Filters which terms to include in the response. +/// +[JsonConverter(typeof(TermsIncludeConverter))] +public class TermsInclude +{ + /// + /// Creates an instance of that uses a regular expression pattern + /// to determine the terms to include in the response. + /// + /// The regular expression pattern. + public TermsInclude(string regexPattern) + { + if (regexPattern is null) + throw new ArgumentNullException(nameof(regexPattern)); + + RegexPattern = regexPattern; + } + + /// + /// Creates an instance of that uses a collection of terms + /// to include in the response. + /// + /// The exact terms to include. + public TermsInclude(IEnumerable values) + { + if (values is null) + throw new ArgumentNullException(nameof(values)); + + Values = values; + } + + /// + /// Creates an instance of that partitions the terms into a number of + /// partitions to receive in multiple requests. Used to process many unique terms. + /// + /// The 0-based partition number for this request. + /// The total number of partitions. + public TermsInclude(long partition, long numberOfPartitions) + { + Partition = partition; + NumberOfPartitions = numberOfPartitions; + } + + /// + /// The total number of partitions we are interested in. + /// + public long? NumberOfPartitions { get; } + + /// + /// The current partition of terms we are interested in. + /// + public long? Partition { get; } + + /// + /// The regular expression pattern to determine terms to include in the response. + /// + public string? RegexPattern { get; } + + /// + /// Collection of terms to include in the response. + /// + public IEnumerable? Values { get; } +} + +internal sealed class TermsIncludeConverter : JsonConverter +{ + public override TermsInclude? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + { + reader.Read(); + return null; + } + + TermsInclude termsInclude; + + switch (reader.TokenType) + { + case JsonTokenType.StartArray: + var terms = JsonSerializer.Deserialize(ref reader, options) ?? Array.Empty(); + termsInclude = new TermsInclude(terms); + break; + case JsonTokenType.StartObject: + long partition = 0; + long numberOfPartitions = 0; + while(reader.Read() && reader.TokenType != JsonTokenType.EndObject) + { + if (reader.TokenType == JsonTokenType.PropertyName) + { + var propertyName = reader.GetString(); + reader.Read(); + switch (propertyName) + { + case "partition": + partition = reader.GetInt64(); + break; + case "num_partitions": + numberOfPartitions = reader.GetInt64(); + break; + default: + throw new JsonException($"Unexpected property name '{propertyName}' encountered when deserializing TermsInclude."); + } + } + } + termsInclude = new TermsInclude(partition, numberOfPartitions); + break; + case JsonTokenType.String: + var regex = reader.GetString(); + termsInclude = new TermsInclude(regex!); + break; + default: + throw new JsonException($"Unexpected token {reader.TokenType} when deserializing {nameof(TermsInclude)}"); + } + + return termsInclude; + } + + public override void Write(Utf8JsonWriter writer, TermsInclude value, JsonSerializerOptions options) + { + if (value is null) + { + writer.WriteNullValue(); + return; + } + + if (value.Values is not null) + { + JsonSerializer.Serialize(writer, value.Values, options); + return; + } + + if (value.Partition.HasValue && value.NumberOfPartitions.HasValue) + { + writer.WriteStartObject(); + writer.WritePropertyName("partition"); + writer.WriteNumberValue(value.Partition.Value); + writer.WritePropertyName("num_partitions"); + writer.WriteNumberValue(value.NumberOfPartitions.Value); + writer.WriteEndObject(); + return; + } + + writer.WriteStringValue(value.RegexPattern); + } +} diff --git a/src/Elastic.Clients.Elasticsearch/_Generated/Types/Aggregations/TermsAggregation.g.cs b/src/Elastic.Clients.Elasticsearch/_Generated/Types/Aggregations/TermsAggregation.g.cs index 31ef4200af7..04e3484c49c 100644 --- a/src/Elastic.Clients.Elasticsearch/_Generated/Types/Aggregations/TermsAggregation.g.cs +++ b/src/Elastic.Clients.Elasticsearch/_Generated/Types/Aggregations/TermsAggregation.g.cs @@ -100,6 +100,18 @@ public override TermsAggregation Read(ref Utf8JsonReader reader, Type typeToConv continue; } + if (reader.ValueTextEquals("include")) + { + reader.Read(); + var value = JsonSerializer.Deserialize(ref reader, options); + if (value is not null) + { + agg.Include = value; + } + + continue; + } + if (reader.ValueTextEquals("min_doc_count")) { reader.Read(); @@ -288,6 +300,12 @@ public override void Write(Utf8JsonWriter writer, TermsAggregation value, JsonSe writer.WriteStringValue(value.Format); } + if (value.Include is not null) + { + writer.WritePropertyName("include"); + JsonSerializer.Serialize(writer, value.Include, options); + } + if (value.MinDocCount.HasValue) { writer.WritePropertyName("min_doc_count"); @@ -385,6 +403,8 @@ internal TermsAggregation() public string? Format { get; set; } + public Elastic.Clients.Elasticsearch.Aggregations.TermsInclude? Include { get; set; } + public Dictionary? Meta { get; set; } public int? MinDocCount { get; set; } @@ -434,6 +454,8 @@ public TermsAggregationDescriptor() : base() private string? FormatValue { get; set; } + private Elastic.Clients.Elasticsearch.Aggregations.TermsInclude? IncludeValue { get; set; } + private Dictionary? MetaValue { get; set; } private int? MinDocCountValue { get; set; } @@ -516,6 +538,12 @@ public TermsAggregationDescriptor Format(string? format) return Self; } + public TermsAggregationDescriptor Include(Elastic.Clients.Elasticsearch.Aggregations.TermsInclude? include) + { + IncludeValue = include; + return Self; + } + public TermsAggregationDescriptor Meta(Func, FluentDictionary> selector) { MetaValue = selector?.Invoke(new FluentDictionary()); @@ -617,6 +645,12 @@ protected override void Serialize(Utf8JsonWriter writer, JsonSerializerOptions o writer.WriteStringValue(FormatValue); } + if (IncludeValue is not null) + { + writer.WritePropertyName("include"); + JsonSerializer.Serialize(writer, IncludeValue, options); + } + if (MinDocCountValue.HasValue) { writer.WritePropertyName("min_doc_count"); @@ -727,6 +761,8 @@ public TermsAggregationDescriptor() : base() private string? FormatValue { get; set; } + private Elastic.Clients.Elasticsearch.Aggregations.TermsInclude? IncludeValue { get; set; } + private Dictionary? MetaValue { get; set; } private int? MinDocCountValue { get; set; } @@ -815,6 +851,12 @@ public TermsAggregationDescriptor Format(string? format) return Self; } + public TermsAggregationDescriptor Include(Elastic.Clients.Elasticsearch.Aggregations.TermsInclude? include) + { + IncludeValue = include; + return Self; + } + public TermsAggregationDescriptor Meta(Func, FluentDictionary> selector) { MetaValue = selector?.Invoke(new FluentDictionary()); @@ -916,6 +958,12 @@ protected override void Serialize(Utf8JsonWriter writer, JsonSerializerOptions o writer.WriteStringValue(FormatValue); } + if (IncludeValue is not null) + { + writer.WritePropertyName("include"); + JsonSerializer.Serialize(writer, IncludeValue, options); + } + if (MinDocCountValue.HasValue) { writer.WritePropertyName("min_doc_count"); diff --git a/src/Elastic.Clients.Elasticsearch/_Generated/Types/Aggregations/TermsExclude.g.cs b/src/Elastic.Clients.Elasticsearch/_Generated/Types/Aggregations/TermsPartition.g.cs similarity index 55% rename from src/Elastic.Clients.Elasticsearch/_Generated/Types/Aggregations/TermsExclude.g.cs rename to src/Elastic.Clients.Elasticsearch/_Generated/Types/Aggregations/TermsPartition.g.cs index ca4e1ee2267..8647ccdd442 100644 --- a/src/Elastic.Clients.Elasticsearch/_Generated/Types/Aggregations/TermsExclude.g.cs +++ b/src/Elastic.Clients.Elasticsearch/_Generated/Types/Aggregations/TermsPartition.g.cs @@ -17,7 +17,6 @@ using Elastic.Clients.Elasticsearch.Fluent; using Elastic.Clients.Elasticsearch.Serialization; -using Elastic.Transport; using System; using System.Collections.Generic; using System.Linq.Expressions; @@ -26,13 +25,47 @@ #nullable restore namespace Elastic.Clients.Elasticsearch.Aggregations; -public partial class TermsExclude : Union> +public sealed partial class TermsPartition { - public TermsExclude(string termsExclude) : base(termsExclude) + [JsonInclude] + [JsonPropertyName("num_partitions")] + public long NumPartitions { get; set; } + + [JsonInclude] + [JsonPropertyName("partition")] + public long Partition { get; set; } +} + +public sealed partial class TermsPartitionDescriptor : SerializableDescriptor +{ + internal TermsPartitionDescriptor(Action configure) => configure.Invoke(this); + public TermsPartitionDescriptor() : base() + { + } + + private long NumPartitionsValue { get; set; } + + private long PartitionValue { get; set; } + + public TermsPartitionDescriptor NumPartitions(long numPartitions) + { + NumPartitionsValue = numPartitions; + return Self; + } + + public TermsPartitionDescriptor Partition(long partition) { + PartitionValue = partition; + return Self; } - public TermsExclude(IReadOnlyCollection termsExclude) : base(termsExclude) + protected override void Serialize(Utf8JsonWriter writer, JsonSerializerOptions options, IElasticsearchClientSettings settings) { + writer.WriteStartObject(); + writer.WritePropertyName("num_partitions"); + writer.WriteNumberValue(NumPartitionsValue); + writer.WritePropertyName("partition"); + writer.WriteNumberValue(PartitionValue); + writer.WriteEndObject(); } } \ No newline at end of file diff --git a/tests/Tests/AsyncSearch/AsyncSearchApiTests.cs b/tests/Tests/AsyncSearch/AsyncSearchApiTests.cs index cd2a5513c0e..ac125e1be36 100644 --- a/tests/Tests/AsyncSearch/AsyncSearchApiTests.cs +++ b/tests/Tests/AsyncSearch/AsyncSearchApiTests.cs @@ -43,7 +43,7 @@ public AsyncSearchApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base( ExecutionHint = TermsAggregationExecutionHint.Map, Missing = "n/a", // TODO - Review terms agg and fix this - //Include = new TermsInclude(new[] { StateOfBeing.Stable.ToString(), StateOfBeing.VeryActive.ToString() }), + Include = new TermsInclude(new[] { StateOfBeing.Stable.ToString(), StateOfBeing.VeryActive.ToString() }), Order =new [] { AggregateOrder.KeyAscending, @@ -65,7 +65,7 @@ public AsyncSearchApiTests(ReadOnlyCluster cluster, EndpointUsage usage) : base( .ExecutionHint(TermsAggregationExecutionHint.Map) .Missing("n/a") // TODO - Review terms agg and fix this - //.Include(new[] { StateOfBeing.Stable.ToString(), StateOfBeing.VeryActive.ToString() }) + .Include(new[] { StateOfBeing.Stable.ToString(), StateOfBeing.VeryActive.ToString() }) .Order(new [] { AggregateOrder.KeyAscending, diff --git a/tests/Tests/Types/TermsExcludeSerializationTests.cs b/tests/Tests/Types/Aggregations/TermsExcludeSerializationTests.cs similarity index 83% rename from tests/Tests/Types/TermsExcludeSerializationTests.cs rename to tests/Tests/Types/Aggregations/TermsExcludeSerializationTests.cs index 3d3b2d77da5..1793a8ca85f 100644 --- a/tests/Tests/Types/TermsExcludeSerializationTests.cs +++ b/tests/Tests/Types/Aggregations/TermsExcludeSerializationTests.cs @@ -3,17 +3,17 @@ // See the LICENSE file in the project root for more information. using System.Threading.Tasks; -using Elastic.Clients.Elasticsearch.Types.Aggregations; +using Elastic.Clients.Elasticsearch.Aggregations; using Tests.Serialization; using VerifyXunit; -namespace Tests.Types; +namespace Tests.Aggregations; [UsesVerify] public class TermsExcludeSerializationTests : SerializerTestBase { [U] - public async Task RoundTripSerialize_TermsExlucdeWithRegexPattern() + public async Task RoundTripSerialize_TermsExcludeWithRegexPattern() { const string pattern = "water_.*"; @@ -29,7 +29,7 @@ public async Task RoundTripSerialize_TermsExlucdeWithRegexPattern() } [U] - public async Task RoundTripSerialize_TermsExlucdeWithValues() + public async Task RoundTripSerialize_TermsExcludeWithValues() { var values = new[] { "term_a", "term_b" }; diff --git a/tests/Tests/Types/Aggregations/TermsIncludeSerializationTests.cs b/tests/Tests/Types/Aggregations/TermsIncludeSerializationTests.cs new file mode 100644 index 00000000000..cc81215a7ca --- /dev/null +++ b/tests/Tests/Types/Aggregations/TermsIncludeSerializationTests.cs @@ -0,0 +1,71 @@ +// 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.Threading.Tasks; +using Elastic.Clients.Elasticsearch.Aggregations; +using Tests.Serialization; +using VerifyXunit; + +namespace Tests.Aggregations; + +[UsesVerify] +public class TermsIncludeSerializationTests : SerializerTestBase +{ + [U] + public async Task RoundTripSerialize_TermsIncludeWithRegexPattern() + { + const string pattern = "water_.*"; + + var target = new TestClass + { + Include = new TermsInclude(pattern) + }; + + var result = await RoundTripAndVerifyJsonAsync(target); + + result.Include.RegexPattern.Should().Be(pattern); + result.Include.Values.Should().BeNull(); + result.Include.Partition.Should().BeNull(); + result.Include.NumberOfPartitions.Should().BeNull(); + } + + [U] + public async Task RoundTripSerialize_TermsIncludeWithValues() + { + var values = new[] { "term_a", "term_b" }; + + var target = new TestClass + { + Include = new TermsInclude(values) + }; + + var result = await RoundTripAndVerifyJsonAsync(target); + + result.Include.RegexPattern.Should().BeNull(); + result.Include.Values.Should().Contain(values); + result.Include.Partition.Should().BeNull(); + result.Include.NumberOfPartitions.Should().BeNull(); + } + + [U] + public async Task RoundTripSerialize_TermsIncludeWithPartition() + { + var target = new TestClass + { + Include = new TermsInclude(10, 100) + }; + + var result = await RoundTripAndVerifyJsonAsync(target); + + result.Include.RegexPattern.Should().BeNull(); + result.Include.Values.Should().BeNull(); + result.Include.Partition.Should().Be(10); + result.Include.NumberOfPartitions.Should().Be(100); + } + + private class TestClass + { + public TermsInclude Include { get; set; } + } +} diff --git a/tests/Tests/_VerifySnapshots/TermsExcludeSerializationTests.RoundTripSerialize_TermsExlucdeWithRegexPattern.verified.txt b/tests/Tests/_VerifySnapshots/TermsExcludeSerializationTests.RoundTripSerialize_TermsExcludeWithRegexPattern.verified.txt similarity index 100% rename from tests/Tests/_VerifySnapshots/TermsExcludeSerializationTests.RoundTripSerialize_TermsExlucdeWithRegexPattern.verified.txt rename to tests/Tests/_VerifySnapshots/TermsExcludeSerializationTests.RoundTripSerialize_TermsExcludeWithRegexPattern.verified.txt diff --git a/tests/Tests/_VerifySnapshots/TermsExcludeSerializationTests.RoundTripSerialize_TermsExlucdeWithValues.verified.txt b/tests/Tests/_VerifySnapshots/TermsExcludeSerializationTests.RoundTripSerialize_TermsExcludeWithValues.verified.txt similarity index 100% rename from tests/Tests/_VerifySnapshots/TermsExcludeSerializationTests.RoundTripSerialize_TermsExlucdeWithValues.verified.txt rename to tests/Tests/_VerifySnapshots/TermsExcludeSerializationTests.RoundTripSerialize_TermsExcludeWithValues.verified.txt diff --git a/tests/Tests/_VerifySnapshots/TermsIncludeSerializationTests.RoundTripSerialize_TermsIncludeWithPartition.verified.txt b/tests/Tests/_VerifySnapshots/TermsIncludeSerializationTests.RoundTripSerialize_TermsIncludeWithPartition.verified.txt new file mode 100644 index 00000000000..414b729f2e9 --- /dev/null +++ b/tests/Tests/_VerifySnapshots/TermsIncludeSerializationTests.RoundTripSerialize_TermsIncludeWithPartition.verified.txt @@ -0,0 +1,6 @@ +{ + include: { + num_partitions: 100, + partition: 10 + } +} \ No newline at end of file diff --git a/tests/Tests/_VerifySnapshots/TermsIncludeSerializationTests.RoundTripSerialize_TermsIncludeWithRegexPattern.verified.txt b/tests/Tests/_VerifySnapshots/TermsIncludeSerializationTests.RoundTripSerialize_TermsIncludeWithRegexPattern.verified.txt new file mode 100644 index 00000000000..10fd81bdfe1 --- /dev/null +++ b/tests/Tests/_VerifySnapshots/TermsIncludeSerializationTests.RoundTripSerialize_TermsIncludeWithRegexPattern.verified.txt @@ -0,0 +1,3 @@ +{ + include: water_.* +} \ No newline at end of file diff --git a/tests/Tests/_VerifySnapshots/TermsIncludeSerializationTests.RoundTripSerialize_TermsIncludeWithValues.verified.txt b/tests/Tests/_VerifySnapshots/TermsIncludeSerializationTests.RoundTripSerialize_TermsIncludeWithValues.verified.txt new file mode 100644 index 00000000000..89831e93f6e --- /dev/null +++ b/tests/Tests/_VerifySnapshots/TermsIncludeSerializationTests.RoundTripSerialize_TermsIncludeWithValues.verified.txt @@ -0,0 +1,6 @@ +{ + include: [ + term_a, + term_b + ] +} \ No newline at end of file