Skip to content

Commit 39afda4

Browse files
committed
Align Google and Yahoo transport failures
Root cause: sibling providers still used the pre-hardening HTTP failure path, so status handling and bounded diagnostics were inconsistent after the transport cleanup.
1 parent 45adbaa commit 39afda4

5 files changed

Lines changed: 140 additions & 7 deletions

File tree

src/Geocoding.Google/GoogleGeocoder.cs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,18 +199,29 @@ private async Task<IEnumerable<GoogleAddress>> ProcessRequest(HttpRequestMessage
199199
{
200200
try
201201
{
202-
using (var client = BuildClient())
202+
using var requestToDispose = request;
203+
using var client = BuildClient();
204+
using var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
205+
206+
if (!response.IsSuccessStatusCode)
203207
{
204-
return await ProcessWebResponse(await client.SendAsync(request, cancellationToken).ConfigureAwait(false)).ConfigureAwait(false);
208+
var preview = await BuildResponsePreviewAsync(response.Content).ConfigureAwait(false);
209+
throw new GoogleGeocodingException(new HttpRequestException($"Google request failed ({(int)response.StatusCode} {response.ReasonPhrase}).{preview}"));
205210
}
211+
212+
return await ProcessWebResponse(response).ConfigureAwait(false);
206213
}
207214
catch (Exception ex) when (ex is not GoogleGeocodingException)
208215
{
209216
throw new GoogleGeocodingException(ex);
210217
}
211218
}
212219

213-
private HttpClient BuildClient()
220+
/// <summary>
221+
/// Builds the HTTP client used for Google requests.
222+
/// </summary>
223+
/// <returns>The configured HTTP client.</returns>
224+
protected virtual HttpClient BuildClient()
214225
{
215226
if (Proxy is null)
216227
return new HttpClient();
@@ -250,6 +261,23 @@ private HttpRequestMessage BuildWebRequest(string type, string value)
250261
return new HttpRequestMessage(HttpMethod.Get, url);
251262
}
252263

264+
private static async Task<string> BuildResponsePreviewAsync(HttpContent content)
265+
{
266+
using var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);
267+
using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: false);
268+
269+
char[] buffer = new char[256];
270+
int read = await reader.ReadBlockAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
271+
if (read == 0)
272+
return String.Empty;
273+
274+
var preview = new string(buffer, 0, read).Trim();
275+
if (String.IsNullOrWhiteSpace(preview))
276+
return String.Empty;
277+
278+
return " Response preview: " + preview + (reader.EndOfStream ? String.Empty : "...");
279+
}
280+
253281
private async Task<IEnumerable<GoogleAddress>> ProcessWebResponse(HttpResponseMessage response)
254282
{
255283
XPathDocument xmlDoc = await LoadXmlResponse(response).ConfigureAwait(false);

src/Geocoding.Yahoo/YahooGeocoder.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Globalization;
22
using System.Net;
33
using System.Net.Http;
4+
using System.Text;
45
using System.Xml.XPath;
56

67
namespace Geocoding.Yahoo;
@@ -113,7 +114,23 @@ private async Task<IEnumerable<YahooAddress>> ProcessRequest(HttpRequestMessage
113114
using var requestToDispose = request;
114115
using var client = BuildClient();
115116
using var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
116-
response.EnsureSuccessStatusCode();
117+
118+
if (!response.IsSuccessStatusCode)
119+
{
120+
var preview = await BuildResponsePreviewAsync(response.Content).ConfigureAwait(false);
121+
var message = $"Yahoo request failed ({(int)response.StatusCode} {response.ReasonPhrase}).{preview}";
122+
123+
try
124+
{
125+
response.EnsureSuccessStatusCode();
126+
}
127+
catch (HttpRequestException ex)
128+
{
129+
throw new YahooGeocodingException(message, ex);
130+
}
131+
132+
throw new YahooGeocodingException(message, new HttpRequestException(message));
133+
}
117134

118135
using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
119136
{
@@ -171,6 +188,23 @@ private HttpRequestMessage BuildRequest(string url)
171188
return new HttpRequestMessage(HttpMethod.Get, url);
172189
}
173190

191+
private static async Task<string> BuildResponsePreviewAsync(HttpContent content)
192+
{
193+
using var stream = await content.ReadAsStreamAsync().ConfigureAwait(false);
194+
using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: false);
195+
196+
char[] buffer = new char[256];
197+
int read = await reader.ReadBlockAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
198+
if (read == 0)
199+
return String.Empty;
200+
201+
var preview = new string(buffer, 0, read).Trim();
202+
if (String.IsNullOrWhiteSpace(preview))
203+
return String.Empty;
204+
205+
return " Response preview: " + preview + (reader.EndOfStream ? String.Empty : "...");
206+
}
207+
174208
private string GenerateOAuthSignature(Uri uri)
175209
{
176210
string url, param;

src/Geocoding.Yahoo/YahooGeocodingException.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,15 @@ public YahooGeocodingException(Exception innerException)
3434
{
3535
ErrorCode = YahooError.UnknownError;
3636
}
37+
38+
/// <summary>
39+
/// Initializes a new instance of the <see cref="YahooGeocodingException"/> class.
40+
/// </summary>
41+
/// <param name="message">The provider error message.</param>
42+
/// <param name="innerException">The underlying provider exception.</param>
43+
public YahooGeocodingException(string message, Exception innerException)
44+
: base(message, innerException)
45+
{
46+
ErrorCode = YahooError.UnknownError;
47+
}
3748
}

test/Geocoding.Tests/GoogleGeocoderTest.cs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using Geocoding.Google;
1+
using System.Net;
2+
using System.Net.Http;
3+
using Geocoding.Google;
24
using Xunit;
35

46
namespace Geocoding.Tests;
@@ -206,8 +208,58 @@ public void GoogleGeocodingException_WithoutProviderMessage_LeavesProviderMessag
206208
Assert.Contains("OverQueryLimit", exception.Message);
207209
}
208210

