11package com .datadog .featureflag ;
22
33import com .squareup .moshi .JsonAdapter ;
4+ import com .squareup .moshi .JsonDataException ;
45import com .squareup .moshi .JsonReader ;
56import com .squareup .moshi .JsonWriter ;
67import com .squareup .moshi .Moshi ;
8+ import com .squareup .moshi .Types ;
79import datadog .communication .ddagent .SharedCommunicationObjects ;
810import datadog .remoteconfig .Capabilities ;
911import datadog .remoteconfig .ConfigurationChangesTypedListener ;
1315import datadog .remoteconfig .Product ;
1416import datadog .trace .api .Config ;
1517import datadog .trace .api .featureflag .FeatureFlaggingGateway ;
18+ import datadog .trace .api .featureflag .ufc .v1 .Flag ;
1619import datadog .trace .api .featureflag .ufc .v1 .ServerConfiguration ;
1720import java .io .ByteArrayInputStream ;
1821import java .io .IOException ;
19- import java .time .OffsetDateTime ;
22+ import java .lang .annotation .Annotation ;
23+ import java .lang .reflect .Type ;
24+ import java .time .Instant ;
25+ import java .time .format .DateTimeFormatter ;
2026import java .util .Date ;
27+ import java .util .HashMap ;
28+ import java .util .Map ;
29+ import java .util .Set ;
2130import javax .annotation .Nonnull ;
2231import javax .annotation .Nullable ;
2332import okio .Okio ;
@@ -59,18 +68,74 @@ static class UniversalFlagConfigDeserializer
5968
6069 static final UniversalFlagConfigDeserializer INSTANCE = new UniversalFlagConfigDeserializer ();
6170
71+ private static final Moshi MOSHI =
72+ new Moshi .Builder ().add (Date .class , new DateAdapter ()).add (FlagMapAdapter .FACTORY ).build ();
6273 private static final JsonAdapter <ServerConfiguration > V1_ADAPTER =
63- new Moshi .Builder ()
64- .add (Date .class , new DateAdapter ())
65- .build ()
66- .adapter (ServerConfiguration .class );
74+ MOSHI .adapter (ServerConfiguration .class );
6775
6876 @ Override
6977 public ServerConfiguration deserialize (final byte [] content ) throws IOException {
7078 return V1_ADAPTER .fromJson (Okio .buffer (Okio .source (new ByteArrayInputStream (content ))));
7179 }
7280 }
7381
82+ static class FlagMapAdapter extends JsonAdapter <Map <String , Flag >> {
83+
84+ private static final Type FLAGS_TYPE =
85+ Types .newParameterizedType (Map .class , String .class , Flag .class );
86+
87+ static final Factory FACTORY =
88+ new Factory () {
89+ @ Nullable
90+ @ Override
91+ public JsonAdapter <?> create (
92+ @ Nonnull final Type type ,
93+ @ Nonnull final Set <? extends Annotation > annotations ,
94+ @ Nonnull final Moshi moshi ) {
95+ if (!annotations .isEmpty () || !Types .equals (type , FLAGS_TYPE )) {
96+ return null ;
97+ }
98+ return new FlagMapAdapter (moshi .adapter (Flag .class ));
99+ }
100+ };
101+
102+ private final JsonAdapter <Flag > flagAdapter ;
103+
104+ FlagMapAdapter (final JsonAdapter <Flag > flagAdapter ) {
105+ this .flagAdapter = flagAdapter ;
106+ }
107+
108+ @ Nullable
109+ @ Override
110+ public Map <String , Flag > fromJson (@ Nonnull final JsonReader reader ) throws IOException {
111+ if (reader .peek () == JsonReader .Token .NULL ) {
112+ return reader .nextNull ();
113+ }
114+ final Map <String , Flag > flags = new HashMap <>();
115+ reader .beginObject ();
116+ while (reader .hasNext ()) {
117+ final String flagKey = reader .nextName ();
118+ final Object rawFlag = reader .readJsonValue ();
119+ try {
120+ final Flag flag = flagAdapter .fromJsonValue (rawFlag );
121+ if (flag != null ) {
122+ flags .put (flagKey , flag );
123+ }
124+ } catch (JsonDataException | IllegalArgumentException ignored ) {
125+ // A malformed flag must not prevent other flags in the same config from evaluating.
126+ }
127+ }
128+ reader .endObject ();
129+ return flags ;
130+ }
131+
132+ @ Override
133+ public void toJson (@ Nonnull final JsonWriter writer , @ Nullable final Map <String , Flag > value )
134+ throws IOException {
135+ throw new UnsupportedOperationException ("Reading only adapter" );
136+ }
137+ }
138+
74139 static class DateAdapter extends JsonAdapter <Date > {
75140
76141 @ Nullable
@@ -81,10 +146,8 @@ public Date fromJson(@Nonnull final JsonReader reader) throws IOException {
81146 return null ;
82147 }
83148 try {
84- // Use OffsetDateTime which handles variable precision fractional seconds (0-9 digits)
85- // and UTC offsets (+01:00, -05:00, Z)
86- final OffsetDateTime odt = OffsetDateTime .parse (date );
87- return Date .from (odt .toInstant ());
149+ final Instant instant = DateTimeFormatter .ISO_OFFSET_DATE_TIME .parse (date , Instant ::from );
150+ return Date .from (instant );
88151 } catch (Exception e ) {
89152 // ignore wrongly set dates
90153 return null ;
0 commit comments