import (
"bytes"
- "errors"
"fmt"
"slices"
+ "strconv"
+ "strings"
"time"
"go.cypherpunks.su/tai64n/v4"
CmdTimePrec = "TP"
CmdType = "T"
CmdUTC = "UTC"
-
- Magic = "schema"
+ Magic = "schema"
)
+type BaseErr struct {
+ Data any
+ w error
+ SchemaName string
+ CmdName string
+ Taken string
+ Msg string
+ CmdIdx int
+}
+
+type SchemaErr struct{ BaseErr }
+
+type DataErr struct{ BaseErr }
+
+func (err *BaseErr) Error() string {
+ cols := []string{
+ fmt.Sprintf("schema:%s", err.SchemaName),
+ fmt.Sprintf("cmd:%d:%s", err.CmdIdx, err.CmdName),
+ }
+ if err.Taken != "" {
+ cols = append(cols, fmt.Sprintf("taken:%s", err.Taken))
+ }
+ if err.Data != nil {
+ cols = append(cols, fmt.Sprintf("data:%T:%+v", err.Data, err.Data))
+ }
+ cols = append(cols, err.Msg)
+ return strings.Join(cols, " ")
+}
+
+func (err *BaseErr) Unwrap() error {
+ return err.w
+}
+
func Check(schemaName string, schemas map[string][][]any, data any) error {
- acts := schemas[schemaName]
- if acts == nil {
- return errors.New(schemaName + ": no schema")
+ cmds := schemas[schemaName]
+ if cmds == nil {
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName, Msg: "no such schema",
+ }}
}
+ var taken string
var single bool // TAKEn, not EACH
var vs []any
- for i, act := range acts {
- if len(act) == 0 {
- return fmt.Errorf("%s: %d: empty command", schemaName, i)
+ for cmdIdx, cmd := range cmds {
+ if len(cmd) == 0 {
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ Msg: "empty command",
+ }}
}
- cmd, ok := act[0].(string)
+ cmdName, ok := cmd[0].(string)
if !ok {
- return fmt.Errorf("%s: %d: non command: %+v", schemaName, i, act[0])
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ Msg: "non str command",
+ }}
}
- switch cmd {
+ switch cmdName {
case CmdExists:
if vs == nil {
- return fmt.Errorf("%s: %d: %s", schemaName, i, cmd)
+ return &DataErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ }}
}
case CmdNotExists:
if vs != nil {
- return fmt.Errorf("%s: %d: %s", schemaName, i, cmd)
+ return &DataErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ }}
}
case CmdTake:
+ taken = ""
single = true
- if len(act) != 2 {
- return fmt.Errorf("%s: %d: %s: wrong number of args", schemaName, i, cmd)
+ if len(cmd) != 2 {
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "wrong number of args",
+ }}
}
- switch k := act[1].(type) {
+ switch k := cmd[1].(type) {
case string:
+ taken = k
if k == "." {
vs = []any{data}
} else {
vs = []any{v}
}
case uint64:
+ taken = strconv.FormatUint(k, 10)
l := data.([]any)
- vs = []any{l[k]}
+ if int(k) < len(l) {
+ vs = []any{l[k]}
+ }
default:
- return fmt.Errorf("%s: %d: %s: bad target", schemaName, i, cmd)
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "bad target",
+ }}
}
case CmdEach:
single = false
case []any:
vs = v
default:
- return fmt.Errorf("%s: %d: %s: non-iterable", schemaName, i, cmd)
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ Msg: "non iterable",
+ }}
}
case CmdEq:
if vs == nil {
continue
}
- if len(act) != 2 {
- return fmt.Errorf("%s: %d: %s: wrong number of args", schemaName, i, cmd)
+ if len(cmd) != 2 {
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "wrong number of args",
+ }}
}
- expect, ok := act[1].([]byte)
+ var expect []byte
+ expect, ok = cmd[1].([]byte)
if !ok {
- return fmt.Errorf("%s: %d: %s: bad bin: %+v", schemaName, i, cmd, act[1])
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "bad bin",
+ Data: cmd[1],
+ }}
}
for _, v := range vs {
var eq bool
eq = bytes.Equal(got, expect)
default:
if !ok {
- return fmt.Errorf("%s: %d: %s: non-comparable: %T", schemaName, i, cmd, v)
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ Msg: "non comparable",
+ Data: v,
+ }}
}
}
if !eq {
- return fmt.Errorf("%s: %d: %s: !=", schemaName, i, cmd)
+ return &DataErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ Data: v,
+ }}
}
}
case CmdType:
if vs == nil {
continue
}
- expected := make([]types.Type, 0, len(act)-1)
- if len(act) < 2 {
- return fmt.Errorf("%s: %d: %s: wrong number of args", schemaName, i, cmd)
+ expected := make([]types.Type, 0, len(cmd)-1)
+ if len(cmd) < 2 {
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "wrong number of args",
+ }}
}
- for _, tAny := range act[1:] {
- t, ok := tAny.(string)
+ for _, tAny := range cmd[1:] {
+ var t string
+ t, ok = tAny.(string)
if !ok {
- return fmt.Errorf("%s: %d: %s: non-str: %+v", schemaName, i, cmd, tAny)
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "non str",
+ Data: tAny,
+ }}
}
switch t {
case "NIL":
case "STR":
expected = append(expected, types.Str)
default:
- return fmt.Errorf("%s: %d: %s: unknown type: %s", schemaName, i, cmd, t)
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "unknown type",
+ Data: t,
+ }}
}
}
var typ types.Type
- for n, v := range vs {
+ for _, v := range vs {
switch v.(type) {
case nil:
typ = types.NIL
case keks.Raw:
typ = types.Raw
default:
- return fmt.Errorf("%s: %d: %s: unsupported type: %T", schemaName, i, cmd, v)
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ Msg: "unsupported type",
+ Data: v,
+ }}
}
if !slices.Contains(expected, typ) {
- return fmt.Errorf("%s: %d: %d: %s: %T", schemaName, i, n, cmd, v)
+ return &DataErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ Data: v,
+ }}
}
}
case CmdGT, CmdLT:
if vs == nil {
continue
}
- if len(act) != 2 {
- return fmt.Errorf("%s: %d: %s: wrong number of args", schemaName, i, cmd)
+ if len(cmd) != 2 {
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "wrong number of args",
+ }}
}
var expect int64
- switch v := act[1].(type) {
+ switch v := cmd[1].(type) {
case uint64:
expect = int64(v)
case int64:
expect = v
default:
- return fmt.Errorf("%s: %d: %s: unsupported type: %T", schemaName, i, cmd, v)
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "unsupported type",
+ Data: v,
+ }}
}
for _, v := range vs {
var got int64
case int64:
got = v
default:
- return fmt.Errorf("%s: %d: %s: non len-able: %T", schemaName, i, cmd, v)
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ Msg: "non len-able",
+ Data: v,
+ }}
}
}
- switch cmd {
+ ok = false
+ switch cmdName {
case CmdGT:
- if got <= expect {
- return fmt.Errorf("%s: %d: %d <= %d", schemaName, i, got, expect)
+ if got > expect {
+ ok = true
}
case CmdLT:
- if got >= expect {
- return fmt.Errorf("%s: %d: %d >= %d", schemaName, i, got, expect)
+ if got < expect {
+ ok = true
}
}
+ if !ok {
+ return &DataErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ Data: got,
+ }}
+ }
}
case CmdSchema:
if vs == nil {
continue
}
- if len(act) != 2 {
- return fmt.Errorf("%s: %d: %s: wrong number of args", schemaName, i, cmd)
+ if len(cmd) != 2 {
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "wrong number of args",
+ }}
}
- name, ok := act[1].(string)
+ name, ok := cmd[1].(string)
if !ok {
- return fmt.Errorf("%s: %d: %s: bad name: %+v", schemaName, i, cmd, act[1])
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "non str",
+ Data: name,
+ }}
}
- for n, v := range vs {
+ for _, v := range vs {
if err := Check(name, schemas, v); err != nil {
- return fmt.Errorf("%s: %d: %d: %s: %w", schemaName, i, n, cmd, err)
+ return &DataErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ Data: v,
+ w: err,
+ }}
}
}
case CmdTimePrec:
if vs == nil {
continue
}
- if len(act) != 2 {
- return fmt.Errorf("%s: %d: %s: wrong number of args", schemaName, i, cmd)
+ if len(cmd) != 2 {
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "wrong number of args",
+ }}
}
- prec, ok := act[1].(uint64)
+ prec, ok := cmd[1].(uint64)
if !ok {
- return fmt.Errorf("%s: %d: %s: bad prec: %+v", schemaName, i, cmd, act[1])
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "bad precision value",
+ Data: cmd[1],
+ }}
}
for _, v := range vs {
- switch v := v.(type) {
+ switch v.(type) {
case *tai64n.TAI64:
case *tai64n.TAI64N:
case *tai64n.TAI64NA:
case time.Time:
default:
- return fmt.Errorf("%s: %d: %s: unsupported data type: %T",
- schemaName, i, cmd, v)
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ Msg: "unsupported data type",
+ Data: v,
+ }}
}
+ err := DataErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ Data: v,
+ }}
switch prec {
- case 0: // s
+ case 0:
+ err.Msg = "s"
switch v := v.(type) {
case *tai64n.TAI64:
case time.Time:
if v.Nanosecond() != 0 {
- return fmt.Errorf("%s: %d: %s: >s", schemaName, i, cmd)
+ return &err
}
default:
- return fmt.Errorf("%s: %d: %s: >s", schemaName, i, cmd)
+ return &err
}
- case 3: // ms
+ case 3:
+ err.Msg = "ms"
switch v := v.(type) {
case *tai64n.TAI64:
case *tai64n.TAI64N:
d := be.Get(v[8:])
if d%1000000 != 0 {
- return fmt.Errorf("%s: %d: %s: >ms", schemaName, i, cmd)
+ return &err
}
case time.Time:
if v.Nanosecond()%1000000 != 0 {
- return fmt.Errorf("%s: %d: %s: >ms", schemaName, i, cmd)
+ return &err
}
default:
- return fmt.Errorf("%s: %d: %s: >ms", schemaName, i, cmd)
+ return &err
}
- case 6: // µs
+ case 6:
+ err.Msg = "µs"
switch v := v.(type) {
case *tai64n.TAI64:
case *tai64n.TAI64N:
d := be.Get(v[8:])
if d%1000 != 0 {
- return fmt.Errorf("%s: %d: %s: >µs", schemaName, i, cmd)
+ return &err
}
case time.Time:
if v.Nanosecond()%1000 != 0 {
- return fmt.Errorf("%s: %d: %s: >µs", schemaName, i, cmd)
+ return &err
}
default:
- return fmt.Errorf("%s: %d: %s: >µs", schemaName, i, cmd)
+ return &err
}
- case 9: // ns
+ case 9:
+ err.Msg = "ns"
switch v.(type) {
case *tai64n.TAI64:
case *tai64n.TAI64N:
case time.Time:
default:
- return fmt.Errorf("%s: %d: %s: >ns", schemaName, i, cmd)
+ return &err
}
- case 12: // ps
+ case 12:
+ err.Msg = "ps"
switch v := v.(type) {
case time.Time:
case *tai64n.TAI64:
case *tai64n.TAI64NA:
d := be.Get(v[12:])
if d%1000000 != 0 {
- return fmt.Errorf("%s: %d: %s: >ps", schemaName, i, cmd)
+ return &err
}
}
- case 15: // fs
+ case 15:
+ err.Msg = "fs"
switch v := v.(type) {
case time.Time:
case *tai64n.TAI64:
case *tai64n.TAI64NA:
d := be.Get(v[12:])
if d%1000 != 0 {
- return fmt.Errorf("%s: %d: %s: >fs", schemaName, i, cmd)
+ return &err
}
}
- case 18: // as
+ case 18:
+ err.Msg = "as"
default:
- return fmt.Errorf("%s: %d: %s: unknown value: %d",
- schemaName, i, cmd, prec)
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "unknown value",
+ Data: v,
+ }}
}
}
case CmdUTC:
case time.Time:
continue
default:
- return fmt.Errorf("%s: %d: %s: unsupported data type: %T",
- schemaName, i, cmd, v)
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ Msg: "unsupported data type",
+ Data: v,
+ }}
}
if isLeap {
- return fmt.Errorf("%s: %d: %s: is leap", schemaName, i, cmd)
+ return &DataErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Taken: taken,
+ Data: v,
+ }}
}
}
default:
- return fmt.Errorf("%s: %d: %s: unknown command", schemaName, i, cmd)
+ return &SchemaErr{BaseErr: BaseErr{
+ SchemaName: schemaName,
+ CmdIdx: cmdIdx,
+ CmdName: cmdName,
+ Msg: "unknown command",
+ }}
}
}
return nil