yao/assert/helpers.go
Max 0c9bdb8000 Implement P3 Run Phase with Enhanced Validation and Execution Logic
- Completed the implementation of the P3 Run phase, integrating task execution and validation mechanisms.
- Introduced a new `RunConfig` struct to manage execution parameters such as retries and validation thresholds.
- Developed a two-layer validation system using the new `yao/assert` package, supporting both natural language and structured JSON rules.
- Enhanced the `RunExecution` method to execute tasks sequentially with progress tracking and a retry mechanism for validation failures.
- Updated task structures to include comprehensive validation rules and expected outputs, ensuring robust task management.
- Added unit tests for the new execution and validation features, achieving high test coverage across the implementation.
- Revised documentation to reflect changes in the architecture and functionality of the P3 phase.
2026-01-17 12:22:23 +08:00

174 lines
3.4 KiB
Go

package assert
import (
"encoding/json"
"fmt"
"strconv"
"strings"
jsoniter "github.com/json-iterator/go"
"github.com/yaoapp/gou/text"
)
// ValidateOutput compares two values for equality using JSON serialization
func ValidateOutput(actual, expected interface{}) bool {
actualJSON, err1 := jsoniter.Marshal(actual)
expectedJSON, err2 := jsoniter.Marshal(expected)
if err1 != nil || err2 != nil {
return false
}
return string(actualJSON) == string(expectedJSON)
}
// ToString converts a value to string for comparison
func ToString(v interface{}) string {
if v == nil {
return ""
}
switch val := v.(type) {
case string:
return val
case []byte:
return string(val)
default:
b, err := json.Marshal(v)
if err != nil {
return fmt.Sprintf("%v", v)
}
return string(b)
}
}
// GetType returns the type name of a value
func GetType(v interface{}) string {
if v == nil {
return "null"
}
switch v.(type) {
case string:
return "string"
case float64, float32, int, int64, int32:
return "number"
case bool:
return "boolean"
case []interface{}:
return "array"
case map[string]interface{}:
return "object"
default:
return fmt.Sprintf("%T", v)
}
}
// ExtractPath extracts a value from JSON using dot-notation path with array index support
// Supports: "field", "field.nested", "field[0]", "field[0].nested", "field.nested[0].value"
func ExtractPath(data interface{}, path string) interface{} {
current := data
segments := ParsePathSegments(path)
for _, segment := range segments {
if segment == "" {
continue
}
// Check if this is an array index like "[0]"
if strings.HasPrefix(segment, "[") && strings.HasSuffix(segment, "]") {
indexStr := segment[1 : len(segment)-1]
index, err := strconv.Atoi(indexStr)
if err != nil {
return nil
}
arr, ok := current.([]interface{})
if !ok {
return nil
}
if index < 0 || index >= len(arr) {
return nil
}
current = arr[index]
} else {
// Regular field access
switch v := current.(type) {
case map[string]interface{}:
current = v[segment]
default:
return nil
}
}
}
return current
}
// ParsePathSegments splits a path like "wheres[0].like" into ["wheres", "[0]", "like"]
func ParsePathSegments(path string) []string {
var segments []string
var current strings.Builder
for i := 0; i < len(path); i++ {
ch := path[i]
switch ch {
case '.':
if current.Len() > 0 {
segments = append(segments, current.String())
current.Reset()
}
case '[':
if current.Len() > 0 {
segments = append(segments, current.String())
current.Reset()
}
j := i + 1
for j < len(path) && path[j] != ']' {
j++
}
if j < len(path) {
segments = append(segments, path[i:j+1])
i = j
}
default:
current.WriteByte(ch)
}
}
if current.Len() > 0 {
segments = append(segments, current.String())
}
return segments
}
// TruncateOutput truncates output for error messages
func TruncateOutput(output interface{}, maxLen int) string {
var s string
switch v := output.(type) {
case string:
s = v
case nil:
return "<nil>"
default:
bytes, err := jsoniter.Marshal(v)
if err != nil {
s = fmt.Sprintf("%v", v)
} else {
s = string(bytes)
}
}
if len(s) > maxLen {
return s[:maxLen] + "..."
}
return s
}
// ExtractJSON extracts JSON from text (handles markdown code blocks, etc.)
func ExtractJSON(content string) interface{} {
return text.ExtractJSON(content)
}