File/package naming convention conforming better to go standards - as ugly as it is
This commit is contained in:
1 parent
be4770cbab
commit
d6688a6754
32 files changed
+36
-36
No files matched your search
@@ -0,0 +1,191 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.avsdev.uk/Go/job-search/internal/mock/findatender/ocds"
|
||||
)
|
||||
|
||||
// Method is a procurement method and its details, as published on a tender.
|
||||
type Method struct {
|
||||
Method string `json:"procurementMethod"`
|
||||
Details string `json:"procurementMethodDetails,omitempty"`
|
||||
}
|
||||
|
||||
// embeddedCatalog is data/catalog.json, compiled into the binary. Rebuild after
|
||||
// changing the file.
|
||||
//
|
||||
//go:embed data/catalog.json
|
||||
var embeddedCatalog []byte
|
||||
|
||||
// Catalog is the pool the generator draws from. It is read from
|
||||
// ./data/catalog.json. Classifications holds every classification the subjects
|
||||
// use, keyed "{scheme}-{id}" (e.g. "CPV-90513000"), and subjects and their
|
||||
// items refer to entries by that key.
|
||||
type Catalog struct {
|
||||
Buyers []ocds.Organization `json:"buyers"`
|
||||
Suppliers []ocds.Organization `json:"suppliers"`
|
||||
Subjects []Subject `json:"subjects"`
|
||||
Methods []Method `json:"methods"`
|
||||
Classifications map[string]ocds.Classification `json:"classifications"`
|
||||
}
|
||||
|
||||
// Subject is a tender as stored in the catalog: an ocds.Tender whose main
|
||||
// classification and additionalClassifications, on the tender and on each
|
||||
// item, are keys into Catalog.Classifications. The outer fields shadow the
|
||||
// embedded ones of the same JSON name, so everything else is read as
|
||||
// ocds.Tender.
|
||||
type Subject struct {
|
||||
ocds.Tender
|
||||
Classification string `json:"classification,omitempty"`
|
||||
AdditionalClassifications []string `json:"additionalClassifications,omitempty"`
|
||||
Items []SubjectItem `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
// SubjectItem is an ocds.Item whose additionalClassifications are keys into
|
||||
// Catalog.Classifications.
|
||||
type SubjectItem struct {
|
||||
ocds.Item
|
||||
AdditionalClassifications []string `json:"additionalClassifications,omitempty"`
|
||||
}
|
||||
|
||||
// LoadCatalog parses the embedded catalog and checks it is usable: every
|
||||
// pool has at least one entry and every classification key resolves.
|
||||
func LoadCatalog() (*Catalog, error) {
|
||||
return parseCatalog(embeddedCatalog, "embedded catalog")
|
||||
}
|
||||
|
||||
// parseCatalog decodes and validates catalog JSON; name labels any error.
|
||||
func parseCatalog(b []byte, name string) (*Catalog, error) {
|
||||
var c Catalog
|
||||
if err := json.Unmarshal(b, &c); err != nil {
|
||||
return nil, fmt.Errorf("decoding %s: %w", name, err)
|
||||
}
|
||||
if err := c.validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid %s: %w", name, err)
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// validate checks that every pool has at least one entry and that every
|
||||
// classification key used by a subject or its items is in Classifications.
|
||||
// It reports all the problems found, listing at most five missing keys.
|
||||
func (c *Catalog) validate() error {
|
||||
var errs []error
|
||||
|
||||
for _, pool := range []struct {
|
||||
name string
|
||||
n int
|
||||
}{
|
||||
{"buyers", len(c.Buyers)},
|
||||
{"suppliers", len(c.Suppliers)},
|
||||
{"subjects", len(c.Subjects)},
|
||||
{"methods", len(c.Methods)},
|
||||
{"classifications", len(c.Classifications)},
|
||||
} {
|
||||
if pool.n == 0 {
|
||||
errs = append(errs, fmt.Errorf("no %s", pool.name))
|
||||
}
|
||||
}
|
||||
|
||||
missing := map[string]bool{}
|
||||
check := func(keys ...string) {
|
||||
for _, k := range keys {
|
||||
if _, ok := c.Classifications[k]; k != "" && !ok {
|
||||
missing[k] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range c.Subjects {
|
||||
check(s.Classification)
|
||||
check(s.AdditionalClassifications...)
|
||||
for _, it := range s.Items {
|
||||
check(it.AdditionalClassifications...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
keys := make([]string, 0, len(missing))
|
||||
for k := range missing {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
if len(keys) > 5 {
|
||||
keys = append(keys[:5], "...")
|
||||
}
|
||||
errs = append(errs, fmt.Errorf(
|
||||
"%d classification key(s) not found in classifications: %s",
|
||||
len(missing), strings.Join(keys, ", "),
|
||||
))
|
||||
}
|
||||
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// ---------- picking (always returns a copy the caller may modify) ----------
|
||||
|
||||
func (c *Catalog) buyer(r *rand.Rand) ocds.Organization {
|
||||
return clone(c.Buyers[r.Intn(len(c.Buyers))])
|
||||
}
|
||||
|
||||
func (c *Catalog) supplier(r *rand.Rand) ocds.Organization {
|
||||
return clone(c.Suppliers[r.Intn(len(c.Suppliers))])
|
||||
}
|
||||
|
||||
func (c *Catalog) method(r *rand.Rand) Method {
|
||||
return c.Methods[r.Intn(len(c.Methods))]
|
||||
}
|
||||
|
||||
// subject picks a subject and returns it as an ocds.Tender, with its
|
||||
// classification keys resolved to full classifications.
|
||||
func (c *Catalog) subject(r *rand.Rand) ocds.Tender {
|
||||
s := clone(c.Subjects[r.Intn(len(c.Subjects))])
|
||||
t := s.Tender
|
||||
t.Classification = c.resolveOne(s.Classification)
|
||||
t.AdditionalClassifications = c.resolve(s.AdditionalClassifications)
|
||||
t.Items = nil
|
||||
for _, it := range s.Items {
|
||||
item := it.Item
|
||||
item.AdditionalClassifications = c.resolve(it.AdditionalClassifications)
|
||||
t.Items = append(t.Items, item)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// resolveOne returns a copy of the classification with key, or nil if there
|
||||
// is none.
|
||||
func (c *Catalog) resolveOne(key string) *ocds.Classification {
|
||||
if cl, ok := c.Classifications[key]; ok {
|
||||
return &cl
|
||||
}
|
||||
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 {
|
||||
if cl, ok := c.Classifications[k]; ok {
|
||||
out = append(out, cl)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// clone deep-copies a value through JSON, which is all the generated types need.
|
||||
func clone[T any](v T) T {
|
||||
var out T
|
||||
b, err := json.Marshal(v)
|
||||
if err == nil {
|
||||
json.Unmarshal(b, &out) //nolint:errcheck,gosec // b was just marshalled from a T
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in new issue
Block a user