Skip to content
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 @@ -14,6 +14,10 @@ public static class ElasticsearchErrorExtensions
/// <summary> Try to parse an Elasticsearch <see cref="ElasticsearchServerError"/> </summary>
public static bool TryGetElasticsearchServerError(this StringResponse response, out ElasticsearchServerError? serverError)
{
// Prefer the factory-extracted error if available
if (TryGetFactoryExtractedError(response, out serverError))
return true;

serverError = null;
if (string.IsNullOrEmpty(response.Body) || response.ApiCallDetails.ResponseContentType != BoundConfiguration.DefaultContentType)
return false;
Expand All @@ -26,6 +30,10 @@ public static bool TryGetElasticsearchServerError(this StringResponse response,
/// <summary> Try to parse an Elasticsearch <see cref="ElasticsearchServerError"/> </summary>
public static bool TryGetElasticsearchServerError(this BytesResponse response, out ElasticsearchServerError? serverError)
{
// Prefer the factory-extracted error if available
if (TryGetFactoryExtractedError(response, out serverError))
return true;

serverError = null;
if (response.Body == null || response.Body.Length == 0 || response.ApiCallDetails.ResponseContentType != BoundConfiguration.DefaultContentType)
return false;
Expand All @@ -41,6 +49,10 @@ public static bool TryGetElasticsearchServerError(this BytesResponse response, o
/// </summary>
public static bool TryGetElasticsearchServerError(this TransportResponse response, out ElasticsearchServerError? serverError)
{
// Prefer the factory-extracted error if available
if (TryGetFactoryExtractedError(response, out serverError))
return true;

serverError = null;
var bytes = response.ApiCallDetails.ResponseBodyInBytes;
if (bytes == null || response.ApiCallDetails.ResponseContentType != BoundConfiguration.DefaultContentType)
Expand All @@ -50,4 +62,10 @@ public static bool TryGetElasticsearchServerError(this TransportResponse respons
using var stream = settings.MemoryStreamFactory.Create(bytes);
return ElasticsearchServerError.TryCreate(stream, out serverError);
}

private static bool TryGetFactoryExtractedError(TransportResponse response, out ElasticsearchServerError? serverError)
{
serverError = response.ApiCallDetails?.ProductError as ElasticsearchServerError;
return serverError?.HasError() ?? false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
Expand All @@ -24,8 +25,6 @@ public class ElasticsearchProductRegistration : ProductRegistration
internal const string XFoundHandlingClusterHeader = "X-Found-Handling-Cluster";
internal const string XFoundHandlingInstanceHeader = "X-Found-Handling-Instance";

private readonly HeadersList _headers;
private readonly MetaHeaderProvider _metaHeaderProvider;
private readonly int? _clientMajorVersion;

private static string? _clusterName;
Expand All @@ -37,8 +36,8 @@ public class ElasticsearchProductRegistration : ProductRegistration
/// </summary>
internal ElasticsearchProductRegistration()
{
_headers = new HeadersList("warning");
_metaHeaderProvider = null!;
ResponseHeadersToParse = new HeadersList("warning");
MetaHeaderProvider = null!;
ProductAssemblyVersion = null!;
}

Expand All @@ -52,7 +51,7 @@ public ElasticsearchProductRegistration(Type markerType) : this()

var identifier = ServiceIdentifier;
if (!string.IsNullOrEmpty(identifier))
_metaHeaderProvider = new DefaultMetaHeaderProvider(clientVersionInfo, identifier!);
MetaHeaderProvider = new DefaultMetaHeaderProvider(clientVersionInfo, identifier!);

// Only set this if we have a version.
// If we don't have a version we won't apply the vendor-based REST API compatibility Accept header.
Expand All @@ -71,7 +70,7 @@ public ElasticsearchProductRegistration(Type markerType) : this()
public override string Name { get; } = "elasticsearch-net";

/// <inheritdoc cref="ProductRegistration.ServiceIdentifier"/>
public override string? ServiceIdentifier => "es";
public sealed override string ServiceIdentifier => "es";

/// <inheritdoc cref="ProductRegistration.SupportsPing"/>
public override bool SupportsPing { get; } = true;
Expand All @@ -80,10 +79,10 @@ public ElasticsearchProductRegistration(Type markerType) : this()
public override bool SupportsSniff { get; } = true;

/// <inheritdoc cref="ProductRegistration.ResponseHeadersToParse"/>
public override HeadersList ResponseHeadersToParse => _headers;
public override HeadersList ResponseHeadersToParse { get; }

/// <inheritdoc cref="ProductRegistration.MetaHeaderProvider"/>
public override MetaHeaderProvider MetaHeaderProvider => _metaHeaderProvider;
public override MetaHeaderProvider MetaHeaderProvider { get; }

/// <inheritdoc cref="ProductRegistration.DefaultContentType"/>
public override string? DefaultContentType => _clientMajorVersion.HasValue ? $"application/vnd.elasticsearch+json;compatible-with={_clientMajorVersion.Value}" : null;
Expand All @@ -103,25 +102,32 @@ public override int SniffOrder(Node node) =>
/// API calls. They are considered for ping and sniff requests.
/// </summary>
public override bool NodePredicate(Node node) =>
// skip master only nodes (holds no data and is master eligible)
// Skip master only nodes (holds no data and is master eligible)
!(node.HasFeature(ElasticsearchNodeFeatures.MasterEligible) &&
!node.HasFeature(ElasticsearchNodeFeatures.HoldsData));

/// <inheritdoc cref="ProductRegistration.HttpStatusCodeClassifier"/>
/// <remarks>
/// We consider all status codes &gt;= 200 and &lt; 300 valid by default.
/// Elasticsearch might return 404 for valid responses in some cases (e.g. `GET /my-index/_doc/missing-doc-id`) but also for actual error cases like
/// missing endpoints, missing indices (e.g. `GET /missing-index/_mapping`), etc.
/// The 404 case is handled on a per-request basis (see <see cref="ElasticsearchResponseHelper.IsValidResponse"/> for details).
/// </remarks>
public override bool HttpStatusCodeClassifier(HttpMethod method, int statusCode) =>
statusCode is >= 200 and < 300;

/// <inheritdoc cref="ProductRegistration.TryGetServerErrorReason{TResponse}"/>>
public override bool TryGetServerErrorReason<TResponse>(TResponse response, out string? reason)
{
reason = null;
if (response is StringResponse s && s.TryGetElasticsearchServerError(out var e))
reason = e?.Error?.ToString();
else if (response is BytesResponse b && b.TryGetElasticsearchServerError(out e))
reason = e?.Error?.ToString();
else if (response.TryGetElasticsearchServerError(out e))
reason = e?.Error?.ToString();
return e != null;

if (response.ApiCallDetails?.ProductError is ElasticsearchServerError error && error.HasError())
{
reason = error.Error?.ToString();
return true;
}

return false;
}

//TODO remove settings dependency
Expand Down Expand Up @@ -228,5 +234,40 @@ public override IReadOnlyCollection<string> DefaultHeadersToParse()
};

/// <inheritdoc/>
public override IReadOnlyCollection<IResponseBuilder> ResponseBuilders { get; } = [new ElasticsearchResponseBuilder()];
public override IReadOnlyCollection<IResponseBuilder> ResponseBuilders { get; } =
[
new StringResponseBuilder<ElasticsearchStringResponse>(),
new DynamicResponseBuilder<ElasticsearchDynamicResponse>(),
new JsonResponseBuilder<ElasticsearchJsonResponse>(),
new ElasticsearchStreamResponseBuilder(),
#if NET10_0_OR_GREATER
new ElasticsearchPipeResponseBuilder(),
#endif
new ElasticsearchResponseBuilder()
];

/// <inheritdoc />
public override bool IsErrorContentType(string? contentType) =>
contentType is not null && (
contentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase) ||
contentType.StartsWith("application/vnd.elasticsearch+json", StringComparison.OrdinalIgnoreCase));

/// <inheritdoc />
public override ErrorResponse? TryExtractError(BoundConfiguration boundConfiguration, Stream responseStream)
{
try
{
var error = boundConfiguration.ConnectionSettings.RequestResponseSerializer
.Deserialize<ElasticsearchServerError>(responseStream);

if (error?.HasError() == true)
return error;
}
catch (System.Text.Json.JsonException)
{
// If the error deserialization fails, we'll let the builder try the original response type.
}

return null;
}
}
113 changes: 0 additions & 113 deletions src/Elastic.Transport/Products/Elasticsearch/ElasticsearchResponse.cs

This file was deleted.

Loading
Loading