Load a default written out in every position, refuse null outside a column again, read a field once per alternation, refuse an empty segment in the CLI's lone reference, and hold a declared datatype as one value
This commit is contained in:
@@ -120,8 +120,8 @@ echo '{/users}' | fejkdata --seed 1 -d ./mydata --format sql --table people # IN
|
|||||||
|
|
||||||
`--repeat` streams that many records — `json` frames them as one array document,
|
`--repeat` streams that many records — `json` frames them as one array document,
|
||||||
`ndjson` writes one object per line, `csv` a row after a header, `sql` one INSERT
|
`ndjson` writes one object per line, `csv` a row after a header, `sql` one INSERT
|
||||||
per line. Only a category-level template is a record; a field, choice or folder
|
per line. Only a category-level template is a record, or a one-item choice of one; a
|
||||||
errors, and so does a `repeat` on
|
field, a choice of more items or a folder errors, and so does a `repeat` on
|
||||||
the template itself, which composes the format into one string rather than
|
the template itself, which composes the format into one string rather than
|
||||||
projecting columns — ask for more records with `--repeat`. A `repeat` on a column
|
projecting columns — ask for more records with `--repeat`. A `repeat` on a column
|
||||||
is fine.
|
is fine.
|
||||||
@@ -342,7 +342,8 @@ A `*Record` carries its columns via `Columns()` — each a `Column` of `Name`,
|
|||||||
`DataType`, rendered `Value` and `Null` — and serializes them with `JSON()`
|
`DataType`, rendered `Value` and `Null` — and serializes them with `JSON()`
|
||||||
(one object), `CSVHeader()`/`CSVLine()`, or `SQLInsert(table)` — the shapes the
|
(one object), `CSVHeader()`/`CSVLine()`, or `SQLInsert(table)` — the shapes the
|
||||||
CLI's `--format` writes. `FakeRecord` and `FakeRecordTemplate` take a record; a
|
CLI's `--format` writes. `FakeRecord` and `FakeRecordTemplate` take a record; a
|
||||||
path or template that is not one — a bare string, a choice, or a folder — errors.
|
path or template that is not one — a bare string, a choice of more than one item, or a
|
||||||
|
folder — errors.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type User struct {
|
type User struct {
|
||||||
@@ -369,7 +370,8 @@ its own. `fake:"-"` leaves a field unfilled.
|
|||||||
Untagged fields keep their values, and so does a pointer back to a struct already being filled; a type
|
Untagged fields keep their values, and so does a pointer back to a struct already being filled; a type
|
||||||
whose fields reach more than 1024 structs is refused, naming `fake:"-"` to cut it. The
|
whose fields reach more than 1024 structs is refused, naming `fake:"-"` to cut it. The
|
||||||
first call for a type compiles its tags and reports what they get wrong, with the same
|
first call for a type compiles its tags and reports what they get wrong, with the same
|
||||||
error on every later call; a `datatype` in a tag names the Go type that already sets it.
|
error on every later call; a `datatype` in a tag other than the one its Go type sets is
|
||||||
|
refused, naming that type.
|
||||||
|
|
||||||
A `*Generator` is safe for concurrent use; a seeded sequence is reproducible only
|
A `*Generator` is safe for concurrent use; a seeded sequence is reproducible only
|
||||||
when drawn from one goroutine. Changing how a value is composed shifts the seeded
|
when drawn from one goroutine. Changing how a value is composed shifts the seeded
|
||||||
@@ -427,8 +429,8 @@ An item of a choice may carry a `weight` (default `1`) to skew its odds:
|
|||||||
Renders `070-412 38 91` ten times as often as `08-…`. A string item is weighted by
|
Renders `070-412 38 91` ten times as often as `08-…`. A string item is weighted by
|
||||||
writing it as `{ "format": "AB", "weight": 3 }`. A repeated item counts as written,
|
writing it as `{ "format": "AB", "weight": 3 }`. A repeated item counts as written,
|
||||||
so `["a", "a", "b"]` draws `a` two times in three. Rejected at load: a weight that is
|
so `["a", "a", "b"]` draws `a` two times in three. Rejected at load: a weight that is
|
||||||
negative, non-numeric or `0` (never drawn — remove the item), and one where it does
|
negative, non-numeric or `0` (never drawn — remove the item), and one other than `1`
|
||||||
nothing: outside a choice, or on the item of a one-item choice.
|
where nothing is drawn against it: outside a choice, or on the item of a one-item choice.
|
||||||
|
|
||||||
### Repeat
|
### Repeat
|
||||||
|
|
||||||
@@ -439,8 +441,8 @@ that many times — each an independent draw — joined by `separator` (default
|
|||||||
{ "format": "{word}", "repeat": 3, "separator": " ", "word": ["foo", "bar", "baz"] }
|
{ "format": "{word}", "repeat": 3, "separator": " ", "word": ["foo", "bar", "baz"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
Renders e.g. `bar foo baz`. Rejected at load: a `separator` without a `repeat` above
|
Renders e.g. `bar foo baz`. Rejected at load: a `separator` other than `""` without a
|
||||||
`1`, and a `repeat` that multiplies to more than 1 048 576 renders along any path of
|
`repeat` above `1`, and a `repeat` that multiplies to more than 1 048 576 renders along any path of
|
||||||
nested repeats.
|
nested repeats.
|
||||||
|
|
||||||
### Datatype
|
### Datatype
|
||||||
@@ -495,9 +497,9 @@ renders a null as `""`. The other items' weights skew its odds:
|
|||||||
{ "format": "", "deleted_at": null, "middle": [null, { "format": "{n}", "n": ["Ann", "Eva"], "weight": 3 }] }
|
{ "format": "", "deleted_at": null, "middle": [null, { "format": "{n}", "n": ["Ann", "Eva"], "weight": 3 }] }
|
||||||
```
|
```
|
||||||
|
|
||||||
`deleted_at` is null every draw, `middle` a name three draws in four. Anywhere but a
|
`deleted_at` is null every draw, `middle` a name three draws in four. Rejected at
|
||||||
column, `null` is `""`. Rejected at load: a column whose items hold different
|
load: `null` anywhere but a column, where it only renders `""`, naming `""`, and a
|
||||||
datatypes.
|
column whose items hold different datatypes.
|
||||||
|
|
||||||
### Table
|
### Table
|
||||||
|
|
||||||
@@ -629,8 +631,10 @@ namespace too.
|
|||||||
|
|
||||||
A `{name(args)}` token calls a builtin. Arguments are checked at `New`: a bad
|
A `{name(args)}` token calls a builtin. Arguments are checked at `New`: a bad
|
||||||
count, range, country or expression fails fast; an integer may carry a sign or leading
|
count, range, country or expression fails fast; an integer may carry a sign or leading
|
||||||
zeros (`+5` and `05` are `5`); bounds are finite; a sample that could only ever emit one
|
zeros (`+5` and `05` are `5`); bounds are finite; a constant sample — `int` or `float`
|
||||||
value (`int(5,5)`, `float(1,1,2)`) is rejected naming the text to write instead;
|
with min equal to max, `date` with from equal to to and a layout with no clock field,
|
||||||
|
and a `date` or `time` layout naming no field — is rejected naming the text to write
|
||||||
|
instead;
|
||||||
and a length, count or decimal place beyond a sane maximum is rejected, so a
|
and a length, count or decimal place beyond a sane maximum is rejected, so a
|
||||||
fat-fingered `hex(2000000000)` never tries to allocate gigabytes. Every builtin draws only from the seed — a
|
fat-fingered `hex(2000000000)` never tries to allocate gigabytes. Every builtin draws only from the seed — a
|
||||||
time-based id takes its timestamp from the rng, not the clock — so seeded output
|
time-based id takes its timestamp from the rng, not the clock — so seeded output
|
||||||
|
|||||||
+30
-11
@@ -291,12 +291,15 @@ func operandError(arg string) error {
|
|||||||
if fi, err := os.Stat(arg); err == nil && fi.Mode().IsRegular() {
|
if fi, err := os.Stat(arg); err == nil && fi.Mode().IsRegular() {
|
||||||
return fmt.Errorf("%s; read a file from stdin: fejkdata < %s", unexpected, shellQuoted(arg))
|
return fmt.Errorf("%s; read a file from stdin: fejkdata < %s", unexpected, shellQuoted(arg))
|
||||||
}
|
}
|
||||||
example, ref := pipeHint, "{/"+strings.TrimPrefix(arg, "/")+"}"
|
ref := "{/" + strings.TrimPrefix(arg, "/") + "}"
|
||||||
if _, lone := loneReference(arg); lone {
|
_, argLone := loneReference(arg)
|
||||||
|
inline, err := fejkdata.IsTemplate(arg)
|
||||||
|
_, refLone := loneReference(ref)
|
||||||
|
example := pipeHint
|
||||||
|
switch {
|
||||||
|
case argLone || inline:
|
||||||
example = piped(arg)
|
example = piped(arg)
|
||||||
} else if inline, err := fejkdata.IsTemplate(arg); inline {
|
case err == nil && refLone:
|
||||||
example = piped(arg)
|
|
||||||
} else if _, lone := loneReference(ref); lone && err == nil {
|
|
||||||
example = piped(ref)
|
example = piped(ref)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%s; the template is read from stdin: %s", unexpected, example)
|
return fmt.Errorf("%s; the template is read from stdin: %s", unexpected, example)
|
||||||
@@ -453,11 +456,8 @@ type input struct {
|
|||||||
// allowed.
|
// allowed.
|
||||||
func parseInput(raw string) (input, error) {
|
func parseInput(raw string) (input, error) {
|
||||||
trimmed := strings.Trim(raw, jsonSpace)
|
trimmed := strings.Trim(raw, jsonSpace)
|
||||||
switch trimmed {
|
if trimmed == "" {
|
||||||
case "":
|
|
||||||
return input{}, fmt.Errorf("stdin holds no template: %s", pipeHint)
|
return input{}, fmt.Errorf("stdin holds no template: %s", pipeHint)
|
||||||
case "null":
|
|
||||||
return input{}, fmt.Errorf("stdin holds only null, as jq prints for a missing key: %s", pipeHint)
|
|
||||||
}
|
}
|
||||||
text := raw
|
text := raw
|
||||||
if json.Valid([]byte(trimmed)) {
|
if json.Valid([]byte(trimmed)) {
|
||||||
@@ -487,20 +487,39 @@ func loneReference(text string) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// isPath reports whether a reference's body after its / is a path alone: no second /, no
|
// isPath reports whether a reference's body after its / is a path alone: no second /, no
|
||||||
// folder sigil, and none of the |, ( or " as " a token reads outside a selector.
|
// folder sigil, no empty segment, and none of the |, ( or " as " a token reads outside a selector.
|
||||||
// It copies the grammar, since the CLI reads only the library's public API.
|
// It copies the grammar, since the CLI reads only the library's public API.
|
||||||
func isPath(body string) bool {
|
func isPath(body string) bool {
|
||||||
if body == "" || strings.ContainsAny(body[:1], "/.") {
|
if body == "" || strings.ContainsAny(body[:1], "/.") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
names := withoutSelectors(body)
|
names := withoutSelectors(body)
|
||||||
if strings.ContainsAny(names, "|(") || strings.Contains(names, " as ") {
|
if strings.ContainsAny(names, "|(") || strings.Contains(names, " as ") || hasEmptySegment(names) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
inline, err := fejkdata.IsTemplate(body)
|
inline, err := fejkdata.IsTemplate(body)
|
||||||
return !inline && err == nil
|
return !inline && err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hasEmptySegment reports whether a dotted path holds a segment with no name: two dots are a
|
||||||
|
// step up, and only a step up may end the path.
|
||||||
|
func hasEmptySegment(path string) bool {
|
||||||
|
segments := strings.Split(path, ".")
|
||||||
|
stepUp := false
|
||||||
|
for i, seg := range segments[1:] {
|
||||||
|
last := i == len(segments)-2
|
||||||
|
switch {
|
||||||
|
case seg != "":
|
||||||
|
stepUp = false
|
||||||
|
case stepUp && !last, !stepUp && last:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
stepUp = !stepUp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// spacedReference adds to a record refusal that whitespace around a lone reference made
|
// spacedReference adds to a record refusal that whitespace around a lone reference made
|
||||||
// the template text.
|
// the template text.
|
||||||
func spacedReference(template string, err error) error {
|
func spacedReference(template string, err error) error {
|
||||||
|
|||||||
+15
-15
@@ -20,29 +20,26 @@ const (
|
|||||||
DataTypeBoolean DataType = datatype.Boolean
|
DataTypeBoolean DataType = datatype.Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
// datatypeOf reads a template's "datatype" (default DataTypeString).
|
// datatypeOf reads a template's "datatype", nil where it carries none.
|
||||||
func datatypeOf(m map[string]any, pos position) (DataType, error) {
|
func datatypeOf(m map[string]any, pos position) (*DataType, error) {
|
||||||
v, ok := m["datatype"]
|
v, ok := m["datatype"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return DataTypeString, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
name, ok := v.(string)
|
name, ok := v.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, fmt.Errorf("datatype must be a string, got %T", v)
|
return nil, fmt.Errorf("datatype must be a string, got %T", v)
|
||||||
}
|
}
|
||||||
if name == DataTypeString.String() {
|
for d := DataTypeString; d < datatype.Count; d++ {
|
||||||
return DataTypeString, nil
|
|
||||||
}
|
|
||||||
for d := DataTypeInteger; d < datatype.Count; d++ {
|
|
||||||
if name != d.String() {
|
if name != d.String() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if pos != inColumn {
|
if pos != inColumn && d != DataTypeString {
|
||||||
return 0, errors.New("datatype only types a record column — a field of the top-level template — so it has no effect here")
|
return nil, errors.New(`datatype only types a record column — a field of the top-level template — so it has no effect here; drop "datatype"`)
|
||||||
}
|
}
|
||||||
return d, nil
|
return &d, nil
|
||||||
}
|
}
|
||||||
return 0, fmt.Errorf(`datatype takes "string", "integer", "number" or "boolean", got %q`, name)
|
return nil, fmt.Errorf(`datatype takes "string", "integer", "number" or "boolean", got %q`, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkColumns rejects a record column whose items hold different datatypes, checking a column
|
// checkColumns rejects a record column whose items hold different datatypes, checking a column
|
||||||
@@ -100,10 +97,13 @@ func columnDatatype(n node) DataType {
|
|||||||
|
|
||||||
// itemDatatype is the datatype a column item declares, else that of the column it reads.
|
// itemDatatype is the datatype a column item declares, else that of the column it reads.
|
||||||
func itemDatatype(t *template) DataType {
|
func itemDatatype(t *template) DataType {
|
||||||
if !t.declared && t.readsColumn != nil {
|
switch {
|
||||||
|
case t.datatype != nil:
|
||||||
|
return *t.datatype
|
||||||
|
case t.readsColumn != nil:
|
||||||
return columnDatatype(t.readsColumn.column)
|
return columnDatatype(t.readsColumn.column)
|
||||||
}
|
}
|
||||||
return t.datatype
|
return DataTypeString
|
||||||
}
|
}
|
||||||
|
|
||||||
// columnItems is a column's template items, its choices unwrapped, and whether one is null.
|
// columnItems is a column's template items, its choices unwrapped, and whether one is null.
|
||||||
@@ -139,7 +139,7 @@ const (
|
|||||||
|
|
||||||
func kindOf(t *template) itemKind {
|
func kindOf(t *template) itemKind {
|
||||||
switch {
|
switch {
|
||||||
case t.declared:
|
case t.datatype != nil:
|
||||||
return kindDeclares
|
return kindDeclares
|
||||||
case itemDatatype(t) != DataTypeString:
|
case itemDatatype(t) != DataTypeString:
|
||||||
return kindReads
|
return kindReads
|
||||||
|
|||||||
+5
-1
@@ -77,6 +77,7 @@ func refuseTwiceDrawnIn(addressed map[pickKey]string, read string, t *template,
|
|||||||
reads := map[string]int{}
|
reads := map[string]int{}
|
||||||
var into []arm
|
var into []arm
|
||||||
for _, o := range t.compiled.ops {
|
for _, o := range t.compiled.ops {
|
||||||
|
heads := map[string]bool{} // one op draws once, whichever arm it takes
|
||||||
for _, a := range slices.Concat(o.arms, o.operands) {
|
for _, a := range slices.Concat(o.arms, o.operands) {
|
||||||
if grammar.IsRef(a.head) || a.kind == namedRead {
|
if grammar.IsRef(a.head) || a.kind == namedRead {
|
||||||
continue
|
continue
|
||||||
@@ -84,7 +85,10 @@ func refuseTwiceDrawnIn(addressed map[pickKey]string, read string, t *template,
|
|||||||
if _, kept := addressed[key.under(a.key())]; kept {
|
if _, kept := addressed[key.under(a.key())]; kept {
|
||||||
into = append(into, a)
|
into = append(into, a)
|
||||||
}
|
}
|
||||||
reads[a.head]++
|
heads[a.head] = true
|
||||||
|
}
|
||||||
|
for head := range heads {
|
||||||
|
reads[head]++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, head := range sortedNames(reads) {
|
for _, head := range sortedNames(reads) {
|
||||||
|
|||||||
@@ -51,8 +51,7 @@ type template struct {
|
|||||||
fields map[string]node
|
fields map[string]node
|
||||||
repeat int
|
repeat int
|
||||||
separator string
|
separator string
|
||||||
datatype DataType
|
datatype *DataType // the "datatype" it carries, which wins over that of a column it reads; nil where none
|
||||||
declared bool // carries a "datatype", which wins over the datatype of a column it reads
|
|
||||||
fromString bool // written as a JSON string rather than an object
|
fromString bool // written as a JSON string rather than an object
|
||||||
isRecord bool // compiled at the top without a repeat, so its fields are record columns
|
isRecord bool // compiled at the top without a repeat, so its fields are record columns
|
||||||
unbound []unboundRead // the heads its tokens read that no field holds
|
unbound []unboundRead // the heads its tokens read that no field holds
|
||||||
@@ -116,13 +115,15 @@ const (
|
|||||||
inColumn // a column, or a choice item standing in for one
|
inColumn // a column, or a choice item standing in for one
|
||||||
)
|
)
|
||||||
|
|
||||||
// compileAt compiles a node that is no choice's item. Only a choice's items carry a
|
// compileAt compiles a node drawn against no other: no choice's item, or a one-item choice's.
|
||||||
// weight, so one here would be inert whatever its type.
|
// A weight there does nothing, bar the default 1 written out.
|
||||||
func compileAt(v any, pos position) (node, error) {
|
func compileAt(v any, pos position) (node, error) {
|
||||||
if m, ok := v.(map[string]any); ok {
|
w, err := weightOf(v)
|
||||||
if _, weighted := m["weight"]; weighted {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("weight only skews a choice's items, so it has no effect here; it is an option and can never be a field")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if w != 1 {
|
||||||
|
return nil, fmt.Errorf(`weight %v skews a draw between items, and nothing here is drawn against this one; drop "weight"`, w)
|
||||||
}
|
}
|
||||||
return compileItem(v, pos)
|
return compileItem(v, pos)
|
||||||
}
|
}
|
||||||
@@ -142,7 +143,7 @@ func compileItem(v any, pos position) (node, error) {
|
|||||||
return compileTemplate(v, pos)
|
return compileTemplate(v, pos)
|
||||||
case nil:
|
case nil:
|
||||||
if pos != inColumn {
|
if pos != inColumn {
|
||||||
return compileString("")
|
return nil, fmt.Errorf(`null is a record column's value, and here it only renders "", so write ""`)
|
||||||
}
|
}
|
||||||
return &nullItem{}, nil
|
return &nullItem{}, nil
|
||||||
default:
|
default:
|
||||||
@@ -158,6 +159,12 @@ func jsonKind(v any) string {
|
|||||||
return "a number"
|
return "a number"
|
||||||
case bool:
|
case bool:
|
||||||
return "a boolean"
|
return "a boolean"
|
||||||
|
case string:
|
||||||
|
return "a string"
|
||||||
|
case []any:
|
||||||
|
return "a list"
|
||||||
|
case map[string]any:
|
||||||
|
return "an object"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%T", v)
|
return fmt.Sprintf("%T", v)
|
||||||
}
|
}
|
||||||
@@ -242,13 +249,12 @@ func compileTemplate(m map[string]any, pos position) (node, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &template{format: o.format, tokens: toks, fields: fields, repeat: o.repeat, separator: o.separator, datatype: o.datatype, declared: o.declared, isRecord: fieldPos == inColumn, unbound: unbound}, nil
|
return &template{format: o.format, tokens: toks, fields: fields, repeat: o.repeat, separator: o.separator, datatype: o.datatype, isRecord: fieldPos == inColumn, unbound: unbound}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// templateOptions is what a template object's option keys say.
|
// templateOptions is what a template object's option keys say.
|
||||||
type templateOptions struct {
|
type templateOptions struct {
|
||||||
datatype DataType
|
datatype *DataType
|
||||||
declared bool
|
|
||||||
format string
|
format string
|
||||||
repeat int
|
repeat int
|
||||||
separator string
|
separator string
|
||||||
@@ -266,7 +272,6 @@ func readOptions(m map[string]any, pos position) (templateOptions, error) {
|
|||||||
return o, err
|
return o, err
|
||||||
}
|
}
|
||||||
o.repeat = repeat
|
o.repeat = repeat
|
||||||
_, o.declared = m["datatype"]
|
|
||||||
if o.datatype, err = datatypeOf(m, pos); err != nil {
|
if o.datatype, err = datatypeOf(m, pos); err != nil {
|
||||||
return o, err
|
return o, err
|
||||||
}
|
}
|
||||||
@@ -274,8 +279,8 @@ func readOptions(m map[string]any, pos position) (templateOptions, error) {
|
|||||||
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, got %T", sv)
|
||||||
}
|
}
|
||||||
if repeat == 1 {
|
if repeat == 1 && o.separator != "" {
|
||||||
return o, fmt.Errorf("separator joins repeated renders, so it has no effect without a repeat above 1")
|
return o, fmt.Errorf(`separator %q joins repeated renders, and this template renders once; drop "separator", or add a repeat above 1`, o.separator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return o, nil
|
return o, nil
|
||||||
@@ -332,7 +337,7 @@ func weightOf(raw any) (float64, error) {
|
|||||||
}
|
}
|
||||||
w, ok := wv.(float64)
|
w, ok := wv.(float64)
|
||||||
if !ok {
|
if !ok {
|
||||||
return 0, fmt.Errorf("weight must be a number, got %T", wv)
|
return 0, fmt.Errorf(`weight is an option and takes a number, not %s; a field cannot be named "weight"`, jsonKind(wv))
|
||||||
}
|
}
|
||||||
if w < 0 || math.IsNaN(w) || math.IsInf(w, 0) {
|
if w < 0 || math.IsNaN(w) || math.IsInf(w, 0) {
|
||||||
return 0, fmt.Errorf("weight must be finite and positive, got %v", w)
|
return 0, fmt.Errorf("weight must be finite and positive, got %v", w)
|
||||||
|
|||||||
+1
-1
@@ -104,7 +104,7 @@ type columnRead struct {
|
|||||||
// or name, and nil where it does not or t declares a string, which reads the column as text.
|
// or name, and nil where it does not or t declares a string, which reads the column as text.
|
||||||
func columnReadOf(t *template, targets map[*nameBinding]nameTarget) *columnRead {
|
func columnReadOf(t *template, targets map[*nameBinding]nameTarget) *columnRead {
|
||||||
ops := t.compiled.ops
|
ops := t.compiled.ops
|
||||||
if t.declared && t.datatype == DataTypeString || t.repeat != 1 || len(ops) != 1 || ops[0].Kind != grammar.PathRead || len(ops[0].arms) != 1 {
|
if t.datatype != nil && *t.datatype == DataTypeString || t.repeat != 1 || len(ops) != 1 || ops[0].Kind != grammar.PathRead || len(ops[0].arms) != 1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
a := ops[0].arms[0]
|
a := ops[0].arms[0]
|
||||||
|
|||||||
+1
-1
@@ -86,7 +86,7 @@ func TestADefaultWrittenOutIsTheDefault(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
sameRenders(t, long, short)
|
sameRenders(t, long, short)
|
||||||
}
|
}
|
||||||
if r := compiled(t, `{"format":"","n":{"format":"1","datatype":"string"}}`).(*template); !r.isRecord || r.fields["n"].(*template).datatype != DataTypeString {
|
if r := compiled(t, `{"format":"","n":{"format":"1","datatype":"string"}}`).(*template); !r.isRecord || *r.fields["n"].(*template).datatype != DataTypeString {
|
||||||
t.Error("a column of datatype string is no string column of a record")
|
t.Error("a column of datatype string is no string column of a record")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-13
@@ -19,31 +19,32 @@ type valueProof struct {
|
|||||||
// checkDatatype rejects a typed column item some render of which is not text of its datatype.
|
// checkDatatype rejects a typed column item some render of which is not text of its datatype.
|
||||||
func (p *valueProof) checkDatatype(label string, n node) error {
|
func (p *valueProof) checkDatatype(label string, n node) error {
|
||||||
t, ok := n.(*template)
|
t, ok := n.(*template)
|
||||||
if !ok || t.datatype == DataTypeString {
|
if !ok || t.datatype == nil || *t.datatype == DataTypeString {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if reason := p.proveColumnItem(t).Not[t.datatype]; reason != "" {
|
if reason := p.proveColumnItem(t).Not[*t.datatype]; reason != "" {
|
||||||
return fmt.Errorf("%s: datatype %s: %s", label, t.datatype, reason)
|
return fmt.Errorf("%s: datatype %s: %s", label, *t.datatype, reason)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkField rejects a column a field of Go type ft cannot fill: a datatype, which the Go type
|
// checkField rejects a column a field of Go type ft cannot fill: a datatype other than the one the
|
||||||
// sets, a null outside a pointer, or a value its kind's datatype or range refuses.
|
// Go type sets, a null outside a pointer, or a value its kind's datatype or range refuses.
|
||||||
func (p *valueProof) checkField(label string, ft reflect.Type, column node) error {
|
func (p *valueProof) checkField(label string, ft reflect.Type, column node) error {
|
||||||
items, _ := columnItems(column)
|
|
||||||
for _, it := range items {
|
|
||||||
if it.declared {
|
|
||||||
return fmt.Errorf("%s: its Go type %s sets the datatype; drop \"datatype\"", label, ft)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elem := ft
|
elem := ft
|
||||||
if ft.Kind() == reflect.Pointer {
|
if ft.Kind() == reflect.Pointer {
|
||||||
elem = ft.Elem()
|
elem = ft.Elem()
|
||||||
} else if p.proveColumn(column).Nullable {
|
|
||||||
return fmt.Errorf("%s: its tag can draw null, which %s cannot hold; make it *%s", label, ft, ft)
|
|
||||||
}
|
}
|
||||||
kind := columnKinds[elem.Kind()]
|
kind := columnKinds[elem.Kind()]
|
||||||
|
items, _ := columnItems(column)
|
||||||
|
for _, it := range items {
|
||||||
|
if it.datatype != nil && *it.datatype != kind.datatype {
|
||||||
|
return fmt.Errorf("%s: its Go type %s sets the datatype %s; drop \"datatype\"", label, ft, kind.datatype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ft.Kind() != reflect.Pointer && p.proveColumn(column).Nullable {
|
||||||
|
return fmt.Errorf("%s: its tag can draw null, which %s cannot hold; make it *%s", label, ft, ft)
|
||||||
|
}
|
||||||
if kind.datatype == DataTypeString {
|
if kind.datatype == DataTypeString {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user