summaryrefslogtreecommitdiffstats
path: root/nws
diff options
context:
space:
mode:
authorsiddharth ravikumar <s@ricketyspace.net>2022-06-05 14:47:11 -0400
committersiddharth ravikumar <s@ricketyspace.net>2022-06-05 14:47:11 -0400
commit2081e91caefda0c5dd92069401eee4ffc2e075dd (patch)
tree5c616c43160fc49ae7d77d56a029aebbf9c529b6 /nws
parentbfe9f0a063ae3120bd07310649ca07966aca538a (diff)
nws: add get
A thin HTTP GET wrapper for hitting NWS API endpoints.
Diffstat (limited to 'nws')
-rw-r--r--nws/nws.go55
-rw-r--r--nws/nws_test.go92
2 files changed, 146 insertions, 1 deletions
diff --git a/nws/nws.go b/nws/nws.go
index 8c28f19..2fc5662 100644
--- a/nws/nws.go
+++ b/nws/nws.go
@@ -8,6 +8,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "time"
"ricketyspace.net/peach/client"
)
@@ -198,3 +199,57 @@ func GetForecastHourly(point *Point) (*Forecast, error) {
}
return forecast, nil
}
+
+// HTTP GET a NWS endpoint.
+func get(url string) ([]byte, *Error) {
+ tries := 5
+ retryDelay := 100 * time.Millisecond
+ for {
+ resp, err := client.Get(url)
+ if err != nil {
+ return nil, &Error{
+ Title: fmt.Sprintf("http get failed: %v", url),
+ Type: "http-get",
+ Status: 500,
+ Detail: err.Error(),
+ }
+ }
+ if tries > 0 && resp.StatusCode != 200 {
+ tries -= 1
+
+ // Wait before re-try.
+ time.Sleep(retryDelay)
+
+ retryDelay *= 2 // Exponential back-off delay.
+ continue // Re-try
+ }
+
+ // Parse response body.
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, &Error{
+ Title: fmt.Sprintf("parsing body: %v", url),
+ Type: "response-body",
+ Status: 500,
+ Detail: err.Error(),
+ }
+ }
+
+ // Check if the request failed.
+ if resp.StatusCode != 200 {
+ nwsErr := Error{}
+ err := json.Unmarshal(body, &nwsErr)
+ if err != nil {
+ return nil, &Error{
+ Title: fmt.Sprintf("json decode: %v", url),
+ Type: "json-decode",
+ Status: 500,
+ Detail: err.Error(),
+ }
+ }
+ return nil, &nwsErr
+ }
+ // Response OK.
+ return body, nil
+ }
+}
diff --git a/nws/nws_test.go b/nws/nws_test.go
index ffa05c8..bac3d83 100644
--- a/nws/nws_test.go
+++ b/nws/nws_test.go
@@ -3,7 +3,13 @@
package nws
-import "testing"
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+)
func TestPoints(t *testing.T) {
// Test valid lat,lng.
@@ -142,3 +148,87 @@ func TestGetForecastHourly(t *testing.T) {
}
}
}
+
+func TestNWSGetWrapper(t *testing.T) {
+ // Initialize test NWS server.
+ fails := 0
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if fails > 0 {
+ fails -= 1
+ http.Error(w, `{"type":"urn:noaa:nws:api:UnexpectedProblem","title":"Unexpected Problem","status":500,"detail":"An unexpected problem has occurred.","instance":"urn:noaa:nws:api:request:493c3a1d-f87e-407f-ae2c-24483f5aab63","correlationId":"493c3a1d-f87e-407f-ae2c-24483f5aab63","additionalProp1":{}}`, 500)
+ return
+ }
+ // Success.
+ fmt.Fprintln(w, `{"@context":[],"properties":{"gridId":"CLE","gridX":82,"gridY":64,"forecast":"https://api.weather.gov/gridpoints/CLE/82,64/forecast","forecastHourly":"https://api.weather.gov/gridpoints/CLE/82,64/forecast/hourly","relativeLocation":{"properties":{"city":"Cleveland","state":"OH"}}}}`)
+ }))
+ defer ts.Close()
+
+ // Test 1 - Server fails 5 times.
+ fails = 5
+ _, err := get(ts.URL)
+ if err != nil {
+ t.Errorf("get failed: %v", err)
+ return
+ }
+
+ // Test 2 - Server fails 6 times.
+ fails = 6
+ respBody, err := get(ts.URL)
+ if err == nil {
+ t.Errorf("get did not fail: %s", respBody)
+ return
+ }
+ if err != nil && respBody != nil {
+ t.Errorf("body is not nil: %s", respBody)
+ }
+ if err.Title != "Unexpected Problem" {
+ t.Errorf("err title: %s", err.Title)
+ return
+ }
+ if err.Type != "urn:noaa:nws:api:UnexpectedProblem" {
+ t.Errorf("err type: %s", err.Type)
+ return
+ }
+ if err.Status != 500 {
+ t.Errorf("err status: %d", err.Status)
+ return
+ }
+ if err.Detail != "An unexpected problem has occurred." {
+ t.Errorf("err detail: %s", err.Detail)
+ return
+ }
+
+ // Test 3 - Server fails 1 time.
+ fails = 1
+ respBody, err = get(ts.URL)
+ if err != nil {
+ t.Errorf("get failed: %v", err)
+ return
+ }
+ if respBody == nil {
+ t.Errorf("body: %s", respBody)
+ return
+ }
+ point := new(Point)
+ jerr := json.Unmarshal(respBody, point)
+ if jerr != nil {
+ t.Errorf("points: decode: %v", jerr)
+ return
+ }
+ if point.Properties.Forecast == "" {
+ t.Errorf("points: forecast empty")
+ return
+ }
+ if point.Properties.ForecastHourly == "" {
+ t.Errorf("points: forecasthourly empty")
+ return
+ }
+ if point.Properties.RelativeLocation.Properties.City == "" {
+ t.Errorf("points: city empty")
+ return
+ }
+ if point.Properties.RelativeLocation.Properties.State == "" {
+ t.Errorf("points: state empty")
+ return
+ }
+}