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