Files
insights-go/feature_flag_errors.go
zeekayandHanzo Dev 8ee8950230 Go 1.27.1, and the code it can say more simply
The module declares go 1.27.1 and the adapter image follows. go fix
applied the modernizers and left ptrString and ptrBool with no callers,
so they are gone; omitzero is not applied, since it changes what the
JSON says.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-16 19:52:31 -07:00

169 lines
5.3 KiB
Go

package insights
import (
"context"
"errors"
"fmt"
"net"
"os"
"strings"
)
// ErrFlagNotFound is returned when a feature flag does not exist or is disabled.
// Use errors.Is(err, ErrFlagNotFound) to check for this error.
var ErrFlagNotFound = errors.New("feature flag not found")
// Feature flag error type constants for the $feature_flag_error property.
// These values are sent in analytics events to track flag evaluation failures.
// They should not be changed without considering impact on existing dashboards
// and queries that filter on these values.
const (
// FeatureFlagErrorErrorsWhileComputing indicates the server returned errorsWhileComputingFlags=true
FeatureFlagErrorErrorsWhileComputing = "errors_while_computing_flags"
// FeatureFlagErrorFlagMissing indicates the requested flag was not in the API response
FeatureFlagErrorFlagMissing = "flag_missing"
// FeatureFlagErrorEvaluationFailed indicates the flag was present but failed to evaluate due to a transient server error
FeatureFlagErrorEvaluationFailed = "evaluation_failed"
// FeatureFlagErrorQuotaLimited indicates rate/quota limit was exceeded
FeatureFlagErrorQuotaLimited = "quota_limited"
// FeatureFlagErrorTimeout indicates request timed out
FeatureFlagErrorTimeout = "timeout"
// FeatureFlagErrorConnectionError indicates network connectivity issue
FeatureFlagErrorConnectionError = "connection_error"
// FeatureFlagErrorUnknownError indicates an unexpected error occurred
FeatureFlagErrorUnknownError = "unknown_error"
// FeatureFlagErrorAPIErrorPrefix is the prefix for API errors with status codes
FeatureFlagErrorAPIErrorPrefix = "api_error_"
)
// APIError represents an HTTP API error with a status code.
// This allows classifyError to generate api_error_{status} strings.
type APIError struct {
// StatusCode is the HTTP status code returned by the PostHog API.
StatusCode int
// Message is the error message returned by or derived from the API response.
Message string
}
// Error returns the API error message.
func (e *APIError) Error() string {
return e.Message
}
// NewAPIError creates an APIError with the given HTTP status code and message.
func NewAPIError(statusCode int, message string) *APIError {
return &APIError{
StatusCode: statusCode,
Message: message,
}
}
// featureFlagEvaluationResult holds internal evaluation context
// along with any error information that occurred during evaluation.
type featureFlagEvaluationResult struct {
Value any
Err error
ErrorsWhileComputingFlags bool
QuotaLimited bool
FlagMissing bool
FlagFailed bool
RequestID *string
EvaluatedAt *int64
FlagDetail *FlagDetail
}
// classifyError determines the error type string for a given error.
// Returns api_error_{status}, timeout, connection_error, or unknown_error.
//
// Classification priority:
// 1. API errors (*APIError) - returns api_error_{status}
// 2. Sentinel errors (context.DeadlineExceeded, context.Canceled)
// 3. Interface checks (net.Error, os.IsTimeout)
// 4. Concrete types (*net.DNSError, *net.OpError)
// 5. Default to unknown_error (acceptable for analytics)
func classifyError(err error) string {
if err == nil {
return ""
}
// Check API errors first (application-level errors with status codes)
if apiErr, ok := errors.AsType[*APIError](err); ok {
return fmt.Sprintf("%s%d", FeatureFlagErrorAPIErrorPrefix, apiErr.StatusCode)
}
// Check sentinel errors (works with wrapped errors)
if errors.Is(err, context.DeadlineExceeded) {
return FeatureFlagErrorTimeout
}
if errors.Is(err, context.Canceled) {
return FeatureFlagErrorTimeout
}
// Check os.IsTimeout (handles various timeout wrappers)
if os.IsTimeout(err) {
return FeatureFlagErrorTimeout
}
// Check net.Error interface
if netErr, ok := errors.AsType[net.Error](err); ok {
if netErr.Timeout() {
return FeatureFlagErrorTimeout
}
return FeatureFlagErrorConnectionError
}
// Check specific network error types
if _, ok := errors.AsType[*net.DNSError](err); ok {
return FeatureFlagErrorConnectionError
}
if _, ok := errors.AsType[*net.OpError](err); ok {
return FeatureFlagErrorConnectionError
}
// Default to unknown - this is acceptable for analytics.
// We prefer reliable classification over broad but fragile string matching.
return FeatureFlagErrorUnknownError
}
// GetErrorString returns the error string for the $feature_flag_error property.
// Returns empty string if there are no errors.
// Multiple errors are joined with commas.
func (r *featureFlagEvaluationResult) GetErrorString() string {
var errorStrings []string
// Classify request error
if r.Err != nil {
errorStrings = append(errorStrings, classifyError(r.Err))
}
// Server had errors computing flags
if r.ErrorsWhileComputingFlags {
errorStrings = append(errorStrings, FeatureFlagErrorErrorsWhileComputing)
}
// Quota limited
if r.QuotaLimited {
errorStrings = append(errorStrings, FeatureFlagErrorQuotaLimited)
}
// Flag was not in the response
if r.FlagMissing {
errorStrings = append(errorStrings, FeatureFlagErrorFlagMissing)
}
// Flag was present but failed to evaluate
if r.FlagFailed {
errorStrings = append(errorStrings, FeatureFlagErrorEvaluationFailed)
}
return strings.Join(errorStrings, ",")
}