Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/run-test-harness.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,4 @@ jobs:
with:
sdks-to-test: java
sdk-github-sha: ${{github.event.pull_request.head.sha}}
github-token: ${{ secrets.TEST_HARNESS_GH_SECRET }}

sdk-capabilities: '{ "Java": ["cloud", "edgeDB", "clientCustomData", "v2Config", "allVariables", "allFeatures", "variablesFeatureId", "evalReason", "cloudEvalReason", "eventsEvalReason"]}'
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11

def wasmResourcePath = "$projectDir/src/main/resources"
def wasmVersion = "1.35.1"
def wasmVersion = "1.41.0"
def wasmUrl = "https://unpkg.com/@devcycle/bucketing-assembly-script@$wasmVersion/build/bucketing-lib.release.wasm"
task downloadDVCBucketingWASM(type: Download) {
src wasmUrl
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package com.devcycle.sdk.server.cloud.api;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import com.devcycle.sdk.server.cloud.model.DevCycleCloudOptions;
import com.devcycle.sdk.server.common.api.IDevCycleApi;
import com.devcycle.sdk.server.common.api.IDevCycleClient;
Expand All @@ -8,19 +15,29 @@
import com.devcycle.sdk.server.common.exception.BeforeHookError;
import com.devcycle.sdk.server.common.exception.DevCycleException;
import com.devcycle.sdk.server.common.logging.DevCycleLogger;
import com.devcycle.sdk.server.common.model.*;
import com.devcycle.sdk.server.common.model.BaseVariable;
import com.devcycle.sdk.server.common.model.DevCycleEvent;
import com.devcycle.sdk.server.common.model.DevCycleResponse;
import com.devcycle.sdk.server.common.model.DevCycleUser;
import com.devcycle.sdk.server.common.model.DevCycleUserAndEvents;
import com.devcycle.sdk.server.common.model.ErrorResponse;
import com.devcycle.sdk.server.common.model.EvalHook;
import com.devcycle.sdk.server.common.model.EvalHooksRunner;
import com.devcycle.sdk.server.common.model.EvalReason;
import com.devcycle.sdk.server.common.model.Feature;
import com.devcycle.sdk.server.common.model.HookContext;
import com.devcycle.sdk.server.common.model.HttpResponseCode;
import com.devcycle.sdk.server.common.model.Variable;
import com.devcycle.sdk.server.common.model.Variable.TypeEnum;
import com.devcycle.sdk.server.openfeature.DevCycleProvider;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;

import dev.openfeature.sdk.FeatureProvider;
import retrofit2.Call;
import retrofit2.Response;

import java.io.IOException;
import java.util.*;

public final class DevCycleCloudClient implements IDevCycleClient {

private static final ObjectMapper OBJECT_MAPPER = ObjectMapperUtils.createDefaultObjectMapper();
Expand Down Expand Up @@ -100,13 +117,11 @@ public <T> Variable<T> variable(DevCycleUser user, String key, T defaultValue) {
validateUser(user);

if (key == null || key.equals("")) {
ErrorResponse errorResponse = new ErrorResponse(500, "Missing parameter: key", null);
throw new IllegalArgumentException("Missing parameter: key");
throw new IllegalArgumentException(ErrorResponse.ErrorMessage.MISSING_PARAMETER.getMessage("key"));
}

if (defaultValue == null) {
ErrorResponse errorResponse = new ErrorResponse(500, "Missing parameter: defaultValue", null);
throw new IllegalArgumentException("Missing parameter: defaultValue");
throw new IllegalArgumentException(ErrorResponse.ErrorMessage.MISSING_PARAMETER.getMessage("defaultValue"));
}

TypeEnum variableType = TypeEnum.fromClass(defaultValue.getClass());
Expand All @@ -128,14 +143,14 @@ public <T> Variable<T> variable(DevCycleUser user, String key, T defaultValue) {
Call<Variable> response = api.getVariableByKey(user, key, dvcOptions.getEnableEdgeDB());
variable = getResponseWithRetries(response, 5);
if (variable.getType() != variableType) {
throw new IllegalArgumentException("Variable type mismatch, returning default value");
throw new IllegalArgumentException(ErrorResponse.ErrorMessage.VARIABLE_TYPE_MISMATCH.getMessage());
}
if (beforeError != null) {
throw beforeError;
}

evalHooksRunner.executeAfter(reversedHooks, context, variable);
variable.setIsDefaulted(false);
evalHooksRunner.executeAfter(reversedHooks, context, variable);
} catch (Throwable exception) {
if (!(exception instanceof BeforeHookError || exception instanceof AfterHookError)) {
variable = (Variable<T>) Variable.builder()
Expand All @@ -145,6 +160,12 @@ public <T> Variable<T> variable(DevCycleUser user, String key, T defaultValue) {
.defaultValue(defaultValue)
.isDefaulted(true)
.build();

if (exception.getMessage().equals(ErrorResponse.ErrorMessage.VARIABLE_TYPE_MISMATCH.getMessage())) {
variable.setEval(EvalReason.defaultReason(EvalReason.DefaultReasonDetailsEnum.VARIABLE_TYPE_MISMATCH));
} else {
variable.setEval(EvalReason.defaultReason(EvalReason.DefaultReasonDetailsEnum.ERROR));
}
}

evalHooksRunner.executeError(reversedHooks, context, exception);
Expand Down Expand Up @@ -204,7 +225,7 @@ public void track(DevCycleUser user, DevCycleEvent event) throws DevCycleExcepti
validateUser(user);

if (event == null || event.getType() == null || event.getType().equals("")) {
throw new IllegalArgumentException("Invalid DevCycleEvent");
throw new IllegalArgumentException(ErrorResponse.ErrorMessage.INVALID_EVENT.getMessage());
}

DevCycleUserAndEvents userAndEvents = DevCycleUserAndEvents.builder()
Expand Down Expand Up @@ -315,10 +336,10 @@ private boolean isValidServerKey(String serverKey) {

private void validateUser(DevCycleUser user) {
if (user == null) {
throw new IllegalArgumentException("DevCycleUser cannot be null");
throw new IllegalArgumentException(ErrorResponse.ErrorMessage.NULL_USER.getMessage());
}
if (user.getUserId().equals("")) {
throw new IllegalArgumentException("userId cannot be empty");
throw new IllegalArgumentException(ErrorResponse.ErrorMessage.USER_ID_MISSING.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
package com.devcycle.sdk.server.common.api;

import com.devcycle.sdk.server.common.model.*;
import java.util.Map;

import com.devcycle.sdk.server.common.model.BaseVariable;
import com.devcycle.sdk.server.common.model.DevCycleResponse;
import com.devcycle.sdk.server.common.model.DevCycleUser;
import com.devcycle.sdk.server.common.model.DevCycleUserAndEvents;
import com.devcycle.sdk.server.common.model.Feature;
import com.devcycle.sdk.server.common.model.ProjectConfig;
import com.devcycle.sdk.server.common.model.Variable;
import com.devcycle.sdk.server.local.model.EventsBatch;
import retrofit2.Call;
import retrofit2.http.*;

import java.util.Map;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.Query;

public interface IDevCycleApi {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.devcycle.sdk.server.common.model.Variable.TypeEnum;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand All @@ -27,4 +28,11 @@ public class BaseVariable {

@Schema(required = true, description = "Variable value can be a string, number, boolean, or JSON")
private Object value;

@Schema(description = "Evaluation reason")
private EvalReason eval;

@Schema(description = "Feature ID")
@JsonProperty("_feature")
private String featureId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package com.devcycle.sdk.server.common.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand All @@ -33,4 +34,22 @@ public class ErrorResponse {

@Schema(description = "Additional error information detailing the error reasoning")
private Object data;

public enum ErrorMessage {
MISSING_PARAMETER("Missing parameter: %s"),
NULL_USER("DevCycleUser cannot be null"),
USER_ID_MISSING("userId cannot be empty"),
INVALID_EVENT("Invalid DevCycleEvent"),
VARIABLE_TYPE_MISMATCH("Variable type mismatch, returning default value");

private final String message;

ErrorMessage(String message) {
this.message = message;
}

public String getMessage(String... args) {
return String.format(message, args);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.devcycle.sdk.server.common.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;

@Data
@AllArgsConstructor
@RequiredArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class EvalReason {
@Schema(description = "Evaluation reason", required = true)
@JsonProperty("reason")
private String reason;

@Schema(description = "Details")
@JsonProperty("details")
private String details;

@Schema(description = "Target ID")
@JsonProperty("target_id")
private String targetId;

private EvalReason(String reason, String details) {
this.reason = reason;
this.details = details;
}

public static EvalReason defaultReason(DefaultReasonDetailsEnum details) {
return new EvalReason("DEFAULT", details.getValue());
}

public String getReason() {
return reason == null ? "UNKNOWN" : reason;
}

public enum DefaultReasonDetailsEnum {
MISSING_CONFIG("Missing Config"),
USER_NOT_TARGETED("User Not Targeted"),
VARIABLE_TYPE_MISMATCH("Variable Type Mismatch"),
ERROR("Error");

private final String value;

DefaultReasonDetailsEnum(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package com.devcycle.sdk.server.common.model;

import java.util.HashMap;
import java.util.LinkedHashMap;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.HashMap;
import java.util.LinkedHashMap;

@Data
@Builder
@AllArgsConstructor
Expand All @@ -32,6 +35,14 @@ public class Variable<T> {
@Builder.Default
private Boolean isDefaulted = false;

@Schema(description = "Evaluation reason")
@JsonProperty("eval")
private EvalReason eval;

@Deprecated()
@JsonIgnore
private final String evalReason = null;

public enum TypeEnum {
STRING("String"),
BOOLEAN("Boolean"),
Expand Down
Loading
Loading