2222import com .cedarpolicy .value .CedarMap ;
2323import com .cedarpolicy .value .DateTime ;
2424import com .cedarpolicy .value .Decimal ;
25+ import com .cedarpolicy .value .Duration ;
2526import com .cedarpolicy .value .EntityIdentifier ;
2627import com .cedarpolicy .value .EntityTypeName ;
2728import com .cedarpolicy .value .EntityUID ;
3132import com .cedarpolicy .value .PrimString ;
3233import com .cedarpolicy .value .Unknown ;
3334import com .cedarpolicy .value .Value ;
35+ import com .cedarpolicy .value .functions .Offset ;
3436import com .fasterxml .jackson .core .JsonParser ;
3537import com .fasterxml .jackson .databind .DeserializationContext ;
3638import com .fasterxml .jackson .databind .JsonDeserializer ;
4042import java .util .Iterator ;
4143import java .util .Map ;
4244import java .util .Optional ;
45+ import java .util .Set ;
4346
4447/** Deserialize Json to Value. This is mostly an implementation detail, but you may need to modify it if you extend the
4548 * `Value` class. */
4649public class ValueDeserializer extends JsonDeserializer <Value > {
4750 private static final String ENTITY_ESCAPE_SEQ = "__entity" ;
4851 private static final String EXTENSION_ESCAPE_SEQ = "__extn" ;
52+ private static final String FN_OFFSET = "offset" ;
53+ private static final String FN_IP = "ip" ;
54+ private static final String FN_DECIMAL = "decimal" ;
55+ private static final String FN_UNKNOWN = "unknown" ;
56+ private static final String FN_DATETIME = "datetime" ;
57+ private static final String FN_DURATION = "duration" ;
58+
59+ private static final Set <String > MULTI_ARG_FN = Set .of (FN_OFFSET );
60+ private static final Set <String > SINGLE_ARG_FN = Set .of (FN_IP , FN_DECIMAL , FN_UNKNOWN , FN_DATETIME , FN_DURATION );
4961
5062 private enum EscapeType {
5163 ENTITY ,
@@ -116,19 +128,13 @@ public Value deserialize(JsonParser parser, DeserializationContext context) thro
116128 throw new InvalidValueDeserializationException (parser ,
117129 "Not textual node: " + fn .toString (), node .asToken (), Map .class );
118130 }
119- JsonNode arg = val .get ("arg" );
120- if (!arg .isTextual ()) {
121- throw new InvalidValueDeserializationException (parser ,
122- "Not textual node: " + arg .toString (), node .asToken (), Map .class );
123- }
124- if (fn .textValue ().equals ("ip" )) {
125- return new IpAddress (arg .textValue ());
126- } else if (fn .textValue ().equals ("decimal" )) {
127- return new Decimal (arg .textValue ());
128- } else if (fn .textValue ().equals ("unknown" )) {
129- return new Unknown (arg .textValue ());
130- } else if (fn .textValue ().equals ("datetime" )) {
131- return new DateTime (arg .textValue ());
131+
132+ String fnName = fn .textValue ();
133+
134+ if (MULTI_ARG_FN .contains (fnName )) {
135+ return deserializeMultiArgFunction (fnName , val , mapper , parser , node );
136+ } else if (SINGLE_ARG_FN .contains (fnName )) {
137+ return deserializeSingleArgFunction (fnName , val , parser , node );
132138 } else {
133139 throw new InvalidValueDeserializationException (parser ,
134140 "Invalid function type: " + fn .toString (), node .asToken (), Map .class );
@@ -153,4 +159,79 @@ public Value deserialize(JsonParser parser, DeserializationContext context) thro
153159 throw new DeserializationRecursionDepthException ("Stack overflow while deserializing value. " + e .toString ());
154160 }
155161 }
162+
163+ private Value deserializeMultiArgFunction (String fnName , JsonNode val , ObjectMapper mapper , JsonParser parser ,
164+ JsonNode node ) throws IOException {
165+ JsonNode args = val .get ("args" );
166+ if (args == null || !args .isArray ()) {
167+ throw new InvalidValueDeserializationException (parser ,
168+ "Expected args to be an array" + (args != null ? ", got: " + args .getNodeType () : "" ),
169+ node .asToken (), Map .class );
170+ }
171+
172+ switch (fnName ) {
173+ case FN_OFFSET :
174+ return deserializeOffset (args , mapper , parser , node );
175+ default :
176+ throw new InvalidValueDeserializationException (parser , "Invalid function type: " + fnName ,
177+ node .asToken (), Map .class );
178+ }
179+ }
180+
181+ private Value deserializeSingleArgFunction (String fnName , JsonNode val , JsonParser parser , JsonNode node )
182+ throws IOException {
183+ JsonNode arg = val .get ("arg" );
184+ if (arg == null || !arg .isTextual ()) {
185+ throw new InvalidValueDeserializationException (parser , "Not textual node: " + fnName , node .asToken (),
186+ Map .class );
187+ }
188+
189+ String argValue = arg .textValue ();
190+ switch (fnName ) {
191+ case FN_IP :
192+ return new IpAddress (argValue );
193+ case FN_DECIMAL :
194+ return new Decimal (argValue );
195+ case FN_UNKNOWN :
196+ return new Unknown (argValue );
197+ case FN_DATETIME :
198+ return new DateTime (argValue );
199+ case FN_DURATION :
200+ return new Duration (argValue );
201+ default :
202+ throw new InvalidValueDeserializationException (parser ,
203+ "Invalid function type: " + fnName , node .asToken (), Map .class );
204+ }
205+ }
206+
207+ private Offset deserializeOffset (JsonNode args , ObjectMapper mapper , JsonParser parser , JsonNode node )
208+ throws IOException {
209+ if (args .size () != 2 ) {
210+ throw new InvalidValueDeserializationException (parser ,
211+ "Offset requires exactly two arguments but got: " + args .size (), node .asToken (), Offset .class );
212+ }
213+
214+ try {
215+ Value dateTimeValue = mapper .treeToValue (args .get (0 ), Value .class );
216+ Value durationValue = mapper .treeToValue (args .get (1 ), Value .class );
217+
218+ if (!(dateTimeValue instanceof DateTime )) {
219+ throw new InvalidValueDeserializationException (parser ,
220+ "Offset first argument must be DateTime but got: " + dateTimeValue .getClass ().getSimpleName (),
221+ node .asToken (), Offset .class );
222+ }
223+
224+ if (!(durationValue instanceof Duration )) {
225+ throw new InvalidValueDeserializationException (parser ,
226+ "Offset second argument must be Duration but got: " + durationValue .getClass ().getSimpleName (),
227+ node .asToken (), Offset .class );
228+ }
229+
230+ return new Offset ((DateTime ) dateTimeValue , (Duration ) durationValue );
231+
232+ } catch (IOException e ) {
233+ throw new InvalidValueDeserializationException (parser ,
234+ "Failed to deserialize Offset arguments: " + e .getMessage (), node .asToken (), Offset .class );
235+ }
236+ }
156237}
0 commit comments