Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.AbstractMap;
import java.util.Date;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -114,7 +114,7 @@ public <T> ProviderEvaluation<T> evaluate(
return error(defaultValue, ErrorCode.GENERAL, "Missing allocations for flag " + key);
}

final Date now = new Date();
final Instant now = Instant.now();
final String targetingKey = context.getTargetingKey();

for (final Allocation allocation : flag.allocations) {
Expand Down Expand Up @@ -197,14 +197,14 @@ private static boolean isEmpty(final List<?> list) {
return list == null || list.isEmpty();
}

private static boolean isAllocationActive(final Allocation allocation, final Date now) {
final Date startDate = allocation.startAt;
if (startDate != null && now.before(startDate)) {
private static boolean isAllocationActive(final Allocation allocation, final Instant now) {
final Instant startDate = allocation.startAtInstant();
if (startDate != null && now.isBefore(startDate)) {
return false;
}

final Date endDate = allocation.endAt;
if (endDate != null && now.after(endDate)) {
final Instant endDate = allocation.endAtInstant();
if (endDate != null && now.isAfter(endDate)) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.squareup.moshi.JsonWriter;
import com.squareup.moshi.Moshi;
import com.squareup.moshi.Types;
import datadog.trace.api.featureflag.ufc.v1.Allocation;
import datadog.trace.api.featureflag.ufc.v1.Flag;
import datadog.trace.api.featureflag.ufc.v1.ServerConfiguration;
import dev.openfeature.sdk.ErrorCode;
Expand All @@ -36,7 +37,8 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.OffsetDateTime;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
Expand Down Expand Up @@ -106,7 +108,7 @@ private static Arguments[] valueMappingTestCases() {
Arguments.of(Value.class, null, null),

// Unsupported
Arguments.of(Date.class, "21-12-2023", IllegalArgumentException.class),
Arguments.of(Long.class, 42L, IllegalArgumentException.class),
};
}

Expand Down Expand Up @@ -195,6 +197,19 @@ public void testNoAllocations() {
assertThat(details.getErrorCode(), nullValue());
}

@Test
public void testAllocationDateAbiAndInstantAccessors() throws Exception {
final Date startAt = Date.from(Instant.parse("2024-01-01T00:00:00Z"));
final Date endAt = Date.from(Instant.parse("2024-12-31T23:59:59Z"));
final Allocation allocation =
new Allocation("allocation", emptyList(), startAt, endAt, emptyList(), true);

assertThat(Allocation.class.getField("startAt").getType(), equalTo(Date.class));
assertThat(Allocation.class.getField("endAt").getType(), equalTo(Date.class));
assertThat(allocation.startAtInstant(), equalTo(startAt.toInstant()));
assertThat(allocation.endAtInstant(), equalTo(endAt.toInstant()));
}

private static Arguments[] flatteningTestCases() {
final List<Arguments> arguments = new ArrayList<>();
arguments.add(Arguments.of(emptyMap(), emptyMap()));
Expand Down Expand Up @@ -393,7 +408,8 @@ public Date fromJson(final JsonReader reader) throws IOException {
return reader.nextNull();
}
try {
return Date.from(OffsetDateTime.parse(reader.nextString()).toInstant());
return Date.from(
DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(reader.nextString(), Instant::from));
} catch (final Exception ignored) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package datadog.trace.api.featureflag.ufc.v1;

import java.time.Instant;
import java.util.Date;
import java.util.List;

Expand All @@ -25,4 +26,12 @@ public Allocation(
this.splits = splits;
this.doLog = doLog;
}

public Instant startAtInstant() {
return startAt == null ? null : startAt.toInstant();
}

public Instant endAtInstant() {
return endAt == null ? null : endAt.toInstant();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import okio.Okio;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RemoteConfigServiceImpl
implements RemoteConfigService, ConfigurationChangesTypedListener<ServerConfiguration> {

private static final Logger LOGGER = LoggerFactory.getLogger(RemoteConfigServiceImpl.class);

private final ConfigurationPoller configurationPoller;

public RemoteConfigServiceImpl(final SharedCommunicationObjects sco, final Config config) {
Expand Down Expand Up @@ -121,8 +125,11 @@ public Map<String, Flag> fromJson(@Nonnull final JsonReader reader) throws IOExc
if (flag != null) {
flags.put(flagKey, flag);
}
} catch (JsonDataException | IllegalArgumentException ignored) {
// A malformed flag must not prevent other flags in the same config from evaluating.
} catch (JsonDataException | IllegalArgumentException error) {
LOGGER.warn(
"Dropping malformed FFE flag {} during remote config deserialization: {}",
flagKey,
error.toString());
}
}
reader.endObject();
Expand All @@ -141,13 +148,15 @@ static class DateAdapter extends JsonAdapter<Date> {
@Nullable
@Override
public Date fromJson(@Nonnull final JsonReader reader) throws IOException {
if (reader.peek() == JsonReader.Token.NULL) {
return reader.nextNull();
}
final String date = reader.nextString();
if (date == null) {
return null;
}
try {
final Instant instant = DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(date, Instant::from);
return Date.from(instant);
return Date.from(DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(date, Instant::from));
} catch (Exception e) {
// ignore wrongly set dates
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import datadog.remoteconfig.Product;
import datadog.trace.api.Config;
import datadog.trace.api.featureflag.FeatureFlaggingGateway;
import datadog.trace.api.featureflag.ufc.v1.Allocation;
import datadog.trace.api.featureflag.ufc.v1.Flag;
import datadog.trace.api.featureflag.ufc.v1.ServerConfiguration;
import java.lang.annotation.Annotation;
Expand Down Expand Up @@ -137,6 +138,41 @@ void ignoresUnknownTopLevelFields() throws Exception {
assertTrue(config.flags.isEmpty());
}

@Test
void parsesAllocationWindowDatesAsDateFieldsWithInstantAccessors() throws Exception {
final ServerConfiguration config =
deserialize(
"{"
+ "\"createdAt\":\"2024-04-17T19:40:53.716Z\","
+ "\"format\":\"SERVER\","
+ "\"environment\":{\"name\":\"Test\"},"
+ "\"flags\":{"
+ "\"dated-flag\":{"
+ "\"key\":\"dated-flag\","
+ "\"enabled\":true,"
+ "\"variationType\":\"STRING\","
+ "\"variations\":{\"expected\":{\"key\":\"expected\",\"value\":\"expected\"}},"
+ "\"allocations\":[{"
+ "\"key\":\"dated-allocation\","
+ "\"rules\":[],"
+ "\"startAt\":\"2023-01-01T01:00:00+01:00\","
+ "\"endAt\":\"2023-01-02T00:00:00Z\","
+ "\"splits\":[{\"variationKey\":\"expected\",\"shards\":[]}],"
+ "\"doLog\":true"
+ "}]"
+ "}"
+ "}"
+ "}");

final Allocation allocation = config.flags.get("dated-flag").allocations.get(0);
assertEquals(Date.class, Allocation.class.getField("startAt").getType());
assertEquals(Date.class, Allocation.class.getField("endAt").getType());
assertEquals(Instant.parse("2023-01-01T00:00:00Z"), allocation.startAt.toInstant());
assertEquals(Instant.parse("2023-01-02T00:00:00Z"), allocation.endAt.toInstant());
assertEquals(Instant.parse("2023-01-01T00:00:00Z"), allocation.startAtInstant());
assertEquals(Instant.parse("2023-01-02T00:00:00Z"), allocation.endAtInstant());
}

@Test
void skipsUnknownOperatorFlagAndKeepsValidFlag() throws Exception {
final ServerConfiguration config =
Expand Down Expand Up @@ -257,37 +293,37 @@ void flagMapAdapterIsReadOnly() {
}

@TableTest({
"scenario | value | expectedEpochMilli",
"utc second | '2023-01-01T00:00:00Z' | 1672531200000 ",
"utc end of year | '2023-12-31T23:59:59Z' | 1704067199000 ",
"leap day | '2024-02-29T12:00:00Z' | 1709208000000 ",
"millisecond precision | '2023-01-01T00:00:00.000Z' | 1672531200000 ",
"three fractional digits | '2023-06-15T14:30:45.123Z' | 1686839445123 ",
"six fractional digits truncate to millis | '2023-06-15T14:30:45.123456Z' | 1686839445123 ",
"six fractional digits preserve millis | '2023-06-15T14:30:45.235982Z' | 1686839445235 ",
"nine fractional digits truncate to millis | '2023-06-15T14:30:45.123456789Z' | 1686839445123 ",
"one fractional digit | '2023-06-15T14:30:45.1Z' | 1686839445100 ",
"two fractional digits | '2023-06-15T14:30:45.12Z' | 1686839445120 ",
"positive offset | '2023-01-01T01:00:00+01:00' | 1672531200000 ",
"negative offset | '2023-01-01T00:00:00-05:00' | 1672549200000 ",
"date only | '2023-01-01' | ",
"invalid | 'invalid-date' | ",
"empty string | '' | ",
"not a date | 'not-a-date' | ",
"slash date | '2023/01/01T00:00:00Z' | ",
"null | | "
"scenario | value | expectedInstant ",
"utc second | '2023-01-01T00:00:00Z' | '2023-01-01T00:00:00Z' ",
"utc end of year | '2023-12-31T23:59:59Z' | '2023-12-31T23:59:59Z' ",
"leap day | '2024-02-29T12:00:00Z' | '2024-02-29T12:00:00Z' ",
"millisecond precision | '2023-01-01T00:00:00.000Z' | '2023-01-01T00:00:00Z' ",
"three fractional digits | '2023-06-15T14:30:45.123Z' | '2023-06-15T14:30:45.123Z'",
"six fractional digits | '2023-06-15T14:30:45.123456Z' | '2023-06-15T14:30:45.123Z'",
"six fractional digits distinct | '2023-06-15T14:30:45.235982Z' | '2023-06-15T14:30:45.235Z'",
"nine fractional digits | '2023-06-15T14:30:45.123456789Z' | '2023-06-15T14:30:45.123Z'",
"one fractional digit | '2023-06-15T14:30:45.1Z' | '2023-06-15T14:30:45.100Z'",
"two fractional digits | '2023-06-15T14:30:45.12Z' | '2023-06-15T14:30:45.120Z'",
"positive offset | '2023-01-01T01:00:00+01:00' | '2023-01-01T00:00:00Z' ",
"negative offset | '2023-01-01T00:00:00-05:00' | '2023-01-01T05:00:00Z' ",
"date only | '2023-01-01' | ",
"invalid | 'invalid-date' | ",
"empty string | '' | ",
"not a date | 'not-a-date' | ",
"slash date | '2023/01/01T00:00:00Z' | ",
"null | | "
})
void testDateParsing(final String value, final Long expectedEpochMilli) throws Exception {
void testDateParsing(final String value, final String expectedInstant) throws Exception {
final JsonReader reader = mock(JsonReader.class);
when(reader.nextString()).thenReturn(value);
final RemoteConfigServiceImpl.DateAdapter adapter = new RemoteConfigServiceImpl.DateAdapter();

final Date parsed = adapter.fromJson(reader);
if (expectedEpochMilli == null) {
if (expectedInstant == null) {
assertNull(parsed);
} else {
assertNotNull(parsed);
assertEquals(Instant.ofEpochMilli(expectedEpochMilli), parsed.toInstant());
assertEquals(expectedInstant, parsed.toInstant().toString());
}
}

Expand All @@ -297,7 +333,7 @@ void testParsingOnlyAdapter() {

assertThrows(
UnsupportedOperationException.class,
() -> adapter.toJson(mock(JsonWriter.class), new Date()));
() -> adapter.toJson(mock(JsonWriter.class), Date.from(Instant.EPOCH)));
}

@SuppressWarnings("unchecked")
Expand Down