diff options
Diffstat (limited to 'vendor/github.com/authzed/zed/internal/printers/debug.go')
| -rw-r--r-- | vendor/github.com/authzed/zed/internal/printers/debug.go | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/vendor/github.com/authzed/zed/internal/printers/debug.go b/vendor/github.com/authzed/zed/internal/printers/debug.go new file mode 100644 index 0000000..2dbb9a6 --- /dev/null +++ b/vendor/github.com/authzed/zed/internal/printers/debug.go @@ -0,0 +1,197 @@ +package printers + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/gookit/color" + + v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" + "github.com/authzed/spicedb/pkg/tuple" +) + +// DisplayCheckTrace prints out the check trace found in the given debug message. +func DisplayCheckTrace(checkTrace *v1.CheckDebugTrace, tp *TreePrinter, hasError bool) { + displayCheckTrace(checkTrace, tp, hasError, map[string]struct{}{}) +} + +func displayCheckTrace(checkTrace *v1.CheckDebugTrace, tp *TreePrinter, hasError bool, encountered map[string]struct{}) { + red := color.FgRed.Render + green := color.FgGreen.Render + cyan := color.FgCyan.Render + white := color.FgWhite.Render + faint := color.FgGray.Render + magenta := color.FgMagenta.Render + yellow := color.FgYellow.Render + + orange := color.C256(166).Sprint + purple := color.C256(99).Sprint + lightgreen := color.C256(35).Sprint + caveatColor := color.C256(198).Sprint + + hasPermission := green("✓") + resourceColor := white + permissionColor := color.FgWhite.Render + + switch checkTrace.PermissionType { + case v1.CheckDebugTrace_PERMISSION_TYPE_PERMISSION: + permissionColor = lightgreen + case v1.CheckDebugTrace_PERMISSION_TYPE_RELATION: + permissionColor = orange + } + + switch checkTrace.Result { + case v1.CheckDebugTrace_PERMISSIONSHIP_CONDITIONAL_PERMISSION: + switch checkTrace.CaveatEvaluationInfo.Result { + case v1.CaveatEvalInfo_RESULT_FALSE: + hasPermission = red("⨉") + resourceColor = faint + permissionColor = faint + + case v1.CaveatEvalInfo_RESULT_MISSING_SOME_CONTEXT: + hasPermission = magenta("?") + resourceColor = faint + permissionColor = faint + } + case v1.CheckDebugTrace_PERMISSIONSHIP_NO_PERMISSION: + hasPermission = red("⨉") + resourceColor = faint + permissionColor = faint + case v1.CheckDebugTrace_PERMISSIONSHIP_UNSPECIFIED: + hasPermission = yellow("∵") + } + + additional := "" + if checkTrace.GetWasCachedResult() { + sourceKind := "" + source := checkTrace.Source + if source != "" { + parts := strings.Split(source, ":") + if len(parts) > 0 { + sourceKind = parts[0] + } + } + switch sourceKind { + case "": + additional = cyan(" (cached)") + + case "spicedb": + additional = cyan(" (cached by spicedb)") + + case "materialize": + additional = purple(" (cached by materialize)") + + default: + additional = cyan(fmt.Sprintf(" (cached by %s)", sourceKind)) + } + } else if hasError && isPartOfCycle(checkTrace, map[string]struct{}{}) { + hasPermission = orange("!") + resourceColor = white + } + + isEndOfCycle := false + if hasError { + key := cycleKey(checkTrace) + _, isEndOfCycle = encountered[key] + if isEndOfCycle { + additional = color.C256(166).Sprint(" (cycle)") + } + encountered[key] = struct{}{} + } + + timing := "" + if checkTrace.Duration != nil { + timing = fmt.Sprintf(" (%s)", checkTrace.Duration.AsDuration().String()) + } + + tp = tp.Child( + fmt.Sprintf( + "%s %s:%s %s%s%s", + hasPermission, + resourceColor(checkTrace.Resource.ObjectType), + resourceColor(checkTrace.Resource.ObjectId), + permissionColor(checkTrace.Permission), + additional, + timing, + ), + ) + + if isEndOfCycle { + return + } + + if checkTrace.GetCaveatEvaluationInfo() != nil { + indicator := "" + exprColor := color.FgWhite.Render + switch checkTrace.CaveatEvaluationInfo.Result { + case v1.CaveatEvalInfo_RESULT_FALSE: + indicator = red("⨉") + exprColor = faint + + case v1.CaveatEvalInfo_RESULT_TRUE: + indicator = green("✓") + + case v1.CaveatEvalInfo_RESULT_MISSING_SOME_CONTEXT: + indicator = magenta("?") + } + + white := color.HEXStyle("fff") + white.SetOpts(color.Opts{color.OpItalic}) + + contextMap := checkTrace.CaveatEvaluationInfo.Context.AsMap() + caveatName := checkTrace.CaveatEvaluationInfo.CaveatName + + c := tp.Child(fmt.Sprintf("%s %s %s", indicator, exprColor(checkTrace.CaveatEvaluationInfo.Expression), caveatColor(caveatName))) + if len(contextMap) > 0 { + contextJSON, _ := json.MarshalIndent(contextMap, "", " ") + c.Child(string(contextJSON)) + } else { + if checkTrace.CaveatEvaluationInfo.Result != v1.CaveatEvalInfo_RESULT_MISSING_SOME_CONTEXT { + c.Child(faint("(no matching context found)")) + } + } + + if checkTrace.CaveatEvaluationInfo.Result == v1.CaveatEvalInfo_RESULT_MISSING_SOME_CONTEXT { + c.Child(fmt.Sprintf("missing context: %s", strings.Join(checkTrace.CaveatEvaluationInfo.PartialCaveatInfo.MissingRequiredContext, ", "))) + } + } + + if checkTrace.GetSubProblems() != nil { + for _, subProblem := range checkTrace.GetSubProblems().Traces { + displayCheckTrace(subProblem, tp, hasError, encountered) + } + } else if checkTrace.Result == v1.CheckDebugTrace_PERMISSIONSHIP_HAS_PERMISSION { + tp.Child(purple(fmt.Sprintf("%s:%s %s", checkTrace.Subject.Object.ObjectType, checkTrace.Subject.Object.ObjectId, checkTrace.Subject.OptionalRelation))) + } +} + +func cycleKey(checkTrace *v1.CheckDebugTrace) string { + return fmt.Sprintf("%s#%s", tuple.V1StringObjectRef(checkTrace.Resource), checkTrace.Permission) +} + +func isPartOfCycle(checkTrace *v1.CheckDebugTrace, encountered map[string]struct{}) bool { + if checkTrace.GetSubProblems() == nil { + return false + } + + encounteredCopy := make(map[string]struct{}, len(encountered)) + for k, v := range encountered { + encounteredCopy[k] = v + } + + key := cycleKey(checkTrace) + if _, ok := encounteredCopy[key]; ok { + return true + } + + encounteredCopy[key] = struct{}{} + + for _, subProblem := range checkTrace.GetSubProblems().Traces { + if isPartOfCycle(subProblem, encounteredCopy) { + return true + } + } + + return false +} |