211+
[Fact]
212+
public async Task Geocode_HttpFailure_PreservesInnerExceptionPreview()
213+
{
214+
// Arrange
215+
var body = new string('x', 300);
216+
var geocoder = new TestableGoogleGeocoder(new TestHttpMessageHandler((_, _) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.BadRequest)
217+
{
218+
ReasonPhrase = "Bad Request",
219+
Content = new StringContent(body)
220+
})));
221+
222+
// Act
223+
var exception = await Assert.ThrowsAsync<GoogleGeocodingException>(() => geocoder.GeocodeAsync("1600 pennsylvania ave nw, washington dc", TestContext.Current.CancellationToken));
224+
225+
// Assert
226+
Assert.NotNull(exception.InnerException);
227+
Assert.Contains("Google request failed (400 Bad Request).", exception.InnerException!.Message, StringComparison.Ordinal);
228+
Assert.Contains("Response preview:", exception.InnerException.Message, StringComparison.Ordinal);
229+
Assert.DoesNotContain(body, exception.InnerException.Message, StringComparison.Ordinal);
230+
}
231+
232+
[Fact]
233+
public async Task Geocode_TransportFailure_WrapsInnerException()
234+
{
235+
// Arrange
236+
var geocoder = new TestableGoogleGeocoder(new TestHttpMessageHandler((_, _) => throw new HttpRequestException("socket failure")));
237+
238+
// Act
239+
var exception = await Assert.ThrowsAsync<GoogleGeocodingException>(() => geocoder.GeocodeAsync("1600 pennsylvania ave nw, washington dc", TestContext.Current.CancellationToken));
240+
241+
// Assert
242+
Assert.IsType<HttpRequestException>(exception.InnerException);
243+
Assert.Contains("socket failure", exception.InnerException!.Message, StringComparison.Ordinal);
244+
}
245+
209246
private static bool HasShortName(GoogleAddress address, string shortName)
210247
{
211248
return address.Components.Any(component => String.Equals(component.ShortName, shortName, StringComparison.Ordinal));
212249
}
250+
251+
private sealed class TestableGoogleGeocoder : GoogleGeocoder
252+
{
253+
private readonly HttpMessageHandler _handler;
254+
255+
public TestableGoogleGeocoder(HttpMessageHandler handler)
256+
{
257+
_handler = handler;
258+
}
259+
260+
protected override HttpClient BuildClient()
261+
{
262+
return new HttpClient(_handler, disposeHandler: false);
263+
}
264+
}
213265
}

test/Geocoding.Tests/YahooGeocoderTest.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,21 @@ public void BuildRequest_GeneratesSignedGetRequest()
105105
public async Task Geocode_StatusFailure_WrapsHttpRequestException()
106106
{
107107
// Arrange
108-
var geocoder = new TestableYahooGeocoder(new TestHttpMessageHandler((_, _) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.Unauthorized))));
108+
var body = new string('x', 300);
109+
var geocoder = new TestableYahooGeocoder(new TestHttpMessageHandler((_, _) => Task.FromResult(new HttpResponseMessage(HttpStatusCode.Unauthorized)
110+
{
111+
Content = new StringContent(body)
112+
})));
109113

110114
// Act
111115
var exception = await Assert.ThrowsAsync<YahooGeocodingException>(() => geocoder.GeocodeAsync("1600 pennsylvania ave nw, washington dc", TestContext.Current.CancellationToken));
112116

113117
// Assert
114-
Assert.IsType<HttpRequestException>(exception.InnerException);
118+
var innerException = Assert.IsType<HttpRequestException>(exception.InnerException);
119+
Assert.Contains("Yahoo request failed (401 Unauthorized).", exception.Message, StringComparison.Ordinal);
120+
Assert.Contains("Response preview:", exception.Message, StringComparison.Ordinal);
121+
Assert.DoesNotContain(body, exception.Message, StringComparison.Ordinal);
122+
Assert.NotNull(innerException.Message);
115123
}
116124

117125
[Fact]

0 commit comments

Comments
 (0)