- fix dispatch route compilation so empty Kinds matches all (nil), not none - introduce internal/sources/common/HTTPSource to centralize HTTP polling boilerplate: - standard cfg parsing (url + user_agent) - default HTTP client + Accept/User-Agent headers - consistent error wrapping - refactor observation sources (nws/openmeteo/openweather) to use HTTPSource - upstream generic HTTP fetch/limits/timeout helper from weatherfeeder to feedkit: - move internal/sources/common/http.go -> feedkit/transport/http.go - keep behavior: status checks, max-body limit, default timeout
77 lines
2.3 KiB
Go
77 lines
2.3 KiB
Go
// FILE: ./internal/sources/common/http_source.go
|
|
package common
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"gitea.maximumdirect.net/ejr/feedkit/config"
|
|
"gitea.maximumdirect.net/ejr/feedkit/transport"
|
|
)
|
|
|
|
// HTTPSource is a tiny, reusable "HTTP polling spine" for weatherfeeder sources.
|
|
//
|
|
// It centralizes the boring parts:
|
|
// - standard config shape (url + user_agent) via RequireHTTPSourceConfig
|
|
// - a default http.Client with timeout
|
|
// - FetchBody / headers / max-body safety limit
|
|
// - consistent error wrapping (driver + source name)
|
|
//
|
|
// Individual drivers remain responsible for:
|
|
// - decoding minimal metadata (for Event.ID / EffectiveAt)
|
|
// - constructing the event envelope (kind/schema/payload)
|
|
type HTTPSource struct {
|
|
Driver string
|
|
Name string
|
|
URL string
|
|
UserAgent string
|
|
Accept string
|
|
Client *http.Client
|
|
}
|
|
|
|
// NewHTTPSource builds an HTTPSource using weatherfeeder's standard HTTP source
|
|
// config (params.url + params.user_agent) and a default HTTP client.
|
|
func NewHTTPSource(driver string, cfg config.SourceConfig, accept string) (*HTTPSource, error) {
|
|
c, err := RequireHTTPSourceConfig(driver, cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &HTTPSource{
|
|
Driver: driver,
|
|
Name: c.Name,
|
|
URL: c.URL,
|
|
UserAgent: c.UserAgent,
|
|
Accept: accept,
|
|
Client: transport.NewHTTPClient(transport.DefaultHTTPTimeout),
|
|
}, nil
|
|
}
|
|
|
|
// FetchBytes fetches the URL and returns the raw response body bytes.
|
|
func (s *HTTPSource) FetchBytes(ctx context.Context) ([]byte, error) {
|
|
client := s.Client
|
|
if client == nil {
|
|
// Defensive: allow tests or callers to nil out Client; keep behavior sane.
|
|
client = transport.NewHTTPClient(transport.DefaultHTTPTimeout)
|
|
}
|
|
|
|
b, err := transport.FetchBody(ctx, client, s.URL, s.UserAgent, s.Accept)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s %q: %w", s.Driver, s.Name, err)
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
// FetchJSON fetches the URL and returns the raw body as json.RawMessage.
|
|
// json.Unmarshal accepts json.RawMessage directly, so callers can decode minimal
|
|
// metadata without keeping both []byte and RawMessage in their own structs.
|
|
func (s *HTTPSource) FetchJSON(ctx context.Context) (json.RawMessage, error) {
|
|
b, err := s.FetchBytes(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return json.RawMessage(b), nil
|
|
}
|