...

Source file src/github.com/launchdarkly/go-sdk-common/v3/ldreason/reason.go

Documentation: github.com/launchdarkly/go-sdk-common/v3/ldreason

     1  package ldreason
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
     7  
     8  	"github.com/launchdarkly/go-jsonstream/v3/jreader"
     9  	"github.com/launchdarkly/go-jsonstream/v3/jwriter"
    10  )
    11  
    12  // EvalReasonKind defines the possible values of [EvaluationReason.GetKind].
    13  type EvalReasonKind string
    14  
    15  const (
    16  	// EvalReasonOff indicates that the flag was off and therefore returned its configured off value.
    17  	EvalReasonOff EvalReasonKind = "OFF"
    18  	// EvalReasonTargetMatch indicates that the context key was specifically targeted for this flag.
    19  	EvalReasonTargetMatch EvalReasonKind = "TARGET_MATCH"
    20  	// EvalReasonRuleMatch indicates that the context matched one of the flag's rules.
    21  	EvalReasonRuleMatch EvalReasonKind = "RULE_MATCH"
    22  	// EvalReasonPrerequisiteFailed indicates that the flag was considered off because it had at
    23  	// least one prerequisite flag that either was off or did not return the desired variation.
    24  	EvalReasonPrerequisiteFailed EvalReasonKind = "PREREQUISITE_FAILED"
    25  	// EvalReasonFallthrough indicates that the flag was on but the context did not match any targets
    26  	// or rules.
    27  	EvalReasonFallthrough EvalReasonKind = "FALLTHROUGH"
    28  	// EvalReasonError indicates that the flag could not be evaluated, e.g. because it does not
    29  	// exist or due to an unexpected error. In this case the result value will be the default value
    30  	// that the caller passed to the client.
    31  	EvalReasonError EvalReasonKind = "ERROR"
    32  )
    33  
    34  // EvalErrorKind defines the possible values of EvaluationReason.GetErrorKind().
    35  type EvalErrorKind string
    36  
    37  const (
    38  	// EvalErrorClientNotReady indicates that the caller tried to evaluate a flag before the client
    39  	// had successfully initialized.
    40  	EvalErrorClientNotReady EvalErrorKind = "CLIENT_NOT_READY"
    41  	// EvalErrorFlagNotFound indicates that the caller provided a flag key that did not match any
    42  	// known flag.
    43  	EvalErrorFlagNotFound EvalErrorKind = "FLAG_NOT_FOUND"
    44  	// EvalErrorMalformedFlag indicates that there was an internal inconsistency in the flag data,
    45  	// e.g. a rule specified a nonexistent variation.
    46  	EvalErrorMalformedFlag EvalErrorKind = "MALFORMED_FLAG"
    47  	// EvalErrorUserNotSpecified indicates that the caller passed an invalid or uninitialized
    48  	// context. The name and value of this constant refer to "user" rather than "context" only for
    49  	// backward compatibility with older SDKs that used the term "user".
    50  	EvalErrorUserNotSpecified EvalErrorKind = "USER_NOT_SPECIFIED"
    51  	// EvalErrorWrongType indicates that the result value was not of the requested type, e.g. you
    52  	// called BoolVariationDetail but the value was an integer.
    53  	EvalErrorWrongType EvalErrorKind = "WRONG_TYPE"
    54  	// EvalErrorException indicates that an unexpected error stopped flag evaluation; check the
    55  	// log for details.
    56  	EvalErrorException EvalErrorKind = "EXCEPTION"
    57  )
    58  
    59  // BigSegmentsStatus defines the possible values of [EvaluationReason.GetBigSegmentsStatus].
    60  //
    61  // "Big segments" are a specific type of segments. For more information, read the LaunchDarkly
    62  // documentation: https://docs.launchdarkly.com/home/contexts/big-segments
    63  type BigSegmentsStatus string
    64  
    65  const (
    66  	// BigSegmentsHealthy indicates that the Big Segment query involved in the flag
    67  	// evaluation was successful, and that the segment state is considered up to date.
    68  	BigSegmentsHealthy BigSegmentsStatus = "HEALTHY"
    69  
    70  	// BigSegmentsStale indicates that the Big Segment query involved in the flag
    71  	// evaluation was successful, but that the segment state may not be up to date.
    72  	BigSegmentsStale BigSegmentsStatus = "STALE"
    73  
    74  	// BigSegmentsNotConfigured indicates that Big Segments could not be queried for the
    75  	// flag evaluation because the SDK configuration did not include a big segment store.
    76  	BigSegmentsNotConfigured BigSegmentsStatus = "NOT_CONFIGURED"
    77  
    78  	// BigSegmentsStoreError indicates that the Big Segment query involved in the flag
    79  	// evaluation failed, for instance due to a database error.
    80  	BigSegmentsStoreError BigSegmentsStatus = "STORE_ERROR"
    81  )
    82  
    83  // EvaluationReason describes the reason that a flag evaluation producted a particular value.
    84  //
    85  // This struct is immutable; its properties can be accessed only via getter methods.
    86  type EvaluationReason struct {
    87  	kind              EvalReasonKind
    88  	ruleIndex         ldvalue.OptionalInt
    89  	ruleID            string
    90  	prerequisiteKey   string
    91  	inExperiment      bool
    92  	errorKind         EvalErrorKind
    93  	bigSegmentsStatus BigSegmentsStatus
    94  }
    95  
    96  // IsDefined returns true if this EvaluationReason has a non-empty [EvaluationReason.GetKind]. It is
    97  // false for a zero value of EvaluationReason{}.
    98  func (r EvaluationReason) IsDefined() bool {
    99  	return r.kind != ""
   100  }
   101  
   102  // String returns a concise string representation of the reason. Examples: "OFF", "ERROR(WRONG_TYPE)".
   103  //
   104  // This value is intended only for convenience in logging or debugging. Application code should not
   105  // rely on its specific format.
   106  func (r EvaluationReason) String() string {
   107  	switch r.kind {
   108  	case EvalReasonRuleMatch:
   109  		return fmt.Sprintf("%s(%d,%s)", r.kind, r.ruleIndex.OrElse(0), r.ruleID)
   110  	case EvalReasonPrerequisiteFailed:
   111  		return fmt.Sprintf("%s(%s)", r.kind, r.prerequisiteKey)
   112  	case EvalReasonError:
   113  		return fmt.Sprintf("%s(%s)", r.kind, r.errorKind)
   114  	default:
   115  		return string(r.GetKind())
   116  	}
   117  }
   118  
   119  // GetKind describes the general category of the reason.
   120  func (r EvaluationReason) GetKind() EvalReasonKind {
   121  	return r.kind
   122  }
   123  
   124  // GetRuleIndex provides the index of the rule that was matched (0 being the first), if
   125  // the Kind is [EvalReasonRuleMatch]. Otherwise it returns -1.
   126  func (r EvaluationReason) GetRuleIndex() int {
   127  	return r.ruleIndex.OrElse(-1)
   128  }
   129  
   130  // GetRuleID provides the unique identifier of the rule that was matched, if the Kind is
   131  // [EvalReasonRuleMatch]. Otherwise it returns an empty string. Unlike the rule index, this
   132  // identifier will not change if other rules are added or deleted.
   133  func (r EvaluationReason) GetRuleID() string {
   134  	return r.ruleID
   135  }
   136  
   137  // GetPrerequisiteKey provides the flag key of the prerequisite that failed, if the Kind
   138  // is [EvalReasonPrerequisiteFailed]. Otherwise it returns an empty string.
   139  func (r EvaluationReason) GetPrerequisiteKey() string {
   140  	return r.prerequisiteKey
   141  }
   142  
   143  // IsInExperiment describes whether the evaluation was part of an experiment. It returns
   144  // true if the evaluation resulted in an experiment rollout *and* served one of the
   145  // variations in the experiment.  Otherwise it returns false.
   146  func (r EvaluationReason) IsInExperiment() bool {
   147  	return r.inExperiment
   148  }
   149  
   150  // GetErrorKind describes the general category of the error, if the Kind is [EvalReasonError].
   151  // Otherwise it returns an empty string.
   152  func (r EvaluationReason) GetErrorKind() EvalErrorKind {
   153  	return r.errorKind
   154  }
   155  
   156  // GetBigSegmentsStatus describes the validity of Big Segment information, if and only if the flag
   157  // evaluation required querying at least one Big Segment. Otherwise it returns an empty string.
   158  //
   159  // "Big segments" are a specific kind of segments. For more information, read the LaunchDarkly
   160  // documentation: https://docs.launchdarkly.com/home/contexts/big-segments
   161  func (r EvaluationReason) GetBigSegmentsStatus() BigSegmentsStatus {
   162  	return r.bigSegmentsStatus
   163  }
   164  
   165  // NewEvalReasonOff returns an EvaluationReason whose Kind is [EvalReasonOff].
   166  func NewEvalReasonOff() EvaluationReason {
   167  	return EvaluationReason{kind: EvalReasonOff}
   168  }
   169  
   170  // NewEvalReasonFallthrough returns an EvaluationReason whose Kind is [EvalReasonFallthrough].
   171  func NewEvalReasonFallthrough() EvaluationReason {
   172  	return EvaluationReason{kind: EvalReasonFallthrough}
   173  }
   174  
   175  // NewEvalReasonFallthroughExperiment returns an EvaluationReason whose Kind is
   176  // [EvalReasonFallthrough]. The inExperiment parameter represents whether the evaluation was
   177  // part of an experiment.
   178  func NewEvalReasonFallthroughExperiment(inExperiment bool) EvaluationReason {
   179  	return EvaluationReason{kind: EvalReasonFallthrough, inExperiment: inExperiment}
   180  }
   181  
   182  // NewEvalReasonTargetMatch returns an EvaluationReason whose Kind is [EvalReasonTargetMatch].
   183  func NewEvalReasonTargetMatch() EvaluationReason {
   184  	return EvaluationReason{kind: EvalReasonTargetMatch}
   185  }
   186  
   187  // NewEvalReasonRuleMatch returns an EvaluationReason whose Kind is [EvalReasonRuleMatch].
   188  func NewEvalReasonRuleMatch(ruleIndex int, ruleID string) EvaluationReason {
   189  	return EvaluationReason{kind: EvalReasonRuleMatch,
   190  		ruleIndex: ldvalue.NewOptionalInt(ruleIndex), ruleID: ruleID}
   191  }
   192  
   193  // NewEvalReasonRuleMatchExperiment returns an EvaluationReason whose Kind is
   194  // [EvalReasonRuleMatch]. The inExperiment parameter represents whether the evaluation was
   195  // part of an experiment.
   196  func NewEvalReasonRuleMatchExperiment(ruleIndex int, ruleID string, inExperiment bool) EvaluationReason {
   197  	return EvaluationReason{
   198  		kind:         EvalReasonRuleMatch,
   199  		ruleIndex:    ldvalue.NewOptionalInt(ruleIndex),
   200  		ruleID:       ruleID,
   201  		inExperiment: inExperiment,
   202  	}
   203  }
   204  
   205  // NewEvalReasonPrerequisiteFailed returns an EvaluationReason whose Kind is [EvalReasonPrerequisiteFailed].
   206  func NewEvalReasonPrerequisiteFailed(prereqKey string) EvaluationReason {
   207  	return EvaluationReason{kind: EvalReasonPrerequisiteFailed, prerequisiteKey: prereqKey}
   208  }
   209  
   210  // NewEvalReasonError returns an EvaluationReason whose Kind is [EvalReasonError].
   211  func NewEvalReasonError(errorKind EvalErrorKind) EvaluationReason {
   212  	return EvaluationReason{kind: EvalReasonError, errorKind: errorKind}
   213  }
   214  
   215  // NewEvalReasonFromReasonWithBigSegmentsStatus returns a copy of an EvaluationReason
   216  // with a specific [BigSegmentsStatus] value added.
   217  func NewEvalReasonFromReasonWithBigSegmentsStatus(
   218  	reason EvaluationReason,
   219  	bigSegmentsStatus BigSegmentsStatus,
   220  ) EvaluationReason {
   221  	reason.bigSegmentsStatus = bigSegmentsStatus
   222  	return reason
   223  }
   224  
   225  // MarshalJSON implements custom JSON serialization for EvaluationReason.
   226  func (r EvaluationReason) MarshalJSON() ([]byte, error) {
   227  	return jwriter.MarshalJSONWithWriter(r)
   228  }
   229  
   230  // UnmarshalJSON implements custom JSON deserialization for EvaluationReason.
   231  func (r *EvaluationReason) UnmarshalJSON(data []byte) error {
   232  	return jreader.UnmarshalJSONWithReader(data, r)
   233  }
   234  
   235  // ReadFromJSONReader provides JSON deserialization for use with the jsonstream API.
   236  //
   237  // This implementation is used by the SDK in cases where it is more efficient than [encoding/json.Unmarshal].
   238  // See [github.com/launchdarkly/go-jsonstream/v3] for more details.
   239  func (r *EvaluationReason) ReadFromJSONReader(reader *jreader.Reader) {
   240  	var ret EvaluationReason
   241  	for obj := reader.ObjectOrNull(); obj.Next(); {
   242  		switch string(obj.Name()) {
   243  		case "kind":
   244  			ret.kind = EvalReasonKind(reader.String())
   245  		case "ruleId":
   246  			ret.ruleID = reader.String()
   247  		case "ruleIndex":
   248  			ret.ruleIndex = ldvalue.NewOptionalInt(reader.Int())
   249  		case "errorKind":
   250  			ret.errorKind = EvalErrorKind(reader.String())
   251  		case "prerequisiteKey":
   252  			ret.prerequisiteKey = reader.String()
   253  		case "inExperiment":
   254  			ret.inExperiment = reader.Bool()
   255  		case "bigSegmentsStatus":
   256  			ret.bigSegmentsStatus = BigSegmentsStatus(reader.String())
   257  		}
   258  	}
   259  	if reader.Error() == nil {
   260  		*r = ret
   261  	}
   262  }
   263  
   264  // WriteToJSONWriter provides JSON serialization for use with the jsonstream API.
   265  //
   266  // This implementation is used by the SDK in cases where it is more efficient than [encoding/json.Marshal].
   267  // See [github.com/launchdarkly/go-jsonstream/v3] for more details.
   268  func (r EvaluationReason) WriteToJSONWriter(w *jwriter.Writer) {
   269  	if r.kind == "" {
   270  		w.Null()
   271  		return
   272  	}
   273  	obj := w.Object()
   274  	obj.Name("kind").String(string(r.kind))
   275  	if r.ruleIndex.IsDefined() {
   276  		obj.Name("ruleIndex").Int(r.ruleIndex.OrElse(0))
   277  		obj.Maybe("ruleId", r.ruleID != "").String(r.ruleID)
   278  	}
   279  	obj.Maybe("inExperiment", r.inExperiment).Bool(r.inExperiment)
   280  	if r.kind == EvalReasonPrerequisiteFailed {
   281  		obj.Name("prerequisiteKey").String(r.prerequisiteKey)
   282  	}
   283  	if r.kind == EvalReasonError {
   284  		obj.Name("errorKind").String(string(r.errorKind))
   285  	}
   286  	if r.bigSegmentsStatus != "" {
   287  		obj.Name("bigSegmentsStatus").String(string(r.bigSegmentsStatus))
   288  	}
   289  	obj.End()
   290  }
   291  

View as plain text