Skip to content

Commit c5d76be

Browse files
authored
Refactor Field and Fields (#8156)
1 parent 700db83 commit c5d76be

File tree

10 files changed

+330
-197
lines changed

10 files changed

+330
-197
lines changed

Diff for: src/Elastic.Clients.Elasticsearch.Shared/Core/Extensions/Extensions.cs

+7-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Concurrent;
77
using System.Collections.Generic;
8+
using System.Diagnostics.CodeAnalysis;
89
using System.Linq;
910
using System.Reflection;
1011
using System.Runtime.ExceptionServices;
@@ -172,13 +173,14 @@ internal static bool HasAny<T>(this IEnumerable<T> list, Func<T, bool> predicate
172173

173174
internal static bool HasAny<T>(this IEnumerable<T> list) => list != null && list.Any();
174175

175-
internal static bool IsEmpty<T>(this IEnumerable<T> list)
176+
internal static bool IsNullOrEmpty<T>(this IEnumerable<T>? list)
176177
{
177-
if (list == null)
178+
if (list is null)
178179
return true;
179180

180181
var enumerable = list as T[] ?? list.ToArray();
181-
return !enumerable.Any() || enumerable.All(t => t == null);
182+
183+
return (enumerable.Length == 0) || enumerable.All(x => x is null);
182184
}
183185

184186
internal static void ThrowIfNull<T>(this T value, string name, string message = null)
@@ -189,9 +191,9 @@ internal static void ThrowIfNull<T>(this T value, string name, string message =
189191
throw new ArgumentNullException(name, "Argument can not be null when " + message);
190192
}
191193

192-
internal static bool IsNullOrEmpty(this string value) => string.IsNullOrWhiteSpace(value);
194+
internal static bool IsNullOrEmpty(this string? value) => string.IsNullOrWhiteSpace(value);
193195

194-
internal static bool IsNullOrEmptyCommaSeparatedList(this string value, out string[] split)
196+
internal static bool IsNullOrEmptyCommaSeparatedList(this string? value, [NotNullWhen(false)] out string[]? split)
195197
{
196198
split = null;
197199
if (string.IsNullOrWhiteSpace(value))

Diff for: src/Elastic.Clients.Elasticsearch.Shared/Core/Infer/Field/Field.cs

+159-96
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,29 @@
44

55
using System;
66
using System.Diagnostics;
7+
using System.Diagnostics.CodeAnalysis;
78
using System.Globalization;
89
using System.Linq.Expressions;
910
using System.Reflection;
1011
using System.Text.Json.Serialization;
12+
1113
using Elastic.Transport;
1214

1315
#if ELASTICSEARCH_SERVERLESS
1416
namespace Elastic.Clients.Elasticsearch.Serverless;
1517
#else
18+
1619
namespace Elastic.Clients.Elasticsearch;
1720
#endif
1821

1922
[JsonConverter(typeof(FieldConverter))]
20-
[DebuggerDisplay("{" + nameof(DebugDisplay) + ",nq}")]
21-
public sealed class Field : IEquatable<Field>, IUrlParameter
23+
[DebuggerDisplay($"{nameof(DebuggerDisplay)},nq")]
24+
public sealed class Field :
25+
IEquatable<Field>,
26+
IUrlParameter
2227
{
2328
private readonly object _comparisonValue;
24-
private readonly Type _type;
29+
private readonly Type? _type;
2530

2631
// Pseudo and metadata fields
2732

@@ -30,143 +35,173 @@ public sealed class Field : IEquatable<Field>, IUrlParameter
3035
public static Field KeyField = new("_key");
3136
public static Field CountField = new("_count");
3237

33-
public Field(string name) : this(name, null, null) { }
38+
/// <summary>
39+
/// The name of the field
40+
/// </summary>
41+
public string? Name { get; }
42+
43+
/// <summary>
44+
/// An expression from which the name of the field can be inferred
45+
/// </summary>
46+
public Expression? Expression { get; }
47+
48+
/// <summary>
49+
/// A property from which the name of the field can be inferred
50+
/// </summary>
51+
public PropertyInfo? Property { get; }
52+
53+
/// <summary>
54+
/// A boost to apply to the field
55+
/// </summary>
56+
public double? Boost { get; set; }
57+
58+
/// <summary>
59+
/// A format to apply to the field.
60+
/// </summary>
61+
/// <remarks>
62+
/// Can be used only for Doc Value Fields Elasticsearch 6.4.0+
63+
/// </remarks>
64+
public string? Format { get; set; }
65+
66+
internal bool CachableExpression { get; }
67+
68+
#region Constructors
69+
70+
public Field(string name) : this(name, null, null)
71+
{
72+
}
3473

35-
public Field(string name, double boost) : this(name, boost, null) { }
74+
public Field(string name, double boost) : this(name, boost, null)
75+
{
76+
}
3677

37-
public Field(string name, string format) : this(name, null, format) { }
78+
public Field(string name, string format) : this(name, null, format)
79+
{
80+
}
3881

3982
public Field(string name, double? boost, string? format)
4083
{
41-
name.ThrowIfNullOrEmpty(nameof(name));
84+
if (string.IsNullOrEmpty(name))
85+
throw new ArgumentException($"{name} can not be null or empty.", nameof(name));
86+
4287
Name = ParseFieldName(name, out var b);
4388
Boost = b ?? boost;
4489
Format = format;
90+
4591
_comparisonValue = Name;
4692
}
4793

48-
public Field(Expression expression, double? boost = null, string format = null)
94+
public Field(Expression expression, double? boost = null, string? format = null)
4995
{
5096
Expression = expression ?? throw new ArgumentNullException(nameof(expression));
5197
Boost = boost;
5298
Format = format;
99+
53100
_comparisonValue = expression.ComparisonValueFromExpression(out var type, out var cachable);
54101
_type = type;
102+
55103
CachableExpression = cachable;
56104
}
57105

58-
public Field(PropertyInfo property, double? boost = null, string format = null)
106+
public Field(PropertyInfo property, double? boost = null, string? format = null)
59107
{
60108
Property = property ?? throw new ArgumentNullException(nameof(property));
61109
Boost = boost;
62110
Format = format;
111+
63112
_comparisonValue = property;
64113
_type = property.DeclaringType;
65114
}
66115

67-
/// <summary>
68-
/// A boost to apply to the field
69-
/// </summary>
70-
public double? Boost { get; set; }
116+
#endregion Constructors
71117

72-
/// <summary>
73-
/// A format to apply to the field.
74-
/// </summary>
75-
/// <remarks>
76-
/// Can be used only for Doc Value Fields Elasticsearch 6.4.0+
77-
/// </remarks>
78-
public string? Format { get; set; }
118+
#region Factory Methods
79119

80-
internal bool CachableExpression { get; }
120+
public static Field? FromString(string? name) => string.IsNullOrEmpty(name) ? null : new Field(name);
81121

82-
/// <summary>
83-
/// An expression from which the name of the field can be inferred
84-
/// </summary>
85-
public Expression Expression { get; }
122+
public static Field? FromExpression(Expression? expression) => expression is null ? null : new Field(expression);
86123

87-
/// <summary>
88-
/// The name of the field
89-
/// </summary>
90-
public string Name { get; }
124+
public static Field? FromProperty(PropertyInfo? property) => property is null ? null : new Field(property);
91125

92-
/// <summary>
93-
/// A property from which the name of the field can be inferred
94-
/// </summary>
95-
public PropertyInfo Property { get; }
126+
#endregion Factory Methods
96127

97-
internal string DebugDisplay =>
98-
$"{Expression?.ToString() ?? PropertyDebug ?? Name}{(Boost.HasValue ? "^" + Boost.Value : string.Empty)}"
99-
+ $"{(!string.IsNullOrEmpty(Format) ? " format: " + Format : string.Empty)}"
100-
+ $"{(_type == null ? string.Empty : " typeof: " + _type.Name)}";
128+
#region Conversion Operators
101129

102-
private string PropertyDebug => Property == null ? null : $"PropertyInfo: {Property.Name}";
130+
public static implicit operator Field?(string? name) => FromString(name);
103131

104-
public bool Equals(Field other) => _type != null
105-
? other != null && _type == other._type && _comparisonValue.Equals(other._comparisonValue)
106-
: other != null && _comparisonValue.Equals(other._comparisonValue);
132+
public static implicit operator Field?(Expression? expression) => FromExpression(expression);
107133

108-
string IUrlParameter.GetString(ITransportConfiguration settings)
134+
public static implicit operator Field?(PropertyInfo? property) => FromProperty(property);
135+
136+
#endregion Conversion Operators
137+
138+
#region Combinator Methods
139+
140+
public Fields And(Field field)
109141
{
110-
if (settings is not IElasticsearchClientSettings elasticsearchSettings)
111-
{
112-
throw new ArgumentNullException(nameof(settings),
113-
$"Can not resolve {nameof(Field)} if no {nameof(IElasticsearchClientSettings)} is provided");
114-
}
142+
if (field is null)
143+
throw new ArgumentNullException(nameof(field));
115144

116-
return GetStringCore(elasticsearchSettings);
145+
return new([this, field]);
117146
}
118147

119-
private string GetStringCore(IElasticsearchClientSettings settings)
148+
public Fields And<T, TValue>(Expression<Func<T, TValue>> expression, double? boost = null, string? format = null)
120149
{
121-
if (settings is null)
122-
{
123-
throw new ArgumentNullException(nameof(settings),
124-
$"Can not resolve {nameof(Field)} if no {nameof(IElasticsearchClientSettings)} is provided");
125-
}
150+
if (expression is null)
151+
throw new ArgumentNullException(nameof(expression));
126152

127-
return settings.Inferrer.Field(this);
153+
return new([this, new Field(expression, boost, format)]);
128154
}
129155

130-
public override string ToString() => DebugDisplay;
156+
public Fields And<T>(Expression<Func<T, object>> expression, double? boost = null, string? format = null)
157+
{
158+
if (expression is null)
159+
throw new ArgumentNullException(nameof(expression));
131160

132-
public Fields And(Field field) => new(new[] { this, field });
161+
return new([this, new Field(expression, boost, format)]);
162+
}
133163

134-
public Fields And<T, TValue>(Expression<Func<T, TValue>> field, double? boost = null, string format = null)
135-
where T : class =>
136-
new(new[] { this, new Field(field, boost, format) });
164+
public Fields And(string field, double? boost = null, string? format = null)
165+
{
166+
if (field is null)
167+
throw new ArgumentNullException(nameof(field));
137168

138-
public Fields And<T>(Expression<Func<T, object>> field, double? boost = null, string format = null)
139-
where T : class =>
140-
new(new[] { this, new Field(field, boost, format) });
169+
return new([this, new Field(field, boost, format)]);
170+
}
141171

142-
public Fields And(string field, double? boost = null, string format = null) =>
143-
new(new[] { this, new Field(field, boost, format) });
172+
public Fields And(PropertyInfo property, double? boost = null, string? format = null)
173+
{
174+
if (property is null)
175+
throw new ArgumentNullException(nameof(property));
144176

145-
public Fields And(PropertyInfo property, double? boost = null, string format = null) =>
146-
new(new[] { this, new Field(property, boost, format) });
177+
return new([this, new Field(property, boost, format)]);
178+
}
147179

148-
private static string ParseFieldName(string name, out double? boost)
149-
{
150-
boost = null;
151-
if (name == null)
152-
return null;
180+
#endregion Combinator Methods
153181

154-
var caretIndex = name.IndexOf('^');
155-
if (caretIndex == -1)
156-
return name;
182+
#region Equality
157183

158-
var parts = name.Split(new[] { '^' }, 2, StringSplitOptions.RemoveEmptyEntries);
159-
name = parts[0];
160-
boost = double.Parse(parts[1], CultureInfo.InvariantCulture);
161-
return name;
162-
}
184+
public static bool operator ==(Field? a, Field? b) => Equals(a, b);
163185

164-
public static implicit operator Field(string name) => name.IsNullOrEmpty() ? null : new Field(name);
186+
public static bool operator !=(Field? a, Field? b) => !Equals(a, b);
165187

166-
public static implicit operator Field(Expression expression) =>
167-
expression == null ? null : new Field(expression);
188+
public bool Equals(Field? other) =>
189+
other switch
190+
{
191+
not null when _type is not null => (_type == other._type) && _comparisonValue.Equals(other._comparisonValue),
192+
not null when _type is null => _comparisonValue.Equals(other._comparisonValue),
193+
_ => false
194+
};
168195

169-
public static implicit operator Field(PropertyInfo property) => property == null ? null : new Field(property);
196+
public override bool Equals(object? obj) =>
197+
obj switch
198+
{
199+
Field f => Equals(f),
200+
string s => Equals(s),
201+
Expression e => Equals(e),
202+
PropertyInfo p => Equals(p),
203+
_ => false
204+
};
170205

171206
public override int GetHashCode()
172207
{
@@ -178,22 +213,50 @@ public override int GetHashCode()
178213
}
179214
}
180215

181-
public override bool Equals(object obj)
216+
#endregion Equality
217+
218+
#region IUrlParameter
219+
220+
string IUrlParameter.GetString(ITransportConfiguration settings)
182221
{
183-
switch (obj)
222+
if (settings is not IElasticsearchClientSettings elasticsearchSettings)
184223
{
185-
case string s:
186-
return Equals(s);
187-
case PropertyInfo p:
188-
return Equals(p);
189-
case Field f:
190-
return Equals(f);
191-
default:
192-
return false;
224+
throw new ArgumentNullException(nameof(settings),
225+
$"Can not resolve {nameof(Field)} if no {nameof(IElasticsearchClientSettings)} is provided");
193226
}
227+
228+
return elasticsearchSettings.Inferrer.Field(this);
194229
}
195230

196-
public static bool operator ==(Field x, Field y) => Equals(x, y);
231+
#endregion IUrlParameter
232+
233+
#region Debugging
234+
235+
public override string ToString() =>
236+
$"{Expression?.ToString() ?? PropertyDebug ?? Name}{(Boost.HasValue ? "^" + Boost.Value : string.Empty)}" +
237+
$"{(!string.IsNullOrEmpty(Format) ? " format: " + Format : string.Empty)}" +
238+
$"{(_type == null ? string.Empty : " typeof: " + _type.Name)}";
239+
240+
internal string DebuggerDisplay => ToString();
241+
242+
private string? PropertyDebug => Property is null ? null : $"PropertyInfo: {Property.Name}";
197243

198-
public static bool operator !=(Field x, Field y) => !Equals(x, y);
244+
#endregion Debugging
245+
246+
[return: NotNullIfNotNull(nameof(name))]
247+
private static string? ParseFieldName(string? name, out double? boost)
248+
{
249+
boost = null;
250+
if (name is null)
251+
return null;
252+
253+
var caretIndex = name.IndexOf('^');
254+
if (caretIndex == -1)
255+
return name;
256+
257+
var parts = name.Split(new[] { '^' }, 2, StringSplitOptions.RemoveEmptyEntries);
258+
name = parts[0];
259+
boost = double.Parse(parts[1], CultureInfo.InvariantCulture);
260+
return name;
261+
}
199262
}

0 commit comments

Comments
 (0)