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