diff options
| author | mo khan <mo@mokhan.ca> | 2025-07-24 17:58:01 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-07-24 17:58:01 -0600 |
| commit | 72296119fc9755774719f8f625ad03e0e0ec457a (patch) | |
| tree | ed236ddee12a20fb55b7cfecf13f62d3a000dcb5 /vendor/github.com/authzed/spicedb/pkg/spiceerrors | |
| parent | a920a8cfe415858bb2777371a77018599ffed23f (diff) | |
| parent | eaa1bd3b8e12934aed06413d75e7482ac58d805a (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/spicedb/pkg/spiceerrors')
9 files changed, 481 insertions, 0 deletions
diff --git a/vendor/github.com/authzed/spicedb/pkg/spiceerrors/assert_off.go b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/assert_off.go new file mode 100644 index 0000000..20ac7ef --- /dev/null +++ b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/assert_off.go @@ -0,0 +1,21 @@ +//go:build !ci +// +build !ci + +package spiceerrors + +const DebugAssertionsEnabled = false + +// DebugAssert is a no-op in non-CI builds +func DebugAssert(condition func() bool, format string, args ...any) { + // Do nothing on purpose +} + +// DebugAssertNotNil is a no-op in non-CI builds +func DebugAssertNotNil(obj any, format string, args ...any) { + // Do nothing on purpose +} + +// SetFinalizerForDebugging is a no-op in non-CI builds +func SetFinalizerForDebugging[T any](obj interface{}, finalizer func(obj T)) { + // Do nothing on purpose +} diff --git a/vendor/github.com/authzed/spicedb/pkg/spiceerrors/assert_on.go b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/assert_on.go new file mode 100644 index 0000000..a214147 --- /dev/null +++ b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/assert_on.go @@ -0,0 +1,31 @@ +//go:build ci +// +build ci + +package spiceerrors + +import ( + "fmt" + "runtime" +) + +const DebugAssertionsEnabled = true + +// DebugAssert panics if the condition is false in CI builds. +func DebugAssert(condition func() bool, format string, args ...any) { + if !condition() { + panic(fmt.Sprintf(format, args...)) + } +} + +// DebugAssertNotNil panics if the object is nil in CI builds. +func DebugAssertNotNil(obj any, format string, args ...any) { + if obj == nil { + panic(fmt.Sprintf(format, args...)) + } +} + +// SetFinalizerForDebugging sets a finalizer on the object for debugging purposes +// in CI builds. +func SetFinalizerForDebugging[T any](obj interface{}, finalizer func(obj T)) { + runtime.SetFinalizer(obj, finalizer) +} diff --git a/vendor/github.com/authzed/spicedb/pkg/spiceerrors/bug.go b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/bug.go new file mode 100644 index 0000000..7680c33 --- /dev/null +++ b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/bug.go @@ -0,0 +1,35 @@ +package spiceerrors + +import ( + "fmt" + "os" + "strings" + + "github.com/go-errors/errors" +) + +// IsInTests returns true if go test is running +// Based on: https://stackoverflow.com/a/58945030 +func IsInTests() bool { + for _, arg := range os.Args { + if strings.HasPrefix(arg, "-test.") { + return true + } + } + return false +} + +// MustPanic is a special function for panicing when necessary to violate the linter. +func MustPanic(format string, args ...any) { + panic(fmt.Sprintf(format, args...)) +} + +// MustBugf returns an error representing a bug in the system. Will panic if run under testing. +func MustBugf(format string, args ...any) error { + if IsInTests() { + panic(fmt.Sprintf(format, args...)) + } + + e := errors.Errorf(format, args...) + return fmt.Errorf("BUG: %s", e.ErrorStack()) +} diff --git a/vendor/github.com/authzed/spicedb/pkg/spiceerrors/common.go b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/common.go new file mode 100644 index 0000000..625faef --- /dev/null +++ b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/common.go @@ -0,0 +1,88 @@ +package spiceerrors + +import ( + "errors" + "maps" +) + +// SourcePosition is a position in the input source. +type SourcePosition struct { + // LineNumber is the 1-indexed line number in the input source. + LineNumber int + + // ColumnPosition is the 1-indexed column position in the input source. + ColumnPosition int +} + +// WithSourceError is an error that includes the source text and position +// information. +type WithSourceError struct { + error + + // SourceCodeString is the input source code string for the error. + SourceCodeString string + + // LineNumber is the (1-indexed) line number of the error, or 0 if unknown. + LineNumber uint64 + + // ColumnPosition is the (1-indexed) column position of the error, or 0 if + // unknown. + ColumnPosition uint64 +} + +// HasMetadata indicates that the error has metadata defined. +type HasMetadata interface { + // DetailsMetadata returns the metadata for details for this error. + DetailsMetadata() map[string]string +} + +// Unwrap returns the inner, wrapped error. +func (err *WithSourceError) Unwrap() error { + return err.error +} + +// NewWithSourceError creates and returns a new WithSourceError. +func NewWithSourceError(err error, sourceCodeString string, oneIndexedLineNumber uint64, oneIndexedColumnPosition uint64) *WithSourceError { + return &WithSourceError{err, sourceCodeString, oneIndexedLineNumber, oneIndexedColumnPosition} +} + +// AsWithSourceError returns the error as an WithSourceError, if applicable. +func AsWithSourceError(err error) (*WithSourceError, bool) { + var serr *WithSourceError + if errors.As(err, &serr) { + return serr, true + } + return nil, false +} + +// WithAdditionalDetailsError is an error that includes additional details. +type WithAdditionalDetailsError struct { + error + + // AdditionalDetails is a map of additional details for the error. + AdditionalDetails map[string]string +} + +func NewWithAdditionalDetailsError(err error) *WithAdditionalDetailsError { + return &WithAdditionalDetailsError{err, nil} +} + +// Unwrap returns the inner, wrapped error. +func (err *WithAdditionalDetailsError) Unwrap() error { + return err.error +} + +func (err *WithAdditionalDetailsError) WithAdditionalDetails(key string, value string) { + if err.AdditionalDetails == nil { + err.AdditionalDetails = make(map[string]string) + } + err.AdditionalDetails[key] = value +} + +func (err *WithAdditionalDetailsError) AddToDetails(details map[string]string) map[string]string { + if err.AdditionalDetails != nil { + maps.Copy(details, err.AdditionalDetails) + } + + return details +} diff --git a/vendor/github.com/authzed/spicedb/pkg/spiceerrors/details_metadata.go b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/details_metadata.go new file mode 100644 index 0000000..20d7795 --- /dev/null +++ b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/details_metadata.go @@ -0,0 +1,112 @@ +package spiceerrors + +import ( + "fmt" + + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/runtime/protoiface" +) + +// MetadataKey is the type used to represent the keys of the metadata map in +// the error details. +type MetadataKey string + +// DebugTraceErrorDetailsKey is the key used to store the debug trace in the error details. +// The value is expected to be a string containing the proto text of a DebugInformation message. +const DebugTraceErrorDetailsKey MetadataKey = "debug_trace_proto_text" + +// AppendDetailsMetadata appends the key-value pair to the error details metadata. +// If the error is nil or is not a status error, it is returned as is. +func AppendDetailsMetadata(err error, key MetadataKey, value string) error { + if err == nil { + return nil + } + + s, ok := status.FromError(err) + if !ok { + return err + } + + var foundErrDetails *errdetails.ErrorInfo + var otherDetails []protoiface.MessageV1 + for _, details := range s.Details() { + if errInfo, ok := details.(*errdetails.ErrorInfo); ok { + foundErrDetails = errInfo + } else if cast, ok := details.(protoiface.MessageV1); ok { + otherDetails = append(otherDetails, cast) + } + } + + if foundErrDetails != nil { + if foundErrDetails.Metadata == nil { + foundErrDetails.Metadata = make(map[string]string, 1) + } + + foundErrDetails.Metadata[string(key)] = value + } + + return WithCodeAndDetailsAsError( + fmt.Errorf("%s", s.Message()), + s.Code(), + append(otherDetails, foundErrDetails)..., + ) +} + +// WithReplacedDetails replaces the details of the error with the provided details. +// If the error is nil or is not a status error, it is returned as is. +// If the error does not have the details to replace, the provided details are appended. +func WithReplacedDetails[T protoiface.MessageV1](err error, toReplace T) error { + if err == nil { + return nil + } + + s, ok := status.FromError(err) + if !ok { + return err + } + + details := make([]protoiface.MessageV1, 0, len(s.Details())) + wasReplaced := false + for _, current := range s.Details() { + if _, ok := current.(T); ok { + details = append(details, toReplace) + wasReplaced = true + continue + } + + if cast, ok := current.(protoiface.MessageV1); ok { + details = append(details, cast) + } + } + + if !wasReplaced { + details = append(details, toReplace) + } + + return WithCodeAndDetailsAsError( + fmt.Errorf("%s", s.Message()), + s.Code(), + details..., + ) +} + +// GetDetails returns the details of the error if they are of the provided type, if any. +func GetDetails[T protoiface.MessageV1](err error) (T, bool) { + if err == nil { + return *new(T), false + } + + s, ok := status.FromError(err) + if !ok { + return *new(T), false + } + + for _, details := range s.Details() { + if cast, ok := details.(T); ok { + return cast, true + } + } + + return *new(T), false +} diff --git a/vendor/github.com/authzed/spicedb/pkg/spiceerrors/termination.go b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/termination.go new file mode 100644 index 0000000..58360eb --- /dev/null +++ b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/termination.go @@ -0,0 +1,66 @@ +package spiceerrors + +import "time" + +// TerminationError represents an error that captures contextual information to make +// available on process termination. The error will be marshalled as JSON and +// serialized into a file-path specified via CLI arguments +type TerminationError struct { + error + Component string `json:"component"` + Timestamp time.Time `json:"timestamp"` + ErrorString string `json:"error"` + Metadata map[string]string `json:"metadata"` + exitCode int +} + +// ExitCode returns the exit code to be returned on process termination +func (e TerminationError) ExitCode() int { + return e.exitCode +} + +// ErrorBuilder is a fluent-style builder for TerminationError +type ErrorBuilder struct { + terminationErr TerminationError +} + +// TerminationError returns the built termination TerminationError +func (eb *ErrorBuilder) Error() TerminationError { + return eb.terminationErr +} + +// Component specifies the component in SpiceDB that +func (eb *ErrorBuilder) Component(component string) *ErrorBuilder { + eb.terminationErr.Component = component + return eb +} + +// Metadata adds a new key-value pair of metadata to the termination TerminationError being built +func (eb *ErrorBuilder) Metadata(key, value string) *ErrorBuilder { + eb.terminationErr.Metadata[key] = value + return eb +} + +// ExitCode defines the ExitCode to be used upon process termination. Defaults to 1 if not specified. +func (eb *ErrorBuilder) ExitCode(exitCode int) *ErrorBuilder { + eb.terminationErr.exitCode = exitCode + return eb +} + +// Timestamp defines the time of the error. Defaults to time.Now().UTC() if not specified. +func (eb *ErrorBuilder) Timestamp(timestamp time.Time) *ErrorBuilder { + eb.terminationErr.Timestamp = timestamp + return eb +} + +// NewTerminationErrorBuilder returns a new ErrorBuilder for a termination.TerminationError. +func NewTerminationErrorBuilder(err error) *ErrorBuilder { + return &ErrorBuilder{terminationErr: TerminationError{ + error: err, + Component: "unspecified", + Timestamp: time.Now().UTC(), + ErrorString: err.Error(), + Metadata: make(map[string]string, 0), + exitCode: 1, + }} +} diff --git a/vendor/github.com/authzed/spicedb/pkg/spiceerrors/testutil.go b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/testutil.go new file mode 100644 index 0000000..db71540 --- /dev/null +++ b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/testutil.go @@ -0,0 +1,28 @@ +package spiceerrors + +import ( + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc/status" + + v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" +) + +// RequireReason asserts that an error is a gRPC error and returns the expected +// reason in the ErrorInfo. +// TODO(jschorr): Move into grpcutil. +func RequireReason(t testing.TB, reason v1.ErrorReason, err error, expectedMetadataKeys ...string) { + require.Error(t, err) + withStatus, ok := status.FromError(err) + require.True(t, ok) + require.True(t, len(withStatus.Details()) > 0) + + info := withStatus.Details()[0].(*errdetails.ErrorInfo) + require.Equal(t, v1.ErrorReason_name[int32(reason)], info.GetReason()) + + for _, expectedKey := range expectedMetadataKeys { + require.Contains(t, info.Metadata, expectedKey) + } +} diff --git a/vendor/github.com/authzed/spicedb/pkg/spiceerrors/util.go b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/util.go new file mode 100644 index 0000000..744e41b --- /dev/null +++ b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/util.go @@ -0,0 +1,18 @@ +package spiceerrors + +import ( + "maps" +) + +type WithMetadata interface { + DetailsMetadata() map[string]string +} + +// CombineMetadata combines the metadata found on an existing error with that given. +func CombineMetadata(withMetadata WithMetadata, metadata map[string]string) map[string]string { + clone := maps.Clone(withMetadata.DetailsMetadata()) + for key, value := range metadata { + clone[key] = value + } + return clone +} diff --git a/vendor/github.com/authzed/spicedb/pkg/spiceerrors/withstatus.go b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/withstatus.go new file mode 100644 index 0000000..1ee6e0a --- /dev/null +++ b/vendor/github.com/authzed/spicedb/pkg/spiceerrors/withstatus.go @@ -0,0 +1,82 @@ +package spiceerrors + +import ( + "errors" + + log "github.com/authzed/spicedb/internal/logging" + + "google.golang.org/genproto/googleapis/rpc/errdetails" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/runtime/protoiface" + + v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" +) + +// Domain is the domain used for all errors. +const Domain = "authzed.com" + +// WithCodeAndDetails returns a gRPC status message containing the error's message, the given +// status code and any supplied details. +func WithCodeAndDetails(err error, code codes.Code, details ...protoiface.MessageV1) *status.Status { + created := status.New(code, err.Error()) + created, derr := created.WithDetails(details...) + if derr != nil { + log.Err(derr).Str("provided-error", err.Error()).Msg("could not add details to provided error") + } + return created +} + +// WithCodeAndDetailsAsError returns an error containing the error's message, the given +// status code and any supplied details. +func WithCodeAndDetailsAsError(err error, code codes.Code, details ...protoiface.MessageV1) error { + status := WithCodeAndDetails(err, code, details...) + return withStatusError{err, status} +} + +// ForReason returns an ErrorInfo block for a specific error reason as defined in the V1 API. +func ForReason(reason v1.ErrorReason, metadata map[string]string) *errdetails.ErrorInfo { + return &errdetails.ErrorInfo{ + Reason: v1.ErrorReason_name[int32(reason)], + Domain: Domain, + Metadata: metadata, + } +} + +// WithCodeAndReason returns a new error which wraps the existing error with a gRPC code and +// a reason block. +func WithCodeAndReason(err error, code codes.Code, reason v1.ErrorReason) error { + metadata := map[string]string{} + + var hasMetadata HasMetadata + if ok := errors.As(err, &hasMetadata); ok { + metadata = hasMetadata.DetailsMetadata() + } + + status := WithCodeAndDetails(err, code, ForReason(reason, metadata)) + return withStatusError{err, status} +} + +type SupportsAdditionalMetadata interface { + WithAdditionalDetails(key MetadataKey, value string) +} + +// WithAdditionalDetails adds an additional details field to the error if it is possible. +func WithAdditionalDetails(err error, key MetadataKey, value string) bool { + var supportsAdditionalDetails SupportsAdditionalMetadata + if ok := errors.As(err, &supportsAdditionalDetails); ok { + supportsAdditionalDetails.WithAdditionalDetails(key, value) + return true + } + + return false +} + +type withStatusError struct { + error + status *status.Status +} + +func (err withStatusError) GRPCStatus() *status.Status { + return err.status +} |
