@@ -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,14 +674,174 @@ 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 may be 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
+ --- @tparam string avro_name type (or name, in record case) of avro-schema which
686
+ --- was used to create `gql_type`. `avro_name` is used to provide avro-valid names
687
+ --- for fields of boxed types
688
+ --- @treturn table GraphQL Object
689
+ local function box_type (gql_type , avro_name )
690
+ check (gql_type , ' gql_type' , ' table' )
691
+
692
+ local gql_true_type = nullable (gql_type )
693
+
694
+ local box_name = gql_true_type .name or gql_true_type .__type
695
+ box_name = box_name .. ' _box'
696
+
697
+ local box_fields = {[avro_name ] = {name = avro_name , kind = gql_type }}
698
+
699
+ return types .object ({
700
+ name = box_name ,
701
+ description = ' Box (wrapper) around union variant' ,
702
+ fields = box_fields ,
703
+ })
704
+ end
705
+
706
+ --- The functions creates table of GraphQL types from avro-schema union type.
707
+ local function create_union_types (avro_schema , state )
708
+ check (avro_schema , ' avro_schema' , ' table' )
709
+ assert (utils .is_array (avro_schema ), ' union avro-schema must be an array ' ..
710
+ ' , got\n ' .. yaml .encode (avro_schema ))
711
+
712
+ local union_types = {}
713
+ local determinant_to_type = {}
714
+ local is_nullable = false
715
+
716
+ for _ , type in ipairs (avro_schema ) do
717
+ -- If there is a 'null' type among 'union' types (in avro-schema union)
718
+ -- then resulting GraphQL Union type will be nullable
719
+ if type == ' null' then
720
+ is_nullable = true
721
+ else
722
+ local variant_type = gql_type (state , type )
723
+ local box_field_name = type .name or avro_type (type )
724
+ union_types [# union_types + 1 ] = box_type (variant_type , box_field_name )
725
+ local determinant = type .name or type .type or type
726
+ determinant_to_type [determinant ] = union_types [# union_types ]
727
+ end
728
+ end
729
+
730
+ return union_types , determinant_to_type , is_nullable
731
+ end
732
+
733
+ --- The function creates GraphQL Union type from given avro-schema union type.
734
+ --- GraphQL responses, received from tarantool graphql instance should be
735
+ --- avro-valid, so GraphQL Union types must have specific format.
736
+ --- 'Avro-valid' means that mentioned GraphQL responses must be successfully
737
+ --- validated with avro-schema generated from GraphQL query. This goes like this:
738
+ --- a GraphQL query comes to tarantool graphql instance, then the response is
739
+ --- created and it should be valid from point of view of avro-schema (which is
740
+ --- generated from current incoming query)
741
+ --- Specific GraphQL Union types format means the following:
742
+ --- 1) Scalars and Lists must be inside corresponding boxes - GraphQL Object types
743
+ --- with name = 'GraphQLTypeName'_box and a single field with Scalar/List type
744
+ --- and name = 'avro-schema scalar type name' or name = 'array' in List cases
745
+ --- 2) Objects also must be inside boxes - GraphQL Object types with
746
+ --- name = 'avro-shema record name'_box and a single field with type = wrapped
747
+ --- Object type
748
+ --- See example in @treturn for details.
749
+ ---
750
+ --- @tparam table state
751
+ --- @tparam table avro_schema avro-schema union type
752
+ --- @tparam string union_name name for resulting GraphQL Union type
753
+ --- @treturn table GraphQL Union type. Consider the following example:
754
+ --- Avro-schema (inside a record):
755
+ --- ...
756
+ --- "name": "MyUnion", "type": [
757
+ --- "null",
758
+ --- "string",
759
+ --- { "type": "record", "name": "Foo", "fields":[
760
+ --- { "name": "foo1", "type": "string" },
761
+ --- { "name": "foo2", "type": "string" }
762
+ --- ]}
763
+ --- ]
764
+ --- ...
765
+ --- GraphQL Union type (It will be nullable as avro-schema has 'null' variant):
766
+ --- MyUnion {
767
+ --- ... on String_box {
768
+ --- string
769
+ --- }
770
+ ---
771
+ --- ... on Foo_box {
772
+ --- Foo {
773
+ --- foo1
774
+ --- foo2
775
+ --- }
776
+ --- }
777
+ local function create_gql_union (state , avro_schema , union_name )
778
+ check (avro_schema , ' avro_schema' , ' table' )
779
+ assert (utils .is_array (avro_schema ), ' union avro-schema must be an array, ' ..
780
+ ' got ' .. yaml .encode (avro_schema ))
781
+
782
+ -- check avro-schema constraints
783
+ for i , type in ipairs (avro_schema ) do
784
+ assert (avro_type (type ) ~= ' union' , ' unions must not immediately ' ..
785
+ ' contain other unions' )
786
+
787
+ if type .name ~= nil then
788
+ for j , another_type in ipairs (avro_schema ) do
789
+ if i ~= j then
790
+ assert (type .name ~= another_type .name ,
791
+ ' Unions may not contain more than one schema with ' ..
792
+ ' the same name' )
793
+ end
794
+ end
795
+ else
796
+ for j , another_type in ipairs (avro_schema ) do
797
+ if i ~= j then
798
+ assert (avro_type (type ) ~= avro_type (another_type ),
799
+ ' Unions may not contain more than one schema with ' ..
800
+ ' the same type except for the named types: ' ..
801
+ ' record, fixed and enum' )
802
+ end
803
+ end
804
+ end
805
+ end
806
+
807
+ -- create GraphQL union
808
+ local union_types , determinant_to_type , is_nullable =
809
+ create_union_types (avro_schema , state )
810
+
811
+ local union_type = types .union ({
812
+ types = union_types ,
813
+ name = union_name ,
814
+ resolveType = function (result )
815
+ for determinant , type in pairs (determinant_to_type ) do
816
+ if result [determinant ] ~= nil then
817
+ return type
818
+ end
819
+ end
820
+ error ((' result object has no determinant field matching ' ..
821
+ ' determinants for this union\n result object:\n %sdeterminants:\n %s' )
822
+ :format (yaml .encode (result ),
823
+ yaml .encode (determinant_to_type )))
824
+ end
825
+ })
826
+
827
+ if not is_nullable then
828
+ union_type = types .nonNull (union_type )
829
+ end
830
+
831
+ return union_type
832
+ end
833
+
673
834
--- The function converts passed avro-schema to a GraphQL type.
674
835
---
675
836
--- @tparam table state for read state.accessor and previously filled
676
837
--- state.nullable_collection_types (those are gql types)
677
838
--- @tparam table avro_schema input avro-schema
678
839
--- @tparam [opt] table collection table with schema_name, connections fields
679
840
--- described a collection (e.g. tarantool's spaces)
680
- ---
841
+ --- @tparam [opt] string collection_name name of `collection`
842
+ --- @tparam [opt] string field_name it is only for an union generation,
843
+ --- because avro-schema union has no name in it and specific name is necessary
844
+ --- for GraphQL union
681
845
--- If collection is passed, two things are changed within this function:
682
846
---
683
847
--- 1. Connections from the collection will be taken into account to
688
852
--- XXX As it is not clear now what to do with complex types inside arrays
689
853
--- (just pass to results or allow to use filters), only scalar arrays
690
854
--- is allowed for now. Note: map is considered scalar.
691
- gql_type = function (state , avro_schema , collection , collection_name )
855
+ gql_type = function (state , avro_schema , collection , collection_name , field_name )
692
856
assert (type (state ) == ' table' ,
693
857
' state must be a table, got ' .. type (state ))
694
858
assert (avro_schema ~= nil ,
@@ -763,6 +927,8 @@ gql_type = function(state, avro_schema, collection, collection_name)
763
927
764
928
local gql_map = types_map
765
929
return avro_t == ' map' and types .nonNull (gql_map ) or gql_map
930
+ elseif avro_t == ' union' then
931
+ return create_gql_union (state , avro_schema , field_name )
766
932
else
767
933
local res = convert_scalar_type (avro_schema , {raise = false })
768
934
if res == nil then
0 commit comments