@@ -64,6 +64,8 @@ local function avro_type(avro_schema)
64
64
return ' record*'
65
65
elseif utils .is_array (avro_schema ) then
66
66
return ' union'
67
+ elseif utils .is_array (avro_schema .type ) then
68
+ return ' union'
67
69
elseif avro_schema .type == ' array' then
68
70
return ' array'
69
71
elseif avro_schema .type == ' array*' then
@@ -86,6 +88,8 @@ local function avro_type(avro_schema)
86
88
return ' string'
87
89
elseif avro_schema == ' string*' then
88
90
return ' string*'
91
+ elseif avro_schema == ' null' then
92
+ return ' null'
89
93
end
90
94
end
91
95
error (' unrecognized avro-schema type: ' .. json .encode (avro_schema ))
@@ -252,8 +256,10 @@ local function convert_record_fields_to_args(fields, opts)
252
256
if not skip_compound or (
253
257
avro_t ~= ' record' and avro_t ~= ' record*' and
254
258
avro_t ~= ' array' and avro_t ~= ' array*' and
255
- avro_t ~= ' map' and avro_t ~= ' map*' ) then
256
- local gql_class = gql_argument_type (field .type )
259
+ avro_t ~= ' map' and avro_t ~= ' map*' and
260
+ avro_t ~= ' union' ) then
261
+
262
+ local gql_class = gql_argument_type (field .type , field .name )
257
263
args [field .name ] = nullable (gql_class )
258
264
end
259
265
end
@@ -275,10 +281,18 @@ local function convert_record_fields(state, fields)
275
281
(' field.name must be a string, got %s (schema %s)' )
276
282
:format (type (field .name ), json .encode (field )))
277
283
278
- res [field .name ] = {
279
- name = field .name ,
280
- kind = gql_type (state , field .type ),
281
- }
284
+ -- union case
285
+ if utils .is_array (field .type ) then
286
+ res [field .name ] = {
287
+ name = field .name ,
288
+ kind = gql_type (state , {name = field .name , type = field .type }),
289
+ }
290
+ else
291
+ res [field .name ] = {
292
+ name = field .name ,
293
+ kind = gql_type (state , field .type ),
294
+ }
295
+ end
282
296
end
283
297
return res
284
298
end
@@ -670,6 +684,161 @@ local convert_connection_to_field = function(state, connection, collection_name)
670
684
end
671
685
end
672
686
687
+ --- The function 'boxes' given GraphQL type into GraphQL Object 'box' type.
688
+ --- This serves two needs:
689
+ --- 1) GraphQL Union types may contain only GraphQL Objects. GraphQL Scalar
690
+ --- types maybe put inside Union only if they are 'boxed' inside an Object.
691
+ --- 2) GraphQL Union types must have specific form, described in @{create_gql_union}.
692
+ --- 'Boxes' is a part of this specific form.
693
+ ---
694
+ --- @tparam table gql_type GraphQL type to be boxed
695
+ --- @param avro_schema table or string ... necessary for right names for boxed
696
+ --- types. Right names are such names than ... something about validation
697
+ --- @treturn table GraphQL Object. Object name is type of initial GraphQL + '_box'.
698
+ --- Resulting Object has only one field. Field type is initial GraphQL type.
699
+ --- Field name is `avro_name`. Such name is necessary to make GraphQL response
700
+ --- valid
701
+ --- Consider the following example:
702
+ --- String
703
+ --- to
704
+ --- String_box {
705
+ --- string
706
+ --- }
707
+ local function box_type (gql_type , avro_name )
708
+ check (gql_type , ' gql_type' , ' table' )
709
+
710
+ local box_name , box_fields
711
+ local gql_true_type = nullable (gql_type )
712
+
713
+ -- also handle avro-schema 'map' case as it is represented as a scalar in
714
+ -- our GraphQL schema
715
+ if gql_true_type .__type == ' Scalar' then
716
+ box_name = gql_true_type .name .. ' _box'
717
+ box_fields = {[avro_name ] = {name = avro_name , kind = gql_type }}
718
+ end
719
+
720
+ if gql_true_type .__type == ' List' then
721
+ box_name = gql_true_type .__type .. ' _box'
722
+ box_fields = {[avro_name ] = {name = avro_name , kind = gql_type }}
723
+ end
724
+
725
+ if gql_true_type .__type == ' Object' then
726
+ box_name = gql_true_type .name .. ' _box'
727
+ box_fields = {[avro_name ] = {name = avro_name , kind = gql_type }}
728
+ end
729
+
730
+ return types .object ({
731
+ name = box_name ,
732
+ description = ' Box (wrapper) around union field' ,
733
+ fields = box_fields ,
734
+ })
735
+ end
736
+
737
+ --- The functions creates table of GraphQL types from avro-schema union type.
738
+ local function create_union_types (avro_schema , state )
739
+ check (avro_schema , ' avro_schema' , ' table' )
740
+ assert (utils .is_array (avro_schema ), ' union avro-schema must be an array ' ..
741
+ ' , got ' .. yaml .encode (avro_schema ))
742
+
743
+ local union_types = {}
744
+ local field_to_type = {}
745
+ local is_nullable = false
746
+
747
+ for _ , type in ipairs (avro_schema ) do
748
+ if type == ' null' then
749
+ is_nullable = true
750
+ else
751
+ union_types [# union_types + 1 ] = box_type (gql_type (state , type ),
752
+ type .name or avro_type (type ))
753
+ local determinant = type .name or type .type or type
754
+ field_to_type [determinant ] = union_types [# union_types ]
755
+ end
756
+ end
757
+
758
+ return union_types , field_to_type , is_nullable
759
+ end
760
+
761
+ --- The function creates GraphQL Union type from given avro-schema union type.
762
+ --- GraphQL responses, received from tarantool graphql instance should be
763
+ --- successfully validated with initial avro-schema (which was used to configure
764
+ --- this tarantool graphql instance), so GraphQL Union types must have specific
765
+ --- formt to be avro-valid. See example in @treturn for details.
766
+ ---
767
+ --- @tparam table state
768
+ --- @tparam table avro_schema avro-schema union type
769
+ --- @tparam string union_name name for resulting GraphQL Union type
770
+ --- @treturn table GraphQL Union type. Consider the following example:
771
+ --- Avro-schema (inside a record):
772
+ --- ...
773
+ --- "name": "MyUnion", "type": [
774
+ --- "null",
775
+ --- "string",
776
+ --- { "type": "record", "name": "Foo", "fields":[
777
+ --- { "name": "foo1", "type": "string" },
778
+ --- { "name": "foo2", "type": "string" }
779
+ --- ]}
780
+ --- ]
781
+ --- ...
782
+ --- GraphQL Union type:
783
+ --- MyUnion {
784
+ --- ... on String_box {
785
+ --- string
786
+ --- }
787
+ ---
788
+ --- ... on Foo_box {
789
+ --- Foo {
790
+ --- foo1
791
+ --- foo2
792
+ --- }
793
+ --- }
794
+ local function create_gql_union (state , avro_schema , union_name )
795
+ check (avro_schema , ' avro_schema' , ' table' )
796
+ assert (utils .is_array (avro_schema ), ' union avro-schema must be an array ' ..
797
+ ' , got ' .. yaml .encode (avro_schema ))
798
+
799
+ -- check avro-schema constraints
800
+ for i , type in ipairs (avro_schema ) do
801
+ assert (avro_type (type ) ~= ' union' , ' unions must not immediately ' ..
802
+ ' contain other unions' )
803
+
804
+ if type .name == nil then
805
+ for j , another_type in ipairs (avro_schema ) do
806
+ if i ~= j then
807
+ assert (avro_type (type ) ~= avro_type (another_type ),
808
+ ' Unions may not contain more than one schema with ' ..
809
+ ' the same type except for the named types ' ..
810
+ ' record, fixed and enum' )
811
+ end
812
+ end
813
+ end
814
+ end
815
+
816
+ -- create GraphQL union
817
+ local union_types , field_to_type , is_nullable =
818
+ create_union_types (avro_schema , state )
819
+
820
+ local union_type = types .union ({
821
+ types = union_types ,
822
+ name = union_name ,
823
+ resolveType = function (result )
824
+ for determinant , type in pairs (field_to_type ) do
825
+ if result [determinant ] ~= nil then
826
+ return type
827
+ end
828
+ end
829
+ error ((' result objects "%s" has no determinant field matching ' ..
830
+ ' determinants for this union "%s"' ):format (yaml .encode (result ),
831
+ yaml .encode (field_to_type )))
832
+ end
833
+ })
834
+
835
+ if not is_nullable then
836
+ union_type = types .nonNull (union_type )
837
+ end
838
+
839
+ return union_type
840
+ end
841
+
673
842
--- The function converts passed avro-schema to a GraphQL type.
674
843
---
675
844
--- @tparam table state for read state.accessor and previously filled
@@ -763,6 +932,8 @@ gql_type = function(state, avro_schema, collection, collection_name)
763
932
764
933
local gql_map = types_map
765
934
return avro_t == ' map' and types .nonNull (gql_map ) or gql_map
935
+ elseif avro_t == ' union' then
936
+ return create_gql_union (state , avro_schema .type , avro_schema .name )
766
937
else
767
938
local res = convert_scalar_type (avro_schema , {raise = false })
768
939
if res == nil then
0 commit comments