Naming, layout and comments: descriptive full-word names, one item per line in wrapped calls and literals, and doc comments on exported names

Short and single-letter names in application code become full words (tests
keep theirs). Wrapped calls, signatures and composite literals put one item
per line, and one-line function bodies are expanded. Exported names and
groups of constants get doc comments, and comments refer to the renamed
identifiers.
This commit is contained in:
bwilliams committed 2026-10-15 16:12:56 +01:00
1 parent 043e307663
commit 465358609a
78 files changed
+2374 -1402

No files matched your search

+82 -81
View File
@@ -46,7 +46,7 @@ type RequestOptions struct {
AllowRateLimit bool
}
// ---------- Utility methods for handling API irregularities -----------
// ---------- Utility methods for handling API irregularities ----------
// safeReleasePackage is ocds.ReleasePackage with the releases left undecoded,
// so safeUnpackReleases can repair or skip each one.
@@ -65,14 +65,14 @@ type safeReleasePackage struct {
// dateOnly matches a calendar date with no time of day: "2006-10-02".
var dateOnly = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`)
// unmarshalLenientDates decodes raw json into v like json.Unmarshal, after
// unmarshalLenientDates decodes raw json into target like json.Unmarshal, after
// repairing date fields that time.Time cannot parse.
func unmarshalLenientDates(raw json.RawMessage, v any) error {
func unmarshalLenientDates(raw json.RawMessage, target any) error {
fixed, err := repairDates(raw)
if err != nil {
return err
}
return json.Unmarshal(fixed, v)
return json.Unmarshal(fixed, target)
}
// repairDates returns a copy of raw in which every date field holding a
@@ -85,15 +85,15 @@ func unmarshalLenientDates(raw json.RawMessage, v any) error {
// A date field is one whose name is "date" or starts with "date", contains
// "Date", or ends with "Deadline".
func repairDates(raw json.RawMessage) (json.RawMessage, error) {
dec := json.NewDecoder(bytes.NewReader(raw))
dec.UseNumber() // keep numbers exactly as written, not as float64
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.UseNumber() // keep numbers exactly as written, not as float64
var doc any
if err := dec.Decode(&doc); err != nil {
if err := decoder.Decode(&doc); err != nil {
return nil, fmt.Errorf("repairing dates: %w", err)
}
if _, err := dec.Token(); !errors.Is(err, io.EOF) {
if _, err := decoder.Token(); !errors.Is(err, io.EOF) {
return nil, errors.New(
"repairing dates: unexpected data after the JSON value",
)
@@ -106,31 +106,31 @@ func repairDates(raw json.RawMessage) (json.RawMessage, error) {
return json.Marshal(doc)
}
// recursiveRepairDates fixes date fields in v in place and reports whether it
// recursiveRepairDates fixes date fields in value in place and reports whether it
// changed anything.
func recursiveRepairDates(v any) bool {
func recursiveRepairDates(value any) bool {
changed := false
switch v := v.(type) {
switch typed := value.(type) {
case map[string]any:
for key, val := range v {
if s, ok := val.(string); ok && isDateField(key) {
for key, fieldValue := range typed {
if text, ok := fieldValue.(string); ok && isDateField(key) {
switch {
case dateOnly.MatchString(s):
v[key] = s + "T00:00:00Z"
case dateOnly.MatchString(text):
typed[key] = text + "T00:00:00Z"
changed = true
case s == "":
v[key] = nil
case text == "":
typed[key] = nil
changed = true
}
continue
}
if recursiveRepairDates(val) {
if recursiveRepairDates(fieldValue) {
changed = true
}
}
case []any:
for _, el := range v {
if recursiveRepairDates(el) {
for _, element := range typed {
if recursiveRepairDates(element) {
changed = true
}
}
@@ -152,35 +152,35 @@ func isDateField(name string) bool {
// value that wouldn't fit the release struct) doesn't matter. Fields before
// them only need to be valid JSON.
func releaseIDs(raw json.RawMessage) (ocid, id string, err error) {
dec := json.NewDecoder(bytes.NewReader(raw))
dec.UseNumber() // a numeric id keeps its exact digits
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.UseNumber() // a numeric id keeps its exact digits
if tok, err := dec.Token(); err != nil {
if token, err := decoder.Token(); err != nil {
return "", "", fmt.Errorf("reading release: %w", err)
} else if tok != json.Delim('{') {
} else if token != json.Delim('{') {
return "", "", errors.New("reading release: not a JSON object")
}
var haveOCID, haveID bool
for dec.More() {
tok, err := dec.Token()
for decoder.More() {
token, err := decoder.Token()
if err != nil {
return ocid, id, fmt.Errorf("reading release: %w", err)
}
switch key, _ := tok.(string); key {
switch key, _ := token.(string); key {
case "ocid":
if err := dec.Decode(&ocid); err != nil {
if err := decoder.Decode(&ocid); err != nil {
return ocid, id, fmt.Errorf("reading release ocid: %w", err)
}
haveOCID = true
case "id":
if id, err = decodeReleaseID(dec); err != nil {
if id, err = decodeReleaseID(decoder); err != nil {
return ocid, id, err
}
haveID = true
default:
var skip json.RawMessage // must be valid JSON, but is otherwise ignored
if err := dec.Decode(&skip); err != nil {
if err := decoder.Decode(&skip); err != nil {
return ocid, id, fmt.Errorf("reading release field %q: %w", key, err)
}
}
@@ -194,18 +194,18 @@ func releaseIDs(raw json.RawMessage) (ocid, id string, err error) {
// decodeReleaseID decodes a release id. OCDS ids are strings, but a number is
// tolerated and kept as written.
func decodeReleaseID(dec *json.Decoder) (string, error) {
var v any
if err := dec.Decode(&v); err != nil {
func decodeReleaseID(decoder *json.Decoder) (string, error) {
var decoded any
if err := decoder.Decode(&decoded); err != nil {
return "", fmt.Errorf("reading release id: %w", err)
}
switch v := v.(type) {
switch typed := decoded.(type) {
case string:
return v, nil
return typed, nil
case json.Number:
return v.String(), nil
return typed.String(), nil
default:
return "", fmt.Errorf("reading release id: unexpected %T", v)
return "", fmt.Errorf("reading release id: unexpected %T", typed)
}
}
@@ -227,48 +227,49 @@ func missingIDsError(haveOCID, haveID bool) error {
//
// This method takes ownership of the response body and closes it when done.
func safeUnpackReleases(
logger *slog.Logger, response *resty.Response,
logger *slog.Logger,
response *resty.Response,
) (*ocds.ReleasePackage, error) {
var pkg safeReleasePackage
var releasePackage safeReleasePackage
// Take ownership of the response body.
defer response.Body.Close() //nolint:errcheck // read only
decoder := json.NewDecoder(response.Body)
if err := decoder.Decode(&pkg); err != nil {
if err := decoder.Decode(&releasePackage); err != nil {
return nil, err
}
out := &ocds.ReleasePackage{
URI: pkg.URI,
Version: pkg.Version,
Extensions: pkg.Extensions,
Publisher: pkg.Publisher,
License: pkg.License,
PublicationPolicy: pkg.PublicationPolicy,
Links: pkg.Links,
URI: releasePackage.URI,
Version: releasePackage.Version,
Extensions: releasePackage.Extensions,
Publisher: releasePackage.Publisher,
License: releasePackage.License,
PublicationPolicy: releasePackage.PublicationPolicy,
Links: releasePackage.Links,
}
// Correct PublishedDate of "" and "2006-01-02"
if pkg.PublishedDate != nil && len(*pkg.PublishedDate) > 0 {
t, err := time.Parse(time.RFC3339, *pkg.PublishedDate)
if releasePackage.PublishedDate != nil && len(*releasePackage.PublishedDate) > 0 {
published, err := time.Parse(time.RFC3339, *releasePackage.PublishedDate)
if err != nil {
t, err = time.Parse(time.DateOnly, *pkg.PublishedDate)
published, err = time.Parse(time.DateOnly, *releasePackage.PublishedDate)
}
if err == nil {
out.PublishedDate = &t
out.PublishedDate = &published
}
}
for _, rawRel := range pkg.Releases {
for _, rawRelease := range releasePackage.Releases {
var release ocds.Release
err := unmarshalLenientDates(rawRel, &release)
err := unmarshalLenientDates(rawRelease, &release)
if err != nil {
ocid, release_id, _ := releaseIDs(rawRel)
ocid, releaseID, _ := releaseIDs(rawRelease)
logger.Warn(
"skipping unreadable release",
"ocid", ocid,
"release_id", release_id,
"release_id", releaseID,
"error", err,
)
continue
@@ -279,7 +280,7 @@ func safeUnpackReleases(
return out, nil
}
// ---------- API interaction methods -----------
// ---------- API interaction methods ----------
// rateSleep sleeps the fetch worker until either the context is cancelled
// (shutdown), returning ctx.Err(), or the duration has expired. The duration
@@ -303,21 +304,21 @@ func closeBody(res *resty.Response) {
}
}
// fetchURI performs a single GET of uri, using ft's context, and traces it at
// fetchURI performs a single GET of uri, using findATender's context, and traces it at
// debug level. Unlike the method of the same name it does not check the
// response status.
func fetchURI(ft *FindATender, uri string) (*resty.Response, error) {
withTrace := ft.config.Logging.LogLevel() <= slog.LevelDebug
logger := ft.logger()
func fetchURI(findATender *FindATender, uri string) (*resty.Response, error) {
withTrace := findATender.config.Logging.LogLevel() <= slog.LevelDebug
logger := findATender.logger()
logger.Info("fetching", "uri", uri)
ctx := ft.ctx
ctx := findATender.ctx
if ctx == nil {
ctx = context.Background()
}
req := ft.httpClient.R()
req := findATender.httpClient.R()
req.SetContext(ctx)
res, err := req.SetTrace(withTrace).Get(uri)
@@ -373,13 +374,13 @@ func (ft *FindATender) fetchURI(
return nil, ErrRateLimitExceeded
}
retrySecStr := res.Header().Get("Retry-After")
retrySec, err := strconv.ParseInt(retrySecStr, 10, 32)
retryAfterText := res.Header().Get("Retry-After")
retryAfterSeconds, err := strconv.ParseInt(retryAfterText, 10, 32)
if err != nil {
return nil, fmt.Errorf("unknown retry-after value: %w", err)
}
retryAfter := time.Duration(retrySec) * time.Second
retryAfter := time.Duration(retryAfterSeconds) * time.Second
ft.logger().Warn("api rate limit hit, waiting as asked", "retry_after", retryAfter)
if rateSleep(ctx, retryAfter) != nil {
return nil, ErrFetchCancelled
@@ -429,13 +430,13 @@ func (ft *FindATender) fetchWithOpts(
// ErrNoNext if there is no "Next" link; the caller can decide whether asking
// for it was an error.
func (ft *FindATender) fetchNext(
pkg *ocds.ReleasePackage,
releasePackage *ocds.ReleasePackage,
allowRateLimit bool,
) (*resty.Response, error) {
if pkg.Links == nil || len(pkg.Links.Next) == 0 {
if releasePackage.Links == nil || len(releasePackage.Links.Next) == 0 {
return nil, ErrNoNext
}
return ft.fetchURI(pkg.Links.Next, allowRateLimit)
return ft.fetchURI(releasePackage.Links.Next, allowRateLimit)
}
// fetchOnce makes one call on the API. It does not follow any "Next" links.
@@ -445,16 +446,16 @@ func (ft *FindATender) fetchOnce(opts RequestOptions) ([]ocds.Release, error) {
return nil, err
}
pkg, err := safeUnpackReleases(ft.logger(), res)
releasePackage, err := safeUnpackReleases(ft.logger(), res)
if err != nil {
return nil, err
}
if len(pkg.Releases) == 0 {
if len(releasePackage.Releases) == 0 {
return nil, ErrNoReleases
}
return pkg.Releases, nil
return releasePackage.Releases, nil
}
// fetchAll makes a call on the API. If there are any "Next" links, it calls
@@ -467,25 +468,25 @@ func (ft *FindATender) fetchAll(opts RequestOptions) ([]ocds.Release, error) {
return nil, err
}
pkg, err := safeUnpackReleases(ft.logger(), res)
releasePackage, err := safeUnpackReleases(ft.logger(), res)
if err != nil {
return nil, err
}
releases = append(releases, pkg.Releases...)
releases = append(releases, releasePackage.Releases...)
// Next links already followed, so a page that links back to an earlier
// one cannot loop forever.
seen := make(map[string]bool)
for {
if pkg.Links != nil && len(pkg.Links.Next) > 0 {
if seen[pkg.Links.Next] {
ft.logger().Warn("stopping at repeated next link", "link", pkg.Links.Next)
if releasePackage.Links != nil && len(releasePackage.Links.Next) > 0 {
if seen[releasePackage.Links.Next] {
ft.logger().Warn("stopping at repeated next link", "link", releasePackage.Links.Next)
break
}
seen[pkg.Links.Next] = true
seen[releasePackage.Links.Next] = true
}
res, err := ft.fetchNext(pkg, opts.AllowRateLimit)
res, err := ft.fetchNext(releasePackage, opts.AllowRateLimit)
if err == ErrNoNext {
break
}
@@ -493,11 +494,11 @@ func (ft *FindATender) fetchAll(opts RequestOptions) ([]ocds.Release, error) {
return nil, err
}
pkg, err = safeUnpackReleases(ft.logger(), res)
releasePackage, err = safeUnpackReleases(ft.logger(), res)
if err != nil {
return nil, err
}
releases = append(releases, pkg.Releases...)
releases = append(releases, releasePackage.Releases...)
}
if len(releases) == 0 {
@@ -505,4 +506,4 @@ func (ft *FindATender) fetchAll(opts RequestOptions) ([]ocds.Release, error) {
}
return releases, nil
}
}