improve condition evaluate

This commit is contained in:
Yoan.liu
2025-05-20 22:54:41 +08:00
parent 97d692910b
commit faad7cb6d7
8 changed files with 440 additions and 45 deletions

View File

@@ -26,18 +26,276 @@ const (
Not LogicalOperator = "not"
)
type EvalResult struct {
Type string
Value any
}
func (e *EvalResult) GetFloat64() (float64, error) {
if e.Type != "number" {
return 0, fmt.Errorf("type mismatch: %s", e.Type)
}
switch v := e.Value.(type) {
case int:
return float64(v), nil
case float64:
return v, nil
default:
return 0, fmt.Errorf("unsupported type: %T", v)
}
}
func (e *EvalResult) GreaterThan(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case "number":
left, err := e.GetFloat64()
if err != nil {
return nil, err
}
right, err := other.GetFloat64()
if err != nil {
return nil, err
}
return &EvalResult{
Type: "boolean",
Value: left > right,
}, nil
case "string":
return &EvalResult{
Type: "boolean",
Value: e.Value.(string) > other.Value.(string),
}, nil
default:
return nil, fmt.Errorf("unsupported type: %s", e.Type)
}
}
func (e *EvalResult) GreaterOrEqual(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case "number":
left, err := e.GetFloat64()
if err != nil {
return nil, err
}
right, err := other.GetFloat64()
if err != nil {
return nil, err
}
return &EvalResult{
Type: "boolean",
Value: left >= right,
}, nil
case "string":
return &EvalResult{
Type: "boolean",
Value: e.Value.(string) >= other.Value.(string),
}, nil
default:
return nil, fmt.Errorf("unsupported type: %s", e.Type)
}
}
func (e *EvalResult) LessThan(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case "number":
left, err := e.GetFloat64()
if err != nil {
return nil, err
}
right, err := other.GetFloat64()
if err != nil {
return nil, err
}
return &EvalResult{
Type: "boolean",
Value: left < right,
}, nil
case "string":
return &EvalResult{
Type: "boolean",
Value: e.Value.(string) < other.Value.(string),
}, nil
default:
return nil, fmt.Errorf("unsupported type: %s", e.Type)
}
}
func (e *EvalResult) LessOrEqual(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case "number":
left, err := e.GetFloat64()
if err != nil {
return nil, err
}
right, err := other.GetFloat64()
if err != nil {
return nil, err
}
return &EvalResult{
Type: "boolean",
Value: left <= right,
}, nil
case "string":
return &EvalResult{
Type: "boolean",
Value: e.Value.(string) <= other.Value.(string),
}, nil
default:
return nil, fmt.Errorf("unsupported type: %s", e.Type)
}
}
func (e *EvalResult) Equal(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case "number":
left, err := e.GetFloat64()
if err != nil {
return nil, err
}
right, err := other.GetFloat64()
if err != nil {
return nil, err
}
return &EvalResult{
Type: "boolean",
Value: left == right,
}, nil
case "string":
return &EvalResult{
Type: "boolean",
Value: e.Value.(string) == other.Value.(string),
}, nil
default:
return nil, fmt.Errorf("unsupported type: %s", e.Type)
}
}
func (e *EvalResult) NotEqual(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case "number":
left, err := e.GetFloat64()
if err != nil {
return nil, err
}
right, err := other.GetFloat64()
if err != nil {
return nil, err
}
return &EvalResult{
Type: "boolean",
Value: left != right,
}, nil
case "string":
return &EvalResult{
Type: "boolean",
Value: e.Value.(string) != other.Value.(string),
}, nil
default:
return nil, fmt.Errorf("unsupported type: %s", e.Type)
}
}
func (e *EvalResult) And(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case "boolean":
return &EvalResult{
Type: "boolean",
Value: e.Value.(bool) && other.Value.(bool),
}, nil
default:
return nil, fmt.Errorf("unsupported type: %s", e.Type)
}
}
func (e *EvalResult) Or(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case "boolean":
return &EvalResult{
Type: "boolean",
Value: e.Value.(bool) || other.Value.(bool),
}, nil
default:
return nil, fmt.Errorf("unsupported type: %s", e.Type)
}
}
func (e *EvalResult) Not() (*EvalResult, error) {
if e.Type != "boolean" {
return nil, fmt.Errorf("type mismatch: %s", e.Type)
}
return &EvalResult{
Type: "boolean",
Value: !e.Value.(bool),
}, nil
}
func (e *EvalResult) Is(other *EvalResult) (*EvalResult, error) {
if e.Type != other.Type {
return nil, fmt.Errorf("type mismatch: %s vs %s", e.Type, other.Type)
}
switch e.Type {
case "boolean":
return &EvalResult{
Type: "boolean",
Value: e.Value.(bool) == other.Value.(bool),
}, nil
default:
return nil, fmt.Errorf("unsupported type: %s", e.Type)
}
}
type Expr interface {
GetType() string
Eval(variables map[string]map[string]any) (any, error)
Eval(variables map[string]map[string]any) (*EvalResult, error)
}
type ConstExpr struct {
Type string `json:"type"`
Value Value `json:"value"`
Type string `json:"type"`
Value Value `json:"value"`
ValueType string `json:"valueType"`
}
func (c ConstExpr) GetType() string { return c.Type }
func (c ConstExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
return &EvalResult{
Type: c.ValueType,
Value: c.Value,
}, nil
}
type VarExpr struct {
Type string `json:"type"`
Selector WorkflowNodeIOValueSelector `json:"selector"`
@@ -45,7 +303,7 @@ type VarExpr struct {
func (v VarExpr) GetType() string { return v.Type }
func (v VarExpr) Eval(variables map[string]map[string]any) (any, error) {
func (v VarExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
if v.Selector.Id == "" {
return nil, fmt.Errorf("node id is empty")
}
@@ -58,10 +316,12 @@ func (v VarExpr) Eval(variables map[string]map[string]any) (any, error) {
}
if _, ok := variables[v.Selector.Id][v.Selector.Name]; !ok {
return nil, fmt.Errorf("variable %s not found in node %s", v.Selector.Name, v.Selector.NodeId)
return nil, fmt.Errorf("variable %s not found in node %s", v.Selector.Name, v.Selector.Id)
}
return variables[v.Selector.Id][v.Selector.Name], nil
return &EvalResult{
Type: v.Selector.Type,
Value: variables[v.Selector.Id][v.Selector.Name],
}, nil
}
type CompareExpr struct {
@@ -73,7 +333,7 @@ type CompareExpr struct {
func (c CompareExpr) GetType() string { return c.Type }
func (c CompareExpr) Eval(variables map[string]map[string]any) (any, error) {
func (c CompareExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
left, err := c.Left.Eval(variables)
if err != nil {
return nil, err
@@ -85,19 +345,19 @@ func (c CompareExpr) Eval(variables map[string]map[string]any) (any, error) {
switch c.Op {
case GreaterThan:
return left.(float64) > right.(float64), nil
return left.GreaterThan(right)
case LessThan:
return left.(float64) < right.(float64), nil
return left.LessThan(right)
case GreaterOrEqual:
return left.(float64) >= right.(float64), nil
return left.GreaterOrEqual(right)
case LessOrEqual:
return left.(float64) <= right.(float64), nil
return left.LessOrEqual(right)
case Equal:
return left == right, nil
return left.Equal(right)
case NotEqual:
return left != right, nil
return left.NotEqual(right)
case Is:
return left == right, nil
return left.Is(right)
default:
return nil, fmt.Errorf("unknown operator: %s", c.Op)
}
@@ -112,7 +372,7 @@ type LogicalExpr struct {
func (l LogicalExpr) GetType() string { return l.Type }
func (l LogicalExpr) Eval(variables map[string]map[string]any) (any, error) {
func (l LogicalExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
left, err := l.Left.Eval(variables)
if err != nil {
return nil, err
@@ -124,9 +384,9 @@ func (l LogicalExpr) Eval(variables map[string]map[string]any) (any, error) {
switch l.Op {
case And:
return left.(bool) && right.(bool), nil
return left.And(right)
case Or:
return left.(bool) || right.(bool), nil
return left.Or(right)
default:
return nil, fmt.Errorf("unknown operator: %s", l.Op)
}
@@ -139,12 +399,12 @@ type NotExpr struct {
func (n NotExpr) GetType() string { return n.Type }
func (n NotExpr) Eval(variables map[string]map[string]any) (any, error) {
func (n NotExpr) Eval(variables map[string]map[string]any) (*EvalResult, error) {
inner, err := n.Expr.Eval(variables)
if err != nil {
return nil, err
}
return !inner.(bool), nil
return inner.Not()
}
type rawExpr struct {

View File

@@ -0,0 +1,127 @@
package domain
import (
"testing"
)
func TestLogicalEval(t *testing.T) {
// 测试逻辑表达式 and
logicalExpr := LogicalExpr{
Left: ConstExpr{
Type: "const",
Value: true,
ValueType: "boolean",
},
Op: And,
Right: ConstExpr{
Type: "const",
Value: true,
ValueType: "boolean",
},
}
result, err := logicalExpr.Eval(nil)
if err != nil {
t.Errorf("failed to evaluate logical expression: %v", err)
}
if result.Value != true {
t.Errorf("expected true, got %v", result)
}
// 测试逻辑表达式 or
orExpr := LogicalExpr{
Left: ConstExpr{
Type: "const",
Value: true,
ValueType: "boolean",
},
Op: Or,
Right: ConstExpr{
Type: "const",
Value: true,
ValueType: "boolean",
},
}
result, err = orExpr.Eval(nil)
if err != nil {
t.Errorf("failed to evaluate logical expression: %v", err)
}
if result.Value != true {
t.Errorf("expected true, got %v", result)
}
}
func TestUnmarshalExpr(t *testing.T) {
type args struct {
data []byte
}
tests := []struct {
name string
args args
want Expr
wantErr bool
}{
{
name: "test1",
args: args{
data: []byte(`{"left":{"left":{"selector":{"id":"ODnYSOXB6HQP2_vz6JcZE","name":"certificate.validated","type":"boolean"},"type":"var"},"op":"is","right":{"type":"const","value":true,"valueType":"boolean"},"type":"compare"},"op":"and","right":{"left":{"selector":{"id":"ODnYSOXB6HQP2_vz6JcZE","name":"certificate.daysLeft","type":"number"},"type":"var"},"op":"==","right":{"type":"const","value":2,"valueType":"number"},"type":"compare"},"type":"logical"}`),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := UnmarshalExpr(tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("UnmarshalExpr() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got == nil {
t.Errorf("UnmarshalExpr() got = nil, want %v", tt.want)
return
}
})
}
}
func TestExpr_Eval(t *testing.T) {
type args struct {
variables map[string]map[string]any
data []byte
}
tests := []struct {
name string
args args
want *EvalResult
wantErr bool
}{
{
name: "test1",
args: args{
variables: map[string]map[string]any{
"ODnYSOXB6HQP2_vz6JcZE": {
"certificate.validated": true,
"certificate.daysLeft": 2,
},
},
data: []byte(`{"left":{"left":{"selector":{"id":"ODnYSOXB6HQP2_vz6JcZE","name":"certificate.validated","type":"boolean"},"type":"var"},"op":"is","right":{"type":"const","value":true,"valueType":"boolean"},"type":"compare"},"op":"and","right":{"left":{"selector":{"id":"ODnYSOXB6HQP2_vz6JcZE","name":"certificate.daysLeft","type":"number"},"type":"var"},"op":"==","right":{"type":"const","value":2,"valueType":"number"},"type":"compare"},"type":"logical"}`),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c, err := UnmarshalExpr(tt.args.data)
if err != nil {
t.Errorf("UnmarshalExpr() error = %v", err)
return
}
got, err := c.Eval(tt.args.variables)
t.Log("got:", got)
if (err != nil) != tt.wantErr {
t.Errorf("ConstExpr.Eval() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got.Value != true {
t.Errorf("ConstExpr.Eval() got = %v, want %v", got.Value, true)
}
})
}
}

View File

@@ -1,6 +1,7 @@
package domain
import (
"encoding/json"
"time"
maputil "github.com/usual2970/certimate/internal/pkg/utils/map"
@@ -109,11 +110,13 @@ type WorkflowNodeConfigForNotify struct {
}
func (n *WorkflowNode) GetConfigForCondition() WorkflowNodeConfigForCondition {
raw := maputil.GetString(n.Config, "expression")
if raw == "" {
expression := n.Config["expression"]
if expression == nil {
return WorkflowNodeConfigForCondition{}
}
raw, _ := json.Marshal(expression)
expr, err := UnmarshalExpr([]byte(raw))
if err != nil {
return WorkflowNodeConfigForCondition{}