Files
weatherapi/docs/internal/http-adapter.md
Eric Rakestraw 5a1134b955
All checks were successful
ci/woodpecker/manual/build-image Pipeline was successful
Suppress superseded alerts in the /alerts/active endpoint
2026-06-17 06:48:14 -05:00

185 lines
6.4 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. The
application service prefers alert `ends` over `expires` when deciding whether an
alert has ended. The application service also suppresses alerts referenced by
another alert in the same latest run. This supersession rule uses alert
references from update and cancel messages, even when the referencing message is
not returned by `/alerts/active`.
## 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.