Files
job-search/internal/findATender/findATender.go
T

94 lines
2.6 KiB
Go

package findATender
import (
"context"
"log/slog"
"time"
"git.avsdev.uk/Go/job-search/internal/config"
"git.avsdev.uk/Go/job-search/internal/types"
resty "resty.dev/v3"
)
// FindATender is a client for the Find a Tender OCDS API (or the mock
// server). Create it with New and call Shutdown when finished with it.
type FindATender struct {
httpClient *resty.Client
config *config.JobSearchConfig
apiRoot string
ctx context.Context
}
// New returns a client for the OCDS API at apiRoot. Requests time out after
// 60 seconds and are retried once. Call Shutdown when finished with it.
func New(conf *config.JobSearchConfig, apiRoot string) *FindATender {
ft := &FindATender{
config: conf,
apiRoot: apiRoot,
}
ft.httpClient = resty.New().
SetTimeout(60 * time.Duration(time.Second)).
SetRetryCount(1)
return ft
}
// ReloadConfig replaces the configuration the client reads, such as the log
// level. The HTTP client and API root set by New are not changed.
func (ft *FindATender) ReloadConfig(conf *config.JobSearchConfig) {
ft.config = conf
}
// With returns a copy of the client whose requests use ctx, so they stop
// when it is cancelled. A nil ctx returns the client unchanged.
func (ft *FindATender) With(ctx context.Context) *FindATender {
if ctx == nil {
return ft
}
copied := *ft
copied.ctx = ctx
return &copied
}
// Shutdown closes the client's HTTP connections. A failure to close is
// logged, as there is nothing a caller could do about it.
func (ft *FindATender) Shutdown() {
if err := ft.httpClient.Close(); err != nil {
slog.Warn("findATender: closing HTTP client", "error", err)
}
}
// FetchOnce fetches the first page of releases updated since since, and
// converts them to opportunities. If allowRateLimit is true, a 429 response
// is waited out and retried; otherwise it fails with ErrRateLimitExceeded.
func (ft *FindATender) FetchOnce(
since time.Time,
allowRateLimit bool,
) ([]*types.Opportunity, error) {
releases, err := ft.fetchOnce(RequestOptions{
Since: since,
AllowRateLimit: allowRateLimit,
})
if err != nil {
return nil, err
}
return Convert(releases), nil
}
// FetchAll fetches every page of releases updated since since, following
// each page's next link, and converts them to opportunities. allowRateLimit
// behaves as for FetchOnce.
func (ft *FindATender) FetchAll(
since time.Time,
allowRateLimit bool,
) ([]*types.Opportunity, error) {
releases, err := ft.fetchAll(RequestOptions{
Since: since,
AllowRateLimit: allowRateLimit,
})
if err != nil {
return nil, err
}
return Convert(releases), nil
}