mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
b3f7c2cb30
### Why are the changes in this PR needed? The DAP debugger currently truncates variable values to a hardcoded limit of 100 characters, with no way for callers to configure it. Long values cannot be inspected or copied whole from a debugger UI. ### What are the changes in this PR? - Add a new `SetMaxVariableLength` Debugger option (in both `v1/debug` and the top-level `debug` package) - A value of 0 disables truncation; the default stays 100 characters for backward compatibility - Plumb the limit through `variableManager` and `namedVar`, so it applies to top-level and nested (object/array/set) variables alike - Relax `truncatedString` to return the original string unchanged when the limit is <= 0 - Add `TestTruncatedString` and `TestVariableValueLengthLimit` covering the default limit, unlimited (0), negative, custom limits, and boundary cases ### Notes - `go build ./...` and `go test ./v1/debug/...` both pass - Default behavior is unchanged; the new option is opt-in - This PR was developed with AI assistance (Claude Code) ### Further comments Refs #8890 --------- Signed-off-by: summy wu <summy.wu81@gmail.com>
193 lines
3.8 KiB
Go
193 lines
3.8 KiB
Go
// Copyright 2024 The OPA Authors. All rights reserved.
|
|
// Use of this source code is governed by an Apache2
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package debug
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/open-policy-agent/opa/v1/ast"
|
|
)
|
|
|
|
type Variable interface {
|
|
// Name returns the name of the variable.
|
|
Name() string
|
|
|
|
// Type returns the type of the variable.
|
|
Type() string
|
|
|
|
// Value returns the value of the variable.
|
|
Value() string
|
|
|
|
// VariablesReference returns a reference to the variables that are children of this variable.
|
|
// E.g. this variable is a collection, such as an array, set, or object.
|
|
VariablesReference() VarRef
|
|
}
|
|
|
|
type namedVar struct {
|
|
name string
|
|
value ast.Value
|
|
maxVariableLength int
|
|
}
|
|
|
|
func (nv namedVar) Name() string {
|
|
return nv.name
|
|
}
|
|
|
|
func (nv namedVar) Type() string {
|
|
return valueTypeName(nv.value)
|
|
}
|
|
|
|
func (nv namedVar) Value() string {
|
|
return truncatedString(nv.value.String(), nv.maxVariableLength)
|
|
}
|
|
|
|
type variableGetter func() []namedVar
|
|
|
|
type variableManager struct {
|
|
getters []variableGetter
|
|
maxVariableLength int
|
|
}
|
|
|
|
func newVariableManager(maxVariableLength int) *variableManager {
|
|
return &variableManager{maxVariableLength: maxVariableLength}
|
|
}
|
|
|
|
func (vs *variableManager) addVars(getter variableGetter) VarRef {
|
|
vs.getters = append(vs.getters, getter)
|
|
return VarRef(len(vs.getters))
|
|
}
|
|
|
|
type VarRef int
|
|
|
|
type variable struct {
|
|
v namedVar
|
|
ref VarRef
|
|
}
|
|
|
|
func (v variable) Name() string {
|
|
return v.v.Name()
|
|
}
|
|
|
|
func (v variable) Type() string {
|
|
return v.v.Type()
|
|
}
|
|
|
|
func (v variable) Value() string {
|
|
return v.v.Value()
|
|
}
|
|
|
|
func (v variable) VariablesReference() VarRef {
|
|
return v.ref
|
|
}
|
|
|
|
func (vs *variableManager) vars(varRef VarRef) ([]Variable, error) {
|
|
i := int(varRef) - 1
|
|
if i < 0 || i >= len(vs.getters) {
|
|
return nil, fmt.Errorf("invalid variable reference: %d", varRef)
|
|
}
|
|
|
|
namedVar := vs.getters[i]()
|
|
vars := make([]Variable, len(namedVar))
|
|
|
|
for i, nv := range namedVar {
|
|
nv.maxVariableLength = vs.maxVariableLength
|
|
vars[i] = variable{
|
|
v: nv,
|
|
ref: vs.subVars(nv.value),
|
|
}
|
|
}
|
|
|
|
slices.SortFunc(vars, func(a, b Variable) int {
|
|
return strings.Compare(a.Name(), b.Name())
|
|
})
|
|
|
|
return vars, nil
|
|
}
|
|
|
|
// truncatedString truncates s to at most max characters. When max <= 3 it returns
|
|
// s unchanged to avoid a negative slice index (s[:max-2]) and to prevent producing
|
|
// output longer than max. Otherwise strings longer than max are truncated to
|
|
// s[:max-2] + "...".
|
|
func truncatedString(s string, max int) string {
|
|
if max <= 3 {
|
|
return s
|
|
}
|
|
if len(s) > max {
|
|
return s[:max-2] + "..."
|
|
}
|
|
return s
|
|
}
|
|
|
|
func valueTypeName(v ast.Value) string {
|
|
switch v.(type) {
|
|
case ast.Null:
|
|
return "null"
|
|
case ast.Boolean:
|
|
return "boolean"
|
|
case ast.Number:
|
|
return "number"
|
|
case ast.String:
|
|
return "string"
|
|
case *ast.Array:
|
|
return "array"
|
|
case ast.Object:
|
|
return "object"
|
|
case ast.Set:
|
|
return "set"
|
|
case ast.Ref:
|
|
return "ref"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
func (vs *variableManager) subVars(v ast.Value) VarRef {
|
|
if obj, ok := v.(ast.Object); ok {
|
|
vars := make([]namedVar, 0, obj.Len())
|
|
if err := obj.Iter(func(k, v *ast.Term) error {
|
|
vars = append(vars, namedVar{
|
|
name: k.String(),
|
|
value: v.Value,
|
|
})
|
|
return nil
|
|
}); err != nil {
|
|
return 0
|
|
}
|
|
return vs.addVars(func() []namedVar {
|
|
return vars
|
|
})
|
|
}
|
|
|
|
if arr, ok := v.(*ast.Array); ok {
|
|
vars := make([]namedVar, 0, arr.Len())
|
|
for i := range arr.Len() {
|
|
vars = append(vars, namedVar{
|
|
name: strconv.Itoa(i),
|
|
value: arr.Elem(i).Value,
|
|
})
|
|
}
|
|
return vs.addVars(func() []namedVar {
|
|
return vars
|
|
})
|
|
}
|
|
|
|
if set, ok := v.(ast.Set); ok {
|
|
vars := make([]namedVar, 0, set.Len())
|
|
for _, elem := range set.Slice() {
|
|
vars = append(vars, namedVar{
|
|
value: elem.Value,
|
|
})
|
|
}
|
|
return vs.addVars(func() []namedVar {
|
|
return vars
|
|
})
|
|
}
|
|
|
|
return 0
|
|
}
|