Skip to content
Open
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
12 changes: 6 additions & 6 deletions internal/restapi/routes_for_location_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import (
func (api *RestAPI) routesForLocationHandler(w http.ResponseWriter, r *http.Request) {
queryParams := r.URL.Query()

lat, fieldErrors := utils.ParseRequiredFloatParam(queryParams, "lat", nil)
lon, _ := utils.ParseRequiredFloatParam(queryParams, "lon", fieldErrors)
radius, _ := utils.ParseFloatParam(queryParams, "radius", fieldErrors)
latSpan, _ := utils.ParseFloatParam(queryParams, "latSpan", fieldErrors)
lonSpan, _ := utils.ParseFloatParam(queryParams, "lonSpan", fieldErrors)
maxCount, _ := utils.ParseMaxCount(queryParams, models.DefaultMaxCountForRoutes, fieldErrors)
locParams, fieldErrors := utils.ParseLocationParams(queryParams)
var lat, lon, radius, latSpan, lonSpan float64
if locParams != nil {
lat, lon, radius, latSpan, lonSpan = locParams.Lat, locParams.Lon, locParams.Radius, locParams.LatSpan, locParams.LonSpan
}
maxCount, fieldErrors := utils.ParseMaxCount(queryParams, models.DefaultMaxCountForRoutes, fieldErrors)
query := queryParams.Get("query")

if len(fieldErrors) > 0 {
Expand Down
12 changes: 6 additions & 6 deletions internal/restapi/stops_for_location_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ import (
func (api *RestAPI) stopsForLocationHandler(w http.ResponseWriter, r *http.Request) {
queryParams := r.URL.Query()

lat, fieldErrors := utils.ParseRequiredFloatParam(queryParams, "lat", nil)
lon, _ := utils.ParseRequiredFloatParam(queryParams, "lon", fieldErrors)
radius, _ := utils.ParseFloatParam(queryParams, "radius", fieldErrors)
latSpan, _ := utils.ParseFloatParam(queryParams, "latSpan", fieldErrors)
lonSpan, _ := utils.ParseFloatParam(queryParams, "lonSpan", fieldErrors)
maxCount, _ := utils.ParseMaxCount(queryParams, models.DefaultMaxCountForStops, fieldErrors)
locParams, fieldErrors := utils.ParseLocationParams(queryParams)
var lat, lon, radius, latSpan, lonSpan float64
if locParams != nil {
lat, lon, radius, latSpan, lonSpan = locParams.Lat, locParams.Lon, locParams.Radius, locParams.LatSpan, locParams.LonSpan
}
maxCount, fieldErrors := utils.ParseMaxCount(queryParams, models.DefaultMaxCountForStops, fieldErrors)
query := queryParams.Get("query")

var routeTypes []int
Expand Down
8 changes: 4 additions & 4 deletions internal/restapi/trips_for_location_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,10 @@ func (api *RestAPI) parseAndValidateRequest(r *http.Request) (
) {
queryParams := r.URL.Query()

lat, fieldErrors = utils.ParseRequiredFloatParam(queryParams, "lat", nil)
lon, _ = utils.ParseRequiredFloatParam(queryParams, "lon", fieldErrors)
latSpan, _ = utils.ParseFloatParam(queryParams, "latSpan", fieldErrors)
lonSpan, _ = utils.ParseFloatParam(queryParams, "lonSpan", fieldErrors)
locParams, fieldErrors := utils.ParseLocationParams(queryParams)
if locParams != nil {
lat, lon, latSpan, lonSpan = locParams.Lat, locParams.Lon, locParams.LatSpan, locParams.LonSpan
}
includeTrip = queryParams.Get("includeTrip") == "true"
includeSchedule = queryParams.Get("includeSchedule") == "true"

Expand Down
28 changes: 28 additions & 0 deletions internal/utils/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,34 @@ func ParseRequiredFloatParam(params url.Values, key string, fieldErrors map[stri

}

type LocationParams struct {
Lat float64
Lon float64
LatSpan float64
LonSpan float64
Radius float64
}

func ParseLocationParams(q url.Values) (*LocationParams, map[string][]string) {
lat, fieldErrors := ParseRequiredFloatParam(q, "lat", nil)
lon, fieldErrors := ParseRequiredFloatParam(q, "lon", fieldErrors)
radius, fieldErrors := ParseFloatParam(q, "radius", fieldErrors)
latSpan, fieldErrors := ParseFloatParam(q, "latSpan", fieldErrors)
lonSpan, fieldErrors := ParseFloatParam(q, "lonSpan", fieldErrors)

if len(fieldErrors) > 0 {
return nil, fieldErrors
}

return &LocationParams{
Lat: lat,
Lon: lon,
LatSpan: latSpan,
LonSpan: lonSpan,
Radius: radius,
}, fieldErrors
}

func ParseTimeParameter(timeParam string, currentLocation *time.Location) (string, time.Time, map[string][]string, bool) {
if timeParam == "" {
// No time parameter, use current date
Expand Down
119 changes: 119 additions & 0 deletions internal/utils/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1009,3 +1009,122 @@ func TestParseRequiredFloatParam(t *testing.T) {
assert.Equal(t, []string{"some error"}, fieldErrors["other"])
})
}

func TestParseLocationParams(t *testing.T) {
t.Run("valid full params", func(t *testing.T) {
q := url.Values{
"lat": []string{"47.6097"},
"lon": []string{"-122.3331"},
"latSpan": []string{"0.01"},
"lonSpan": []string{"0.02"},
"radius": []string{"500"},
}
locParams, fieldErrors := ParseLocationParams(q)
assert.Empty(t, fieldErrors)
require.NotNil(t, locParams)
assert.Equal(t, 47.6097, locParams.Lat)
assert.Equal(t, -122.3331, locParams.Lon)
assert.Equal(t, 0.01, locParams.LatSpan)
assert.Equal(t, 0.02, locParams.LonSpan)
assert.Equal(t, 500.0, locParams.Radius)
})

t.Run("missing lat and lon returns field errors", func(t *testing.T) {
q := url.Values{}
locParams, fieldErrors := ParseLocationParams(q)
assert.Nil(t, locParams)
assert.Contains(t, fieldErrors, "lat")
assert.Contains(t, fieldErrors["lat"][0], "Missing required field")
assert.Contains(t, fieldErrors, "lon")
assert.Contains(t, fieldErrors["lon"][0], "Missing required field")
})

t.Run("missing lat only returns field error for lat", func(t *testing.T) {
q := url.Values{
"lon": []string{"-122.3331"},
}
locParams, fieldErrors := ParseLocationParams(q)
assert.Nil(t, locParams)
assert.Contains(t, fieldErrors, "lat")
assert.NotContains(t, fieldErrors, "lon")
})

t.Run("missing lon only returns field error for lon", func(t *testing.T) {
q := url.Values{
"lat": []string{"47.6097"},
}
locParams, fieldErrors := ParseLocationParams(q)
assert.Nil(t, locParams)
assert.Contains(t, fieldErrors, "lon")
assert.NotContains(t, fieldErrors, "lat")
})

t.Run("optional params absent returns zero values and no error", func(t *testing.T) {
q := url.Values{
"lat": []string{"47.6097"},
"lon": []string{"-122.3331"},
}
locParams, fieldErrors := ParseLocationParams(q)
assert.Empty(t, fieldErrors)
require.NotNil(t, locParams)
assert.Equal(t, 47.6097, locParams.Lat)
assert.Equal(t, -122.3331, locParams.Lon)
assert.Equal(t, 0.0, locParams.LatSpan)
assert.Equal(t, 0.0, locParams.LonSpan)
assert.Equal(t, 0.0, locParams.Radius)
})

t.Run("invalid non-numeric lat returns field error", func(t *testing.T) {
q := url.Values{
"lat": []string{"not_a_number"},
"lon": []string{"-122.3331"},
}
locParams, fieldErrors := ParseLocationParams(q)
assert.Nil(t, locParams)
assert.Contains(t, fieldErrors, "lat")
assert.Contains(t, fieldErrors["lat"][0], "Invalid field value")
})

t.Run("invalid non-numeric lon returns field error", func(t *testing.T) {
q := url.Values{
"lat": []string{"47.6097"},
"lon": []string{"abc"},
}
locParams, fieldErrors := ParseLocationParams(q)
assert.Nil(t, locParams)
assert.Contains(t, fieldErrors, "lon")
assert.Contains(t, fieldErrors["lon"][0], "Invalid field value")
})

t.Run("invalid non-numeric optional params return field errors", func(t *testing.T) {
q := url.Values{
"lat": []string{"47.6097"},
"lon": []string{"-122.3331"},
"radius": []string{"big"},
"latSpan": []string{"wide"},
"lonSpan": []string{"tall"},
}
locParams, fieldErrors := ParseLocationParams(q)
assert.Nil(t, locParams)
assert.Contains(t, fieldErrors, "radius")
assert.Contains(t, fieldErrors, "latSpan")
assert.Contains(t, fieldErrors, "lonSpan")
})

t.Run("all fields invalid returns multiple field errors", func(t *testing.T) {
q := url.Values{
"lat": []string{"x"},
"lon": []string{"y"},
"radius": []string{"z"},
"latSpan": []string{"a"},
"lonSpan": []string{"b"},
}
locParams, fieldErrors := ParseLocationParams(q)
assert.Nil(t, locParams)
assert.Contains(t, fieldErrors, "lat")
assert.Contains(t, fieldErrors, "lon")
assert.Contains(t, fieldErrors, "radius")
assert.Contains(t, fieldErrors, "latSpan")
assert.Contains(t, fieldErrors, "lonSpan")
})
}
Loading