Skip to content

Commit 0ef60d5

Browse files
authored
Align ThrowExceptions decision with ProductRegistration.IsValidResponse (#208)
1 parent 53dd572 commit 0ef60d5

11 files changed

Lines changed: 134 additions & 26 deletions

src/Elastic.Transport/Components/Pipeline/RequestPipeline.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ private async ValueTask<TResponse> CallProductEndpointCoreAsync<TResponse>(
206206
)
207207
where TResponse : TransportResponse, new()
208208
{
209-
if (callDetails?.HasSuccessfulStatusCodeAndExpectedContentType ?? false)
209+
if (_productRegistration.IsValidResponse(callDetails))
210210
return null;
211211

212212
var pipelineFailure = callDetails?.HttpStatusCode != null ? PipelineFailure.BadResponse : PipelineFailure.BadRequest;

src/Elastic.Transport/Products/Elasticsearch/ElasticsearchProductRegistration.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,35 @@ public override bool NodePredicate(Node node) =>
164164
/// We consider all status codes &gt;= 200 and &lt; 300 valid by default.
165165
/// 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
166166
/// missing endpoints, missing indices (e.g. `GET /missing-index/_mapping`), etc.
167-
/// The 404 case is handled on a per-request basis (see <see cref="ElasticsearchResponseHelper.IsValidResponse"/> for details).
167+
/// The 404 case is handled on a per-request basis (see <see cref="IsValidResponse(ApiCallDetails)"/> for details).
168168
/// </remarks>
169169
public override bool HttpStatusCodeClassifier(HttpMethod method, int statusCode) =>
170170
statusCode is >= 200 and < 300;
171171

172+
/// <inheritdoc cref="ProductRegistration.IsValidResponse(ApiCallDetails)"/>
173+
/// <remarks>
174+
/// A response is valid when:
175+
/// <list type="bullet">
176+
/// <item>The content-type matches what the caller asked for.</item>
177+
/// <item>For 404 responses: there is no extracted Elasticsearch server error
178+
/// (404s without an error body — e.g. <c>GET /my-index/_doc/missing-doc-id</c> — represent
179+
/// a legitimate "missing entity" rather than a failure).</item>
180+
/// <item>Otherwise: the status code is in the success range (or explicitly allowed via
181+
/// <see cref="IRequestConfiguration.AllowedStatusCodes"/>).</item>
182+
/// </list>
183+
/// </remarks>
184+
public override bool IsValidResponse(ApiCallDetails? details)
185+
{
186+
if (details is null || !details.HasExpectedContentType)
187+
return false;
188+
189+
var serverError = details.ProductError as ElasticsearchServerError;
190+
if (details.HttpStatusCode is 404)
191+
return !serverError?.HasError() ?? true;
192+
193+
return details.HasSuccessfulStatusCode;
194+
}
195+
172196
/// <inheritdoc cref="ProductRegistration.TryGetServerErrorReason{TResponse}"/>>
173197
public override bool TryGetServerErrorReason<TResponse>(TResponse response, out string? reason)
174198
{

src/Elastic.Transport/Products/Elasticsearch/Responses/ElasticsearchDynamicResponse.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ public ElasticsearchDynamicResponse(DynamicDictionary dictionary) : base(diction
2424
public ElasticsearchServerError? ElasticsearchServerError => ElasticsearchResponseHelper.GetElasticsearchError(ApiCallDetails);
2525

2626
/// <inheritdoc />
27-
public bool IsValidResponse => ElasticsearchResponseHelper.IsValidResponse(ApiCallDetails);
27+
public bool IsValidResponse =>
28+
ApiCallDetails?.TransportConfiguration?.ProductRegistration?.IsValidResponse(ApiCallDetails) ?? false;
2829

2930
/// <inheritdoc />
3031
public IEnumerable<string> ElasticsearchWarnings => ElasticsearchResponseHelper.GetElasticsearchWarnings(ApiCallDetails);

src/Elastic.Transport/Products/Elasticsearch/Responses/ElasticsearchJsonResponse.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ public ElasticsearchJsonResponse(JsonNode node) : base(node) { }
2525
public ElasticsearchServerError? ElasticsearchServerError => ElasticsearchResponseHelper.GetElasticsearchError(ApiCallDetails);
2626

2727
/// <inheritdoc />
28-
public bool IsValidResponse => ElasticsearchResponseHelper.IsValidResponse(ApiCallDetails);
28+
public bool IsValidResponse =>
29+
ApiCallDetails?.TransportConfiguration?.ProductRegistration?.IsValidResponse(ApiCallDetails) ?? false;
2930

3031
/// <inheritdoc />
3132
public IEnumerable<string> ElasticsearchWarnings => ElasticsearchResponseHelper.GetElasticsearchWarnings(ApiCallDetails);

src/Elastic.Transport/Products/Elasticsearch/Responses/ElasticsearchPipeResponse.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public ElasticsearchPipeResponse(Stream responseStream, string? contentType) : b
3535
public ElasticsearchServerError? ElasticsearchServerError => ElasticsearchResponseHelper.GetElasticsearchError(ApiCallDetails);
3636

3737
/// <inheritdoc />
38-
public bool IsValidResponse => ElasticsearchResponseHelper.IsValidResponse(ApiCallDetails);
38+
public bool IsValidResponse =>
39+
ApiCallDetails?.TransportConfiguration?.ProductRegistration?.IsValidResponse(ApiCallDetails) ?? false;
3940

4041
/// <inheritdoc />
4142
public IEnumerable<string> ElasticsearchWarnings => ElasticsearchResponseHelper.GetElasticsearchWarnings(ApiCallDetails);

src/Elastic.Transport/Products/Elasticsearch/Responses/ElasticsearchResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public abstract class ElasticsearchResponse : TransportResponse, IElasticsearchR
2727
/// <inheritdoc />
2828
[JsonIgnore]
2929
public virtual bool IsValidResponse =>
30-
ElasticsearchResponseHelper.IsValidResponse(ApiCallDetails);
30+
ApiCallDetails?.TransportConfiguration?.ProductRegistration?.IsValidResponse(ApiCallDetails) ?? false;
3131

3232
/// <inheritdoc />
3333
[JsonIgnore]

src/Elastic.Transport/Products/Elasticsearch/Responses/ElasticsearchResponseHelper.cs

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,6 @@ namespace Elastic.Transport.Products.Elasticsearch;
1515
/// </summary>
1616
internal static class ElasticsearchResponseHelper
1717
{
18-
public static bool IsValidResponse(ApiCallDetails? apiCallDetails)
19-
{
20-
if (apiCallDetails is null || !apiCallDetails.HasExpectedContentType)
21-
return false;
22-
23-
// Elasticsearch returns 404 for valid responses in some cases (e.g. `GET /my-index/_doc/missing-doc-id`) but also for actual error cases like
24-
// missing endpoints, missing indices (e.g. `GET /missing-index/_mapping`), etc.
25-
// We consider all status codes >= 200 and < 300 valid by default. For 404, we assume "invalid" and try to parse the Elasticsearch
26-
// error response from the body.
27-
// A 404 status code without an error body indicates a valid response.
28-
29-
var serverError = GetElasticsearchError(apiCallDetails);
30-
if (apiCallDetails.HttpStatusCode is 404)
31-
return !serverError?.HasError() ?? true;
32-
33-
return apiCallDetails.HasSuccessfulStatusCode;
34-
}
35-
3618
public static ElasticsearchServerError? GetElasticsearchError(ApiCallDetails? apiCallDetails) =>
3719
apiCallDetails?.ProductError as ElasticsearchServerError;
3820

src/Elastic.Transport/Products/Elasticsearch/Responses/ElasticsearchStreamResponse.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ public ElasticsearchStreamResponse(Stream body, string? contentType) : base(body
3333
public ElasticsearchServerError? ElasticsearchServerError => ElasticsearchResponseHelper.GetElasticsearchError(ApiCallDetails);
3434

3535
/// <inheritdoc />
36-
public bool IsValidResponse => ElasticsearchResponseHelper.IsValidResponse(ApiCallDetails);
36+
public bool IsValidResponse =>
37+
ApiCallDetails?.TransportConfiguration?.ProductRegistration?.IsValidResponse(ApiCallDetails) ?? false;
3738

3839
/// <inheritdoc />
3940
public IEnumerable<string> ElasticsearchWarnings => ElasticsearchResponseHelper.GetElasticsearchWarnings(ApiCallDetails);

src/Elastic.Transport/Products/Elasticsearch/Responses/ElasticsearchStringResponse.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ public ElasticsearchStringResponse(string body) : base(body) { }
2424
public ElasticsearchServerError? ElasticsearchServerError => ElasticsearchResponseHelper.GetElasticsearchError(ApiCallDetails);
2525

2626
/// <inheritdoc />
27-
public bool IsValidResponse => ElasticsearchResponseHelper.IsValidResponse(ApiCallDetails);
27+
public bool IsValidResponse =>
28+
ApiCallDetails?.TransportConfiguration?.ProductRegistration?.IsValidResponse(ApiCallDetails) ?? false;
2829

2930
/// <inheritdoc />
3031
public IEnumerable<string> ElasticsearchWarnings => ElasticsearchResponseHelper.GetElasticsearchWarnings(ApiCallDetails);

src/Elastic.Transport/Products/ProductRegistration.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,16 @@ public abstract class ProductRegistration
171171
/// <param name="responseStream">A seekable stream containing the response body.</param>
172172
/// <returns>An <see cref="ErrorResponse"/> if one was successfully extracted; otherwise <c>null</c>.</returns>
173173
public virtual ErrorResponse? TryExtractError(BoundConfiguration boundConfiguration, Stream responseStream) => null;
174+
175+
/// <summary>
176+
/// Whether the response should be treated as valid for the caller — meaning no exception is
177+
/// raised under <see cref="IRequestConfiguration.ThrowExceptions"/>, and the product-level
178+
/// <c>IsValidResponse</c> reads <c>true</c>.
179+
/// <para>Default: a successful HTTP status code with the expected content type. Products override
180+
/// to encode their own semantics (e.g. Elasticsearch treats a 404 with no extracted server error
181+
/// as valid because it represents a missing entity rather than a true failure).</para>
182+
/// </summary>
183+
/// <param name="details">The <see cref="ApiCallDetails"/> for the response.</param>
184+
public virtual bool IsValidResponse(ApiCallDetails? details) =>
185+
details?.HasSuccessfulStatusCodeAndExpectedContentType ?? false;
174186
}

0 commit comments

Comments
 (0)