|
1 | 1 | package dev.openfeature.contrib.providers.flagd;
|
2 | 2 |
|
3 |
| -import dev.openfeature.javasdk.Client; |
4 |
| -import dev.openfeature.javasdk.NoOpProvider; |
5 |
| -import dev.openfeature.javasdk.OpenFeatureAPI; |
| 3 | +import java.util.HashMap; |
| 4 | +import java.util.Map; |
6 | 5 |
|
7 |
| -/** |
8 |
| - * A placeholder. |
| 6 | +import org.apache.commons.lang3.EnumUtils; |
| 7 | +import org.apache.commons.lang3.NotImplementedException; |
| 8 | + |
| 9 | +import com.google.protobuf.Struct; |
| 10 | +import com.google.protobuf.Value; |
| 11 | + |
| 12 | +import dev.openfeature.javasdk.EvaluationContext; |
| 13 | +import dev.openfeature.javasdk.FeatureProvider; |
| 14 | +import dev.openfeature.javasdk.FlagEvaluationOptions; |
| 15 | +import dev.openfeature.javasdk.Metadata; |
| 16 | +import dev.openfeature.javasdk.ProviderEvaluation; |
| 17 | +import dev.openfeature.javasdk.Reason; |
| 18 | +import io.grpc.ManagedChannelBuilder; |
| 19 | +import lombok.extern.slf4j.Slf4j; |
| 20 | +import dev.openfeature.flagd.grpc.Schema.ResolveBooleanRequest; |
| 21 | +import dev.openfeature.flagd.grpc.Schema.ResolveBooleanResponse; |
| 22 | +import dev.openfeature.flagd.grpc.Schema.ResolveFloatRequest; |
| 23 | +import dev.openfeature.flagd.grpc.Schema.ResolveFloatResponse; |
| 24 | +import dev.openfeature.flagd.grpc.Schema.ResolveIntRequest; |
| 25 | +import dev.openfeature.flagd.grpc.Schema.ResolveIntResponse; |
| 26 | +import dev.openfeature.flagd.grpc.Schema.ResolveStringRequest; |
| 27 | +import dev.openfeature.flagd.grpc.Schema.ResolveStringResponse; |
| 28 | +import dev.openfeature.flagd.grpc.ServiceGrpc; |
| 29 | +import dev.openfeature.flagd.grpc.ServiceGrpc.ServiceBlockingStub; |
| 30 | + |
| 31 | +/** |
| 32 | + * OpenFeature provider for flagd. |
9 | 33 | */
|
10 |
| -public class FlagdProvider { |
| 34 | +@Slf4j |
| 35 | +public class FlagdProvider implements FeatureProvider { |
| 36 | + |
| 37 | + private ServiceBlockingStub serviceStub; |
| 38 | + static final String PROVIDER_NAME = "flagD Provider"; |
11 | 39 |
|
12 |
| - /** |
| 40 | + /** |
| 41 | + * Create a new FlagdProvider instance. |
| 42 | + * |
| 43 | + * @param protocol transport protocol, "http" or "https" |
| 44 | + * @param host flagd host, defaults to localhost |
| 45 | + * @param port flagd port, defaults to 8013 |
| 46 | + */ |
| 47 | + public FlagdProvider(String protocol, String host, int port) { |
| 48 | + |
| 49 | + this("https".equalsIgnoreCase(protocol) |
| 50 | + ? ServiceGrpc.newBlockingStub(ManagedChannelBuilder.forAddress(host, port) |
| 51 | + .useTransportSecurity() |
| 52 | + .build()) : |
| 53 | + ServiceGrpc.newBlockingStub(ManagedChannelBuilder.forAddress(host, port) |
| 54 | + .usePlaintext() |
| 55 | + .build())); |
| 56 | + } |
| 57 | + |
| 58 | + /** |
13 | 59 | * Create a new FlagdProvider instance.
|
14 | 60 | */
|
15 | 61 | public FlagdProvider() {
|
| 62 | + this("http", "localhost", 8013); |
16 | 63 | }
|
17 | 64 |
|
18 |
| - /** |
19 |
| - * A test method... |
20 |
| - * |
21 |
| - * @return {boolean} |
| 65 | + /** |
| 66 | + * Create a new FlagdProvider instance. |
| 67 | + * |
| 68 | + * @param serviceStub service stub instance to use |
22 | 69 | */
|
23 |
| - public static boolean test() { |
24 |
| - OpenFeatureAPI.getInstance().setProvider(new NoOpProvider()); |
25 |
| - Client client = OpenFeatureAPI.getInstance().getClient(); |
26 |
| - return client.getBooleanValue("test", true); |
| 70 | + public FlagdProvider(ServiceBlockingStub serviceStub) { |
| 71 | + this.serviceStub = serviceStub; |
| 72 | + } |
| 73 | + |
| 74 | + @Override |
| 75 | + public Metadata getMetadata() { |
| 76 | + return new Metadata() { |
| 77 | + @Override |
| 78 | + public String getName() { |
| 79 | + return PROVIDER_NAME; |
| 80 | + } |
| 81 | + }; |
| 82 | + } |
| 83 | + |
| 84 | + @Override |
| 85 | + public ProviderEvaluation<Boolean> getBooleanEvaluation(String key, Boolean defaultValue, |
| 86 | + EvaluationContext ctx, FlagEvaluationOptions options) { |
| 87 | + |
| 88 | + ResolveBooleanRequest request = ResolveBooleanRequest.newBuilder() |
| 89 | + .setFlagKey(key) |
| 90 | + .setContext(this.convertContext(ctx)) |
| 91 | + .build(); |
| 92 | + ResolveBooleanResponse r = this.serviceStub.resolveBoolean(request); |
| 93 | + return ProviderEvaluation.<Boolean>builder() |
| 94 | + .value(r.getValue()) |
| 95 | + .variant(r.getVariant()) |
| 96 | + .reason(this.mapReason(r.getReason())) |
| 97 | + .build(); |
| 98 | + } |
| 99 | + |
| 100 | + @Override |
| 101 | + public ProviderEvaluation<String> getStringEvaluation(String key, String defaultValue, |
| 102 | + EvaluationContext ctx, FlagEvaluationOptions options) { |
| 103 | + ResolveStringRequest request = ResolveStringRequest.newBuilder() |
| 104 | + .setFlagKey(key) |
| 105 | + .setContext(this.convertContext(ctx)).build(); |
| 106 | + ResolveStringResponse r = this.serviceStub.resolveString(request); |
| 107 | + return ProviderEvaluation.<String>builder().value(r.getValue()) |
| 108 | + .variant(r.getVariant()) |
| 109 | + .reason(this.mapReason(r.getReason())) |
| 110 | + .build(); |
| 111 | + } |
| 112 | + |
| 113 | + @Override |
| 114 | + public ProviderEvaluation<Double> getDoubleEvaluation(String key, Double defaultValue, |
| 115 | + EvaluationContext ctx, FlagEvaluationOptions options) { |
| 116 | + ResolveFloatRequest request = ResolveFloatRequest.newBuilder() |
| 117 | + .setFlagKey(key) |
| 118 | + .setContext(this.convertContext(ctx)) |
| 119 | + .build(); |
| 120 | + ResolveFloatResponse r = this.serviceStub.resolveFloat(request); |
| 121 | + return ProviderEvaluation.<Double>builder() |
| 122 | + .value(r.getValue()) |
| 123 | + .variant(r.getVariant()) |
| 124 | + .reason(this.mapReason(r.getReason())) |
| 125 | + .build(); |
| 126 | + } |
| 127 | + |
| 128 | + @Override |
| 129 | + public ProviderEvaluation<Integer> getIntegerEvaluation(String key, Integer defaultValue, |
| 130 | + EvaluationContext ctx, FlagEvaluationOptions options) { |
| 131 | + ResolveIntRequest request = ResolveIntRequest.newBuilder() |
| 132 | + .setFlagKey(key) |
| 133 | + .setContext(this.convertContext(ctx)) |
| 134 | + .build(); |
| 135 | + ResolveIntResponse r = this.serviceStub.resolveInt(request); |
| 136 | + return ProviderEvaluation.<Integer>builder() |
| 137 | + .value((int) r.getValue()) |
| 138 | + .variant(r.getVariant()) |
| 139 | + .reason(this.mapReason(r.getReason())) |
| 140 | + .build(); |
| 141 | + } |
| 142 | + |
| 143 | + @Override |
| 144 | + public <T> ProviderEvaluation<T> getObjectEvaluation(String key, T defaultValue, |
| 145 | + EvaluationContext ctx, FlagEvaluationOptions options) { |
| 146 | + throw new NotImplementedException(); |
| 147 | + } |
| 148 | + |
| 149 | + // Map FlagD reasons to Java SDK reasons. |
| 150 | + private Reason mapReason(String flagDreason) { |
| 151 | + if (!EnumUtils.isValidEnum(Reason.class, flagDreason)) { |
| 152 | + // until we have "STATIC" in the spec and SDK, we map STATIC to DEFAULT |
| 153 | + if ("STATIC".equals(flagDreason)) { |
| 154 | + return Reason.DEFAULT; |
| 155 | + } else { |
| 156 | + return Reason.UNKNOWN; |
| 157 | + } |
| 158 | + } else { |
| 159 | + return Reason.valueOf(flagDreason); |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + private Struct convertContext(EvaluationContext ctx) { |
| 164 | + // TODO: structure attributes? |
| 165 | + Map<String, Boolean> booleanAttributes = ctx.getBooleanAttributes(); |
| 166 | + Map<String, String> stringAttributes = ctx.getStringAttributes(); |
| 167 | + Map<String, Integer> intAttributes = ctx.getIntegerAttributes(); |
| 168 | + Map<String, Value> values = new HashMap<>(); |
| 169 | + booleanAttributes.keySet().stream().forEach((String key) -> values.put(key, Value |
| 170 | + .newBuilder() |
| 171 | + .setBoolValue(booleanAttributes.get(key)) |
| 172 | + .build())); |
| 173 | + stringAttributes.keySet().stream().forEach((String key) -> values.put(key, |
| 174 | + Value.newBuilder().setStringValue(stringAttributes |
| 175 | + .get(key)) |
| 176 | + .build())); |
| 177 | + intAttributes.keySet().stream().forEach((String key) -> values.put(key, Value |
| 178 | + .newBuilder().setNumberValue(intAttributes.get(key)) |
| 179 | + .build())); |
| 180 | + return Struct.newBuilder() |
| 181 | + .putAllFields(values) |
| 182 | + .build(); |
27 | 183 | }
|
28 | 184 |
|
29 | 185 | }
|
0 commit comments