# HTTP Adapter This document describes the internal HTTP adapter under `internal/adapters/inbound/httpapi`. The public endpoint contract belongs in [`docs/api.md`](../api.md). ## Purpose The HTTP adapter turns feedapi route definitions into calls on the application service boundary. It owns route registration, query binding, request validation, forecast day-slice filtering, outlook active filter construction, alert active-time selection, response envelopes, and template names. ## Inputs and Outputs Inputs: - feedapi HTTP requests; - query parameters; - an implementation of the adapter-local `Service` interface. Outputs: - `endpoint.Definition` values registered by runtime composition; - `response.Envelope{Data: ...}` values for successful requests; - typed feedapi errors for invalid query parameters; - endpoint-specific template names for text rendering. ## Boundaries The adapter may: - define routes and supported response formats; - bind and validate query parameters; - call the `Service` interface; - choose the presenter function for an endpoint; - filter forecast copies for `/today` and `/tomorrow`; - pass the current UTC instant to active-alert application filtering; - construct outlook active filters. The adapter must not: - execute SQL; - open databases; - mutate repository-returned models in place; - duplicate public API reference text; - move unit conversion or timezone presentation out of presenters. ## Config Fields Used The HTTP adapter does not read config directly. Feedapi applies server and renderer configuration before requests reach these handlers. Template names are declared in endpoint definitions, but `templates.base_dir` is loaded by feedapi. ## External Adapters Used - `feedapi/endpoint` for route definitions. - `feedapi/render` for declared output formats. - `feedapi/response` for success envelopes. - `feedapi/bind` and `feedapi/errors` for query validation failures. - `presenter` package for payload shaping. ## State The adapter has no durable state. `forecastNow`, `alertNow`, and `outlookNow` are package-level state only to make time-dependent endpoint tests deterministic. Do not add request caches or cross-request mutable state here. ## Route Registry `Definitions(svc)` returns all implemented route definitions. It includes: - observations; - active alerts; - current conditions; - weather stories; - convective outlooks; - forecast discussions; - hourly and narrative forecasts. Each route uses `endpoint.GET`, declares JSON/XML/text production, and names a text template. Feedapi owns routing, format negotiation, response rendering, middleware, and error normalization after definitions are registered. ## Query Binding Binder shapes include: - `bindQuery`: `format` and `units`; - `bindPrecisionQuery`: `format`, `units`, and `precision`; - `bindForecastPrecisionQuery`: `format`, `units`, `precision`, and timezone; - `bindTimezoneQuery`: `format`, `units`, and timezone. - outlook binders: `format`, `units`, timezone, and outlook filters. All binders use feedapi binding helpers with `RejectUnknown: true`. Supported common query values are lowercased and trimmed before binding where applicable. `precision` defaults to `0` and must be between `0` and `2`. Timezone parsing is available only through binders used by forecast, discussion, weather story, and outlook routes. Outlook routes accept `day` and `outlookType`. `containsLocation` is a response field only and is rejected as a request parameter. ## Timezone Parsing Timezone parsing accepts: - IANA names through `time.LoadLocation`; - configured aliases such as `Chicago` and `Stl`; - common US abbreviations handled as fixed zones; - signed UTC offsets. If both `tz` and `TZ` are provided, they must match case-insensitively. Invalid timezone input is converted to a feedapi invalid-parameter error. ## Forecast Day Slices Forecast base routes return the latest run unchanged except for presentation. `/today` and `/tomorrow` routes call `filterForecastRunByDaySlice`. Filtering behavior: - nil runs remain nil; - missing timezone means UTC; - day selection is based on `forecastNow()` in the resolved timezone; - periods are included when `period.StartTime` falls on the target local date; - the run is shallow-copied and `Periods` is replaced with the filtered slice. The package variable `forecastNow` exists so endpoint tests can make day-slice behavior deterministic. ## Alert Active Time `/alerts/active` uses the shared `format` and `units` binder. The handler calls the application service with `alertNow().UTC()` so active alert filtering uses the request-time instant while remaining deterministic in endpoint tests. ## Outlook Filters Outlook route filters are built at the HTTP boundary and passed to the application service: - `/outlooks/convective` uses only user-supplied filters; - `/outlooks/convective/active` adds `ActiveAt=outlookNow().UTC()`. The package variable `outlookNow` exists so endpoint tests can make active filtering deterministic. The application service returns filtered outlook copies and trims run-level discussions to days represented by retained outlooks. ## Failure Behavior Binder failures become feedapi invalid-parameter responses. Handler service errors are returned unchanged to feedapi for runtime normalization. Nil service payloads are presented as nil data, so response rendering can produce `data: null`. ## Templates Endpoint definitions bind template names only. Template loading and rendering belong to feedapi. Template files live under `templates/` and are operator configuration through `templates.base_dir`. ## Tests to Inspect Before Changing - `internal/adapters/inbound/httpapi/endpoints_test.go` - `internal/adapters/inbound/httpapi/presenter/payload_test.go` when changing presenter interaction - `docs/api.md` when query behavior, route behavior, or payload behavior changes ## Invariants - Preserve strict unknown-query rejection. - Keep route-specific query policy in binder functions. - Keep endpoint handlers thin: bind, call service, present, envelope. - Keep day-slice filtering in the HTTP adapter, not in the repository. - Preserve top-level `data` envelopes and nil-data behavior.