package protocol import ( "encoding/json" "fmt" "math" "sort" "strconv" "strings" ) // Manifest expressions are JSON values with a deliberately small set of // $-prefixed operators. They can construct provider payloads without running // plugin code or exposing host objects. func evaluateManifestValue(template any, env map[string]any) (any, error) { switch value := template.(type) { case nil, bool, float64, float32, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: return value, nil case string: trimmed := strings.TrimSpace(value) if strings.HasPrefix(trimmed, "${") && strings.HasSuffix(trimmed, "}") { return manifestPathValue(env, strings.TrimSpace(trimmed[2:len(trimmed)-1])), nil } return value, nil case []any: result := make([]any, 0, len(value)) for _, item := range value { evaluated, err := evaluateManifestValue(item, env) if err != nil { return nil, err } if evaluated != nil { result = append(result, evaluated) } } return result, nil case map[string]any: if len(value) == 1 { for operator, operand := range value { if strings.HasPrefix(operator, "$") { return evaluateManifestOperator(operator, operand, env) } } } result := make(map[string]any, len(value)) for key, item := range value { evaluated, err := evaluateManifestValue(item, env) if err != nil { return nil, fmt.Errorf("evaluate %s: %w", key, err) } if evaluated != nil { result[key] = evaluated } } return result, nil default: encoded, err := json.Marshal(value) if err != nil { return nil, fmt.Errorf("unsupported manifest template value %T", template) } var normalized any if err := json.Unmarshal(encoded, &normalized); err != nil { return nil, err } return evaluateManifestValue(normalized, env) } } func evaluateManifestOperator(operator string, operand any, env map[string]any) (any, error) { switch operator { case "$ref": path, ok := operand.(string) if !ok { return nil, fmt.Errorf("$ref requires a string path") } return manifestPathValue(env, path), nil case "$literal": return operand, nil case "$coalesce", "$default": values, ok := operand.([]any) if !ok { return nil, fmt.Errorf("%s requires an array", operator) } for _, item := range values { value, err := evaluateManifestValue(item, env) if err != nil { return nil, err } if !manifestEmpty(value) { return value, nil } } return nil, nil case "$concat": values, ok := operand.([]any) if !ok { return nil, fmt.Errorf("$concat requires an array") } var result strings.Builder for _, item := range values { value, err := evaluateManifestValue(item, env) if err != nil { return nil, err } if value != nil { result.WriteString(manifestString(value)) } } return result.String(), nil case "$concatArrays": values, ok := operand.([]any) if !ok { return nil, fmt.Errorf("$concatArrays requires an array") } result := make([]any, 0) for _, item := range values { value, err := evaluateManifestValue(item, env) if err != nil { return nil, err } result = append(result, manifestArray(value)...) } return result, nil case "$map", "$filter": spec, ok := operand.(map[string]any) if !ok { return nil, fmt.Errorf("%s requires an object", operator) } from, err := evaluateManifestValue(spec["from"], env) if err != nil { return nil, err } alias := strings.TrimSpace(manifestString(spec["as"])) if alias == "" { alias = "item" } items := manifestArray(from) result := make([]any, 0, len(items)) for index, item := range items { child := cloneManifestEnv(env) child[alias] = item child[alias+"Index"] = index if operator == "$filter" { condition, err := evaluateManifestValue(spec["where"], child) if err != nil { return nil, err } if manifestTruthy(condition) { result = append(result, item) } continue } mapped, err := evaluateManifestValue(spec["in"], child) if err != nil { return nil, err } if mapped != nil { result = append(result, mapped) } } return result, nil case "$indexObject": spec, ok := operand.(map[string]any) if !ok { return nil, fmt.Errorf("$indexObject requires an object") } from, err := evaluateManifestValue(spec["from"], env) if err != nil { return nil, err } alias := strings.TrimSpace(manifestString(spec["as"])) if alias == "" { alias = "item" } prefixValue, err := evaluateManifestValue(spec["prefix"], env) if err != nil { return nil, err } prefix := manifestString(prefixValue) maxItems := len(manifestArray(from)) if spec["max"] != nil { value, err := evaluateManifestValue(spec["max"], env) if err != nil { return nil, err } if configured := manifestInt(value); configured >= 0 && configured < maxItems { maxItems = configured } } result := make(map[string]any, maxItems) for index, item := range manifestArray(from)[:maxItems] { child := cloneManifestEnv(env) child[alias] = item child[alias+"Index"] = index value, err := evaluateManifestValue(spec["value"], child) if err != nil { return nil, err } if value != nil && !manifestEmpty(value) { result[prefix+strconv.Itoa(index)] = value } } return result, nil case "$first", "$last": value, err := evaluateManifestValue(operand, env) if err != nil { return nil, err } items := manifestArray(value) if len(items) == 0 { return nil, nil } if operator == "$last" { return items[len(items)-1], nil } return items[0], nil case "$at": values, ok := operand.([]any) if !ok || len(values) != 2 { return nil, fmt.Errorf("$at requires [array, index]") } value, err := evaluateManifestValue(values[0], env) if err != nil { return nil, err } indexValue, err := evaluateManifestValue(values[1], env) if err != nil { return nil, err } items := manifestArray(value) index := manifestInt(indexValue) if index < 0 || index >= len(items) { return nil, nil } return items[index], nil case "$len": value, err := evaluateManifestValue(operand, env) if err != nil { return nil, err } switch typed := value.(type) { case []any: return len(typed), nil case map[string]any: return len(typed), nil case string: return len([]rune(typed)), nil default: return 0, nil } case "$if": spec, ok := operand.(map[string]any) if !ok { return nil, fmt.Errorf("$if requires an object") } condition, err := evaluateManifestValue(spec["condition"], env) if err != nil { return nil, err } if manifestTruthy(condition) { return evaluateManifestValue(spec["then"], env) } return evaluateManifestValue(spec["else"], env) case "$switch": spec, ok := operand.(map[string]any) if !ok { return nil, fmt.Errorf("$switch requires an object") } for _, rawCase := range manifestArray(spec["cases"]) { item, _ := rawCase.(map[string]any) when, err := evaluateManifestValue(item["when"], env) if err != nil { return nil, err } if manifestTruthy(when) { return evaluateManifestValue(item["then"], env) } } return evaluateManifestValue(spec["default"], env) case "$omitEmpty": value, err := evaluateManifestValue(operand, env) if err != nil || manifestEmpty(value) { return nil, err } return value, nil case "$lower", "$upper", "$trim", "$toString", "$toInt", "$toBool", "$dataMime", "$dataPayload", "$json": value, err := evaluateManifestValue(operand, env) if err != nil { return nil, err } text := manifestString(value) switch operator { case "$lower": return strings.ToLower(text), nil case "$upper": return strings.ToUpper(text), nil case "$trim": return strings.TrimSpace(text), nil case "$toString": return text, nil case "$toInt": return manifestInt(value), nil case "$toBool": return manifestTruthy(value), nil case "$dataMime": return dataMIME(text), nil case "$dataPayload": return dataPayload(text), nil default: encoded, err := json.Marshal(value) return string(encoded), err } case "$merge": values, ok := operand.([]any) if !ok { return nil, fmt.Errorf("$merge requires an array") } result := make(map[string]any) for _, item := range values { value, err := evaluateManifestValue(item, env) if err != nil { return nil, err } for key, entry := range manifestObject(value) { result[key] = entry } } return result, nil case "$sortByOrder": value, err := evaluateManifestValue(operand, env) if err != nil { return nil, err } items := append([]any(nil), manifestArray(value)...) sort.SliceStable(items, func(i, j int) bool { return manifestInt(manifestPathValue(items[i], "order")) < manifestInt(manifestPathValue(items[j], "order")) }) return items, nil case "$add", "$multiply", "$min", "$max": values, ok := operand.([]any) if !ok || len(values) == 0 { return nil, fmt.Errorf("%s requires an array", operator) } result := 0.0 if operator == "$multiply" { result = 1 } else if operator == "$min" { result = math.Inf(1) } else if operator == "$max" { result = math.Inf(-1) } for _, item := range values { value, err := evaluateManifestValue(item, env) if err != nil { return nil, err } number := manifestFloat(value) switch operator { case "$multiply": result *= manifestFloat(value) case "$min": result = math.Min(result, number) case "$max": result = math.Max(result, number) default: result += manifestFloat(value) } } return normalizeManifestNumber(result), nil case "$ceilStep": values, ok := operand.([]any) if !ok || len(values) != 2 { return nil, fmt.Errorf("$ceilStep requires [value, step]") } value, err := evaluateManifestValue(values[0], env) if err != nil { return nil, err } step, err := evaluateManifestValue(values[1], env) if err != nil { return nil, err } stepValue := manifestFloat(step) if stepValue <= 0 { return nil, fmt.Errorf("$ceilStep step must be positive") } return normalizeManifestNumber(math.Ceil(manifestFloat(value)/stepValue) * stepValue), nil case "$eq", "$ne", "$gt", "$gte", "$lt", "$lte", "$in", "$and", "$or": return evaluateManifestComparison(operator, operand, env) case "$not": value, err := evaluateManifestValue(operand, env) return !manifestTruthy(value), err default: return nil, fmt.Errorf("unsupported manifest operator %q", operator) } } func evaluateManifestComparison(operator string, operand any, env map[string]any) (any, error) { values, ok := operand.([]any) if !ok { return nil, fmt.Errorf("%s requires an array", operator) } evaluated := make([]any, 0, len(values)) for _, item := range values { value, err := evaluateManifestValue(item, env) if err != nil { return nil, err } evaluated = append(evaluated, value) } if operator == "$and" || operator == "$or" { result := operator == "$and" for _, value := range evaluated { if operator == "$and" { result = result && manifestTruthy(value) } else { result = result || manifestTruthy(value) } } return result, nil } if len(evaluated) != 2 { return nil, fmt.Errorf("%s requires two operands", operator) } left, right := evaluated[0], evaluated[1] switch operator { case "$eq", "$ne": leftJSON, _ := json.Marshal(left) rightJSON, _ := json.Marshal(right) equal := string(leftJSON) == string(rightJSON) if operator == "$ne" { return !equal, nil } return equal, nil case "$gt": return manifestFloat(left) > manifestFloat(right), nil case "$gte": return manifestFloat(left) >= manifestFloat(right), nil case "$lt": return manifestFloat(left) < manifestFloat(right), nil case "$lte": return manifestFloat(left) <= manifestFloat(right), nil case "$in": for _, item := range manifestArray(right) { leftJSON, _ := json.Marshal(left) itemJSON, _ := json.Marshal(item) if string(leftJSON) == string(itemJSON) { return true, nil } } return false, nil } return false, nil } func interpolateManifestString(value string, env map[string]any) string { result := value for { start := strings.Index(result, "{{") if start < 0 { return result } endOffset := strings.Index(result[start+2:], "}}") if endOffset < 0 { return result } end := start + 2 + endOffset path := strings.TrimSpace(result[start+2 : end]) result = result[:start] + manifestString(manifestPathValue(env, path)) + result[end+2:] } } func manifestPathValue(root any, path string) any { if strings.TrimSpace(path) == "" { return root } value := root for _, part := range strings.Split(strings.Trim(path, "."), ".") { switch current := value.(type) { case map[string]any: value = current[part] case []any: index, err := strconv.Atoi(part) if err != nil || index < 0 || index >= len(current) { return nil } value = current[index] default: return nil } } return value } func cloneManifestEnv(env map[string]any) map[string]any { result := make(map[string]any, len(env)+2) for key, value := range env { result[key] = value } return result } func manifestArray(value any) []any { if value == nil { return nil } if items, ok := value.([]any); ok { return items } return []any{value} } func manifestObject(value any) map[string]any { result, _ := value.(map[string]any) return result } func manifestEmpty(value any) bool { switch typed := value.(type) { case nil: return true case string: return strings.TrimSpace(typed) == "" case []any: return len(typed) == 0 case map[string]any: return len(typed) == 0 default: return false } } func manifestTruthy(value any) bool { switch typed := value.(type) { case nil: return false case bool: return typed case string: normalized := strings.ToLower(strings.TrimSpace(typed)) return normalized != "" && normalized != "false" && normalized != "0" && normalized != "no" && normalized != "null" case float64: return typed != 0 case float32: return typed != 0 case int: return typed != 0 case int64: return typed != 0 case []any: return len(typed) > 0 case map[string]any: return len(typed) > 0 default: return true } } func manifestString(value any) string { switch typed := value.(type) { case nil: return "" case string: return typed case json.Number: return typed.String() case float64: return strconv.FormatFloat(typed, 'f', -1, 64) case float32: return strconv.FormatFloat(float64(typed), 'f', -1, 32) case int: return strconv.Itoa(typed) case int64: return strconv.FormatInt(typed, 10) case bool: return strconv.FormatBool(typed) default: encoded, _ := json.Marshal(value) return string(encoded) } } func manifestFloat(value any) float64 { switch typed := value.(type) { case float64: return typed case float32: return float64(typed) case int: return float64(typed) case int64: return float64(typed) case json.Number: result, _ := typed.Float64() return result case string: result, _ := strconv.ParseFloat(strings.TrimSpace(typed), 64) return result default: return 0 } } func manifestInt(value any) int { return int(manifestFloat(value)) } func normalizeManifestNumber(value float64) any { if math.Trunc(value) == value { return int(value) } return value }