...

Source file src/github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel/model_unmarshal.go

Documentation: github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel

     1  package ldmodel
     2  
     3  import (
     4  	"github.com/launchdarkly/go-sdk-common/v3/ldattr"
     5  	"github.com/launchdarkly/go-sdk-common/v3/ldcontext"
     6  	"github.com/launchdarkly/go-sdk-common/v3/ldtime"
     7  	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
     8  
     9  	"github.com/launchdarkly/go-jsonstream/v3/jreader"
    10  )
    11  
    12  func unmarshalFeatureFlagFromBytes(data []byte) (FeatureFlag, error) {
    13  	r := jreader.NewReader(data)
    14  	parsed := unmarshalFeatureFlagFromReader(&r)
    15  	if err := r.Error(); err != nil {
    16  		return FeatureFlag{}, jreader.ToJSONError(err, &parsed)
    17  	}
    18  	return parsed, nil
    19  }
    20  
    21  func unmarshalFeatureFlagFromReader(r *jreader.Reader) FeatureFlag {
    22  	var parsed FeatureFlag
    23  	readFeatureFlag(r, &parsed)
    24  	if r.Error() == nil {
    25  		PreprocessFlag(&parsed)
    26  	}
    27  	return parsed
    28  }
    29  
    30  func unmarshalSegmentFromBytes(data []byte) (Segment, error) {
    31  	r := jreader.NewReader(data)
    32  	parsed := unmarshalSegmentFromReader(&r)
    33  	if err := r.Error(); err != nil {
    34  		return Segment{}, jreader.ToJSONError(err, &parsed)
    35  	}
    36  	return parsed, nil
    37  }
    38  
    39  func unmarshalSegmentFromReader(r *jreader.Reader) Segment {
    40  	var parsed Segment
    41  	readSegment(r, &parsed)
    42  	if r.Error() == nil {
    43  		PreprocessSegment(&parsed)
    44  	}
    45  	return parsed
    46  }
    47  
    48  func readFeatureFlag(r *jreader.Reader, flag *FeatureFlag) {
    49  	deprecatedClientSide := false
    50  
    51  	for obj := r.Object(); obj.Next(); {
    52  		name := obj.Name()
    53  		switch string(name) {
    54  		case "key":
    55  			flag.Key = r.String()
    56  		case "on":
    57  			flag.On = r.Bool()
    58  		case "prerequisites":
    59  			readPrerequisites(r, &flag.Prerequisites)
    60  		case "targets":
    61  			readTargets(r, &flag.Targets)
    62  		case "contextTargets":
    63  			readTargets(r, &flag.ContextTargets)
    64  		case "rules":
    65  			readFlagRules(r, &flag.Rules)
    66  		case "fallthrough":
    67  			readVariationOrRollout(r, &flag.Fallthrough)
    68  		case "offVariation":
    69  			flag.OffVariation.ReadFromJSONReader(r)
    70  		case "variations":
    71  			readValueList(r, &flag.Variations)
    72  		case "clientSideAvailability":
    73  			readClientSideAvailability(r, &flag.ClientSideAvailability)
    74  		case "clientSide":
    75  			deprecatedClientSide = r.Bool()
    76  		case "salt":
    77  			flag.Salt = r.String()
    78  		case "trackEvents":
    79  			flag.TrackEvents = r.Bool()
    80  		case "trackEventsFallthrough":
    81  			flag.TrackEventsFallthrough = r.Bool()
    82  		case "debugEventsUntilDate":
    83  			val, _ := r.Float64OrNull() // val will be zero if null
    84  			flag.DebugEventsUntilDate = ldtime.UnixMillisecondTime(val)
    85  		case "version":
    86  			flag.Version = r.Int()
    87  		case "deleted":
    88  			flag.Deleted = r.Bool()
    89  		}
    90  	}
    91  
    92  	if !flag.ClientSideAvailability.Explicit {
    93  		flag.ClientSideAvailability = ClientSideAvailability{
    94  			UsingMobileKey:     true, // always assumed to be true in the old schema
    95  			UsingEnvironmentID: deprecatedClientSide,
    96  			Explicit:           false,
    97  		}
    98  	}
    99  }
   100  
   101  func readPrerequisites(r *jreader.Reader, out *[]Prerequisite) {
   102  	for arr := r.ArrayOrNull(); arr.Next(); {
   103  		var prereq Prerequisite
   104  		for obj := r.Object(); obj.Next(); {
   105  			switch string(obj.Name()) {
   106  			case "key":
   107  				prereq.Key = r.String()
   108  			case "variation":
   109  				prereq.Variation = r.Int()
   110  			}
   111  		}
   112  		*out = append(*out, prereq)
   113  	}
   114  }
   115  
   116  func readTargets(r *jreader.Reader, out *[]Target) {
   117  	for arr := r.ArrayOrNull(); arr.Next(); {
   118  		var t Target
   119  		for obj := r.Object(); obj.Next(); {
   120  			switch string(obj.Name()) {
   121  			case "contextKind":
   122  				t.ContextKind = ldcontext.Kind(r.String())
   123  			case "values":
   124  				readStringList(r, &t.Values)
   125  			case "variation":
   126  				t.Variation = r.Int()
   127  			}
   128  		}
   129  		*out = append(*out, t)
   130  	}
   131  }
   132  
   133  func readFlagRules(r *jreader.Reader, out *[]FlagRule) {
   134  	for arr := r.ArrayOrNull(); arr.Next(); {
   135  		rule := FlagRule{}
   136  		for obj := r.Object(); obj.Next(); {
   137  			switch string(obj.Name()) {
   138  			case "id":
   139  				rule.ID = r.String()
   140  			case "variation":
   141  				rule.Variation.ReadFromJSONReader(r)
   142  			case "rollout":
   143  				readRollout(r, &rule.Rollout)
   144  			case "clauses":
   145  				readClauses(r, &rule.Clauses)
   146  			case "trackEvents":
   147  				rule.TrackEvents = r.Bool()
   148  			}
   149  		}
   150  		*out = append(*out, rule)
   151  	}
   152  }
   153  
   154  func readClauses(r *jreader.Reader, out *[]Clause) {
   155  	for arr := r.ArrayOrNull(); arr.Next(); {
   156  		var clause Clause
   157  		var attrStr string
   158  		for obj := r.Object(); obj.Next(); {
   159  			switch string(obj.Name()) {
   160  			case "contextKind":
   161  				clause.ContextKind = ldcontext.Kind(r.String())
   162  			case "attribute":
   163  				attrStr, _ = r.StringOrNull()
   164  			case "op":
   165  				clause.Op = Operator(r.String())
   166  			case "values":
   167  				readValueList(r, &clause.Values)
   168  			case "negate":
   169  				clause.Negate = r.Bool()
   170  			}
   171  		}
   172  		setAttrNameOrRef(attrStr, clause.ContextKind, &clause.Attribute)
   173  		*out = append(*out, clause)
   174  	}
   175  }
   176  
   177  func readVariationOrRollout(r *jreader.Reader, out *VariationOrRollout) {
   178  	for obj := r.Object(); obj.Next(); {
   179  		switch string(obj.Name()) {
   180  		case "variation":
   181  			out.Variation.ReadFromJSONReader(r)
   182  		case "rollout":
   183  			readRollout(r, &out.Rollout)
   184  		}
   185  	}
   186  }
   187  
   188  func readRollout(r *jreader.Reader, out *Rollout) {
   189  	obj := r.ObjectOrNull()
   190  	if !obj.IsDefined() {
   191  		*out = Rollout{}
   192  		return
   193  	}
   194  	var bucketByStr string
   195  	for obj.Next() {
   196  		switch string(obj.Name()) {
   197  		case "kind":
   198  			out.Kind = RolloutKind(r.String())
   199  		case "contextKind":
   200  			out.ContextKind = ldcontext.Kind(r.String())
   201  		case "variations":
   202  			for arr := r.Array(); arr.Next(); {
   203  				var wv WeightedVariation
   204  				for wrObj := r.Object(); wrObj.Next(); {
   205  					switch string(wrObj.Name()) {
   206  					case "variation":
   207  						wv.Variation = r.Int()
   208  					case "weight":
   209  						wv.Weight = r.Int()
   210  					case "untracked":
   211  						wv.Untracked = r.Bool()
   212  					}
   213  				}
   214  				out.Variations = append(out.Variations, wv)
   215  			}
   216  		case "bucketBy":
   217  			bucketByStr, _ = r.StringOrNull()
   218  		case "seed":
   219  			if n, ok := r.IntOrNull(); ok {
   220  				out.Seed = ldvalue.NewOptionalInt(n)
   221  			}
   222  		}
   223  	}
   224  	setAttrNameOrRef(bucketByStr, out.ContextKind, &out.BucketBy)
   225  }
   226  
   227  func readClientSideAvailability(r *jreader.Reader, out *ClientSideAvailability) {
   228  	obj := r.ObjectOrNull()
   229  	out.Explicit = obj.IsDefined()
   230  	for obj.Next() {
   231  		switch string(obj.Name()) {
   232  		case "usingEnvironmentId":
   233  			out.UsingEnvironmentID = r.Bool()
   234  		case "usingMobileKey":
   235  			out.UsingMobileKey = r.Bool()
   236  		}
   237  	}
   238  }
   239  
   240  func readSegment(r *jreader.Reader, segment *Segment) {
   241  	for obj := r.Object(); obj.Next(); {
   242  		switch string(obj.Name()) {
   243  		case "key":
   244  			segment.Key = r.String()
   245  		case "version":
   246  			segment.Version = r.Int()
   247  		case "generation":
   248  			segment.Generation.ReadFromJSONReader(r)
   249  		case "deleted":
   250  			segment.Deleted = r.Bool()
   251  		case "included":
   252  			readStringList(r, &segment.Included)
   253  		case "excluded":
   254  			readStringList(r, &segment.Excluded)
   255  		case "includedContexts":
   256  			readSegmentTargets(r, &segment.IncludedContexts)
   257  		case "excludedContexts":
   258  			readSegmentTargets(r, &segment.ExcludedContexts)
   259  		case "rules":
   260  			for rulesArr := r.ArrayOrNull(); rulesArr.Next(); {
   261  				rule := SegmentRule{}
   262  				var bucketByStr string
   263  				for ruleObj := r.Object(); ruleObj.Next(); {
   264  					switch string(ruleObj.Name()) {
   265  					case "id":
   266  						rule.ID = r.String()
   267  					case "clauses":
   268  						readClauses(r, &rule.Clauses)
   269  					case "weight":
   270  						if v, ok := r.IntOrNull(); ok {
   271  							rule.Weight = ldvalue.NewOptionalInt(v)
   272  						}
   273  					case "bucketBy":
   274  						bucketByStr, _ = r.StringOrNull()
   275  					case "rolloutContextKind":
   276  						rule.RolloutContextKind = ldcontext.Kind(r.String())
   277  					}
   278  				}
   279  				setAttrNameOrRef(bucketByStr, rule.RolloutContextKind, &rule.BucketBy)
   280  				segment.Rules = append(segment.Rules, rule)
   281  			}
   282  		case "salt":
   283  			segment.Salt = r.String()
   284  		case "unbounded":
   285  			segment.Unbounded = r.Bool()
   286  		case "unboundedContextKind":
   287  			segment.UnboundedContextKind = ldcontext.Kind(r.String())
   288  		}
   289  	}
   290  }
   291  
   292  func readSegmentTargets(r *jreader.Reader, out *[]SegmentTarget) {
   293  	for arr := r.ArrayOrNull(); arr.Next(); {
   294  		var t SegmentTarget
   295  		for obj := r.Object(); obj.Next(); {
   296  			switch string(obj.Name()) {
   297  			case "contextKind":
   298  				t.ContextKind = ldcontext.Kind(r.String())
   299  			case "values":
   300  				readStringList(r, &t.Values)
   301  			}
   302  		}
   303  		*out = append(*out, t)
   304  	}
   305  }
   306  
   307  func readStringList(r *jreader.Reader, out *[]string) {
   308  	for arr := r.ArrayOrNull(); arr.Next(); {
   309  		*out = append(*out, r.String())
   310  	}
   311  }
   312  
   313  func readValueList(r *jreader.Reader, out *[]ldvalue.Value) {
   314  	for arr := r.ArrayOrNull(); arr.Next(); {
   315  		var v ldvalue.Value
   316  		v.ReadFromJSONReader(r)
   317  		*out = append(*out, v)
   318  	}
   319  }
   320  
   321  func setAttrNameOrRef(value string, contextKind ldcontext.Kind, out *ldattr.Ref) {
   322  	switch {
   323  	case value == "":
   324  		*out = ldattr.Ref{}
   325  		// Note that we're not distinguishing here between an empty string and an omitted property;
   326  		// "" would not be valid parameter to NewRef, and historically been a value LD may send for
   327  		// these fields so we are treating either "" or null as "undefined".
   328  
   329  	case contextKind == "":
   330  		// If the context kind was not specified in this clause/rollout/etc., then this is old-style
   331  		// data and we must interpret the attribute property as a plain attribute name, not an attribute
   332  		// reference (in other words, a leading slash would be just part of the name).
   333  		*out = ldattr.NewLiteralRef(value)
   334  
   335  	default:
   336  		*out = ldattr.NewRef(value)
   337  	}
   338  }
   339  

View as plain text