@@ -156,6 +156,7 @@ public FeatureFlagsState Build()
156156 /// </summary>
157157 /// <param name="valid">true if valid, false if invalid (default is valid)</param>
158158 /// <returns>the same builder</returns>
159+ [ Obsolete ( "Unused, construct a FeatureFlagState with valid/invalid state directly" ) ]
159160 public FeatureFlagsStateBuilder Valid ( bool valid )
160161 {
161162 _valid = valid ;
@@ -167,8 +168,21 @@ public FeatureFlagsStateBuilder Valid(bool valid)
167168 /// </summary>
168169 /// <param name="flagKey">the flag key</param>
169170 /// <param name="result">the evaluation result</param>
170- /// <returns></returns>
171+ /// <returns>the same builder </returns>
171172 public FeatureFlagsStateBuilder AddFlag ( string flagKey , EvaluationDetail < LdValue > result )
173+ {
174+ return AddFlag ( flagKey , result , new List < string > ( ) ) ;
175+ }
176+
177+
178+ /// <summary>
179+ /// Adds the result of a flag evaluation, including direct prerequisites.
180+ /// </summary>
181+ /// <param name="flagKey">the flag key</param>
182+ /// <param name="result">the evaluation result</param>
183+ /// <param name="prerequisites">the direct prerequisites evaluated for this flag</param>
184+ /// <returns>the same builder</returns>
185+ public FeatureFlagsStateBuilder AddFlag ( string flagKey , EvaluationDetail < LdValue > result , List < string > prerequisites )
172186 {
173187 return AddFlag ( flagKey ,
174188 result . Value ,
@@ -177,13 +191,14 @@ public FeatureFlagsStateBuilder AddFlag(string flagKey, EvaluationDetail<LdValue
177191 0 ,
178192 false ,
179193 false ,
180- null ) ;
194+ null ,
195+ prerequisites ) ;
181196 }
182197
183198 // This method is defined with internal scope because metadata fields like trackEvents aren't
184199 // relevant to the main external use case for the builder (testing server-side code)
185200 internal FeatureFlagsStateBuilder AddFlag ( string flagKey , LdValue value , int ? variationIndex , EvaluationReason reason ,
186- int flagVersion , bool flagTrackEvents , bool trackReason , UnixMillisecondTime ? flagDebugEventsUntilDate )
201+ int flagVersion , bool flagTrackEvents , bool trackReason , UnixMillisecondTime ? flagDebugEventsUntilDate , List < string > prerequisites )
187202 {
188203 bool flagIsTracked = flagTrackEvents || flagDebugEventsUntilDate != null ;
189204 var flag = new FlagState
@@ -194,14 +209,15 @@ internal FeatureFlagsStateBuilder AddFlag(string flagKey, LdValue value, int? va
194209 Reason = trackReason || ( _withReasons && ( ! _detailsOnlyIfTracked || flagIsTracked ) ) ? reason : ( EvaluationReason ? ) null ,
195210 DebugEventsUntilDate = flagDebugEventsUntilDate ,
196211 TrackEvents = flagTrackEvents ,
197- TrackReason = trackReason
212+ TrackReason = trackReason ,
213+ Prerequisites = prerequisites
198214 } ;
199215 _flags [ flagKey ] = flag ;
200216 return this ;
201217 }
202218 }
203219
204- internal struct FlagState
220+ internal struct FlagState : IEquatable < FlagState >
205221 {
206222 internal LdValue Value { get ; set ; }
207223 internal int ? Variation { get ; set ; }
@@ -211,24 +227,38 @@ internal struct FlagState
211227 internal UnixMillisecondTime ? DebugEventsUntilDate { get ; set ; }
212228 internal EvaluationReason ? Reason { get ; set ; }
213229
214- public override bool Equals ( object other )
230+ internal IReadOnlyList < string > Prerequisites { get ; set ; }
231+
232+
233+ public bool Equals ( FlagState o )
215234 {
216- if ( other is FlagState o )
217- {
218- return Variation == o . Variation &&
219- Version == o . Version &&
220- TrackEvents == o . TrackEvents &&
221- TrackReason == o . TrackReason &&
222- DebugEventsUntilDate . Equals ( o . DebugEventsUntilDate ) &&
223- Object . Equals ( Reason , o . Reason ) ;
224- }
225- return false ;
235+ return Variation == o . Variation &&
236+ Version == o . Version &&
237+ TrackEvents == o . TrackEvents &&
238+ TrackReason == o . TrackReason &&
239+ DebugEventsUntilDate . Equals ( o . DebugEventsUntilDate ) &&
240+ Object . Equals ( Reason , o . Reason ) &&
241+ Prerequisites . SequenceEqual ( o . Prerequisites ) ;
242+ }
243+ public override bool Equals ( object obj )
244+ {
245+ return obj is FlagState other && Equals ( other ) ;
246+ }
247+
248+ public static bool operator == ( FlagState lhs , FlagState rhs )
249+ {
250+ return lhs . Equals ( rhs ) ;
251+ }
252+
253+ public static bool operator != ( FlagState lhs , FlagState rhs )
254+ {
255+ return ! ( lhs == rhs ) ;
226256 }
227257
228258 public override int GetHashCode ( )
229259 {
230- return new HashCodeBuilder ( ) . With ( Variation ) . With ( Version ) . With ( TrackEvents ) . With ( TrackReason ) .
231- With ( DebugEventsUntilDate ) . With ( Reason ) . Value ;
260+ return new HashCodeBuilder ( ) . With ( Variation ) . With ( Version ) . With ( TrackEvents ) . With ( TrackReason )
261+ . With ( DebugEventsUntilDate ) . With ( Reason ) . With ( Prerequisites ) . Value ;
232262 }
233263 }
234264
@@ -271,6 +301,14 @@ public override void Write(Utf8JsonWriter w, FeatureFlagsState state, JsonSerial
271301 w . WritePropertyName ( "reason" ) ;
272302 EvaluationReasonConverter . WriteJsonValue ( meta . Reason . Value , w ) ;
273303 }
304+ if ( meta . Prerequisites . Count > 0 ) {
305+ w . WriteStartArray ( "prerequisites" ) ;
306+ foreach ( var p in meta . Prerequisites )
307+ {
308+ w . WriteStringValue ( p ) ;
309+ }
310+ w . WriteEndArray ( ) ;
311+ }
274312 w . WriteEndObject ( ) ;
275313 }
276314 w . WriteEndObject ( ) ;
@@ -282,6 +320,7 @@ public override FeatureFlagsState Read(ref Utf8JsonReader reader, Type typeToCon
282320 {
283321 var valid = true ;
284322 var flags = new Dictionary < string , FlagState > ( ) ;
323+
285324 for ( var topLevelObj = RequireObject ( ref reader ) ; topLevelObj . Next ( ref reader ) ; )
286325 {
287326 var key = topLevelObj . Name ;
@@ -295,7 +334,15 @@ public override FeatureFlagsState Read(ref Utf8JsonReader reader, Type typeToCon
295334 for ( var flagsObj = RequireObject ( ref reader ) ; flagsObj . Next ( ref reader ) ; )
296335 {
297336 var subKey = flagsObj . Name ;
298- var flag = flags . ContainsKey ( subKey ) ? flags [ subKey ] : new FlagState ( ) ;
337+
338+ var flag = flags . ContainsKey ( subKey )
339+ ? flags [ subKey ]
340+ : new FlagState
341+ {
342+ // Most flags have no prerequisites, don't allocate capacity unless we need to.
343+ Prerequisites = new List < string > ( 0 )
344+ } ;
345+
299346 for ( var metaObj = RequireObject ( ref reader ) ; metaObj . Next ( ref reader ) ; )
300347 {
301348 switch ( metaObj . Name )
@@ -318,14 +365,39 @@ public override FeatureFlagsState Read(ref Utf8JsonReader reader, Type typeToCon
318365 flag . Reason = reader . TokenType == JsonTokenType . Null ? ( EvaluationReason ? ) null :
319366 EvaluationReasonConverter . ReadJsonValue ( ref reader ) ;
320367 break ;
368+ case "prerequisites" :
369+ // Note: there is an assumption in this code that a given flag key could already
370+ // have been seen before: specifically in the "values" section of the data
371+ // (where it's a simple map of flag key -> evaluated value), but *also* if we
372+ // have duplicate flag keys under the $flagState key.
373+ //
374+ // The first case is expected, but the second is not. LaunchDarkly SaaS / SDKs
375+ // should never generate JSON that has duplicate keys. If this did happen,
376+ // we don't want to 'merge' prerequisites in an arbitrary order: it's important
377+ // that they remain the order they were serialized originally.
378+ //
379+ // Therefore, the behavior here is that the last seen value for a key will 'win'
380+ // and overwrite any previous value.
381+ var prereqList = new List < string > ( ) ;
382+ for ( var prereqs = RequireArray ( ref reader ) ; prereqs . Next ( ref reader ) ; )
383+ {
384+ prereqList . Add ( reader . GetString ( ) ) ;
385+
386+ }
387+ flag . Prerequisites = prereqList ;
388+ break ;
321389 }
322390 }
323391 flags [ subKey ] = flag ;
324392 }
325393 break ;
326394
327395 default :
328- var flagForValue = flags . ContainsKey ( key ) ? flags [ key ] : new FlagState ( ) ;
396+ var flagForValue = flags . ContainsKey ( key ) ? flags [ key ] : new FlagState
397+ {
398+ // Most flags have no prerequisites, don't allocate capacity unless we need to.
399+ Prerequisites = new List < string > ( 0 )
400+ } ;
329401 flagForValue . Value = LdValueConverter . ReadJsonValue ( ref reader ) ;
330402 flags [ key ] = flagForValue ;
331403 break ;
0 commit comments