Skip to content

Commit d5b9881

Browse files
More v6 tests (#44)
* Add more v6 tests. Handle json file override unicode problems. Gson disableHtmlEscaping added. Update Double to String formatting. * Small fixes - config deserialization called on cache. - getKeyAndValue optimized - error text fix * Update version to 9.0.1 * Add test case for skipped sdkKey validation in case of LOCAL_ONLY OverrideBehavior. * Update double format. Add missing required variation ids to json overrides. * Run code format * Fixes based on code review. * Move configSalt check from deserialization. * Fix platform based line ending. * Move LogHelper into EvaluateLogger. * Fix expectedLog replaceAll * Fix configSalt checks
1 parent 764b9c4 commit d5b9881

32 files changed

+1257
-254
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version=9.0.0
1+
version=9.0.1

src/main/java/com/configcat/Config.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ public class Config {
2323
public Preferences getPreferences() {
2424
return preferences;
2525
}
26+
2627
/**
2728
* The list of segments.
2829
*/
2930
public Segment[] getSegments() {
3031
return segments;
3132
}
33+
3234
/**
3335
* The map of settings.
3436
*/

src/main/java/com/configcat/ConfigCatClient.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -518,10 +518,11 @@ private <T> Map.Entry<String, T> getKeyAndValueFromSettingsMap(Class<T> classOfT
518518
}
519519

520520
for (TargetingRule targetingRule : setting.getTargetingRules()) {
521-
if (targetingRule.getSimpleValue() != null && variationId.equals(targetingRule.getSimpleValue().getVariationId())) {
522-
return new AbstractMap.SimpleEntry<>(settingKey, (T) this.parseObject(classOfT, targetingRule.getSimpleValue().getValue(), setting.getType()));
523-
}
524-
if (targetingRule.getPercentageOptions() != null) {
521+
if (targetingRule.getSimpleValue() != null) {
522+
if (variationId.equals(targetingRule.getSimpleValue().getVariationId())) {
523+
return new AbstractMap.SimpleEntry<>(settingKey, (T) this.parseObject(classOfT, targetingRule.getSimpleValue().getValue(), setting.getType()));
524+
}
525+
} else if (targetingRule.getPercentageOptions() != null) {
525526
for (PercentageOption percentageRule : targetingRule.getPercentageOptions()) {
526527
if (variationId.equals(percentageRule.getVariationId())) {
527528
return new AbstractMap.SimpleEntry<>(settingKey, (T) this.parseObject(classOfT, percentageRule.getValue(), setting.getType()));
@@ -556,10 +557,10 @@ private Object parseObject(Class<?> classOfT, SettingsValue settingsValue, Setti
556557
} else if ((classOfT == Boolean.class || classOfT == boolean.class) && settingsValue.getBooleanValue() != null && SettingType.BOOLEAN.equals(settingType)) {
557558
return settingsValue.getBooleanValue();
558559
}
559-
throw new IllegalArgumentException("The type of a setting must match the type of the setting's default value. "
560+
throw new IllegalArgumentException("The type of a setting must match the type of the specified default value. "
560561
+ "Setting's type was {" + settingType + "} but the default value's type was {" + classOfT + "}. "
561562
+ "Please use a default value which corresponds to the setting type {" + settingType + "}."
562-
+ "Learn more: https://configcat.com/docs/sdk-reference/dotnet/#setting-type-mapping");
563+
+ "Learn more: https://configcat.com/docs/sdk-reference/java/#setting-type-mapping");
563564
}
564565

565566
private void validateReturnType(Class<?> classOfT) throws IllegalArgumentException {

src/main/java/com/configcat/ConfigCatLogMessages.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ public static String getUserObjectMissing(final String key) {
206206
* @return The formatted warn message.
207207
*/
208208
public static String getUserAttributeMissing(final String key, final UserCondition userCondition, final String attributeName) {
209-
return "Cannot evaluate condition (" + LogHelper.formatUserCondition(userCondition) + ") for setting '" + key + "' (the User." + attributeName + " attribute is missing). You should set the User." + attributeName + " attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/";
209+
return "Cannot evaluate condition (" + EvaluateLogger.formatUserCondition(userCondition) + ") for setting '" + key + "' (the User." + attributeName + " attribute is missing). You should set the User." + attributeName + " attribute in order to make targeting work properly. Read more: https://configcat.com/docs/advanced/user-object/";
210210
}
211211

212212
/**
@@ -230,7 +230,7 @@ public static String getUserAttributeMissing(final String key, final String attr
230230
* @return The formatted warn message.
231231
*/
232232
public static String getUserAttributeInvalid(final String key, final UserCondition userCondition, final String reason, final String attributeName) {
233-
return "Cannot evaluate condition (" + LogHelper.formatUserCondition(userCondition) + ") for setting '" + key + "' (" + reason + "). Please check the User." + attributeName + " attribute and make sure that its value corresponds to the comparison operator.";
233+
return "Cannot evaluate condition (" + EvaluateLogger.formatUserCondition(userCondition) + ") for setting '" + key + "' (" + reason + "). Please check the User." + attributeName + " attribute and make sure that its value corresponds to the comparison operator.";
234234
}
235235

236236

@@ -244,7 +244,7 @@ public static String getUserAttributeInvalid(final String key, final UserConditi
244244
* @return The formatted warn message.
245245
*/
246246
public static String getUserObjectAttributeIsAutoConverted(String key, UserCondition userCondition, String attributeName, String attributeValue) {
247-
return "Evaluation of condition (" + LogHelper.formatUserCondition(userCondition) + ") for setting '" + key + "' may not produce the expected result (the User." + attributeName + " attribute is not a string value, thus it was automatically converted to the string value '" + attributeValue + "'). Please make sure that using a non-string value was intended.";
247+
return "Evaluation of condition (" + EvaluateLogger.formatUserCondition(userCondition) + ") for setting '" + key + "' may not produce the expected result (the User." + attributeName + " attribute is not a string value, thus it was automatically converted to the string value '" + attributeValue + "'). Please make sure that using a non-string value was intended.";
248248
}
249249

250250
/**

src/main/java/com/configcat/ConfigurationProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public interface ConfigurationProvider extends Closeable {
2222
<T> T getValue(Class<T> classOfT, String key, T defaultValue);
2323

2424
/**
25-
* Gets the value of a feature flag or setting as T asynchronously identified by the given {@code key}.
25+
* Gets the value of a feature flag or setting as T identified by the given {@code key}.
2626
*
2727
* @param classOfT the class of T. Only {@link String}, {@link Integer}, {@link Double} or {@link Boolean} types are supported.
2828
* @param key the identifier of the feature flag or setting.
@@ -34,7 +34,7 @@ public interface ConfigurationProvider extends Closeable {
3434
<T> T getValue(Class<T> classOfT, String key, User user, T defaultValue);
3535

3636
/**
37-
* Gets the value of a feature flag or setting as T identified by the given {@code key}.
37+
* Gets the value of a feature flag or setting as T asynchronously identified by the given {@code key}.
3838
*
3939
* @param classOfT the class of T. Only {@link String}, {@link Integer}, {@link Double} or {@link Boolean} types are supported.
4040
* @param key the identifier of the feature flag or setting.

src/main/java/com/configcat/Constants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ private Constants() { /* prevent from instantiation*/ }
77
static final long DISTANT_PAST = 0;
88
static final String CONFIG_JSON_NAME = "config_v6.json";
99
static final String SERIALIZATION_FORMAT_VERSION = "v2";
10-
static final String VERSION = "9.0.0";
10+
static final String VERSION = "9.0.1";
1111

1212
static final String SDK_KEY_PROXY_PREFIX = "configcat-proxy/";
1313
static final String SDK_KEY_PREFIX = "configcat-sdk-1";

src/main/java/com/configcat/Entry.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public static Entry fromString(String cacheValue) throws IllegalArgumentExceptio
6969
throw new IllegalArgumentException("Empty config jsom value.");
7070
}
7171
try {
72-
Config config = Utils.gson.fromJson(configJson, Config.class);
72+
Config config = Utils.deserializeConfig(configJson);
7373
return new Entry(config, eTag, configJson, fetchTimeUnixMillis);
7474
} catch (Exception e) {
7575
throw new IllegalArgumentException("Invalid config JSON content: " + configJson);

src/main/java/com/configcat/EvaluateLogger.java

Lines changed: 166 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
package com.configcat;
22

3+
import java.text.DecimalFormat;
4+
import java.util.ArrayList;
5+
import java.util.Arrays;
6+
import java.util.List;
7+
38
public class EvaluateLogger {
49

10+
private static final String HASHED_VALUE = "<hashed value>";
11+
public static final String INVALID_VALUE = "<invalid value>";
12+
public static final String INVALID_NAME = "<invalid name>";
13+
public static final String INVALID_REFERENCE = "<invalid reference>";
14+
15+
private static final int MAX_LIST_ELEMENT = 10;
516
private final StringBuilder stringBuilder = new StringBuilder();
617

718
public EvaluateLogger(LogLevel logLevel) {
@@ -90,7 +101,7 @@ public final void newLine() {
90101
if (!isLoggable) {
91102
return;
92103
}
93-
stringBuilder.append("\n");
104+
stringBuilder.append(System.lineSeparator());
94105
for (int i = 0; i < indentLevel; i++) {
95106
stringBuilder.append(" ");
96107
}
@@ -170,7 +181,7 @@ public void logPercentageEvaluationReturnValue(int hashValue, int i, int percent
170181
if (!isLoggable) {
171182
return;
172183
}
173-
String percentageOptionValue = settingsValue != null ? settingsValue.toString() : LogHelper.INVALID_VALUE;
184+
String percentageOptionValue = settingsValue != null ? settingsValue.toString() : INVALID_VALUE;
174185
newLine();
175186
append("- Hash value " + hashValue + " selects % option " + (i + 1) + " (" + percentage + "%), '" + percentageOptionValue + "'.");
176187
}
@@ -194,7 +205,7 @@ public void logSegmentEvaluationResult(SegmentCondition segmentCondition, Segmen
194205
String segmentResultComparator = segmentResult ? SegmentComparator.IS_IN_SEGMENT.getName() : SegmentComparator.IS_NOT_IN_SEGMENT.getName();
195206
append("Segment evaluation result: User " + segmentResultComparator + ".");
196207
newLine();
197-
append("Condition (" + LogHelper.formatSegmentFlagCondition(segmentCondition, segment) + ") evaluates to " + result + ".");
208+
append("Condition (" + formatSegmentFlagCondition(segmentCondition, segment) + ") evaluates to " + result + ".");
198209
decreaseIndentLevel();
199210
newLine();
200211
append(")");
@@ -208,7 +219,7 @@ public void logSegmentEvaluationError(SegmentCondition segmentCondition, Segment
208219

209220
append("Segment evaluation result: " + error + ".");
210221
newLine();
211-
append("Condition (" + LogHelper.formatSegmentFlagCondition(segmentCondition, segment) + ") failed to evaluate.");
222+
append("Condition (" + formatSegmentFlagCondition(segmentCondition, segment) + ") failed to evaluate.");
212223
decreaseIndentLevel();
213224
newLine();
214225
append(")");
@@ -230,12 +241,161 @@ public void logPrerequisiteFlagEvaluationResult(PrerequisiteFlagCondition prereq
230241
return;
231242
}
232243
newLine();
233-
String prerequisiteFlagValueFormat = prerequisiteFlagValue != null ? prerequisiteFlagValue.toString() : LogHelper.INVALID_VALUE;
244+
String prerequisiteFlagValueFormat = prerequisiteFlagValue != null ? prerequisiteFlagValue.toString() : INVALID_VALUE;
234245
append("Prerequisite flag evaluation result: '" + prerequisiteFlagValueFormat + "'.");
235246
newLine();
236-
append("Condition (" + LogHelper.formatPrerequisiteFlagCondition(prerequisiteFlagCondition) + ") evaluates to " + result + ".");
247+
append("Condition (" + formatPrerequisiteFlagCondition(prerequisiteFlagCondition) + ") evaluates to " + result + ".");
237248
decreaseIndentLevel();
238249
newLine();
239250
append(")");
240251
}
252+
253+
private static String formatStringListComparisonValue(String[] comparisonValue, boolean isSensitive) {
254+
if (comparisonValue == null) {
255+
return INVALID_VALUE;
256+
}
257+
List<String> comparisonValues = new ArrayList<>(Arrays.asList(comparisonValue));
258+
if (comparisonValues.isEmpty()) {
259+
return INVALID_VALUE;
260+
}
261+
String formattedList;
262+
if (isSensitive) {
263+
String sensitivePostFix = comparisonValues.size() == 1 ? "value" : "values";
264+
formattedList = "<" + comparisonValues.size() + " hashed " + sensitivePostFix + ">";
265+
} else {
266+
String listPostFix = "";
267+
if (comparisonValues.size() > MAX_LIST_ELEMENT) {
268+
int count = comparisonValues.size() - MAX_LIST_ELEMENT;
269+
String countPostFix = count == 1 ? "value" : "values";
270+
listPostFix = ", ... <" + count + " more " + countPostFix + ">";
271+
}
272+
List<String> subList = comparisonValues.subList(0, Math.min(MAX_LIST_ELEMENT, comparisonValues.size()));
273+
StringBuilder formatListBuilder = new StringBuilder();
274+
int subListSize = subList.size();
275+
for (int i = 0; i < subListSize; i++) {
276+
formatListBuilder.append("'").append(subList.get(i)).append("'");
277+
if (i != subListSize - 1) {
278+
formatListBuilder.append(", ");
279+
}
280+
}
281+
formatListBuilder.append(listPostFix);
282+
formattedList = formatListBuilder.toString();
283+
}
284+
285+
return "[" + formattedList + "]";
286+
}
287+
288+
private static String formatStringComparisonValue(String comparisonValue, boolean isSensitive) {
289+
return "'" + (isSensitive ? HASHED_VALUE : comparisonValue) + "'";
290+
}
291+
292+
private static String formatDoubleComparisonValue(Double comparisonValue, boolean isDate) {
293+
if (comparisonValue == null) {
294+
return INVALID_VALUE;
295+
}
296+
DecimalFormat decimalFormat = Utils.getDecimalFormat();
297+
if (isDate) {
298+
return "'" + decimalFormat.format(comparisonValue) + "' (" + DateTimeUtils.doubleToFormattedUTC(comparisonValue) + " UTC)";
299+
}
300+
return "'" + decimalFormat.format(comparisonValue) + "'";
301+
}
302+
303+
public static String formatUserCondition(UserCondition userCondition) {
304+
UserComparator userComparator = UserComparator.fromId(userCondition.getComparator());
305+
if (userComparator == null) {
306+
throw new IllegalArgumentException("Comparison operator is invalid.");
307+
}
308+
String comparisonValue;
309+
switch (userComparator) {
310+
case IS_ONE_OF:
311+
case IS_NOT_ONE_OF:
312+
case CONTAINS_ANY_OF:
313+
case NOT_CONTAINS_ANY_OF:
314+
case SEMVER_IS_ONE_OF:
315+
case SEMVER_IS_NOT_ONE_OF:
316+
case TEXT_STARTS_WITH:
317+
case TEXT_NOT_STARTS_WITH:
318+
case TEXT_ENDS_WITH:
319+
case TEXT_NOT_ENDS_WITH:
320+
case TEXT_ARRAY_CONTAINS:
321+
case TEXT_ARRAY_NOT_CONTAINS:
322+
comparisonValue = formatStringListComparisonValue(userCondition.getStringArrayValue(), false);
323+
break;
324+
case SEMVER_LESS:
325+
case SEMVER_LESS_EQUALS:
326+
case SEMVER_GREATER:
327+
case SEMVER_GREATER_EQUALS:
328+
case TEXT_EQUALS:
329+
case TEXT_NOT_EQUALS:
330+
comparisonValue = formatStringComparisonValue(userCondition.getStringValue(), false);
331+
break;
332+
case NUMBER_EQUALS:
333+
case NUMBER_NOT_EQUALS:
334+
case NUMBER_LESS:
335+
case NUMBER_LESS_EQUALS:
336+
case NUMBER_GREATER:
337+
case NUMBER_GREATER_EQUALS:
338+
comparisonValue = formatDoubleComparisonValue(userCondition.getDoubleValue(), false);
339+
break;
340+
case SENSITIVE_IS_ONE_OF:
341+
case SENSITIVE_IS_NOT_ONE_OF:
342+
case HASHED_STARTS_WITH:
343+
case HASHED_NOT_STARTS_WITH:
344+
case HASHED_ENDS_WITH:
345+
case HASHED_NOT_ENDS_WITH:
346+
case HASHED_ARRAY_CONTAINS:
347+
case HASHED_ARRAY_NOT_CONTAINS:
348+
comparisonValue = formatStringListComparisonValue(userCondition.getStringArrayValue(), true);
349+
break;
350+
case DATE_BEFORE:
351+
case DATE_AFTER:
352+
comparisonValue = formatDoubleComparisonValue(userCondition.getDoubleValue(), true);
353+
break;
354+
case HASHED_EQUALS:
355+
case HASHED_NOT_EQUALS:
356+
comparisonValue = formatStringComparisonValue(userCondition.getStringValue(), true);
357+
break;
358+
default:
359+
comparisonValue = INVALID_VALUE;
360+
}
361+
362+
return "User." + userCondition.getComparisonAttribute() + " " + userComparator.getName() + " " + comparisonValue;
363+
}
364+
365+
public static String formatSegmentFlagCondition(SegmentCondition segmentCondition, Segment segment) {
366+
String segmentName;
367+
if (segment != null) {
368+
segmentName = segment.getName();
369+
if (segmentName == null || segmentName.isEmpty()) {
370+
segmentName = INVALID_NAME;
371+
}
372+
} else {
373+
segmentName = INVALID_REFERENCE;
374+
}
375+
SegmentComparator segmentComparator = SegmentComparator.fromId(segmentCondition.getSegmentComparator());
376+
if (segmentComparator == null) {
377+
throw new IllegalArgumentException("Segment comparison operator is invalid.");
378+
}
379+
return "User " + segmentComparator.getName() + " '" + segmentName + "'";
380+
}
381+
382+
public static String formatPrerequisiteFlagCondition(PrerequisiteFlagCondition prerequisiteFlagCondition) {
383+
String prerequisiteFlagKey = prerequisiteFlagCondition.getPrerequisiteFlagKey();
384+
PrerequisiteComparator prerequisiteComparator = PrerequisiteComparator.fromId(prerequisiteFlagCondition.getPrerequisiteComparator());
385+
if (prerequisiteComparator == null) {
386+
throw new IllegalArgumentException("Prerequisite Flag comparison operator is invalid.");
387+
}
388+
SettingsValue prerequisiteValue = prerequisiteFlagCondition.getValue();
389+
String comparisonValue = prerequisiteValue == null ? INVALID_VALUE : prerequisiteValue.toString();
390+
return "Flag '" + prerequisiteFlagKey + "' " + prerequisiteComparator.getName() + " '" + comparisonValue + "'";
391+
}
392+
393+
394+
public static String formatCircularDependencyList(List<String> visitedKeys, String key) {
395+
StringBuilder builder = new StringBuilder();
396+
visitedKeys.forEach(visitedKey -> builder.append("'").append(visitedKey).append("' -> "));
397+
builder.append("'").append(key).append("'");
398+
return builder.toString();
399+
}
400+
241401
}

src/main/java/com/configcat/LocalFileDataSource.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import java.io.ByteArrayOutputStream;
1010
import java.io.IOException;
1111
import java.io.InputStream;
12-
import java.nio.charset.Charset;
12+
import java.nio.charset.StandardCharsets;
1313
import java.nio.file.*;
1414
import java.util.HashMap;
1515
import java.util.Map;
@@ -126,11 +126,11 @@ private String readFile() throws IOException {
126126
while ((temp = stream.read(buffer)) != -1) {
127127
outputStream.write(buffer, 0, temp);
128128
}
129-
return new String(outputStream.toByteArray(), Charset.defaultCharset());
129+
return new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
130130
}
131131
} else {
132132
byte[] content = Files.readAllBytes(Paths.get(this.filePath));
133-
return new String(content, Charset.defaultCharset());
133+
return new String(content, StandardCharsets.UTF_8);
134134
}
135135
}
136136

0 commit comments

Comments
 (0)