Files
Eric Rakestraw f4dd701204
All checks were successful
ci/woodpecker/push/build-image Pipeline was successful
Cleanup outlook endpoints and completed roadmap documentation
2026-06-12 09:40:49 -05:00

210 lines
5.1 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) {
normalizeCommonQueryValue(r, "units")
normalizeCommonQueryValue(r, "format")
normalizeCommonQueryValue(r, "outlookType")
common, err := bind.CommonQueryParams(r, bind.QueryPolicy{
AllowUnits: true,
AllowFormat: true,
DefaultUnits: string(presenter.UnitsMetric),
RejectUnknown: true,
}, "tz", "TZ", "day", "outlookType")
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)
if err != nil {
return outlookQueryRequest{}, err
}
return outlookQueryRequest{
Units: units,
Timezone: tz,
Filter: filter,
}, nil
}
func bindOutlookFilter(r *http.Request) (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]")
}
}
return filter, nil
}