Fold the range check into columnKind.past, refuse a leading / in IsTemplate, reserve the name -, and document fake:"-" on an embedded struct
Tests / vet + fmt + tests (pull_request) Successful in 1m1s
Tests / vet + fmt + tests (pull_request) Successful in 1m1s
This commit is contained in:
@@ -31,9 +31,9 @@ spot. Its tokens reach the data by reference from the root —
|
|||||||
`{/sv_SE.person.last}`, so shipped and `--data-path` categories are alike
|
`{/sv_SE.person.last}`, so shipped and `--data-path` categories are alike
|
||||||
available. An inline template sits in no folder, so the folder-relative `{.name}`
|
available. An inline template sits in no folder, so the folder-relative `{.name}`
|
||||||
and `{..name}` are rejected naming the root spelling, and one reference alone —
|
and `{..name}` are rejected naming the root spelling, and one reference alone —
|
||||||
`{/sv_SE.person}` — is the path written as a template, rejected naming the path. A
|
`{/sv_SE.person}` — is the path written as a template, rejected naming the path, as is
|
||||||
path never contains a brace, a bracket or a quote, so the two cannot collide (see
|
a path written `/sv_SE.person`. A path never contains a brace, a bracket or a quote,
|
||||||
[Decisions](#decisions)).
|
so the two cannot collide (see [Decisions](#decisions)).
|
||||||
|
|
||||||
| Flag | |
|
| Flag | |
|
||||||
|------|--|
|
|------|--|
|
||||||
@@ -191,9 +191,9 @@ which a [`null`](#null) item leaves nil. An integer stays within int64 whatever
|
|||||||
kind, and a value the kind cannot hold, such as `{int(0,300)}` in a `uint8`, is refused
|
kind, and a value the kind cannot hold, such as `{int(0,300)}` in a `uint8`, is refused
|
||||||
naming a kind that holds it. The fields an embedded struct promotes are columns of the
|
naming a kind that holds it. The fields an embedded struct promotes are columns of the
|
||||||
same record; a named struct field, or a pointer to one, fills from its own tags as a
|
same record; a named struct field, or a pointer to one, fills from its own tags as a
|
||||||
record of its own, so its references draw apart from its parent's, and `fake:"-"`
|
record of its own, so its references draw apart from its parent's. `fake:"-"` leaves a
|
||||||
leaves it unfilled. Untagged fields keep their values, and so does a pointer back to a
|
struct field, embedded or named, or a pointer to one, unfilled. Untagged fields keep
|
||||||
struct already being filled. The first call for a type compiles its tags and reports
|
their values, and so does a pointer back to a struct already being filled. The 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
|
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.
|
names the Go type that already sets it.
|
||||||
|
|
||||||
@@ -209,8 +209,8 @@ work with no data on disk. A directory is a namespace: each JSON file is a
|
|||||||
category named after the file, each subdirectory a dot-path segment, so
|
category named after the file, each subdirectory a dot-path segment, so
|
||||||
`mydata/sv_SE/person.json` is `sv_SE.person` and replaces the shipped one.
|
`mydata/sv_SE/person.json` is `sv_SE.person` and replaces the shipped one.
|
||||||
Sources merge in order; matching folders combine, any other clash is won by the
|
Sources merge in order; matching folders combine, any other clash is won by the
|
||||||
last loaded. Names may not use `.`, `|`, `(`, `{`, `}`, `[`, `]`, `"` or `/`;
|
last loaded. Names may not use `.`, `|`, `(`, `{`, `}`, `[`, `]`, `"` or `/`, nor be
|
||||||
dot-prefixed entries are skipped, so a data directory can also be a checkout.
|
`-`, which a struct tag reserves; dot-prefixed entries are skipped, so a data directory can also be a checkout.
|
||||||
|
|
||||||
Each locale carries `address`, `color`, `company`, `date`, `email`, `ip`,
|
Each locale carries `address`, `color`, `company`, `date`, `email`, `ip`,
|
||||||
`person`, `phone`, `price`, `sentence`, `ssn`, `time`, `url`, `username`,
|
`person`, `phone`, `price`, `sentence`, `ssn`, `time`, `url`, `username`,
|
||||||
@@ -625,9 +625,9 @@ tokens add cost in proportion to the output.
|
|||||||
fields an embedded struct promotes are the struct's own — `e.First`, as
|
fields an embedded struct promotes are the struct's own — `e.First`, as
|
||||||
`encoding/json` and SQL mappers read them — so they are columns of its record and
|
`encoding/json` and SQL mappers read them — so they are columns of its record and
|
||||||
share its draws; a tagged field that another field hides is refused, not dropped. A
|
share its draws; a tagged field that another field hides is refused, not dropped. A
|
||||||
named struct field is another entity and a record of its own; `fake:"-"` leaves it
|
named struct field is another entity and a record of its own. `fake:"-"` leaves a
|
||||||
unfilled, whatever a category named `-` holds, and a pointer back to a struct
|
struct field, embedded or named, unfilled, so no name may be `-`; a pointer back to
|
||||||
already being filled is left alone, since filling it would never end. `New` cannot
|
a struct already being filled is left alone, since filling it would never end. `New` cannot
|
||||||
see a caller's types, so the first `FakeStruct` for a type compiles its tags and the
|
see a caller's types, so the first `FakeStruct` for a type compiles its tags and the
|
||||||
answer, error included, is kept per type: a test's first call is its load, and no
|
answer, error included, is kept per type: a test's first call is its load, and no
|
||||||
`NewStruct` handle is needed, as the cache already compiles once.
|
`NewStruct` handle is needed, as the cache already compiles once.
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ func (f *Generator) FakeTemplate(input string) (string, error) {
|
|||||||
// IsTemplate reports whether arg is an inline template rather than a path, by its shape: a {
|
// IsTemplate reports whether arg is an inline template rather than a path, by its shape: a {
|
||||||
// token, or a JSON object, array or string, is a template, and anything else is a path. A
|
// token, or a JSON object, array or string, is a template, and anything else is a path. A
|
||||||
// name never holds a bracket, a brace or a quote, so an arg holding one that is not valid
|
// name never holds a bracket, a brace or a quote, so an arg holding one that is not valid
|
||||||
// JSON names neither, and errors; so does a template of one reference alone, which is a path
|
// JSON names neither, and errors; so do a template of one reference alone, which is a path
|
||||||
// written as a template.
|
// written as a template, and a path written with a leading /.
|
||||||
func IsTemplate(arg string) (bool, error) {
|
func IsTemplate(arg string) (bool, error) {
|
||||||
inline, err := isTemplate(arg)
|
inline, err := isTemplate(arg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -72,6 +72,9 @@ func isTemplate(arg string) (bool, error) {
|
|||||||
if i := strings.IndexAny(arg, `[]}"`); i >= 0 {
|
if i := strings.IndexAny(arg, `[]}"`); i >= 0 {
|
||||||
return false, fmt.Errorf("%q holds a %q, which no path may, and it is not valid JSON, so it names no template either", arg, arg[i:i+1])
|
return false, fmt.Errorf("%q holds a %q, which no path may, and it is not valid JSON, so it names no template either", arg, arg[i:i+1])
|
||||||
}
|
}
|
||||||
|
if len(arg) > 1 && strings.HasPrefix(arg, "/") {
|
||||||
|
return false, fmt.Errorf("path %s starts with /, and every path starts at the root already; write %s", arg, arg[1:])
|
||||||
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -378,13 +378,17 @@ const reservedInName = ".|({}/[]\""
|
|||||||
// reservedList spells reservedInName for an error message, so the two cannot drift.
|
// reservedList spells reservedInName for an error message, so the two cannot drift.
|
||||||
var reservedList = strings.Join(strings.Split(reservedInName, ""), " ")
|
var reservedList = strings.Join(strings.Split(reservedInName, ""), " ")
|
||||||
|
|
||||||
// checkName rejects a name the dot path, {token} and JSON grammars cannot spell.
|
// checkName rejects a name the dot path, {token} and JSON grammars cannot spell, or a struct
|
||||||
|
// tag cannot read.
|
||||||
// Both a category or folder and a field go through it, so there is one answer to
|
// Both a category or folder and a field go through it, so there is one answer to
|
||||||
// what a name may contain.
|
// what a name may contain.
|
||||||
func checkName(name string) error {
|
func checkName(name string) error {
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return fmt.Errorf("%q is empty, which is not a path segment, so List never offers it", name)
|
return fmt.Errorf("%q is empty, which is not a path segment, so List never offers it", name)
|
||||||
}
|
}
|
||||||
|
if name == "-" {
|
||||||
|
return fmt.Errorf(`%q is reserved: the struct tag fake:"-" leaves a field unfilled, so no tag could read it; rename it`, name)
|
||||||
|
}
|
||||||
if i := strings.IndexAny(name, reservedInName); i >= 0 {
|
if i := strings.IndexAny(name, reservedInName); i >= 0 {
|
||||||
return fmt.Errorf("%q contains %q; a name may not use %s, which the dot path, {token} and JSON grammars reserve",
|
return fmt.Errorf("%q contains %q; a name may not use %s, which the dot path, {token} and JSON grammars reserve",
|
||||||
name, name[i:i+1], reservedList)
|
name, name[i:i+1], reservedList)
|
||||||
|
|||||||
@@ -206,8 +206,6 @@ func tagValue(sf reflect.StructField, tag string) (any, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
case inline:
|
case inline:
|
||||||
return inputValue(tag)
|
return inputValue(tag)
|
||||||
case strings.HasPrefix(tag, "/"):
|
|
||||||
return nil, fmt.Errorf("path %q starts with /, and every path starts at the root already; write %s", tag, tag[1:])
|
|
||||||
}
|
}
|
||||||
for _, seg := range strings.Split(tag, ".") {
|
for _, seg := range strings.Split(tag, ".") {
|
||||||
if err := checkName(seg); err != nil {
|
if err := checkName(seg); err != nil {
|
||||||
@@ -290,13 +288,23 @@ var columnKinds = map[reflect.Kind]columnKind{
|
|||||||
reflect.Uint8: {DataTypeInteger, 0, math.MaxUint8, reflect.Int64},
|
reflect.Uint8: {DataTypeInteger, 0, math.MaxUint8, reflect.Int64},
|
||||||
}
|
}
|
||||||
|
|
||||||
// reach is the least and greatest value v proves a field of this kind receives. An integer
|
// past is the bound of v a field of this kind cannot hold, if either is. An integer prints
|
||||||
// prints whole, so its bounds round inward.
|
// whole, so its bounds round inward first.
|
||||||
func (k columnKind) reach(v proven) (lo, hi float64) {
|
func (k columnKind) past(v proven) (float64, bool) {
|
||||||
if k.datatype == DataTypeInteger {
|
lo, hi := v.lo, v.hi
|
||||||
return math.Ceil(v.lo), math.Floor(v.hi)
|
switch k.datatype {
|
||||||
|
case DataTypeString, DataTypeBoolean:
|
||||||
|
return 0, false
|
||||||
|
case DataTypeInteger:
|
||||||
|
lo, hi = math.Ceil(lo), math.Floor(hi)
|
||||||
}
|
}
|
||||||
return v.lo, v.hi
|
switch {
|
||||||
|
case lo < k.lo:
|
||||||
|
return lo, true
|
||||||
|
case hi > k.hi:
|
||||||
|
return hi, true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkField rejects a column some render of which a field of Go type ft cannot hold: a null
|
// checkField rejects a column some render of which a field of Go type ft cannot hold: a null
|
||||||
@@ -318,15 +326,8 @@ func (p *valueProof) checkField(label string, ft reflect.Type, column node) erro
|
|||||||
if reason := v.not[kind.datatype]; reason != "" {
|
if reason := v.not[kind.datatype]; reason != "" {
|
||||||
return fmt.Errorf("%s (%s): %s", label, ft, reason)
|
return fmt.Errorf("%s (%s): %s", label, ft, reason)
|
||||||
}
|
}
|
||||||
if kind.datatype == DataTypeBoolean {
|
if bound, over := kind.past(v); over {
|
||||||
continue
|
return fmt.Errorf("%s (%s): %q can reach %s, past %s; make it %s", label, ft, it.format, strconv.FormatFloat(bound, 'g', -1, 64), elem.Kind(), kind.wider)
|
||||||
}
|
|
||||||
if lo, hi := kind.reach(v); lo < kind.lo || hi > kind.hi {
|
|
||||||
past := hi
|
|
||||||
if lo < kind.lo {
|
|
||||||
past = lo
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%s (%s): %q can reach %s, past %s; make it %s", label, ft, it.format, strconv.FormatFloat(past, 'g', -1, 64), elem.Kind(), kind.wider)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user