-
Notifications
You must be signed in to change notification settings - Fork 167
Expand file tree
/
Copy pathFeatureFlagsSdk.cs
More file actions
200 lines (177 loc) · 8.06 KB
/
Copy pathFeatureFlagsSdk.cs
File metadata and controls
200 lines (177 loc) · 8.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// <copyright file="FeatureFlagsSdk.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Datadog.Trace.FeatureFlags;
using OpenFeature.Constant;
using OpenFeature.Model;
namespace Datadog.FeatureFlags.OpenFeature;
/// <summary>
/// Functions to retrieve FeatureFlags from server
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Browsable(false)]
internal static class FeatureFlagsSdk
{
/// <summary> Gets a value indicating whether FeatureFlags framework is available or not </summary>
/// <returns> True if FeatureFlagsSDK is instrumented </returns>
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool IsAvailable() => false;
/// <summary>Gets a value indicating whether APM span enrichment is enabled.</summary>
/// <returns> True when the span-enrichment gate is on </returns>
[MethodImpl(MethodImplOptions.NoInlining)]
internal static bool IsSpanEnrichmentEnabled() => false;
/// <summary>
/// Activates flag configuration delivery and waits for the first configuration to arrive.
/// Delivery only starts here, because requesting configuration is billable and installing the
/// tracer alone must not do it.
/// </summary>
/// <param name="cancellationToken"> Cancellation token supplied by OpenFeature </param>
/// <returns> A task that completes once configuration has arrived, or the initialization timeout has elapsed </returns>
[MethodImpl(MethodImplOptions.NoInlining)]
public static Task InitializeAsync(CancellationToken cancellationToken) => Task.CompletedTask;
/// <summary> Installs an event handler to be fired when a new config has been received </summary>
/// <param name="onNewConfig"> Action to be called when the event is fired </param>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void RegisterOnNewConfigEventHandler(Action onNewConfig)
{
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static IEvaluation? Evaluate(string flagKey, Trace.FeatureFlags.ValueType targetType, object? defaultValue, string? targetingKey, IDictionary<string, object?>? attributes)
{
if (flagKey is null)
{
throw new ArgumentNullException(nameof(flagKey));
}
return null;
}
/// <summary>Accumulates a single flag evaluation into the active root span's FFE span-enrichment state.</summary>
/// <param name="serialId"> Split serial id, or null when absent </param>
/// <param name="doLog"> Whether the allocation authorizes subject logging </param>
/// <param name="targetingKey"> Evaluation-context targeting key, or null </param>
/// <param name="hasVariant"> Whether the evaluation produced a non-empty variant </param>
/// <param name="flagKey"> The flag key (used for runtime defaults) </param>
/// <param name="value"> The evaluated value (used for runtime defaults) </param>
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void AccumulateSpanEnrichment(long? serialId, bool doLog, string? targetingKey, bool hasVariant, string flagKey, object? value)
{
}
public static ResolutionDetails<T> Resolve<T>(string flagKey, Trace.FeatureFlags.ValueType targetType, object? defaultValue, EvaluationContext? context) =>
GetResolutionDetails<T>(Evaluate(flagKey, targetType, defaultValue, context?.TargetingKey, GetContextAttributes(context)));
private static IDictionary<string, object?>? GetContextAttributes(EvaluationContext? context)
{
if (context == null)
{
return null;
}
return context.AsDictionary().Select(p => new KeyValuePair<string, object?>(p.Key, ToObject(p.Value))).ToDictionary(p => p.Key, p => p.Value);
}
private static object? ToObject(Value value) => value switch
{
null => null,
{ IsBoolean: true } => value.AsBoolean,
{ IsString: true } => value.AsString,
{ IsNumber: true } => value.AsDouble,
_ => value.AsObject,
};
private static ResolutionDetails<T> GetResolutionDetails<T>(Datadog.Trace.FeatureFlags.IEvaluation? evaluation)
{
if (evaluation is null)
{
return new ResolutionDetails<T>(
string.Empty,
default!,
ErrorType.ProviderNotReady,
default,
default,
"FeatureFlagsSdk is disabled",
null);
}
var value = typeof(T) == typeof(Value) ? JsonToValue(evaluation.Value) : evaluation.Value!;
var res = new ResolutionDetails<T>(
evaluation.FlagKey,
(T)value,
ToErrorType(evaluation.Reason, evaluation.Error),
ReasonToLowerSnakeCase(evaluation.Reason),
evaluation.Variant,
evaluation.Error,
ToMetadata(evaluation.FlagMetadata));
return res;
}
private static ErrorType ToErrorType(Datadog.Trace.FeatureFlags.EvaluationReason reason, string? errorMessage)
{
return errorMessage switch
{
"FLAG_NOT_FOUND" => ErrorType.FlagNotFound,
"INVALID_CONTEXT" => ErrorType.InvalidContext,
"PARSE_ERROR" => ErrorType.ParseError,
"PROVIDER_FATAL" => ErrorType.ProviderFatal,
"PROVIDER_NOT_READY" => ErrorType.ProviderNotReady,
"TARGETING_KEY_MISSING" => ErrorType.TargetingKeyMissing,
"TYPE_MISMATCH" => ErrorType.TypeMismatch,
"GENERAL" => ErrorType.General,
_ => ErrorType.None,
};
}
// Converts EvaluationReason enum to lower_snake_case string for OpenFeature Reason field.
// Uses cached strings to avoid allocation.
private static string ReasonToLowerSnakeCase(Datadog.Trace.FeatureFlags.EvaluationReason reason) => reason switch
{
Datadog.Trace.FeatureFlags.EvaluationReason.Static => "static",
Datadog.Trace.FeatureFlags.EvaluationReason.Default => "default",
Datadog.Trace.FeatureFlags.EvaluationReason.TargetingMatch => "targeting_match",
Datadog.Trace.FeatureFlags.EvaluationReason.Split => "split",
Datadog.Trace.FeatureFlags.EvaluationReason.Disabled => "disabled",
Datadog.Trace.FeatureFlags.EvaluationReason.Cached => "cached",
Datadog.Trace.FeatureFlags.EvaluationReason.Unknown => "unknown",
Datadog.Trace.FeatureFlags.EvaluationReason.Error => "error",
_ => "unknown"
};
private static ImmutableMetadata ToMetadata(IDictionary<string, string>? metadata)
{
var dic = (metadata ?? new Dictionary<string, string>()).ToDictionary(p => p.Key, p => (object)p.Value);
return new ImmutableMetadata(dic);
}
public static Value JsonToValue(object? obj)
{
try
{
if (obj is null)
{
return new Value();
}
return ConvertObject(obj);
}
catch
{
return new Value();
}
}
private static Value ConvertObject(object? obj) => obj switch
{
Dictionary<string, object?> dic => ConvertStructure(dic),
object?[] arr => ConvertArray(arr),
long intVal => new Value(intVal),
double doubleVal => new Value(doubleVal),
string strVal => new Value(strVal),
bool boolVal => new Value(boolVal),
_ => new Value()
};
private static Value ConvertStructure(Dictionary<string, object?> structure)
{
var dic = structure.ToDictionary(p => p.Key, p => ConvertObject(p.Value));
return new Value(new Structure(dic));
}
private static Value ConvertArray(object?[] array)
{
return new Value(array.Select(ConvertObject).ToList());
}
}