Fixed lint issues and missing documentation
This commit is contained in:
1 parent
7fd321df97
commit
6e6dec68c0
34 files changed
+346
-156
No files matched your search
@@ -168,6 +168,8 @@ func (c *Catalog) resolveOne(key string) *ocds.Classification {
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolve returns copies of the classifications with keys, skipping keys
|
||||
// that have none.
|
||||
func (c *Catalog) resolve(keys []string) []ocds.Classification {
|
||||
var out []ocds.Classification
|
||||
for _, k := range keys {
|
||||
|
||||
@@ -29,7 +29,7 @@ func NewGenerator(cat *Catalog, store *Store, seed int64) *Generator {
|
||||
return &Generator{
|
||||
cat: cat,
|
||||
store: store,
|
||||
rnd: rand.New(rand.NewSource(seed)),
|
||||
rnd: rand.New(rand.NewSource(seed)), //nolint:gosec // seeded for repeatable mock data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,10 @@ func (g *Generator) Make(at time.Time) ocds.Release {
|
||||
return g.newProcedure(at)
|
||||
}
|
||||
|
||||
// newProcedure returns the first release of a new procedure: a tender (or,
|
||||
// one time in five, a planning notice) from a random catalog buyer and
|
||||
// subject, using a random procurement method. Values are scaled by a random
|
||||
// factor, and a tender without lots gets a single lot covering it all.
|
||||
func (g *Generator) newProcedure(at time.Time) ocds.Release {
|
||||
buyer := g.cat.buyer(g.rnd)
|
||||
ensurePartyID(&buyer)
|
||||
@@ -189,6 +193,7 @@ func (g *Generator) advance(prev ocds.Release, at time.Time) ocds.Release {
|
||||
return r
|
||||
}
|
||||
|
||||
// newOCID returns a random ocid not already in the store.
|
||||
func (g *Generator) newOCID() string {
|
||||
for {
|
||||
id := fmt.Sprintf("%s%06x", ocidPrefix, g.rnd.Intn(1<<24))
|
||||
@@ -263,6 +268,7 @@ func setStatus(t *ocds.Tender, status string) {
|
||||
}
|
||||
}
|
||||
|
||||
// scaleValue multiplies v's amounts by f, rounded to the nearest 100.
|
||||
func scaleValue(v *ocds.Value, f float64) {
|
||||
if v == nil {
|
||||
return
|
||||
@@ -274,11 +280,13 @@ func scaleValue(v *ocds.Value, f float64) {
|
||||
}
|
||||
}
|
||||
|
||||
// ensurePartyID gives p an id derived from its name if it has none, so the
|
||||
// same organisation always gets the same id.
|
||||
func ensurePartyID(p *ocds.Organization) {
|
||||
if p.ID != "" {
|
||||
return
|
||||
}
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(p.Name))
|
||||
_, _ = h.Write([]byte(p.Name)) // never fails
|
||||
p.ID = fmt.Sprintf("GB-FTS-%d", h.Sum32()%1000000)
|
||||
}
|
||||
@@ -22,6 +22,7 @@ const (
|
||||
BasePath = "/api/1.0/ocdsReleasePackages"
|
||||
)
|
||||
|
||||
// server serves release packages from a Store; see NewHandler.
|
||||
type server struct {
|
||||
store *Store
|
||||
baseURL string
|
||||
@@ -39,6 +40,8 @@ func NewHandler(
|
||||
return (&server{store: store, baseURL: baseURL, loc: loc}).handle
|
||||
}
|
||||
|
||||
// encodeCursor makes the opaque cursor for a next link that resumes at store
|
||||
// position i.
|
||||
func encodeCursor(i int) string {
|
||||
return base64.RawURLEncoding.EncodeToString([]byte("idx:" + strconv.Itoa(i)))
|
||||
}
|
||||
@@ -62,7 +65,7 @@ func decodeCursor(c string) (int, error) {
|
||||
func badRequest(w http.ResponseWriter, msg string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]string{"error": msg})
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{"error": msg}) // the client has gone
|
||||
}
|
||||
|
||||
// parseTime reads a query timestamp. Values without an offset (the documented
|
||||
@@ -93,6 +96,8 @@ type pageQuery struct {
|
||||
limit int
|
||||
}
|
||||
|
||||
// handle answers a GET for a page of releases as an OCDS release package,
|
||||
// with a links.next when more releases match.
|
||||
func (s *server) handle(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != http.MethodGet {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
// ---------- rate limiting ----------
|
||||
|
||||
// clientWindow is one client's rate limiting state.
|
||||
type clientWindow struct {
|
||||
hour time.Time // clock hour the anchor belongs to
|
||||
anchor time.Time // first request of that hour
|
||||
@@ -18,6 +19,10 @@ type clientWindow struct {
|
||||
count int // accepted requests in the current window
|
||||
}
|
||||
|
||||
// RateLimiter limits each client (by remote host) to a number of requests per
|
||||
// fixed window, answering the rest with 429 Too Many Requests and a
|
||||
// Retry-After header, as the Find a Tender API does. A client's windows are
|
||||
// counted from its first request in each clock hour.
|
||||
type RateLimiter struct {
|
||||
mu sync.Mutex
|
||||
limit int
|
||||
@@ -26,6 +31,7 @@ type RateLimiter struct {
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
// NewRateLimiter returns a RateLimiter allowing limit requests per window.
|
||||
func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
|
||||
return &RateLimiter{
|
||||
limit: limit,
|
||||
@@ -108,7 +114,7 @@ func (rl *RateLimiter) Middleware(next http.HandlerFunc) http.HandlerFunc {
|
||||
w.Header().Set("Retry-After", strconv.Itoa(retry))
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
w.WriteHeader(http.StatusTooManyRequests)
|
||||
fmt.Fprintf(
|
||||
_, _ = fmt.Fprintf(
|
||||
w, "Rate limit of %d exceeded. Please retry after %d seconds.",
|
||||
rl.limit, retry,
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
@@ -69,14 +70,14 @@ func OpenStore(path string) (*Store, error) {
|
||||
// readReleases reads the releases stored at path, skipping lines that are
|
||||
// blank or can't be decoded. A missing file holds no releases.
|
||||
func readReleases(path string) ([]ocds.Release, error) {
|
||||
in, err := os.Open(path)
|
||||
in, err := os.Open(filepath.Clean(path))
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer in.Close()
|
||||
defer in.Close() //nolint:errcheck // read only
|
||||
|
||||
var releases []ocds.Release
|
||||
sc := bufio.NewScanner(in)
|
||||
@@ -103,7 +104,7 @@ func readReleases(path string) ([]ocds.Release, error) {
|
||||
// mid-write, it ends the partial record so the next one starts on a fresh
|
||||
// line.
|
||||
func openForAppend(path string) (*os.File, error) {
|
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0o600)
|
||||
f, err := os.OpenFile(filepath.Clean(path), os.O_CREATE|os.O_RDWR|os.O_APPEND, 0o600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -111,8 +112,7 @@ func openForAppend(path string) (*os.File, error) {
|
||||
last := make([]byte, 1)
|
||||
if _, err := f.ReadAt(last, st.Size()-1); err == nil && last[0] != '\n' {
|
||||
if _, err := f.Write([]byte("\n")); err != nil {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("repairing %s: %w", path, err)
|
||||
return nil, errors.Join(fmt.Errorf("repairing %s: %w", path, err), f.Close())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,8 +166,9 @@ func (s *Store) Sync() error {
|
||||
return s.f.Sync()
|
||||
}
|
||||
|
||||
// A date earlier than the newest stored release (for example after a clock
|
||||
// change) is moved up so the order stays valid.
|
||||
// append stores r, syncing the file to disk if sync is true. A date earlier
|
||||
// than the newest stored release (for example after a clock change) is moved
|
||||
// up so the order stays valid.
|
||||
func (s *Store) append(r ocds.Release, sync bool) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@@ -224,12 +225,14 @@ func (s *Store) Last() (time.Time, bool) {
|
||||
return s.times[len(s.times)-1], true
|
||||
}
|
||||
|
||||
// Len returns the number of stored releases.
|
||||
func (s *Store) Len() int {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return len(s.releases)
|
||||
}
|
||||
|
||||
// HasOCID reports whether any release of the procedure ocid is stored.
|
||||
func (s *Store) HasOCID(ocid string) bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
@@ -261,6 +264,7 @@ func (s *Store) RandomOpen(r *rand.Rand, since time.Time) (ocds.Release, bool) {
|
||||
return ocds.Release{}, false
|
||||
}
|
||||
|
||||
// Close closes the store's file. The store must not be used afterwards.
|
||||
func (s *Store) Close() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
Reference in new issue
Block a user