209 lines
4.9 KiB
Go
209 lines
4.9 KiB
Go
// Package engine 依据已发布规则和持久化快照计算有限推进命令;不调用业务处理器或外部系统。
|
||
package engine
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"errors"
|
||
"math/big"
|
||
"strings"
|
||
|
||
"git.yuntuo.hk/k2safe/orbit/internal/jsonwire"
|
||
"git.yuntuo.hk/k2safe/orbit/weave/definition"
|
||
)
|
||
|
||
type values map[string]json.RawMessage
|
||
|
||
func copyJSON(raw json.RawMessage) json.RawMessage { return append(json.RawMessage(nil), raw...) }
|
||
func copyValues(in values) values {
|
||
out := make(values, len(in)+8)
|
||
for k, v := range in {
|
||
out[k] = v
|
||
}
|
||
return out
|
||
}
|
||
|
||
// 表达式来自已验证且由Program持有的不可变定义;每个值仍按运行数据上限检查。
|
||
func resolve(v definition.Value, expected definition.Type, env values) (json.RawMessage, error) {
|
||
raw, err := evaluate(v, env, 0)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err := definition.ValidateData(expected, raw); err != nil {
|
||
return nil, err
|
||
}
|
||
return raw, nil
|
||
}
|
||
func evaluate(v definition.Value, env values, depth int) (json.RawMessage, error) {
|
||
if depth > 8 {
|
||
return nil, errors.New("expression depth exceeded")
|
||
}
|
||
var out json.RawMessage
|
||
switch v.Kind {
|
||
case "LITERAL":
|
||
out = copyJSON(v.Literal)
|
||
case "REFERENCE":
|
||
if v.Reference == nil {
|
||
return nil, errors.New("reference required")
|
||
}
|
||
raw, ok := env[v.Reference.Source]
|
||
if !ok {
|
||
return nil, errors.New("reference unavailable")
|
||
}
|
||
for _, key := range v.Reference.Path {
|
||
var object map[string]json.RawMessage
|
||
if json.Unmarshal(raw, &object) != nil {
|
||
return nil, errors.New("reference is not an object")
|
||
}
|
||
raw, ok = object[key]
|
||
if !ok {
|
||
return nil, errors.New("reference field missing")
|
||
}
|
||
}
|
||
out = copyJSON(raw)
|
||
case "OBJECT":
|
||
object := make(map[string]json.RawMessage, len(v.Fields))
|
||
size := 0
|
||
for k, child := range v.Fields {
|
||
raw, err := evaluate(child, env, depth+1)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
size += len(raw)
|
||
if size > definition.MaxSpecificationBytes {
|
||
return nil, errors.New("resolved object exceeds bounds")
|
||
}
|
||
object[k] = raw
|
||
}
|
||
raw, err := jsonwire.Marshal(object)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
out = raw
|
||
case "ARRAY":
|
||
array := make([]json.RawMessage, len(v.Items))
|
||
size := 0
|
||
for i, child := range v.Items {
|
||
raw, err := evaluate(child, env, depth+1)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
size += len(raw)
|
||
if size > definition.MaxSpecificationBytes {
|
||
return nil, errors.New("resolved array exceeds bounds")
|
||
}
|
||
array[i] = raw
|
||
}
|
||
raw, err := jsonwire.Marshal(array)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
out = raw
|
||
default:
|
||
return nil, errors.New("unknown expression")
|
||
}
|
||
if len(out) == 0 || len(out) > definition.MaxSpecificationBytes {
|
||
return nil, errors.New("resolved value exceeds bounds")
|
||
}
|
||
return out, nil
|
||
}
|
||
func compare(p definition.Predicate, env values) (bool, error) {
|
||
switch p.Op {
|
||
case "AND", "OR", "NOT":
|
||
for _, child := range p.Arguments {
|
||
ok, err := compare(child, env)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
if p.Op == "NOT" {
|
||
return !ok, nil
|
||
}
|
||
if p.Op == "AND" && !ok {
|
||
return false, nil
|
||
}
|
||
if p.Op == "OR" && ok {
|
||
return true, nil
|
||
}
|
||
}
|
||
return p.Op == "AND", nil
|
||
}
|
||
if p.Left == nil || p.Right == nil {
|
||
return false, errors.New("comparison operands missing")
|
||
}
|
||
left, err := resolve(*p.Left, definition.Type{Kind: "JSON"}, env)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
right, err := resolve(*p.Right, definition.Type{Kind: "JSON"}, env)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
decode := func(raw []byte) (any, error) {
|
||
var v any
|
||
d := json.NewDecoder(bytes.NewReader(raw))
|
||
d.UseNumber()
|
||
err := d.Decode(&v)
|
||
return v, err
|
||
}
|
||
a, err := decode(left)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
b, err := decode(right)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
var order int
|
||
switch v := a.(type) {
|
||
case string:
|
||
w, ok := b.(string)
|
||
if !ok {
|
||
return false, errors.New("comparison type mismatch")
|
||
}
|
||
order = strings.Compare(v, w)
|
||
case json.Number:
|
||
w, ok := b.(json.Number)
|
||
if !ok {
|
||
return false, errors.New("comparison type mismatch")
|
||
}
|
||
x, ok := new(big.Rat).SetString(string(v))
|
||
if !ok {
|
||
return false, errors.New("invalid exact number")
|
||
}
|
||
y, ok := new(big.Rat).SetString(string(w))
|
||
if !ok {
|
||
return false, errors.New("invalid exact number")
|
||
}
|
||
order = x.Cmp(y)
|
||
case bool:
|
||
w, ok := b.(bool)
|
||
if !ok || p.Op != "EQ" && p.Op != "NE" {
|
||
return false, errors.New("comparison type mismatch")
|
||
}
|
||
if v != w {
|
||
order = 1
|
||
}
|
||
case nil:
|
||
if b != nil || p.Op != "EQ" && p.Op != "NE" {
|
||
return false, errors.New("comparison type mismatch")
|
||
}
|
||
default:
|
||
return false, errors.New("comparison requires scalars")
|
||
}
|
||
switch p.Op {
|
||
case "EQ":
|
||
return order == 0, nil
|
||
case "NE":
|
||
return order != 0, nil
|
||
case "LT":
|
||
return order < 0, nil
|
||
case "LE":
|
||
return order <= 0, nil
|
||
case "GT":
|
||
return order > 0, nil
|
||
case "GE":
|
||
return order >= 0, nil
|
||
}
|
||
return false, errors.New("unknown comparison")
|
||
}
|