summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/zed/internal/printers/debug.go
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-24 17:58:01 -0600
committermo khan <mo@mokhan.ca>2025-07-24 17:58:01 -0600
commit72296119fc9755774719f8f625ad03e0e0ec457a (patch)
treeed236ddee12a20fb55b7cfecf13f62d3a000dcb5 /vendor/github.com/authzed/zed/internal/printers/debug.go
parenta920a8cfe415858bb2777371a77018599ffed23f (diff)
parenteaa1bd3b8e12934aed06413d75e7482ac58d805a (diff)
Merge branch 'the-spice-must-flow' into 'main'
Add SpiceDB Authorization See merge request gitlab-org/software-supply-chain-security/authorization/sparkled!19
Diffstat (limited to 'vendor/github.com/authzed/zed/internal/printers/debug.go')
-rw-r--r--vendor/github.com/authzed/zed/internal/printers/debug.go197
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
+}