Skip to content

Commit 13a44f5

Browse files
committed
RE1-T118 NWS API fix
1 parent 592d607 commit 13a44f5

4 files changed

Lines changed: 44 additions & 6 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
3+
namespace Resgrid.Model
4+
{
5+
/// <summary>
6+
/// Thrown when a weather alert API request fails due to a permanent, non-retriable condition
7+
/// (e.g. invalid NWS zone code). Unlike <see cref="TransientWeatherAlertException"/>, callers
8+
/// should mark the source as permanently failed and stop retrying until the user corrects the configuration.
9+
/// </summary>
10+
public class PermanentWeatherAlertException : Exception
11+
{
12+
public PermanentWeatherAlertException(string message) : base(message) { }
13+
14+
public PermanentWeatherAlertException(string message, Exception innerException)
15+
: base(message, innerException) { }
16+
}
17+
}

Core/Resgrid.Services/WeatherAlertService.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,17 @@ public async Task ProcessWeatherAlertSourceAsync(Guid sourceId, CancellationToke
241241
source.ErrorMessage = null;
242242
await _weatherAlertSourceRepository.UpdateAsync(source, ct, true);
243243
}
244+
catch (PermanentWeatherAlertException ex)
245+
{
246+
// Permanent failures (invalid zone codes, etc.) — record the error
247+
// and mark the source as failed. Don't rethrow so the poller continues
248+
// without noisy Fatal logs. The source will be retried only after the
249+
// user edits and saves it.
250+
source.LastPollUtc = DateTime.UtcNow;
251+
source.IsFailure = true;
252+
source.ErrorMessage = ex.Message;
253+
await _weatherAlertSourceRepository.UpdateAsync(source, ct, true);
254+
}
244255
catch (TransientWeatherAlertException ex)
245256
{
246257
// Transient failures (rate-limit, server errors) — record the error
@@ -268,6 +279,12 @@ public async Task ProcessAllActiveSourcesAsync(CancellationToken ct = default)
268279

269280
foreach (var source in sources)
270281
{
282+
// Skip sources with permanent failures (e.g. invalid zone codes).
283+
// They will be retried only after the user edits and saves the source.
284+
if (source.IsFailure && !string.IsNullOrEmpty(source.ErrorMessage) &&
285+
!source.ErrorMessage.StartsWith("Transient:"))
286+
continue;
287+
271288
// Check if it's time to poll based on interval
272289
if (source.LastPollUtc.HasValue)
273290
{

Providers/Resgrid.Providers.Weather/NwsWeatherAlertProvider.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ public async Task<List<WeatherAlert>> FetchAlertsAsync(WeatherAlertSource source
5555
var stateCodes = zones.Where(z => z.Length == 2).ToArray();
5656
var zoneCodes = zones.Where(z => z.Length > 2).ToArray();
5757

58-
// Validate zone codes before calling the NWS API to produce a clear error
59-
// instead of a cryptic 400 Bad Request from the upstream API.
60-
var validationError = GetZoneValidationError(source.AreaFilter);
61-
if (validationError != null)
62-
throw new HttpRequestException(
63-
$"Invalid NWS zone code in area filter for department {source.DepartmentId}: {validationError}");
58+
// Validate zone codes before calling the NWS API to produce a clear error
59+
// instead of a cryptic 400 Bad Request from the upstream API.
60+
var validationError = GetZoneValidationError(source.AreaFilter);
61+
if (validationError != null)
62+
throw new PermanentWeatherAlertException(
63+
$"Invalid NWS zone code in area filter for department {source.DepartmentId}: {validationError}");
6464

6565
var queryParams = new List<string>();
6666
if (stateCodes.Length > 0)

Web/Resgrid.Web.Services/Controllers/v4/WeatherAlertsController.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ public async Task<ActionResult<GetWeatherAlertSourcesResult>> SaveSource([FromBo
206206
}
207207
}
208208

209+
// Clear any previous permanent failure so the source will be retried on next poll.
210+
source.IsFailure = false;
211+
source.ErrorMessage = null;
212+
209213
await _weatherAlertService.SaveSourceAsync(source);
210214

211215
var department = await _departmentsService.GetDepartmentByIdAsync(DepartmentId, false);

0 commit comments

Comments
 (0)