234 lines
6.0 KiB
Go
234 lines
6.0 KiB
Go
// query_bind.go binds endpoint query parameters into typed request config.
|
|
// Layer: adapters/inbound/httpapi request binding.
|
|
package httpapi
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"gitea.maximumdirect.net/ejr/feedapi/bind"
|
|
apierrors "gitea.maximumdirect.net/ejr/feedapi/errors"
|
|
"gitea.maximumdirect.net/ejr/weatherapi/internal/adapters/inbound/httpapi/presenter"
|
|
"gitea.maximumdirect.net/ejr/weatherapi/internal/app"
|
|
)
|
|
|
|
type queryRequest struct {
|
|
Units presenter.Units
|
|
}
|
|
|
|
type precisionQueryRequest struct {
|
|
Units presenter.Units
|
|
Precision int
|
|
Timezone *time.Location
|
|
}
|
|
|
|
type timezoneQueryRequest struct {
|
|
Units presenter.Units
|
|
Timezone *time.Location
|
|
}
|
|
|
|
type outlookQueryRequest struct {
|
|
Units presenter.Units
|
|
Timezone *time.Location
|
|
Filter app.OutlookFilter
|
|
}
|
|
|
|
func bindQuery(r *http.Request) (queryRequest, error) {
|
|
normalizeCommonQueryValue(r, "units")
|
|
normalizeCommonQueryValue(r, "format")
|
|
|
|
common, err := bind.CommonQueryParams(r, bind.QueryPolicy{
|
|
AllowUnits: true,
|
|
AllowFormat: true,
|
|
DefaultUnits: string(presenter.UnitsMetric),
|
|
RejectUnknown: true,
|
|
})
|
|
if err != nil {
|
|
return queryRequest{}, err
|
|
}
|
|
|
|
units := presenter.Units(strings.ToLower(strings.TrimSpace(common.Units)))
|
|
if units == "" {
|
|
units = presenter.UnitsMetric
|
|
}
|
|
return queryRequest{Units: units}, nil
|
|
}
|
|
|
|
func bindPrecisionQuery(r *http.Request) (precisionQueryRequest, error) {
|
|
return bindPrecisionQueryInternal(r, false)
|
|
}
|
|
|
|
func bindForecastPrecisionQuery(r *http.Request) (precisionQueryRequest, error) {
|
|
return bindPrecisionQueryInternal(r, true)
|
|
}
|
|
|
|
func bindTimezoneQuery(r *http.Request) (timezoneQueryRequest, error) {
|
|
normalizeCommonQueryValue(r, "units")
|
|
normalizeCommonQueryValue(r, "format")
|
|
|
|
common, err := bind.CommonQueryParams(r, bind.QueryPolicy{
|
|
AllowUnits: true,
|
|
AllowFormat: true,
|
|
DefaultUnits: string(presenter.UnitsMetric),
|
|
RejectUnknown: true,
|
|
}, "tz", "TZ")
|
|
if err != nil {
|
|
return timezoneQueryRequest{}, err
|
|
}
|
|
|
|
units := presenter.Units(strings.ToLower(strings.TrimSpace(common.Units)))
|
|
if units == "" {
|
|
units = presenter.UnitsMetric
|
|
}
|
|
|
|
tz, err := parseTimezoneQuery(r)
|
|
if err != nil {
|
|
return timezoneQueryRequest{}, err
|
|
}
|
|
|
|
return timezoneQueryRequest{
|
|
Units: units,
|
|
Timezone: tz,
|
|
}, nil
|
|
}
|
|
|
|
func bindPrecisionQueryInternal(r *http.Request, allowTimezone bool) (precisionQueryRequest, error) {
|
|
normalizeCommonQueryValue(r, "units")
|
|
normalizeCommonQueryValue(r, "format")
|
|
normalizeCommonQueryValue(r, "precision")
|
|
|
|
allowedExtra := []string{"precision"}
|
|
if allowTimezone {
|
|
allowedExtra = append(allowedExtra, "tz", "TZ")
|
|
}
|
|
|
|
common, err := bind.CommonQueryParams(r, bind.QueryPolicy{
|
|
AllowUnits: true,
|
|
AllowFormat: true,
|
|
DefaultUnits: string(presenter.UnitsMetric),
|
|
RejectUnknown: true,
|
|
}, allowedExtra...)
|
|
if err != nil {
|
|
return precisionQueryRequest{}, err
|
|
}
|
|
|
|
precision, err := bind.OptionalInt(r, "precision", 0)
|
|
if err != nil {
|
|
return precisionQueryRequest{}, err
|
|
}
|
|
if err := bind.MinInt(precision, 0, "precision"); err != nil {
|
|
return precisionQueryRequest{}, err
|
|
}
|
|
if err := bind.MaxInt(precision, 2, "precision"); err != nil {
|
|
return precisionQueryRequest{}, err
|
|
}
|
|
|
|
units := presenter.Units(strings.ToLower(strings.TrimSpace(common.Units)))
|
|
if units == "" {
|
|
units = presenter.UnitsMetric
|
|
}
|
|
|
|
var tz *time.Location
|
|
if allowTimezone {
|
|
tz, err = parseTimezoneQuery(r)
|
|
if err != nil {
|
|
return precisionQueryRequest{}, err
|
|
}
|
|
}
|
|
|
|
return precisionQueryRequest{
|
|
Units: units,
|
|
Precision: precision,
|
|
Timezone: tz,
|
|
}, nil
|
|
}
|
|
|
|
func bindOutlookQuery(r *http.Request) (outlookQueryRequest, error) {
|
|
return bindOutlookQueryInternal(r, true)
|
|
}
|
|
|
|
func bindOutlookLocationQuery(r *http.Request) (outlookQueryRequest, error) {
|
|
return bindOutlookQueryInternal(r, false)
|
|
}
|
|
|
|
func bindOutlookQueryInternal(r *http.Request, allowContainsLocation bool) (outlookQueryRequest, error) {
|
|
normalizeCommonQueryValue(r, "units")
|
|
normalizeCommonQueryValue(r, "format")
|
|
normalizeCommonQueryValue(r, "outlookType")
|
|
|
|
allowedExtra := []string{"tz", "TZ", "day", "outlookType"}
|
|
if allowContainsLocation {
|
|
allowedExtra = append(allowedExtra, "containsLocation")
|
|
}
|
|
|
|
common, err := bind.CommonQueryParams(r, bind.QueryPolicy{
|
|
AllowUnits: true,
|
|
AllowFormat: true,
|
|
DefaultUnits: string(presenter.UnitsMetric),
|
|
RejectUnknown: true,
|
|
}, allowedExtra...)
|
|
if err != nil {
|
|
return outlookQueryRequest{}, err
|
|
}
|
|
|
|
units := presenter.Units(strings.ToLower(strings.TrimSpace(common.Units)))
|
|
if units == "" {
|
|
units = presenter.UnitsMetric
|
|
}
|
|
|
|
tz, err := parseTimezoneQuery(r)
|
|
if err != nil {
|
|
return outlookQueryRequest{}, err
|
|
}
|
|
|
|
filter, err := bindOutlookFilter(r, allowContainsLocation)
|
|
if err != nil {
|
|
return outlookQueryRequest{}, err
|
|
}
|
|
|
|
return outlookQueryRequest{
|
|
Units: units,
|
|
Timezone: tz,
|
|
Filter: filter,
|
|
}, nil
|
|
}
|
|
|
|
func bindOutlookFilter(r *http.Request, allowContainsLocation bool) (app.OutlookFilter, error) {
|
|
var filter app.OutlookFilter
|
|
|
|
if strings.TrimSpace(r.URL.Query().Get("day")) != "" {
|
|
day, err := bind.OptionalInt(r, "day", 0)
|
|
if err != nil {
|
|
return app.OutlookFilter{}, err
|
|
}
|
|
if day < 1 || day > 3 {
|
|
return app.OutlookFilter{}, apierrors.InvalidParameter("day must be one of [1, 2, 3]")
|
|
}
|
|
filter.Day = &day
|
|
}
|
|
|
|
outlookType := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("outlookType")))
|
|
if outlookType != "" {
|
|
switch outlookType {
|
|
case "categorical", "tornado", "hail", "wind":
|
|
filter.OutlookType = outlookType
|
|
default:
|
|
return app.OutlookFilter{}, apierrors.InvalidParameter("outlookType must be one of [categorical, tornado, hail, wind]")
|
|
}
|
|
}
|
|
|
|
if strings.TrimSpace(r.URL.Query().Get("containsLocation")) != "" {
|
|
if !allowContainsLocation {
|
|
return app.OutlookFilter{}, apierrors.InvalidParameter("containsLocation is not allowed on this endpoint")
|
|
}
|
|
containsLocation, err := bind.OptionalBool(r, "containsLocation", false)
|
|
if err != nil {
|
|
return app.OutlookFilter{}, err
|
|
}
|
|
filter.ContainsLocation = &containsLocation
|
|
}
|
|
|
|
return filter, nil
|
|
}
|