@@ -5,14 +5,19 @@ use serde::{Deserialize, Serialize};
5
5
use smallvec:: SmallVec ;
6
6
use tracing:: debug;
7
7
8
- use crate :: { Builder , IterBufferable } ;
8
+ use crate :: { Builder , IterBufferable , Output } ;
9
9
10
10
use super :: { DiagramError , DynOutput , NextOperation , NodeRegistry , SerializeMessage } ;
11
11
12
12
#[ derive( Debug , Serialize , Deserialize , JsonSchema ) ]
13
13
#[ serde( rename_all = "snake_case" ) ]
14
14
pub struct JoinOp {
15
15
pub ( super ) next : NextOperation ,
16
+
17
+ /// Whether to serialize incoming outputs before perform the join. This allows for joining
18
+ /// outputs of different types at the cost of serialization overhead. If there is true, the
19
+ /// resulting output will be of [`serde_json::Value`].
20
+ pub ( super ) serialize : Option < bool > ,
16
21
}
17
22
18
23
pub ( super ) fn register_join_impl < T , Serializer > ( registry : & mut NodeRegistry )
34
39
// register_serialize::<Vec<T>, Serializer>(registry);
35
40
}
36
41
42
+ /// Serialize the outputs before joining them, and convert the resulting joined output into a
43
+ /// [`serde_json::Value`].
44
+ pub ( super ) fn serialize_and_join (
45
+ builder : & mut Builder ,
46
+ registry : & NodeRegistry ,
47
+ outputs : Vec < DynOutput > ,
48
+ ) -> Result < Output < serde_json:: Value > , DiagramError > {
49
+ debug ! ( "serialize and join outputs {:?}" , outputs) ;
50
+
51
+ if outputs. is_empty ( ) {
52
+ // do not allow empty joins
53
+ return Err ( DiagramError :: EmptyJoin ) ;
54
+ }
55
+
56
+ let outputs = outputs
57
+ . into_iter ( )
58
+ . map ( |o| {
59
+ let serialize_impl = registry
60
+ . serialize_impls
61
+ . get ( & o. type_id )
62
+ . ok_or ( DiagramError :: NotSerializable ) ?;
63
+ let serialized_output = serialize_impl ( builder, o) ?;
64
+ Ok ( serialized_output)
65
+ } )
66
+ . collect :: < Result < Vec < _ > , DiagramError > > ( ) ?;
67
+
68
+ // we need to convert the joined output to [`serde_json::Value`] in order for it to be
69
+ // serializable.
70
+ let joined_output = outputs. join_vec :: < 4 > ( builder) . output ( ) ;
71
+ let json_output = joined_output
72
+ . chain ( builder)
73
+ . map_block ( |o| serde_json:: to_value ( o) . unwrap ( ) )
74
+ . output ( ) ;
75
+ Ok ( json_output)
76
+ }
77
+
37
78
fn join_impl < T > ( builder : & mut Builder , outputs : Vec < DynOutput > ) -> Result < DynOutput , DiagramError >
38
79
where
39
80
T : Send + Sync + ' static ,
51
92
let outputs = outputs
52
93
. into_iter ( )
53
94
. map ( |o| {
54
- // joins is only supported for outputs of the same type. This is because joins of
55
- // different types produces a tuple and we cannot output a tuple as we don't
56
- // know the number and order of join inputs at compile time.
57
- // A workaround is to serialize them all the `serde_json::Value` or convert them to `Box<dyn Any>`.
58
- // But the problem with `Box<dyn Any>` is that we can't convert it back to the original type,
59
- // so nodes need to take a request of `JoinOutput<Box<dyn Any>>`.
60
95
if o. type_id != first_type {
61
96
Err ( DiagramError :: TypeMismatch )
62
97
} else {
@@ -215,4 +250,65 @@ mod tests {
215
250
let err = fixture. spawn_io_workflow ( & diagram) . unwrap_err ( ) ;
216
251
assert ! ( matches!( err, DiagramError :: EmptyJoin ) ) ;
217
252
}
253
+
254
+ #[ test]
255
+ fn test_serialize_and_join ( ) {
256
+ let mut fixture = DiagramTestFixture :: new ( ) ;
257
+
258
+ fn num_output ( _: serde_json:: Value ) -> i64 {
259
+ 1
260
+ }
261
+
262
+ fixture. registry . register_node_builder (
263
+ "num_output" . to_string ( ) ,
264
+ "num_output" . to_string ( ) ,
265
+ |builder, _config : ( ) | builder. create_map_block ( num_output) ,
266
+ ) ;
267
+
268
+ fn string_output ( _: serde_json:: Value ) -> String {
269
+ "hello" . to_string ( )
270
+ }
271
+
272
+ fixture. registry . register_node_builder (
273
+ "string_output" . to_string ( ) ,
274
+ "string_output" . to_string ( ) ,
275
+ |builder, _config : ( ) | builder. create_map_block ( string_output) ,
276
+ ) ;
277
+
278
+ let diagram = Diagram :: from_json ( json ! ( {
279
+ "version" : "0.1.0" ,
280
+ "start" : "fork_clone" ,
281
+ "ops" : {
282
+ "fork_clone" : {
283
+ "type" : "fork_clone" ,
284
+ "next" : [ "op1" , "op2" ]
285
+ } ,
286
+ "op1" : {
287
+ "type" : "node" ,
288
+ "builder" : "num_output" ,
289
+ "next" : "join" ,
290
+ } ,
291
+ "op2" : {
292
+ "type" : "node" ,
293
+ "builder" : "string_output" ,
294
+ "next" : "join" ,
295
+ } ,
296
+ "join" : {
297
+ "type" : "join" ,
298
+ "next" : { "builtin" : "terminate" } ,
299
+ "serialize" : true ,
300
+ } ,
301
+ }
302
+ } ) )
303
+ . unwrap ( ) ;
304
+
305
+ let result = fixture
306
+ . spawn_and_run ( & diagram, serde_json:: Value :: Null )
307
+ . unwrap ( ) ;
308
+ assert_eq ! ( result. as_array( ) . unwrap( ) . len( ) , 2 ) ;
309
+ // order is not guaranteed so need to test for both possibility
310
+ assert ! ( result[ 0 ] == 1 || result[ 0 ] == "hello" ) ;
311
+ assert ! ( result[ 1 ] == 1 || result[ 1 ] == "hello" ) ;
312
+ assert ! ( result[ 0 ] != result[ 1 ] ) ;
313
+ }
218
314
}
0 commit comments