diff --git a/pom.xml b/pom.xml index 90aad9e8..dacd26a2 100644 --- a/pom.xml +++ b/pom.xml @@ -88,6 +88,11 @@ + + com.google.code.gson + gson + 2.10.1 + io.grpc grpc-netty-shaded diff --git a/src/main/java/com/spotify/confidence/ConfidenceFeatureProvider.java b/src/main/java/com/spotify/confidence/ConfidenceFeatureProvider.java index 3396227f..bb488602 100644 --- a/src/main/java/com/spotify/confidence/ConfidenceFeatureProvider.java +++ b/src/main/java/com/spotify/confidence/ConfidenceFeatureProvider.java @@ -1,6 +1,8 @@ package com.spotify.confidence; import com.google.common.base.Strings; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; import com.google.protobuf.Struct; import com.google.protobuf.util.Values; import com.spotify.confidence.flags.resolver.v1.FlagResolverServiceGrpc; @@ -8,6 +10,8 @@ import com.spotify.confidence.flags.resolver.v1.ResolveFlagsRequest; import com.spotify.confidence.flags.resolver.v1.ResolveFlagsResponse; import com.spotify.confidence.flags.resolver.v1.ResolvedFlag; +import com.spotify.confidence.flags.resolver.v1.Sdk; +import com.spotify.confidence.flags.resolver.v1.SdkId; import dev.openfeature.sdk.EvaluationContext; import dev.openfeature.sdk.FeatureProvider; import dev.openfeature.sdk.Metadata; @@ -19,13 +23,15 @@ import dev.openfeature.sdk.exceptions.InvalidContextError; import dev.openfeature.sdk.exceptions.TargetingKeyMissingError; import dev.openfeature.sdk.exceptions.TypeMismatchError; -import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.Status.Code; import io.grpc.StatusRuntimeException; import io.grpc.netty.shaded.io.netty.util.internal.StringUtil; +import java.io.FileNotFoundException; +import java.io.FileReader; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.regex.Pattern; @@ -38,6 +44,9 @@ public class ConfidenceFeatureProvider implements FeatureProvider { private final FlagResolverServiceBlockingStub stub; private final String clientSecret; + private final String SDK_VERSION; + private static final SdkId SDK_ID = SdkId.SDK_ID_JAVA_PROVIDER; + static final String TARGETING_KEY = "targeting_key"; /** @@ -49,6 +58,21 @@ public class ConfidenceFeatureProvider implements FeatureProvider { public ConfidenceFeatureProvider(String clientSecret, FlagResolverServiceBlockingStub stub) { this.clientSecret = clientSecret; this.stub = stub; + + if (Strings.isNullOrEmpty(clientSecret)) { + throw new IllegalArgumentException("clientSecret must be a non-empty string."); + } + + try { + final Map versionManifest = + new Gson() + .fromJson( + new FileReader(".release-please-manifest.json"), + new TypeToken>() {}.getType()); + this.SDK_VERSION = versionManifest.get("."); + } catch (FileNotFoundException e) { + throw new RuntimeException("Can't determine version of the SDK", e); + } } /** @@ -57,14 +81,10 @@ public ConfidenceFeatureProvider(String clientSecret, FlagResolverServiceBlockin * @param clientSecret generated from Confidence */ public ConfidenceFeatureProvider(String clientSecret) { - final ManagedChannel channel = - ManagedChannelBuilder.forAddress("edge-grpc.spotify.com", 443).build(); - this.stub = FlagResolverServiceGrpc.newBlockingStub(channel); - - if (Strings.isNullOrEmpty(clientSecret)) { - throw new IllegalArgumentException("clientSecret must be a non-empty string."); - } - this.clientSecret = clientSecret; + this( + clientSecret, + FlagResolverServiceGrpc.newBlockingStub( + ManagedChannelBuilder.forAddress("edge-grpc.spotify.com", 443).build())); } @Override @@ -157,6 +177,7 @@ public ProviderEvaluation getObjectEvaluation( .setClientSecret(clientSecret) .addAllFlags(List.of(requestFlagName)) .setEvaluationContext(evaluationContext.build()) + .setSdk(Sdk.newBuilder().setId(SDK_ID).setVersion(SDK_VERSION).build()) .setApply(true) .build()); diff --git a/src/main/proto/confidence/flags/resolver/v1/api.proto b/src/main/proto/confidence/flags/resolver/v1/api.proto index c1422b96..c3218fca 100644 --- a/src/main/proto/confidence/flags/resolver/v1/api.proto +++ b/src/main/proto/confidence/flags/resolver/v1/api.proto @@ -72,6 +72,11 @@ message ResolveFlagsRequest { bool apply = 4 [ (google.api.field_behavior) = REQUIRED ]; + + // Information about the SDK used to initiate the request. + Sdk sdk = 5 [ + (google.api.field_behavior) = OPTIONAL + ]; } message ResolveFlagsResponse { @@ -82,6 +87,9 @@ message ResolveFlagsResponse { // An opaque token that is used when `apply` is set to false in `ResolveFlags`. // When `apply` is set to false, the token must be passed to `ApplyFlags`. bytes resolve_token = 2; + + // Unique identifier for this particular resolve request. + string resolve_id = 3; } message ApplyFlagsRequest { @@ -104,6 +112,11 @@ message ApplyFlagsRequest { google.protobuf.Timestamp send_time = 4 [ (google.api.field_behavior) = REQUIRED ]; + + // Information about the SDK used to initiate the request. + Sdk sdk = 5 [ + (google.api.field_behavior) = OPTIONAL + ]; } message ApplyFlagsResponse { diff --git a/src/main/proto/confidence/flags/resolver/v1/types.proto b/src/main/proto/confidence/flags/resolver/v1/types.proto index babe1700..12ba524f 100644 --- a/src/main/proto/confidence/flags/resolver/v1/types.proto +++ b/src/main/proto/confidence/flags/resolver/v1/types.proto @@ -2,10 +2,29 @@ syntax = "proto3"; package confidence.flags.resolver.v1; +import "google/api/field_behavior.proto"; + option java_package = "com.spotify.confidence.flags.resolver.v1"; option java_multiple_files = true; option java_outer_classname = "TypesProto"; +// (-- api-linter: core::0123::resource-annotation=disabled +// aip.dev/not-precedent: SDKs are not internal Confidence resources. --) +message Sdk { + // Identifier of the SDK used to interact with the API. + oneof sdk { + // Name of a Confidence SDKs. + SdkId id = 1; + // Custom name for non-Confidence SDKs. + string custom_id = 2; + } + + // Version of the SDK. + string version = 3 [ + (google.api.field_behavior) = REQUIRED + ]; +} + enum ResolveReason { // Unspecified enum. RESOLVE_REASON_UNSPECIFIED = 0; @@ -18,6 +37,27 @@ enum ResolveReason { RESOLVE_REASON_NO_TREATMENT_MATCH = 3 [deprecated = true]; // The flag could not be resolved because it was archived. RESOLVE_REASON_FLAG_ARCHIVED = 4; + // The flag could not be resolved because the targeting key field was invalid + RESOLVE_REASON_TARGETING_KEY_ERROR = 5; // Unknown error occurred during the resolve - RESOLVE_REASON_ERROR = 5; + RESOLVE_REASON_ERROR = 6; +} + +enum SdkId { + // Unspecified enum. + SDK_ID_UNSPECIFIED = 0; + // Confidence OpenFeature Java Provider. + SDK_ID_JAVA_PROVIDER = 1; + // Confidence OpenFeature Kotlin Provider. + SDK_ID_KOTLIN_PROVIDER = 2; + // Confidence OpenFeature Swift Provider. + SDK_ID_SWIFT_PROVIDER = 3; + // Confidence OpenFeature JavaScript Provider for Web (client). + SDK_ID_JS_WEB_PROVIDER = 4; + // Confidence OpenFeature JavaScript Provider for server. + SDK_ID_JS_SERVER_PROVIDER = 5; + // Confidence OpenFeature Python Provider. + SDK_ID_PYTHON_PROVIDER = 6; + // Confidence OpenFeature GO Provider. + SDK_ID_GO_PROVIDER = 7; }