@@ -10,13 +10,16 @@ local introspection = require(path .. '.introspection')
10
10
local query_util = require (path .. ' .query_util' )
11
11
local avro_helpers = require (' graphql.avro_helpers' )
12
12
local convert_schema_helpers = require (' graphql.convert_schema.helpers' )
13
+ local utils = require (' graphql.utils' )
14
+ local check = utils .check
13
15
14
16
-- module functions
15
17
local query_to_avro = {}
16
18
17
19
-- forward declaration
18
20
local object_to_avro
19
21
local map_to_avro
22
+ local union_to_avro
20
23
21
24
local gql_scalar_to_avro_index = {
22
25
String = " string" ,
48
51
--- @tparam table context current traversal context, here it just falls to the
49
52
--- called functions (internal graphql-lua format)
50
53
---
54
+ --- @tparam table opts the following options:
55
+ ---
56
+ --- * is_nullable (boolean) whether resulting type should be nullable or not.
57
+ --- If passed, then the function will not care if original GraphQL type was
58
+ --- nullable or not.
59
+ ---
51
60
--- @treturn table `result` is the resulting avro-schema
52
- local function gql_type_to_avro (fieldType , subSelections , context )
61
+ local function gql_type_to_avro (fieldType , subSelections , context , opts )
53
62
local fieldTypeName = fieldType .__type
54
63
local isNonNull = false
55
64
@@ -74,12 +83,19 @@ local function gql_type_to_avro(fieldType, subSelections, context)
74
83
result = gql_scalar_to_avro (fieldType )
75
84
elseif fieldTypeName == ' Object' then
76
85
result = object_to_avro (fieldType , subSelections , context )
77
- elseif fieldTypeName == ' Interface' or fieldTypeName == ' Union' then
78
- error (' Interfaces and Unions are not supported yet' )
86
+ elseif fieldTypeName == ' Union' then
87
+ result = union_to_avro (fieldType , subSelections , context )
88
+ elseif fieldTypeName == ' Interface' then
89
+ error (' Interfaces are not supported yet' )
79
90
else
80
91
error (string.format (' Unknown type "%s"' , tostring (fieldTypeName )))
81
92
end
82
93
94
+ local opts = opts or {}
95
+ check (opts .is_nullable , ' opts.is_nullable' , ' nil' , ' boolean' )
96
+ if opts .is_nullable ~= nil then
97
+ isNonNull = not opts .is_nullable
98
+ end
83
99
if not isNonNull then
84
100
result = avro_helpers .make_avro_type_nullable (result , {
85
101
raise_on_nullable = true ,
@@ -97,6 +113,88 @@ map_to_avro = function(mapType)
97
113
}
98
114
end
99
115
116
+ --- The function converts a GraphQL Union type to avro-schema type.
117
+ ---
118
+ --- Currently we use GraphQL Unions to implement both multi-head connections
119
+ --- and avro-schema unions. The function distinguishes between them relying on
120
+ --- 'fieldType.resolveType'. GraphQL Union implementing multi-head
121
+ --- connection does not have such field, as it has another mechanism of union
122
+ --- type resolving.
123
+ ---
124
+ --- We have to distinguish between these two types of GraphQL Unions because
125
+ --- we want to create different avro-schemas for them.
126
+ ---
127
+ --- GraphQL Unions implementing avro-schema unions are to be converted back
128
+ --- to avro-schema unions.
129
+ ---
130
+ --- GraphQL Unions implementing multi-head connections are to be converted to
131
+ --- avro-schema records. Each field represents one union variant. Variant type
132
+ --- name is taken as a field name. Such records must have all fields nullable.
133
+ ---
134
+ --- We convert multi-head Unions to records instead of unions because in case
135
+ --- of 1:N connections we would not have valid avro-schema (if use unions).
136
+ --- This is because according to avro-schema standard unions may not contain
137
+ --- more than one schema with the same type (in case of 1:N multi-head
138
+ --- connections we would have more than one 'array' in union)
139
+ union_to_avro = function (fieldType , subSelections , context )
140
+ assert (fieldType .types ~= nil , " GraphQL Union must have 'types' field" )
141
+ check (fieldType .types , " fieldType.types" , " table" )
142
+ local is_multihead = (fieldType .resolveType == nil )
143
+ local result = {}
144
+
145
+ if is_multihead then
146
+ check (fieldType .name , " fieldType.name" , " string" )
147
+ result = {
148
+ type = ' record' ,
149
+ name = fieldType .name ,
150
+ fields = {}
151
+ }
152
+ end
153
+
154
+ for _ , box_type in ipairs (fieldType .types ) do
155
+ -- In GraphQL schema all types in Unions are 'boxed'. Here we
156
+ -- 'Unbox' types and selectionSets. More info on 'boxing' can be
157
+ -- found at @{convert_schema.types.convert_multihead_connection}
158
+ -- and at @{convert_schema.union}.
159
+ check (box_type , " box_type" , " table" )
160
+ assert (box_type .__type == " Object" , " Box type must be a GraphQL Object" )
161
+ assert (utils .table_size (box_type .fields ) == 1 , ' Box Object must ' ..
162
+ ' have exactly one field' )
163
+ local type =
164
+ box_type .fields [utils .get_keys (box_type .fields )[1 ]]
165
+
166
+ local box_sub_selections
167
+ for _ , s in pairs (subSelections ) do
168
+ if s .typeCondition .name .value == box_type .name then
169
+ box_sub_selections = s
170
+ break
171
+ end
172
+ end
173
+
174
+ -- We have to extract subSelections from 'box' type.
175
+ local type_sub_selections
176
+ if box_sub_selections .selectionSet .selections [1 ].selectionSet ~= nil then
177
+ -- Object GraphQL type case.
178
+ type_sub_selections =
179
+ box_sub_selections .selectionSet .selections [1 ].selectionSet .selections
180
+ else
181
+ -- Scalar GraphQL type case.
182
+ type_sub_selections = box_sub_selections .selectionSet .selections [1 ]
183
+ end
184
+
185
+ if is_multihead then
186
+ local avro_type = gql_type_to_avro (type .kind ,
187
+ type_sub_selections , context , {is_nullable = true })
188
+ table.insert (result .fields , {name = type .name , type = avro_type })
189
+ else
190
+ table.insert (result , gql_type_to_avro (type .kind ,
191
+ type_sub_selections , context ))
192
+ end
193
+ end
194
+
195
+ return result
196
+ end
197
+
100
198
--- The function converts a single Object field to avro format.
101
199
local function field_to_avro (object_type , fields , context )
102
200
local firstField = fields [1 ]
0 commit comments