...

Source file src/github.com/launchdarkly/go-sdk-common/v3/ldvalue/value_base.go

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

     1  package ldvalue
     2  
     3  import (
     4  	"encoding/json"
     5  
     6  	"golang.org/x/exp/slices"
     7  )
     8  
     9  // Value represents any of the data types supported by JSON, all of which can be used for a LaunchDarkly
    10  // feature flag variation, or for an attribute in an evaluation context. Value instances are immutable.
    11  //
    12  // # Uses of JSON types in LaunchDarkly
    13  //
    14  // LaunchDarkly feature flags can have variations of any JSON type other than null. If you want to
    15  // evaluate a feature flag in a general way that does not have expectations about the variation type,
    16  // or if the variation value is a complex data structure such as an array or object, you can use the
    17  // SDK method [github.com/launchdarkly/go-server-sdk/v6/LDClient.JSONVariationDetail] to get the value
    18  // and then use Value methods to examine it.
    19  //
    20  // Similarly, attributes of an evaluation context ([github.com/launchdarkly/go-sdk-common/v3/ldcontext.Context])
    21  // can have variations of any JSON type other than null. If you want to set a context attribute in a general
    22  // way that will accept any type, or set the attribute value to a complex data structure such as an array
    23  // or object, you can use the builder method [github.com/launchdarkly/go-sdk-common/v3/ldcontext.Builder.SetValue].
    24  //
    25  // Arrays and objects have special meanings in LaunchDarkly flag evaluation:
    26  //   - An array of values means "try to match any of these values to the targeting rule."
    27  //   - An object allows you to match a property within the object to the targeting rule. For instance,
    28  //     in the example above, a targeting rule could reference /objectAttr1/color to match the value
    29  //     "green". Nested property references like /objectAttr1/address/street are allowed if a property
    30  //     contains another JSON object.
    31  //
    32  // # Constructors and builders
    33  //
    34  // The most efficient way to create a Value is with typed constructors such as [Bool] and [String], or, for
    35  // complex data, the builders [ArrayBuild] and [ObjectBuild]. However, any value that can be represented in
    36  // JSON can be converted to a Value with [CopyArbitraryValue] or [FromJSONMarshal], or you can parse a
    37  // Value from JSON. The following code examples all produce the same value:
    38  //
    39  //	value := ldvalue.ObjectBuild().SetString("thing", "x").Build()
    40  //
    41  //	type MyStruct struct {
    42  //		Thing string `json:"thing"`
    43  //	}
    44  //	value := ldvalue.FromJSONMarshal(MyStruct{Thing: "x"})
    45  //
    46  //	value := ldvalue.Parse([]byte(`{"thing": "x"}`))
    47  //
    48  // # Comparisons
    49  //
    50  // You cannot compare Value instances with the == operator, because the struct may contain a slice. or a
    51  // map. Value has the [Value.Equal] method for this purpose; [reflect.DeepEqual] will also work.
    52  type Value struct {
    53  	valueType ValueType
    54  	// Used when the value is a boolean.
    55  	boolValue bool
    56  	// Used when the value is a number.
    57  	numberValue float64
    58  	// Used when the value is a string.
    59  	stringValue string
    60  	// Used when the value is an array, zero-valued otherwise.
    61  	arrayValue ValueArray
    62  	// Used when the value is an object, zero-valued otherwise.
    63  	objectValue ValueMap
    64  	rawValue    []byte
    65  }
    66  
    67  // ValueType indicates which JSON type is contained in a [Value].
    68  type ValueType int
    69  
    70  // String returns the name of the value type.
    71  func (t ValueType) String() string {
    72  	switch t {
    73  	case NullType:
    74  		return nullAsJSON
    75  	case BoolType:
    76  		return "bool"
    77  	case NumberType:
    78  		return "number"
    79  	case StringType:
    80  		return "string"
    81  	case ArrayType:
    82  		return "array"
    83  	case ObjectType:
    84  		return "object"
    85  	case RawType:
    86  		return "raw"
    87  	default:
    88  		return "unknown"
    89  	}
    90  }
    91  
    92  // Null creates a null Value.
    93  func Null() Value {
    94  	return Value{valueType: NullType}
    95  }
    96  
    97  // Bool creates a boolean Value.
    98  func Bool(value bool) Value {
    99  	return Value{valueType: BoolType, boolValue: value}
   100  }
   101  
   102  // Int creates a numeric Value from an integer.
   103  //
   104  // Note that all numbers are represented internally as the same type (float64), so Int(2) is
   105  // exactly equal to Float64(2).
   106  func Int(value int) Value {
   107  	return Float64(float64(value))
   108  }
   109  
   110  // Float64 creates a numeric Value from a float64.
   111  func Float64(value float64) Value {
   112  	return Value{valueType: NumberType, numberValue: value}
   113  }
   114  
   115  // String creates a string Value.
   116  func String(value string) Value {
   117  	return Value{valueType: StringType, stringValue: value}
   118  }
   119  
   120  // Raw creates an unparsed JSON Value.
   121  //
   122  // This constructor stores a copy of the [json.RawMessage] value as-is without syntax validation, and
   123  // sets the type of the Value to [RawType]. The advantage of this is that if you have some
   124  // data that you do not expect to do any computations with, but simply want to include in JSON data
   125  // to be sent to LaunchDarkly, the original data will be output as-is and does not need to be parsed.
   126  //
   127  // However, if you do anything that involves inspecting the value (such as comparing it to another
   128  // value, or evaluating a feature flag that references the value in a context attribute), the JSON
   129  // data will be parsed automatically each time that happens, so there will be no efficiency gain.
   130  // Therefore, if you expect any such operations to happen, it is better to use [Parse] instead to
   131  // parse the JSON immediately, or use value builder methods such as [ObjectBuild].
   132  //
   133  // If you pass malformed data that is not valid JSON, you will get malformed data if it is re-encoded
   134  // to JSON. It is the caller's responsibility to make sure the json.RawMessage really is valid JSON.
   135  // However, since it is easy to mistakenly write json.RawMessage(nil) (an invalid zero-length value)
   136  // when what is really meant is a JSON null value, this constructor transparently converts both
   137  // json.RawMessage(nil) and json.RawMessage([]byte("")) to Null().
   138  func Raw(value json.RawMessage) Value {
   139  	if len(value) == 0 {
   140  		return Null()
   141  	}
   142  	return Value{valueType: RawType, rawValue: slices.Clone(value)}
   143  }
   144  
   145  // FromJSONMarshal creates a Value from the JSON representation of any Go value.
   146  //
   147  // This is based on [encoding/json.Marshal], so it can be used with any value that can be passed to that
   148  // function, and follows the usual behavior of json.Marshal with regard to types and field names.
   149  // For instance, you could use it with a custom struct type:
   150  //
   151  //	type MyStruct struct {
   152  //		Thing string `json:"thing"`
   153  //	}
   154  //	value := ldvalue.FromJSONMarshal(MyStruct{Thing: "x"})
   155  //
   156  // It is equivalent to calling json.Marshal followed by [Parse], so it incurs the same overhead of
   157  // first marshaling the value and then parsing the resulting JSON.
   158  func FromJSONMarshal(anyValue any) Value {
   159  	jsonBytes, err := json.Marshal(anyValue)
   160  	if err != nil {
   161  		return Null()
   162  	}
   163  	return Parse(jsonBytes)
   164  }
   165  
   166  // CopyArbitraryValue creates a Value from an arbitrary value of any type.
   167  //
   168  // If the value is nil, a boolean, an integer, a floating-point number, or a string, it becomes the
   169  // corresponding JSON primitive value type. If it is a slice of values ([]any or []Value), it is
   170  // deep-copied to an array value. If it is a map of strings to values (map[string]any or
   171  // map[string]Value), it is deep-copied to an object value.
   172  //
   173  // If it is a pointer to any of the above types, then it is dereferenced and treated the same as above,
   174  // unless the pointer is nil, in which case it becomes [Null]().
   175  //
   176  // For all other types, the value is marshaled to JSON and then converted to the corresponding Value
   177  // type, just as if you had called [FromJSONMarshal]. The difference is only that CopyArbitraryValue is
   178  // more efficient for primitive types, slices, and maps, which it can copy without first marshaling
   179  // them to JSON.
   180  func CopyArbitraryValue(anyValue any) Value { //nolint:gocyclo // yes, we know it's a long function
   181  	if anyValue == nil {
   182  		return Null()
   183  		// Note that an interface value can be nil in two ways: nil with no type at all, which is this case,
   184  		// or a nil pointer of some specific pointer type, which we have to check for separately below.
   185  	}
   186  	switch o := anyValue.(type) {
   187  	case Value:
   188  		return o
   189  	case *Value:
   190  		if o == nil {
   191  			return Null()
   192  		}
   193  		return *o
   194  	case OptionalString:
   195  		return o.AsValue()
   196  	case *OptionalString:
   197  		if o == nil {
   198  			return Null()
   199  		}
   200  		return o.AsValue()
   201  	case bool:
   202  		return Bool(o)
   203  	case *bool:
   204  		if o == nil {
   205  			return Null()
   206  		}
   207  		return Bool(*o)
   208  	case int8:
   209  		return Float64(float64(o))
   210  	case *int8:
   211  		if o == nil {
   212  			return Null()
   213  		}
   214  		return Float64(float64(*o))
   215  	case uint8:
   216  		return Float64(float64(o))
   217  	case *uint8:
   218  		if o == nil {
   219  			return Null()
   220  		}
   221  		return Float64(float64(*o))
   222  	case int16:
   223  		return Float64(float64(o))
   224  	case *int16:
   225  		if o == nil {
   226  			return Null()
   227  		}
   228  		return Float64(float64(*o))
   229  	case uint16:
   230  		return Float64(float64(o))
   231  	case *uint16:
   232  		if o == nil {
   233  			return Null()
   234  		}
   235  		return Float64(float64(*o))
   236  	case int:
   237  		return Float64(float64(o))
   238  	case *int:
   239  		if o == nil {
   240  			return Null()
   241  		}
   242  		return Float64(float64(*o))
   243  	case uint:
   244  		return Float64(float64(o))
   245  	case *uint:
   246  		if o == nil {
   247  			return Null()
   248  		}
   249  		return Float64(float64(*o))
   250  	case int32:
   251  		return Float64(float64(o))
   252  	case *int32:
   253  		if o == nil {
   254  			return Null()
   255  		}
   256  		return Float64(float64(*o))
   257  	case uint32:
   258  		return Float64(float64(o))
   259  	case *uint32:
   260  		if o == nil {
   261  			return Null()
   262  		}
   263  		return Float64(float64(*o))
   264  	case float32:
   265  		return Float64(float64(o))
   266  	case *float32:
   267  		if o == nil {
   268  			return Null()
   269  		}
   270  		return Float64(float64(*o))
   271  	case float64:
   272  		return Float64(o)
   273  	case *float64:
   274  		if o == nil {
   275  			return Null()
   276  		}
   277  		return Float64(*o)
   278  	case string:
   279  		return String(o)
   280  	case *string:
   281  		if o == nil {
   282  			return Null()
   283  		}
   284  		return String(*o)
   285  	case []any:
   286  		return copyArbitraryValueArray(o)
   287  	case *[]any:
   288  		if o == nil {
   289  			return Null()
   290  		}
   291  		return copyArbitraryValueArray(*o)
   292  	case []Value:
   293  		return ArrayOf(o...)
   294  	case *[]Value:
   295  		if o == nil {
   296  			return Null()
   297  		}
   298  		return ArrayOf((*o)...)
   299  	case map[string]any:
   300  		return copyArbitraryValueMap(o)
   301  	case *map[string]any:
   302  		if o == nil {
   303  			return Null()
   304  		}
   305  		return copyArbitraryValueMap(*o)
   306  	case map[string]Value:
   307  		return CopyObject(o)
   308  	case *map[string]Value:
   309  		if o == nil {
   310  			return Null()
   311  		}
   312  		return CopyObject(*o)
   313  	case json.RawMessage:
   314  		return Raw(o)
   315  	case *json.RawMessage:
   316  		if o == nil {
   317  			return Null()
   318  		}
   319  		return Raw(*o)
   320  	default:
   321  		return FromJSONMarshal(anyValue)
   322  	}
   323  }
   324  
   325  func copyArbitraryValueArray(o []any) Value {
   326  	return Value{valueType: ArrayType, arrayValue: CopyArbitraryValueArray(o)}
   327  }
   328  
   329  func copyArbitraryValueMap(o map[string]any) Value {
   330  	return Value{valueType: ObjectType, objectValue: CopyArbitraryValueMap(o)}
   331  }
   332  
   333  // Type returns the ValueType of the Value.
   334  func (v Value) Type() ValueType {
   335  	return v.valueType
   336  }
   337  
   338  // IsNull returns true if the Value is a null.
   339  func (v Value) IsNull() bool {
   340  	return v.valueType == NullType || (v.valueType == RawType && v.parseIfRaw().IsNull())
   341  }
   342  
   343  // IsDefined returns true if the Value is anything other than null.
   344  //
   345  // This is exactly equivalent to !v.IsNull(), but is provided as a separate method for consistency
   346  // with other types that have an IsDefined() method.
   347  func (v Value) IsDefined() bool {
   348  	return !v.IsNull()
   349  }
   350  
   351  // IsBool returns true if the Value is a boolean.
   352  func (v Value) IsBool() bool {
   353  	return v.valueType == BoolType || (v.valueType == RawType && v.parseIfRaw().IsBool())
   354  }
   355  
   356  // IsNumber returns true if the Value is numeric.
   357  func (v Value) IsNumber() bool {
   358  	return v.valueType == NumberType || (v.valueType == RawType && v.parseIfRaw().IsNumber())
   359  }
   360  
   361  // IsInt returns true if the Value is an integer.
   362  //
   363  // JSON does not have separate types for integer and floating-point values; they are both just numbers.
   364  // IsInt returns true if and only if the actual numeric value has no fractional component, so
   365  // Int(2).IsInt() and Float64(2.0).IsInt() are both true.
   366  func (v Value) IsInt() bool {
   367  	return (v.valueType == NumberType && v.numberValue == float64(int(v.numberValue))) ||
   368  		(v.valueType == RawType && v.parseIfRaw().IsInt())
   369  }
   370  
   371  // IsString returns true if the Value is a string.
   372  func (v Value) IsString() bool {
   373  	return v.valueType == StringType || (v.valueType == RawType && v.parseIfRaw().IsString())
   374  }
   375  
   376  // BoolValue returns the Value as a boolean.
   377  //
   378  // If the Value is not a boolean, it returns false.
   379  func (v Value) BoolValue() bool {
   380  	switch v.valueType {
   381  	case BoolType:
   382  		return v.boolValue
   383  	case RawType:
   384  		return v.parseIfRaw().BoolValue()
   385  	default:
   386  		return false
   387  	}
   388  }
   389  
   390  // IntValue returns the value as an int.
   391  //
   392  // If the Value is not numeric, it returns zero. If the value is a number but not an integer, it is
   393  // rounded toward zero (truncated).
   394  func (v Value) IntValue() int {
   395  	return int(v.Float64Value())
   396  }
   397  
   398  // Float64Value returns the value as a float64.
   399  //
   400  // If the Value is not numeric, it returns zero.
   401  func (v Value) Float64Value() float64 {
   402  	switch v.valueType {
   403  	case NumberType:
   404  		return v.numberValue
   405  	case RawType:
   406  		return v.parseIfRaw().Float64Value()
   407  	default:
   408  		return 0
   409  	}
   410  }
   411  
   412  // StringValue returns the value as a string.
   413  //
   414  // If the value is not a string, it returns an empty string.
   415  //
   416  // This is different from [String], which returns a string representation of any value type,
   417  // including any necessary JSON delimiters.
   418  func (v Value) StringValue() string {
   419  	switch v.valueType {
   420  	case StringType:
   421  		return v.stringValue
   422  	case RawType:
   423  		return v.parseIfRaw().StringValue()
   424  	default:
   425  		return ""
   426  	}
   427  }
   428  
   429  // AsOptionalString converts the value to the OptionalString type, which contains either a string
   430  // value or nothing if the original value was not a string.
   431  func (v Value) AsOptionalString() OptionalString {
   432  	switch v.valueType {
   433  	case StringType:
   434  		return NewOptionalString(v.stringValue)
   435  	case RawType:
   436  		return v.parseIfRaw().AsOptionalString()
   437  	default:
   438  		return OptionalString{}
   439  	}
   440  }
   441  
   442  // AsRaw returns the value as a [json.RawMessage].
   443  //
   444  // If the value was originally created with [Raw], it returns a copy of the original value. For all
   445  // other values, it converts the value to its JSON representation and returns that representation.
   446  //
   447  // Note that the [Raw] constructor does not do any syntax validation, so if you create a Value from
   448  // a malformed string such as ldvalue.Raw(json.RawMessage("{{{")), you will get back the same string
   449  // from AsRaw().
   450  func (v Value) AsRaw() json.RawMessage {
   451  	if v.valueType == RawType {
   452  		return v.rawValue
   453  	}
   454  	bytes, err := json.Marshal(v)
   455  	if err == nil {
   456  		return json.RawMessage(bytes)
   457  	}
   458  	return nil
   459  }
   460  
   461  // AsArbitraryValue returns the value in its simplest Go representation, typed as "any".
   462  //
   463  // This is nil for a null value; for primitive types, it is bool, float64, or string (all numbers
   464  // are represented as float64 because that is Go's default when parsing from JSON). For unparsed
   465  // JSON data created with [Raw], it returns a [json.RawMessage].
   466  //
   467  // Arrays and objects are represented as []any and map[string]any. They are deep-copied, which
   468  // preserves immutability of the Value but may be an expensive operation. To examine array and
   469  // object values without copying the whole data structure, use getter methods: [Value.Count],
   470  // [Value.Keys], [Value.GetByIndex], [Value.TryGetByIndex], [Value.GetByKey], [Value.TryGetByKey].
   471  func (v Value) AsArbitraryValue() any {
   472  	switch v.valueType {
   473  	case NullType:
   474  		return nil
   475  	case BoolType:
   476  		return v.boolValue
   477  	case NumberType:
   478  		return v.numberValue
   479  	case StringType:
   480  		return v.stringValue
   481  	case ArrayType:
   482  		return v.arrayValue.AsArbitraryValueSlice()
   483  	case ObjectType:
   484  		return v.objectValue.AsArbitraryValueMap()
   485  	case RawType:
   486  		return v.AsRaw()
   487  	default:
   488  		return nil
   489  	}
   490  }
   491  
   492  // String converts the value to a string representation, equivalent to [Value.JSONString].
   493  //
   494  // This is different from [Value.StringValue], which returns the actual string for a string value or an empty
   495  // string for anything else. For instance, Int(2).StringValue() returns "2" and String("x").StringValue()
   496  // returns "\"x\"", whereas Int(2).AsString() returns "" and String("x").AsString() returns
   497  // "x".
   498  //
   499  // This method is provided because it is common to use the [fmt.Stringer] interface as a quick way to
   500  // summarize the contents of a value. The simplest way to do so in this case is to use the JSON
   501  // representation.
   502  func (v Value) String() string {
   503  	return v.JSONString()
   504  }
   505  
   506  // Equal tests whether this Value is equal to another, in both type and value.
   507  //
   508  // For arrays and objects, this is a deep equality test. This method behaves the same as
   509  // [reflect.DeepEqual], but is slightly more efficient.
   510  //
   511  // Unparsed JSON values created with [Raw] will be parsed in order to do this comparison.
   512  func (v Value) Equal(other Value) bool {
   513  	if v.valueType == RawType || other.valueType == RawType {
   514  		return v.parseIfRaw().Equal(other.parseIfRaw())
   515  	}
   516  	if v.valueType == other.valueType {
   517  		switch v.valueType {
   518  		case NullType:
   519  			return true
   520  		case BoolType:
   521  			return v.boolValue == other.boolValue
   522  		case NumberType:
   523  			return v.numberValue == other.numberValue
   524  		case StringType, RawType:
   525  			return v.stringValue == other.stringValue
   526  		case ArrayType:
   527  			return v.arrayValue.Equal(other.arrayValue)
   528  		case ObjectType:
   529  			return v.objectValue.Equal(other.objectValue)
   530  		}
   531  	}
   532  	return false
   533  }
   534  
   535  // AsPointer returns either a pointer to a copy of this Value, or nil if it is a null value.
   536  //
   537  // This may be desirable if you are serializing a struct that contains a Value, and you want
   538  // that field to be completely omitted if the Value is null; since the "omitempty" tag only
   539  // works for pointers, you can declare the field as a *Value like so:
   540  //
   541  //	type MyJsonStruct struct {
   542  //	    AnOptionalField *Value `json:"anOptionalField,omitempty"`
   543  //	}
   544  //	s := MyJsonStruct{AnOptionalField: someValue.AsPointer()}
   545  func (v Value) AsPointer() *Value {
   546  	if v.IsNull() {
   547  		return nil
   548  	}
   549  	return &v
   550  }
   551  
   552  func (v Value) parseIfRaw() Value {
   553  	if v.valueType != RawType {
   554  		return v
   555  	}
   556  	return Parse(v.rawValue)
   557  }
   558  

View as plain text