Files
weatherapi/docs/internal/http-adapter.md
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

180 lines
6.1 KiB
Markdown

# 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.