Refuse a calc over digits(0) or a repeat of 0, name an option's wrong kind as JSON does, type the name scope kinds, and compile only from a jsonvalue.Value
This commit is contained in:
@@ -996,6 +996,7 @@ internal/datatype/ the datatype a record column holds; the root's DataType is an
|
|||||||
internal/drawstate/ the seeded randomness and the {seq()} counters a generator draws through
|
internal/drawstate/ the seeded randomness and the {seq()} counters a generator draws through
|
||||||
internal/grammar/ how the template language is written: format tokens, paths and selectors, reference sigils, identifiers, calc syntax, and whether a string is a template or a path
|
internal/grammar/ how the template language is written: format tokens, paths and selectors, reference sigils, identifiers, calc syntax, and whether a string is a template or a path
|
||||||
internal/invariant/ the one phrase every package panics with when an invariant breaks
|
internal/invariant/ the one phrase every package panics with when an invariant breaks
|
||||||
|
internal/jsonvalue/ a decoded JSON value, its numbers kept as written, the one input a template compiles from
|
||||||
internal/proven/ what a proof knows of a value, and the bounds a calc takes from its operands
|
internal/proven/ what a proof knows of a value, and the bounds a calc takes from its operands
|
||||||
internal/rows/ a table's rows: the TSV, the options proved over it, the links between tables, row selection and draws, and the pin set one path or one named pick fixes
|
internal/rows/ a table's rows: the TSV, the options proved over it, the links between tables, row selection and draws, and the pin set one path or one named pick fixes
|
||||||
data/ shipped data (JSON, and a TSV per table), embedded at build: locale folders, geo, misc
|
data/ shipped data (JSON, and a TSV per table), embedded at build: locale folders, geo, misc
|
||||||
|
|||||||
@@ -114,14 +114,17 @@ func constantValue(n grammar.CalcNode, operand func(string) []node) (float64, bo
|
|||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// neverNumeric reports a node no render of which is a number: a null, fixed text that
|
// neverNumeric reports a node no render of which is a number: a null, a repeat of 0, fixed
|
||||||
// does not parse, or a choice of only such items. text is one such render.
|
// text that does not parse, or a choice of only such items. text is one such render.
|
||||||
func neverNumeric(n node) (text string, never bool) {
|
func neverNumeric(n node) (text string, never bool) {
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case *nullItem:
|
case *nullItem:
|
||||||
return "", true
|
return "", true
|
||||||
case *template:
|
case *template:
|
||||||
lit, fixed := n.fixedText()
|
lit, fixed := n.fixedText()
|
||||||
|
if n.repeat == 0 {
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
if !fixed || n.repeat != 1 {
|
if !fixed || n.repeat != 1 {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ func datatypeOf(m map[string]any, pos position) (*DataType, error) {
|
|||||||
}
|
}
|
||||||
name, ok := v.(string)
|
name, ok := v.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("datatype must be a string, got %T", v)
|
return nil, fmt.Errorf("datatype must be a string, not %s", jsonKind(v))
|
||||||
}
|
}
|
||||||
for d := DataTypeString; d < datatype.Count; d++ {
|
for d := DataTypeString; d < datatype.Count; d++ {
|
||||||
if name != d.String() {
|
if name != d.String() {
|
||||||
|
|||||||
+3
-3
@@ -7,8 +7,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"testing/fstest"
|
"testing/fstest"
|
||||||
|
|
||||||
"github.com/larvit/fejkdata/internal/datafiles"
|
|
||||||
"github.com/larvit/fejkdata/internal/drawstate"
|
"github.com/larvit/fejkdata/internal/drawstate"
|
||||||
|
"github.com/larvit/fejkdata/internal/jsonvalue"
|
||||||
)
|
)
|
||||||
|
|
||||||
// newGenerator creates a generator over a single data directory, failing on
|
// newGenerator creates a generator over a single data directory, failing on
|
||||||
@@ -102,9 +102,9 @@ func engine(seed uint64) *Generator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse unmarshals a JSON template fragment into its dynamic form.
|
// parse unmarshals a JSON template fragment into its dynamic form.
|
||||||
func parse(t *testing.T, s string) any {
|
func parse(t *testing.T, s string) jsonvalue.Value {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
v, err := datafiles.DecodeJSON([]byte(s))
|
v, err := jsonvalue.Decode([]byte(s))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("parse %q: %v", s, err)
|
t.Fatalf("parse %q: %v", s, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package fejkdata
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/larvit/fejkdata/internal/datafiles"
|
|
||||||
"github.com/larvit/fejkdata/internal/grammar"
|
"github.com/larvit/fejkdata/internal/grammar"
|
||||||
|
"github.com/larvit/fejkdata/internal/jsonvalue"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Template is an inline template compiled, referenced and validated against a
|
// Template is an inline template compiled, referenced and validated against a
|
||||||
@@ -70,10 +70,10 @@ func compileInput(input string) (node, error) {
|
|||||||
|
|
||||||
// inputValue reads an inline template as the value compile takes: the JSON value it holds, the
|
// inputValue reads an inline template as the value compile takes: the JSON value it holds, the
|
||||||
// whitespace around it dropped, or the input itself as a format string when it is not JSON.
|
// whitespace around it dropped, or the input itself as a format string when it is not JSON.
|
||||||
func inputValue(input string) any {
|
func inputValue(input string) jsonvalue.Value {
|
||||||
raw, err := datafiles.DecodeJSON([]byte(input))
|
raw, err := jsonvalue.Decode([]byte(input))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return input
|
return jsonvalue.String(input)
|
||||||
}
|
}
|
||||||
return raw
|
return raw
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ var builtins = withTransforms(map[string]builtin{
|
|||||||
"nanoid": {arity: 1, checkArgs: countArg, prep: chars(nanoidAlphabet)},
|
"nanoid": {arity: 1, checkArgs: countArg, prep: chars(nanoidAlphabet)},
|
||||||
"hex": {arity: 1, checkArgs: countArg, prep: chars(hexDigits)},
|
"hex": {arity: 1, checkArgs: countArg, prep: chars(hexDigits)},
|
||||||
"digits": {arity: 1, checkArgs: countArg, prep: chars("0123456789"), prints: datatype.String, proveNumber: func(token string, prints datatype.DataType, a []string) proven.Facts {
|
"digits": {arity: 1, checkArgs: countArg, prep: chars("0123456789"), prints: datatype.String, proveNumber: func(token string, prints datatype.DataType, a []string) proven.Facts {
|
||||||
|
if atoi(a[0]) == 0 {
|
||||||
|
return proven.Unproven("{" + token + "} prints nothing, which is no number")
|
||||||
|
}
|
||||||
return proven.Printing(token, prints, proven.Bounded(0, math.Pow(10, float64(atoi(a[0])))-1, true))
|
return proven.Printing(token, prints, proven.Bounded(0, math.Pow(10, float64(atoi(a[0])))-1, true))
|
||||||
}},
|
}},
|
||||||
"upper": {arity: 1, checkArgs: countArg, prep: chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ")},
|
"upper": {arity: 1, checkArgs: countArg, prep: chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ")},
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
package datafiles
|
package datafiles
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
@@ -11,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/larvit/fejkdata/internal/grammar"
|
"github.com/larvit/fejkdata/internal/grammar"
|
||||||
|
"github.com/larvit/fejkdata/internal/jsonvalue"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Source is one tree to load: an fs.FS and the directory in it to start from. label
|
// Source is one tree to load: an fs.FS and the directory in it to start from. label
|
||||||
@@ -35,7 +34,7 @@ func FS(fsys fs.FS, base string) Source { return Source{fsys: fsys, base: base}
|
|||||||
type Category struct {
|
type Category struct {
|
||||||
Folders []string
|
Folders []string
|
||||||
Name string
|
Name string
|
||||||
JSON any
|
JSON jsonvalue.Value
|
||||||
rows *rowsFiles
|
rows *rowsFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +141,7 @@ func (s Source) compileFile(dir []string, file string, rows *rowsFiles, compile
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("%s: %w", s.labelled(full), err)
|
return false, fmt.Errorf("%s: %w", s.labelled(full), err)
|
||||||
}
|
}
|
||||||
raw, err := DecodeJSON(b)
|
raw, err := jsonvalue.Decode(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("%s: %w", s.labelled(full), err)
|
return false, fmt.Errorf("%s: %w", s.labelled(full), err)
|
||||||
}
|
}
|
||||||
@@ -179,14 +178,3 @@ func (r *rowsFiles) read(name string) (string, error) {
|
|||||||
}
|
}
|
||||||
return string(b), nil
|
return string(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecodeJSON decodes one JSON value, keeping each number as the json.Number it is written as.
|
|
||||||
func DecodeJSON(b []byte) (any, error) {
|
|
||||||
var v any
|
|
||||||
if !json.Valid(b) {
|
|
||||||
return nil, json.Unmarshal(b, &v)
|
|
||||||
}
|
|
||||||
dec := json.NewDecoder(bytes.NewReader(b))
|
|
||||||
dec.UseNumber()
|
|
||||||
return v, dec.Decode(&v)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func walk(t *testing.T, src Source) ([]handed, error) {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
var got []handed
|
var got []handed
|
||||||
err := src.Walk(func(c Category) error {
|
err := src.Walk(func(c Category) error {
|
||||||
got = append(got, handed{c.Folders, c.Name, c.JSON})
|
got = append(got, handed{c.Folders, c.Name, c.JSON.Any()})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return got, err
|
return got, err
|
||||||
@@ -133,7 +133,7 @@ func TestLoadHandsOneCategoryAndItsRows(t *testing.T) {
|
|||||||
var got handed
|
var got handed
|
||||||
var rows string
|
var rows string
|
||||||
err := src.Load([]string{"sub"}, "t", func(c Category) error {
|
err := src.Load([]string{"sub"}, "t", func(c Category) error {
|
||||||
got = handed{c.Folders, c.Name, c.JSON}
|
got = handed{c.Folders, c.Name, c.JSON.Any()}
|
||||||
var err error
|
var err error
|
||||||
rows, err = c.ReadRows("t.tsv")
|
rows, err = c.ReadRows("t.tsv")
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// Package jsonvalue holds a decoded JSON value whose numbers keep the text they are written in.
|
||||||
|
package jsonvalue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Value is a JSON value that only Decode, String and Object make, so a template never compiles
|
||||||
|
// from a value encoding/json decoded with its numbers as float64.
|
||||||
|
type Value struct{ v any }
|
||||||
|
|
||||||
|
// Decode decodes one JSON value, keeping each number as the json.Number it is written as.
|
||||||
|
func Decode(b []byte) (Value, error) {
|
||||||
|
var v any
|
||||||
|
if !json.Valid(b) {
|
||||||
|
return Value{}, json.Unmarshal(b, &v)
|
||||||
|
}
|
||||||
|
dec := json.NewDecoder(bytes.NewReader(b))
|
||||||
|
dec.UseNumber()
|
||||||
|
err := dec.Decode(&v)
|
||||||
|
return Value{v}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is the JSON string s.
|
||||||
|
func String(s string) Value { return Value{s} }
|
||||||
|
|
||||||
|
// Object is the JSON object holding fields.
|
||||||
|
func Object(fields map[string]Value) Value {
|
||||||
|
m := make(map[string]any, len(fields))
|
||||||
|
for k, f := range fields {
|
||||||
|
m[k] = f.v
|
||||||
|
}
|
||||||
|
return Value{m}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any is the value as encoding/json decodes it: a map[string]any, []any, string, json.Number,
|
||||||
|
// bool or nil.
|
||||||
|
func (v Value) Any() any { return v.v }
|
||||||
@@ -111,22 +111,37 @@ func (sc *nameScope) bindAll(t *template, where string) error {
|
|||||||
|
|
||||||
// spelled names the scope in an error.
|
// spelled names the scope in an error.
|
||||||
func (sc *nameScope) spelled() string {
|
func (sc *nameScope) spelled() string {
|
||||||
if sc.up == nil && sc.kind() != "repeat" {
|
if k := sc.kind(); k != scopeCategory {
|
||||||
return "outside any repeat"
|
return "in one " + k.String()
|
||||||
}
|
}
|
||||||
return "in one " + sc.kind()
|
return "outside any repeat"
|
||||||
}
|
}
|
||||||
|
|
||||||
// kind names what a scope inside the category's is: a repeat, or a choice's item.
|
// scopeKind is what a name scope is: the category's own, a repeat's, or a choice's item's.
|
||||||
func (sc *nameScope) kind() string {
|
type scopeKind uint8
|
||||||
if t, isTemplate := sc.owner.(*template); isTemplate && t.repeat > 1 {
|
|
||||||
return "repeat"
|
const (
|
||||||
}
|
scopeCategory scopeKind = iota
|
||||||
return "choice item"
|
scopeRepeat
|
||||||
|
scopeChoiceItem
|
||||||
|
)
|
||||||
|
|
||||||
|
func (k scopeKind) String() string {
|
||||||
|
return [...]string{scopeCategory: "category", scopeRepeat: "repeat", scopeChoiceItem: "choice item"}[k]
|
||||||
}
|
}
|
||||||
|
|
||||||
// ownScope is the scope t renders a frame of: a category's, once per render of its root, or a
|
func (sc *nameScope) kind() scopeKind {
|
||||||
// repeat's, once per iteration. It is nil where t owns no scope, or its scope binds no name.
|
switch t, isTemplate := sc.owner.(*template); {
|
||||||
|
case isTemplate && t.repeat > 1:
|
||||||
|
return scopeRepeat
|
||||||
|
case sc.up == nil:
|
||||||
|
return scopeCategory
|
||||||
|
}
|
||||||
|
return scopeChoiceItem
|
||||||
|
}
|
||||||
|
|
||||||
|
// ownScope is the scope t renders a frame of: a category's, once per render of its root, a
|
||||||
|
// repeat's, once per iteration, or a choice item's, once per draw of the item. It is nil where t owns no scope, or its scope binds no name.
|
||||||
func (t *template) ownScope() *nameScope {
|
func (t *template) ownScope() *nameScope {
|
||||||
if sc := t.nameScope; sc != nil && sc.owner == node(t) && len(sc.order) > 0 {
|
if sc := t.nameScope; sc != nil && sc.owner == node(t) && len(sc.order) > 0 {
|
||||||
return sc
|
return sc
|
||||||
@@ -190,7 +205,7 @@ func unanswered(where string, u unboundRead, seen *nameScope, scopes []*nameScop
|
|||||||
if at == "" {
|
if at == "" {
|
||||||
at = "an item of the root choice"
|
at = "an item of the root choice"
|
||||||
}
|
}
|
||||||
if sc.kind() == "choice item" {
|
if sc.kind() == scopeChoiceItem {
|
||||||
return fmt.Errorf("%s%w; name %q is bound at %s, inside an item of a choice, which a read outside that item cannot see; bind it outside the choice", where, u.err, u.head, at)
|
return fmt.Errorf("%s%w; name %q is bound at %s, inside an item of a choice, which a read outside that item cannot see; bind it outside the choice", where, u.err, u.head, at)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%s%w; name %q is bound at %s, inside a repeat, which a read outside the repeat cannot see; bind it outside the repeat to read one pick on every line", where, u.err, u.head, at)
|
return fmt.Errorf("%s%w; name %q is bound at %s, inside a repeat, which a read outside the repeat cannot see; bind it outside the repeat to read one pick on every line", where, u.err, u.head, at)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/larvit/fejkdata/internal/datafiles"
|
"github.com/larvit/fejkdata/internal/datafiles"
|
||||||
"github.com/larvit/fejkdata/internal/grammar"
|
"github.com/larvit/fejkdata/internal/grammar"
|
||||||
"github.com/larvit/fejkdata/internal/invariant"
|
"github.com/larvit/fejkdata/internal/invariant"
|
||||||
|
"github.com/larvit/fejkdata/internal/jsonvalue"
|
||||||
)
|
)
|
||||||
|
|
||||||
// node is a compiled element of the namespace tree: a folder, choice, null,
|
// node is a compiled element of the namespace tree: a folder, choice, null,
|
||||||
@@ -89,8 +90,8 @@ func (t *template) startOf(name string) node {
|
|||||||
|
|
||||||
// compile converts parsed JSON — a category or an inline template — into a node tree,
|
// compile converts parsed JSON — a category or an inline template — into a node tree,
|
||||||
// validating structure up front.
|
// validating structure up front.
|
||||||
func compile(v any) (node, error) {
|
func compile(v jsonvalue.Value) (node, error) {
|
||||||
n, err := compileItem(v, atTop)
|
n, err := compileItem(v.Any(), atTop)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -100,7 +101,7 @@ func compile(v any) (node, error) {
|
|||||||
// compileCategory compiles a data file's value, which may be a table over a rows
|
// compileCategory compiles a data file's value, which may be a table over a rows
|
||||||
// file beside it.
|
// file beside it.
|
||||||
func compileCategory(c datafiles.Category) (node, error) {
|
func compileCategory(c datafiles.Category) (node, error) {
|
||||||
if m, ok := c.JSON.(map[string]any); ok {
|
if m, ok := c.JSON.Any().(map[string]any); ok {
|
||||||
if _, isTable := m["rows"]; isTable {
|
if _, isTable := m["rows"]; isTable {
|
||||||
return compileTable(m, c.Folders, c.Name, c.ReadRows)
|
return compileTable(m, c.Folders, c.Name, c.ReadRows)
|
||||||
}
|
}
|
||||||
@@ -276,7 +277,7 @@ func readOptions(m map[string]any, pos position) (templateOptions, error) {
|
|||||||
}
|
}
|
||||||
if sv, ok := m["separator"]; ok {
|
if sv, ok := m["separator"]; ok {
|
||||||
if o.separator, ok = sv.(string); !ok {
|
if o.separator, ok = sv.(string); !ok {
|
||||||
return o, fmt.Errorf("separator must be a string, got %T", sv)
|
return o, fmt.Errorf("separator must be a string, not %s", jsonKind(sv))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return o, nil
|
return o, nil
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/larvit/fejkdata/internal/drawstate"
|
"github.com/larvit/fejkdata/internal/drawstate"
|
||||||
"github.com/larvit/fejkdata/internal/grammar"
|
"github.com/larvit/fejkdata/internal/grammar"
|
||||||
"github.com/larvit/fejkdata/internal/invariant"
|
"github.com/larvit/fejkdata/internal/invariant"
|
||||||
|
"github.com/larvit/fejkdata/internal/jsonvalue"
|
||||||
"github.com/larvit/fejkdata/internal/proven"
|
"github.com/larvit/fejkdata/internal/proven"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -95,7 +96,7 @@ type structFields struct {
|
|||||||
*structCompile
|
*structCompile
|
||||||
typ reflect.Type
|
typ reflect.Type
|
||||||
label string
|
label string
|
||||||
tags map[string]any
|
tags map[string]jsonvalue.Value
|
||||||
shape *structShape
|
shape *structShape
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +106,7 @@ func (sc *structCompile) compileShape(t reflect.Type, label string) (*structShap
|
|||||||
}
|
}
|
||||||
sc.visiting[t] = true
|
sc.visiting[t] = true
|
||||||
defer delete(sc.visiting, t)
|
defer delete(sc.visiting, t)
|
||||||
c := &structFields{structCompile: sc, typ: t, label: label, tags: map[string]any{}, shape: &structShape{}}
|
c := &structFields{structCompile: sc, typ: t, label: label, tags: map[string]jsonvalue.Value{}, shape: &structShape{}}
|
||||||
if err := c.gatherFields(t, nil); err != nil {
|
if err := c.gatherFields(t, nil); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -214,25 +215,25 @@ func (c *structFields) nest(sf reflect.StructField, elem reflect.Type) error {
|
|||||||
|
|
||||||
// tagValue reads a field's fake tag as the value its column compiles from: an inline template
|
// tagValue reads a field's fake tag as the value its column compiles from: an inline template
|
||||||
// as written, or a path as the reference {/path}.
|
// as written, or a path as the reference {/path}.
|
||||||
func tagValue(sf reflect.StructField, tag string) (any, error) {
|
func tagValue(sf reflect.StructField, tag string) (jsonvalue.Value, error) {
|
||||||
if err := checkTaggedType(sf); err != nil {
|
if err := checkTaggedType(sf); err != nil {
|
||||||
return nil, err
|
return jsonvalue.Value{}, err
|
||||||
}
|
}
|
||||||
inline, err := grammar.IsTemplate(tag)
|
inline, err := grammar.IsTemplate(tag)
|
||||||
switch {
|
switch {
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return nil, err
|
return jsonvalue.Value{}, err
|
||||||
case inline:
|
case inline:
|
||||||
return inputValue(tag), nil
|
return inputValue(tag), nil
|
||||||
}
|
}
|
||||||
path, err := grammar.CallerPath(tag)
|
path, err := grammar.CallerPath(tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return jsonvalue.Value{}, err
|
||||||
}
|
}
|
||||||
if err := grammar.CheckPathIdentifiers(path); err != nil {
|
if err := grammar.CheckPathIdentifiers(path); err != nil {
|
||||||
return nil, err
|
return jsonvalue.Value{}, err
|
||||||
}
|
}
|
||||||
return "{/" + path + "}", nil
|
return jsonvalue.String("{/" + path + "}"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkTaggedType rejects a tagged field no column can fill.
|
// checkTaggedType rejects a tagged field no column can fill.
|
||||||
@@ -254,9 +255,9 @@ 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]any) error {
|
func (s *structShape) compileRecord(root *folder, t reflect.Type, label string, tags map[string]jsonvalue.Value) error {
|
||||||
tags["format"] = ""
|
tags["format"] = jsonvalue.String("")
|
||||||
n, err := compile(tags)
|
n, err := compile(jsonvalue.Object(tags))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %w", label, err)
|
return fmt.Errorf("%s: %w", label, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ func readTableOptions(m map[string]any) (tableOptionValues, error) {
|
|||||||
}
|
}
|
||||||
s, isString := v.(string)
|
s, isString := v.(string)
|
||||||
if !isString {
|
if !isString {
|
||||||
return o, fmt.Errorf("%s must be a string, got %T", k, v)
|
return o, fmt.Errorf("%s must be a string, not %s", k, jsonKind(v))
|
||||||
}
|
}
|
||||||
if k != "format" && s == "" {
|
if k != "format" && s == "" {
|
||||||
return o, fmt.Errorf("%s names a column, so it cannot be empty; drop it", k)
|
return o, fmt.Errorf("%s names a column, so it cannot be empty; drop it", k)
|
||||||
|
|||||||
Reference in New Issue
Block a user