Answer architecture round 6: a manifest's reads names namespaces, loadReached notes only those nothing loaded provides, a mounted module labels its files by its namespace, Source.Load checks its mount, and the README keeps namespace and reads in an edited copy

This commit is contained in:
2026-10-11 12:41:11 +02:00
committed by Lillem4n
parent 3750770735
commit 89cafb1a21
13 changed files with 102 additions and 78 deletions
+3 -3
View File
@@ -131,9 +131,9 @@ replacement, and each removed path, column or flag.
and the CLI carries them all. `New` loads the modules `WithDataFS(modules...)` names and and the CLI carries them all. `New` loads the modules `WithDataFS(modules...)` names and
the folders `WithDataPath` names, and fails, naming `WithDataFS`, where no such option the folders `WithDataPath` names, and fails, naming `WithDataFS`, where no such option
is given. A manifest's `namespace` mounts its source under a dot path, and its `reads` lists the is given. A manifest's `namespace` mounts its source under a dot path, and its `reads` lists the
modules its source reads by default; a reference to a path nothing provides fails to namespaces of the modules its source reads by default; a reference to a path nothing
load, asking whether a module is missing and naming every module the loaded manifests provides fails to load, asking whether a module is missing and naming each namespace
read, or saying there are none. read that nothing loaded provides, or saying no loaded manifest names a module to add.
- A data source whose root holds a `.fejkdata.json` manifest with an `index` loads each - A data source whose root holds a `.fejkdata.json` manifest with an `index` loads each
category on the first call reaching it, as each shipped module does. Data failing to load, category on the first call reaching it, as each shipped module does. Data failing to load,
in `New` or on that first call, gives an error matching `ErrLoad`, and the CLI exits 1. in `New` or on that first call, gives an error matching `ErrLoad`, and the CLI exits 1.
+14 -13
View File
@@ -184,23 +184,24 @@ every category of its source, so a category file it leaves out never loads.
The index maps each category's dot path to an entry: `parent`, the table it links to, The index maps each category's dot path to an entry: `parent`, the table it links to,
which needs its own entry in the same folder; and `paths`, every path `List` advertises which needs its own entry in the same folder; and `paths`, every path `List` advertises
below the category, `""` for the category itself. Each shipped module's manifest, such as below the category, `""` for the category itself. Each shipped module's manifest, such as
[`data/svse/.fejkdata.json`](data/svse/.fejkdata.json), is one, so delete it from a [`data/svse/.fejkdata.json`](data/svse/.fejkdata.json), carries one, so in a copy you
copy you edit, or keep its index current. A table whose parent column differs from its edit, delete its `index` and keep its `namespace` and `reads`, or keep the index current.
entry's `parent` fails at first reach, and a table linking under another source's table A table whose parent column differs from its entry's `parent` fails at first reach, and a
needs a source without an index. `//go:embed` of a directory leaves the manifest out, and table linking under another source's table needs a source without an index. `//go:embed`
the source then loads whole in `New`; name it in the pattern too. of a directory leaves the manifest out, and the source then loads whole in `New` and at
the root, with no `namespace`; name the manifest in the pattern too.
A manifest's `namespace` is the dot path its source's root sits under: a folder holding A manifest's `namespace` is the dot path its source's root sits under: a folder holding
`person.json` with `"namespace": "sv_SE"` provides `sv_SE.person`, its subfolders adding `person.json` with `"namespace": "sv_SE"` provides `sv_SE.person`, its subfolders adding
segments below that. Each shipped module carries one, so `data/svse/person.json` is segments below that. Each shipped module carries one, so `data/svse/person.json` is
`sv_SE.person`. `sv_SE.person`.
A manifest's `reads` lists, by any name its author chooses, every module the source's A manifest's `reads` lists the namespace of every module the source's references read
references read unless another source provides the same paths, directly or through unless another source provides the same paths, directly or through another module.
another module. Nothing loads these modules: pass them yourself. A shipped module names a Nothing loads these modules: pass them yourself. `svse`'s manifest reads `geo.SE`. Data
Go package: `svse`'s manifest reads `github.com/larvit/fejkdata/data/geose`. Data whose whose reference names a path nothing provides fails to load; where a namespace the loaded
reference names a path nothing provides fails to load, asking whether a module is manifests read is one nothing loaded provides, the error asks whether a module is missing
missing and naming every module the loaded manifests read, or saying they name none. and names it, and otherwise it says no loaded manifest names a module to add.
`namespace` and `reads` need no index. `namespace` and `reads` need no index.
Each locale carries `address`, `color`, `company`, `date`, `email`, `first-name`, Each locale carries `address`, `color`, `company`, `date`, `email`, `first-name`,
@@ -305,7 +306,7 @@ What the two locales do not share: `en_US.address` carries a `region` column the
one has no use for, and `sv_SE.title` has no `parent`, so it is selected as one has no use for, and `sv_SE.title` has no `parent`, so it is selected as
`sv_SE.title[dr]` rather than inside a sex. `sv_SE.title[dr]` rather than inside a sex.
A `geo` folder holds one tree per country under its alpha-2 code: five The `geo` namespace holds one tree per country under its alpha-2 code: five
[linked tables](#linked-tables) named alike, and an `address` record binding one locality [linked tables](#linked-tables) named alike, and an `address` record binding one locality
and drawing its street and postal code inside it. The locale's `address` prints that and drawing its street and postal code inside it. The locale's `address` prints that
record, and each of its columns describes the address printed. Do not assert that a Swedish record, and each of its columns describes the address printed. Do not assert that a Swedish
@@ -377,7 +378,7 @@ import (
f, err := fejkdata.New(fejkdata.WithDataFS(svse.FS, geose.FS)) f, err := fejkdata.New(fejkdata.WithDataFS(svse.FS, geose.FS))
``` ```
`enus` reads `geous`; `misc` reads none. `New` fails where no `WithDataFS` or `enus` reads `geous`; `misc` and the geo packages read none. `New` fails where no `WithDataFS` or
`WithDataPath` names the data to load. `WithDataPath` names the data to load.
`New` refuses data that breaks the grammar, could mean two things or cannot render a valid `New` refuses data that breaks the grammar, could mean two things or cannot render a valid
+8 -8
View File
@@ -9,12 +9,12 @@ import (
// loadSources merges sources in order, the last winning a clash. A source whose manifest // loadSources merges sources in order, the last winning a clash. A source whose manifest
// carries an index leaves its categories unloaded; every other source loads here, with // carries an index leaves its categories unloaded; every other source loads here, with
// the unloaded categories it needs. It also returns, sorted, every module the sources' // the unloaded categories it needs. It also returns, sorted, every namespace the sources'
// manifests read by default. // manifests read.
// docs/decisions.md#a-source-whose-manifest-carries-an-index-loads-each-category-on-first-reach-any-other-loads-in-new-with-what-it-reads // docs/decisions.md#a-source-whose-manifest-carries-an-index-loads-each-category-on-first-reach-any-other-loads-in-new-with-what-it-reads
func loadSources(sources []datafiles.Source) (folder, []string, error) { func loadSources(sources []datafiles.Source) (folder, []string, error) {
root := folder{children: map[string]node{}} root := folder{children: map[string]node{}}
var modules []string var reads []string
for _, src := range sources { for _, src := range sources {
m, err := src.Manifest() m, err := src.Manifest()
if err != nil { if err != nil {
@@ -23,14 +23,14 @@ func loadSources(sources []datafiles.Source) (folder, []string, error) {
if err := addSource(&root, src, m); err != nil { if err := addSource(&root, src, m); err != nil {
return folder{}, nil, err return folder{}, nil, err
} }
for _, module := range m.Reads { for _, namespace := range m.Reads {
if !slices.Contains(modules, module) { if !slices.Contains(reads, namespace) {
modules = append(modules, module) reads = append(reads, namespace)
} }
} }
} }
slices.Sort(modules) slices.Sort(reads)
return root, modules, loadReached(&root, categorySites(&root), nil) return root, reads, loadReached(&root, reads, categorySites(&root), nil)
} }
func addSource(root *folder, src datafiles.Source, m datafiles.Manifest) error { func addSource(root *folder, src datafiles.Source, m datafiles.Manifest) error {
+1 -1
View File
@@ -24,5 +24,5 @@
"en_US.word": {"paths":[""]} "en_US.word": {"paths":[""]}
}, },
"namespace": "en_US", "namespace": "en_US",
"reads": ["github.com/larvit/fejkdata/data/geous"] "reads": ["geo.US"]
} }
+1 -1
View File
@@ -24,5 +24,5 @@
"sv_SE.word": {"paths":[""]} "sv_SE.word": {"paths":[""]}
}, },
"namespace": "sv_SE", "namespace": "sv_SE",
"reads": ["github.com/larvit/fejkdata/data/geose"] "reads": ["geo.SE"]
} }
+5 -3
View File
@@ -140,7 +140,7 @@ A directory a machine happens to have would make `--seed 42` machine-dependent;
2026-10-04, Lilleman auf Larv; revised 2026-10-09 to put the packages in the core's Go module. Serves goals 2.3, 6.2, 7.3, 9 and 10.1. Carried out by `todo.md` items 181, 184, 185 and 186. The CLI binary may grow to hundreds of MB. 2026-10-04, Lilleman auf Larv; revised 2026-10-09 to put the packages in the core's Go module. Serves goals 2.3, 6.2, 7.3, 9 and 10.1. Carried out by `todo.md` items 181, 184, 185 and 186. The CLI binary may grow to hundreds of MB.
- One Go package per locale, one per country's `geo/` tree, and one for `misc`, all in the core's Go module. A package registers nothing when imported. - One Go package per locale, one per country's `geo` tree, and one for `misc`, all in the core's Go module. A package registers nothing when imported.
- A bare `New()` loads no data and fails, naming the option to add. `WithoutShippedData` goes. - A bare `New()` loads no data and fails, naming the option to add. `WithoutShippedData` goes.
- A data package's `FS` is an `fs.FS` passed to `WithDataFS`, as anyone's data is, and `New` takes several. It is an exported `var FS embed.FS`, the shape `//go:embed` gives; a function returning it would only stop a program reassigning the FS variable of a package it imports. A module carrying functions loads through an option of its own, so a data module that starts carrying functions breaks its users. Such a function reaches the randomness through an interface the root declares; `internal/drawstate`'s type stays internal. - A data package's `FS` is an `fs.FS` passed to `WithDataFS`, as anyone's data is, and `New` takes several. It is an exported `var FS embed.FS`, the shape `//go:embed` gives; a function returning it would only stop a program reassigning the FS variable of a package it imports. A module carrying functions loads through an option of its own, so a data module that starts carrying functions breaks its users. Such a function reaches the randomness through an interface the root declares; `internal/drawstate`'s type stays internal.
- A module names, in a manifest, every module it reads by default, directly or through another, so the first error names the whole set to import. A manifest is optional: a `--data-path` folder without one is a module. Nothing loads a default on its own. - A module names, in a manifest, every module it reads by default, directly or through another, so the first error names the whole set to import. A manifest is optional: a `--data-path` folder without one is a module. Nothing loads a default on its own.
@@ -160,7 +160,9 @@ custom-CLI builder. Serves goals 1 and 3.3. `sv_SE` ships as `data/svse`, `en_US
Go linters flag a capital or an underscore in a package name, and `SE.FS` read as a type. Go linters flag a capital or an underscore in a package name, and `SE.FS` read as a type.
Each package holds its files at its root, and its manifest's `namespace` puts them under Each package holds its files at its root, and its manifest's `namespace` puts them under
the namespace, so no path repeats it. The bench also chose `reads` for what a module reads the namespace, so no path repeats it. The bench also chose `reads` for what a module reads
by default and the wording of the errors naming it. Valid until a locale's lowered name by default and the wording of the errors naming it. A `reads` entry is a namespace, so the
error names only what nothing loaded provides, and with every module loaded a typo names
none. Valid until a locale's lowered name
clashes with another's. clashes with another's.
## A source whose manifest carries an index loads each category on first reach; any other loads in `New`, with what it reads ## A source whose manifest carries an index loads each category on first reach; any other loads in `New`, with what it reads
@@ -592,7 +594,7 @@ when a source carries it.
2026-09-24, Lilleman auf Larv; revised 2026-10-10, when the shipped data became Go packages. 2026-09-24, Lilleman auf Larv; revised 2026-10-10, when the shipped data became Go packages.
Serves goal 6.2. `svse.FS` loads alone, and `sv_SE.address` then fails at first reach, Serves goal 6.2. `svse.FS` loads alone, and `sv_SE.address` then fails at first reach,
naming `github.com/larvit/fejkdata/data/geose`, which `svse`'s manifest reads. naming `geo.SE`, which `svse`'s manifest reads and nothing loaded provides.
`New` does not check that each module a manifest reads was given. A check by module name `New` does not check that each module a manifest reads was given. A check by module name
would refuse a stand-in providing the same paths (goal 9.2). A check by path needs every would refuse a stand-in providing the same paths (goal 9.2). A check by path needs every
reference, and only loading each indexed category finds them, which goal 13 rules out. reference, and only loading each indexed category finds them, which goal 13 rules out.
+12 -12
View File
@@ -31,15 +31,15 @@ type Generator struct {
drawState *drawstate.State drawState *drawstate.State
root folder // the categories as the node a path walks from, owned here so a walk allocates none root folder // the categories as the node a path walks from, owned here so a walk allocates none
structs map[reflect.Type]structResult structs map[reflect.Type]structResult
// defaultModules is every module the loaded sources' manifests read by default. // manifestReads is every namespace the loaded sources' manifests read.
defaultModules []string manifestReads []string
} }
type config struct { type config struct {
seed uint64 seed uint64
seeded bool seeded bool
sources []datafiles.Source sources []datafiles.Source
err error nilModules error
// given is whether an option named the data to load, though it may name none. // given is whether an option named the data to load, though it may name none.
given bool given bool
} }
@@ -69,7 +69,7 @@ func WithDataFS(modules ...fs.FS) Option {
return func(c *config) { return func(c *config) {
for i, fsys := range modules { for i, fsys := range modules {
if fsys == nil { if fsys == nil {
c.err = errors.Join(c.err, fmt.Errorf("fejkdata: WithDataFS module %d is nil", i+1)) c.nilModules = errors.Join(c.nilModules, fmt.Errorf("fejkdata: WithDataFS module %d is nil", i+1))
continue continue
} }
c.sources = append(c.sources, datafiles.FS(fsys)) c.sources = append(c.sources, datafiles.FS(fsys))
@@ -106,18 +106,18 @@ func New(opts ...Option) (*Generator, error) {
if !c.given { if !c.given {
return nil, errNoData return nil, errNoData
} }
if c.err != nil { if c.nilModules != nil {
return nil, c.err return nil, c.nilModules
} }
root, modules, err := loadSources(c.sources) root, reads, err := loadSources(c.sources)
if err != nil { if err != nil {
return nil, fmt.Errorf("fejkdata: %w", withDefaultModules(loadError{err}, modules)) return nil, fmt.Errorf("fejkdata: %w", loadError{err})
} }
seed, err := c.drawSeed() seed, err := c.drawSeed()
if err != nil { if err != nil {
return nil, fmt.Errorf("fejkdata: %w", err) return nil, fmt.Errorf("fejkdata: %w", err)
} }
return &Generator{drawState: drawstate.New(seed), root: root, defaultModules: modules}, nil return &Generator{drawState: drawstate.New(seed), root: root, manifestReads: reads}, nil
} }
// List returns the sorted dotted paths Fake renders: each category and every field, // List returns the sorted dotted paths Fake renders: each category and every field,
+7 -5
View File
@@ -58,8 +58,8 @@ func (f *Generator) loadCallerPath(path string) (string, []string, error) {
return "", nil, fmt.Errorf("fejkdata: %w", err) return "", nil, fmt.Errorf("fejkdata: %w", err)
} }
if c, ok := categoryOn(&f.root, segs); ok && c.unloaded() { if c, ok := categoryOn(&f.root, segs); ok && c.unloaded() {
if err := loadReached(&f.root, nil, []categoryAt{c}); err != nil { if err := loadReached(&f.root, f.manifestReads, nil, []categoryAt{c}); err != nil {
return "", nil, fmt.Errorf("fejkdata: %w", withDefaultModules(loadError{err}, f.defaultModules)) return "", nil, fmt.Errorf("fejkdata: %w", loadError{err})
} }
} }
return path, segs, nil return path, segs, nil
@@ -105,8 +105,9 @@ func needs(root *folder, s categorySite) []categoryAt {
// loadReached loads the unloaded categories among wanted and every unloaded category they // loadReached loads the unloaded categories among wanted and every unloaded category they
// or sites need, then runs sites and all it loaded through a whole load's pipeline. On an // or sites need, then runs sites and all it loaded through a whole load's pipeline. On an
// error it puts back what it loaded, so the next call fails the same way. // error it puts back what it loaded, so the next call fails the same way, and notes which
func loadReached(root *folder, sites []categorySite, wanted []categoryAt) error { // namespace of reads, every namespace the loaded manifests read, nothing in root provides.
func loadReached(root *folder, reads []string, sites []categorySite, wanted []categoryAt) error {
queue := wanted queue := wanted
for _, s := range sites { for _, s := range sites {
queue = append(queue, needs(root, s)...) queue = append(queue, needs(root, s)...)
@@ -118,8 +119,9 @@ func loadReached(root *folder, sites []categorySite, wanted []categoryAt) error
} }
if err != nil { if err != nil {
b.putBack() b.putBack()
return withMissingModules(err, root, reads)
} }
return err return nil
} }
// batch is the categories one loadReached loaded, each beside the entry it was loaded from. // batch is the categories one loadReached loaded, each beside the entry it was loaded from.
+6 -5
View File
@@ -36,8 +36,8 @@ func (f *Generator) NewTemplate(input string) (*Template, error) {
} }
f.mu.Lock() f.mu.Lock()
defer f.mu.Unlock() defer f.mu.Unlock()
if err := loadInline(&f.root, n, "template", false); err != nil { if err := loadInline(&f.root, f.manifestReads, n, "template", false); err != nil {
return nil, f.noDataNote(fmt.Errorf("fejkdata: %w", withDefaultModules(err, f.defaultModules))) return nil, f.noDataNote(fmt.Errorf("fejkdata: %w", err))
} }
return &Template{g: f, n: n}, nil return &Template{g: f, n: n}, nil
} }
@@ -84,13 +84,14 @@ func inputValue(input string) jsonvalue.Value {
return raw return raw
} }
// loadInline loads the unloaded categories n reads, then runs n through the pipeline against root. // loadInline loads the unloaded categories n reads, then runs n through the pipeline against root;
func loadInline(root *folder, n node, label string, typedByGo bool) error { // reads is every namespace the loaded manifests read.
func loadInline(root *folder, reads []string, n node, label string, typedByGo bool) error {
nodes := inlineNodes(n, label) nodes := inlineNodes(n, label)
if err := refuseFolderRefs(nodes); err != nil { if err := refuseFolderRefs(nodes); err != nil {
return err return err
} }
if err := loadReached(root, nil, referenced(root, nil, nodes)); err != nil { if err := loadReached(root, reads, nil, referenced(root, nil, nodes)); err != nil {
return loadError{err} return loadError{err}
} }
return pipeline{ return pipeline{
+12 -3
View File
@@ -6,6 +6,7 @@ import (
"io/fs" "io/fs"
"os" "os"
"path" "path"
"slices"
"strings" "strings"
"github.com/larvit/fejkdata/internal/grammar" "github.com/larvit/fejkdata/internal/grammar"
@@ -30,10 +31,15 @@ func Dir(path string) Source {
// FS is the tree in fsys. // FS is the tree in fsys.
func FS(fsys fs.FS) Source { return Source{fsys: fsys} } func FS(fsys fs.FS) Source { return Source{fsys: fsys} }
// Mounted is s with its root under the dot path namespace, "" for the namespace's root. // Mounted is s with its root under the dot path namespace, "" for the namespace's root. A
// source with no label of its own names its files in errors by the namespace's folders.
func (s Source) Mounted(namespace string) Source { func (s Source) Mounted(namespace string) Source {
if namespace != "" { if namespace == "" {
s.mount = strings.Split(namespace, ".") return s
}
s.mount = strings.Split(namespace, ".")
if s.label == "" {
s.label = path.Join(s.mount...)
} }
return s return s
} }
@@ -139,6 +145,9 @@ func (s Source) walkFolder(dir []string, compile func(Category) error) (bool, er
// Load hands compile the category file name.json in the folder dir of the namespace, which // Load hands compile the category file name.json in the folder dir of the namespace, which
// lies under the source's mount. // lies under the source's mount.
func (s Source) Load(dir []string, name string, compile func(Category) error) error { func (s Source) Load(dir []string, name string, compile func(Category) error) error {
if len(dir) < len(s.mount) || !slices.Equal(dir[:len(s.mount)], s.mount) {
return fmt.Errorf("%s.%s lies outside the namespace %s", strings.Join(dir, "."), name, strings.Join(s.mount, "."))
}
full := s.dirPath(dir[len(s.mount):]) full := s.dirPath(dir[len(s.mount):])
entries, err := fs.ReadDir(s.fsys, full) entries, err := fs.ReadDir(s.fsys, full)
if err != nil { if err != nil {
+13 -13
View File
@@ -17,12 +17,12 @@ const ManifestFile = ".fejkdata.json"
// Manifest is what a source's manifest says of it. // Manifest is what a source's manifest says of it.
type Manifest struct { type Manifest struct {
// Index is every category of the source by dot path; nil where the manifest has none. // Index is every category of the source by dot path; nil where the manifest has none.
Index map[string]IndexEntry `json:"index"` Index map[string]IndexEntry
// Reads is every module the source reads by default, directly or through another;
// nothing loads them.
Reads []string `json:"reads"`
// Namespace is the dot path the source's root sits under, "" for the namespace's root. // Namespace is the dot path the source's root sits under, "" for the namespace's root.
Namespace string `json:"namespace"` Namespace string
// Reads is the namespace of every module the source reads unless another source
// provides the same paths, directly or through another; nothing loads them.
Reads []string
} }
// IndexEntry is what an index says of a category: its parent table, "" for none, and the // IndexEntry is what an index says of a category: its parent table, "" for none, and the
@@ -108,7 +108,7 @@ func decodeNamespace(v any) (string, error) {
return namespace, nil return namespace, nil
} }
// decodeReads decodes the modules a source reads, each named by a non-empty string. // decodeReads decodes the namespaces of the modules a source reads.
func decodeReads(v any) ([]string, error) { func decodeReads(v any) ([]string, error) {
items, isList := v.([]any) items, isList := v.([]any)
if !isList { if !isList {
@@ -118,17 +118,17 @@ func decodeReads(v any) ([]string, error) {
seen := map[string]bool{} seen := map[string]bool{}
var errs []error var errs []error
for i, item := range items { for i, item := range items {
module, isString := item.(string) namespace, isString := item.(string)
switch { switch {
case !isString: case !isString:
errs = append(errs, fmt.Errorf("reads item %d must be a string, not %s", i+1, jsonvalue.Kind(item))) errs = append(errs, fmt.Errorf("reads item %d must be a string, not %s", i+1, jsonvalue.Kind(item)))
case module == "": case checkCategoryPath(namespace) != nil:
errs = append(errs, fmt.Errorf("reads item %d is empty; it names a module", i+1)) errs = append(errs, fmt.Errorf("reads item %d: %w", i+1, checkCategoryPath(namespace)))
case seen[module]: case seen[namespace]:
errs = append(errs, fmt.Errorf("reads item %d repeats %q", i+1, module)) errs = append(errs, fmt.Errorf("reads item %d repeats %q", i+1, namespace))
default: default:
seen[module] = true seen[namespace] = true
out = append(out, module) out = append(out, namespace)
} }
} }
return out, errors.Join(errs...) return out, errors.Join(errs...)
+14 -6
View File
@@ -121,17 +121,25 @@ type noEntry struct{ seg string }
func (e noEntry) Error() string { return fmt.Sprintf("no entry %q", e.seg) } func (e noEntry) Error() string { return fmt.Sprintf("no entry %q", e.seg) }
// withDefaultModules adds to err, where data fails to load because a segment names nothing, the // withMissingModules adds to err, where a path names nothing, each namespace of reads that
// modules the loaded sources' manifests read by default, or that they name none. // nothing in root provides, as the modules a loaded manifest reads and none loaded; or else
func withDefaultModules(err error, modules []string) error { // that no loaded manifest names a module to add, the mistake being the path's own.
func withMissingModules(err error, root *folder, reads []string) error {
var ne noEntry var ne noEntry
if !errors.Is(err, ErrLoad) || !errors.As(err, &ne) { if !errors.As(err, &ne) {
return err return err
} }
if len(modules) == 0 { var missing []string
for _, namespace := range reads {
segs := strings.Split(namespace, ".")
if _, i := folderAt(root, segs); i < len(segs) {
missing = append(missing, namespace)
}
}
if len(missing) == 0 {
return fmt.Errorf("%w, and no loaded manifest names a module to add", err) return fmt.Errorf("%w, and no loaded manifest names a module to add", err)
} }
return fmt.Errorf("%w; is a module missing? The loaded modules read %s", err, strings.Join(modules, ", ")) return fmt.Errorf("%w; is a module missing? The loaded modules read %s, which nothing loaded provides", err, strings.Join(missing, ", "))
} }
// noDataNote adds to err, where a segment names nothing and f holds no data at all, that no // noDataNote adds to err, where a segment names nothing and f holds no data at all, that no
+6 -5
View File
@@ -31,7 +31,7 @@ func (f *Generator) FakeStruct(v any) error {
defer f.mu.Unlock() defer f.mu.Unlock()
shape, err := f.structShapeOf(p.Elem().Type()) shape, err := f.structShapeOf(p.Elem().Type())
if err != nil { if err != nil {
return fmt.Errorf("fejkdata: %w", withDefaultModules(err, f.defaultModules)) return fmt.Errorf("fejkdata: %w", err)
} }
shape.fill(f.drawState, p.Elem()) shape.fill(f.drawState, p.Elem())
return nil return nil
@@ -55,7 +55,7 @@ func (f *Generator) structShapeOf(t reflect.Type) (*structShape, error) {
if label == "" { if label == "" {
label = "struct" label = "struct"
} }
sc := &structCompile{root: &f.root, visiting: map[reflect.Type]bool{}, structsLeft: maxStructs} sc := &structCompile{root: &f.root, reads: f.manifestReads, visiting: map[reflect.Type]bool{}, structsLeft: maxStructs}
shape, err := sc.compileShape(t, label) shape, err := sc.compileShape(t, label)
if f.structs == nil { if f.structs == nil {
f.structs = map[reflect.Type]structResult{} f.structs = map[reflect.Type]structResult{}
@@ -85,6 +85,7 @@ func (s *structShape) empty() bool { return s.record == nil && len(s.nested) ==
// rather than filled without end, and how many more structs it may walk. // rather than filled without end, and how many more structs it may walk.
type structCompile struct { type structCompile struct {
root *folder root *folder
reads []string
visiting map[reflect.Type]bool visiting map[reflect.Type]bool
structsLeft int structsLeft int
} }
@@ -111,7 +112,7 @@ func (sc *structCompile) compileShape(t reflect.Type, label string) (*structShap
return nil, err return nil, err
} }
if len(c.tags) > 0 { if len(c.tags) > 0 {
if err := c.shape.compileRecord(sc.root, t, label, c.tags); err != nil { if err := c.shape.compileRecord(sc.root, sc.reads, t, label, c.tags); err != nil {
return nil, err return nil, err
} }
} }
@@ -255,13 +256,13 @@ func checkTaggedType(sf reflect.StructField) error {
// compileRecord compiles the tagged fields of t as one record, and proves each column holds // compileRecord compiles the tagged fields of t as one record, and proves each column holds
// only what its field's Go type can. // only what its field's Go type can.
func (s *structShape) compileRecord(root *folder, t reflect.Type, label string, tags map[string]jsonvalue.Value) error { func (s *structShape) compileRecord(root *folder, reads []string, t reflect.Type, label string, tags map[string]jsonvalue.Value) error {
tags["format"] = jsonvalue.String("") tags["format"] = jsonvalue.String("")
n, err := compileFrom(jsonvalue.Object(tags), atGoTop) n, err := compileFrom(jsonvalue.Object(tags), atGoTop)
if err != nil { if err != nil {
return fmt.Errorf("%s: %w", label, err) return fmt.Errorf("%s: %w", label, err)
} }
if err := loadInline(root, n, label, true); err != nil { if err := loadInline(root, reads, n, label, true); err != nil {
return err return err
} }
record, err := recordOf(n) record, err := recordOf(n)