1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | import {
|
13 | Definition,
|
14 | OperationDefinition,
|
15 | FragmentDefinition,
|
16 | FragmentSpread,
|
17 | InlineFragment,
|
18 | SelectionSet,
|
19 | Field,
|
20 | Document,
|
21 | Type,
|
22 | parse,
|
23 | print,
|
24 | visit
|
25 | } from "graphql/language";
|
26 |
|
27 | import {
|
28 | ElmFieldDecl,
|
29 | ElmDecl,
|
30 | ElmTypeDecl,
|
31 | ElmParameterDecl,
|
32 | moduleToString,
|
33 | ElmExpr,
|
34 | ElmFunctionDecl,
|
35 | ElmType,
|
36 | ElmTypeName,
|
37 | ElmTypeRecord,
|
38 | ElmTypeApp,
|
39 | ElmTypeAliasDecl
|
40 | } from './elm-ast';
|
41 |
|
42 | import {
|
43 | GraphQLSchema,
|
44 | GraphQLNonNull,
|
45 | GraphQLList,
|
46 | GraphQLScalarType,
|
47 | GraphQLEnumType,
|
48 | GraphQLType,
|
49 | GraphQLObjectType,
|
50 | GraphQLInterfaceType,
|
51 | GraphQLInputObjectType,
|
52 | GraphQLUnionType
|
53 | } from 'graphql/type';
|
54 |
|
55 | import {
|
56 | TypeInfo,
|
57 | typeFromAST,
|
58 | } from 'graphql/utilities';
|
59 |
|
60 | import {
|
61 | decoderForQuery,
|
62 | decoderForFragment
|
63 | } from './query-to-decoder';
|
64 |
|
65 | export type GraphQLEnumMap = { [name: string]: GraphQLEnumType };
|
66 | export type GraphQLTypeMap = { [name: string]: GraphQLType };
|
67 | export type FragmentDefinitionMap = { [name: string]: FragmentDefinition };
|
68 | export type GraphQLUnionMap = { [name: string]: GraphQLUnionType };
|
69 |
|
70 | const alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
|
71 | 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
|
72 |
|
73 | export function queryToElm(graphql: string, moduleName: string, liveUrl: string, verb: string,
|
74 | schema: GraphQLSchema): string {
|
75 | let queryDocument = parse(graphql);
|
76 | let [decls, expose] = translateQuery(liveUrl, queryDocument, schema, verb);
|
77 | return moduleToString(moduleName, expose, [
|
78 | 'Task exposing (Task)',
|
79 | 'Json.Decode.Extra exposing ((|:))',
|
80 | 'Json.Decode exposing (..)',
|
81 | 'Json.Encode exposing (encode)',
|
82 | 'Http',
|
83 | 'GraphQL.Client as GraphQL exposing (Context, apply, maybeEncode, GQLError, HttpMapper)'
|
84 | ], decls);
|
85 | }
|
86 |
|
87 | function translateQuery(uri: string, doc: Document, schema: GraphQLSchema, verb: string): [Array<ElmDecl>, Array<string>] {
|
88 | let expose: Array<string> = [];
|
89 | let fragmentDefinitionMap: FragmentDefinitionMap = {};
|
90 |
|
91 | function walkQueryDocument(doc: Document, info: TypeInfo): [Array<ElmDecl>, Array<string>] {
|
92 | let decls: Array<ElmDecl> = [];
|
93 |
|
94 |
|
95 | buildFragmentDefinitionMap(doc);
|
96 | let seenFragments: FragmentDefinitionMap = {};
|
97 | let seenEnums: GraphQLEnumMap = {};
|
98 | let seenUnions: GraphQLUnionMap = {};
|
99 |
|
100 | for (let def of doc.definitions) {
|
101 | if (def.kind == 'OperationDefinition') {
|
102 | decls.push(...walkOperationDefinition(<OperationDefinition>def, info));
|
103 | } else if (def.kind == 'FragmentDefinition') {
|
104 | decls.push(...walkFragmentDefinition(<FragmentDefinition>def, info));
|
105 | }
|
106 | collectFragments(def, seenFragments);
|
107 | collectEnums(def, seenEnums);
|
108 | collectUnions(def, seenUnions);
|
109 | }
|
110 |
|
111 | for (let fragName in seenFragments) {
|
112 | let frag = seenFragments[fragName];
|
113 | let decodeFragFuncName = fragName[0].toLowerCase() + fragName.substr(1) + 'Decoder';
|
114 | let fragTypeName = fragName[0].toUpperCase() + fragName.substr(1);
|
115 | decls.push(new ElmFunctionDecl(
|
116 | decodeFragFuncName, [],
|
117 | new ElmTypeName('Decoder ' + fragTypeName),
|
118 | decoderForFragment(frag, info, schema, fragmentDefinitionMap, seenFragments) ));
|
119 | expose.push(decodeFragFuncName);
|
120 | expose.push(fragTypeName);
|
121 | }
|
122 |
|
123 | for (let name in seenEnums) {
|
124 | let seenEnum = seenEnums[name];
|
125 | decls.unshift(walkEnum(seenEnum));
|
126 | decls.push(decoderForEnum(seenEnum));
|
127 | expose.push(seenEnum.name + "(..)");
|
128 | }
|
129 |
|
130 | for (let name in seenUnions) {
|
131 | let union = seenUnions[name];
|
132 | decls.unshift(walkUnion(union));
|
133 | expose.push((getRootType(union)['name'] + "(..)"));
|
134 | }
|
135 |
|
136 | return [decls, expose];
|
137 | }
|
138 |
|
139 | function buildFragmentDefinitionMap(doc: Document): void {
|
140 | visit(doc, {
|
141 | enter: function(node) {
|
142 | if (node.kind == 'FragmentDefinition') {
|
143 | let def = <FragmentDefinition>node;
|
144 | let name = def.name.value;
|
145 | fragmentDefinitionMap[name] = def;
|
146 | }
|
147 | },
|
148 | leave: function(node) {}
|
149 | });
|
150 | }
|
151 |
|
152 | function collectFragments(def: Definition, fragments: FragmentDefinitionMap = {}): FragmentDefinitionMap {
|
153 | visit(doc, {
|
154 | enter: function(node) {
|
155 | if (node.kind == 'FragmentSpread') {
|
156 | let spread = <FragmentSpread>node;
|
157 | let name = spread.name.value;
|
158 | fragments[name] = fragmentDefinitionMap[name];
|
159 | }
|
160 | },
|
161 | leave: function(node) {}
|
162 | });
|
163 | return fragments;
|
164 | }
|
165 |
|
166 |
|
167 | function queryFragments(selectionSet: SelectionSet, fragments: FragmentDefinitionMap = {}): FragmentDefinitionMap {
|
168 | if (selectionSet) {
|
169 | visit(selectionSet, {
|
170 | enter: function (node) {
|
171 | if (node.kind == 'FragmentSpread') {
|
172 | let spread = <FragmentSpread>node;
|
173 | let name = spread.name.value;
|
174 | let frag = fragments[name] = fragmentDefinitionMap[name];
|
175 | fragments = queryFragments(frag.selectionSet, fragments);
|
176 | }
|
177 | },
|
178 | leave: function (node) {
|
179 | }
|
180 | });
|
181 | }
|
182 | return fragments;
|
183 | }
|
184 |
|
185 | function collectUnions(def: Definition, unions: GraphQLUnionMap = {}): GraphQLUnionMap {
|
186 | let info = new TypeInfo(schema);
|
187 | visit(doc, {
|
188 | enter: function(node, key, parent) {
|
189 | if (node.kind == 'InlineFragment') {
|
190 | let parentType = <GraphQLUnionType> info.getType();
|
191 | unions[parentType.name] = parentType;
|
192 | }
|
193 | info.enter(node);
|
194 | },
|
195 | leave: function(node) {
|
196 | info.leave(node);
|
197 | }
|
198 | });
|
199 | return unions;
|
200 | }
|
201 |
|
202 | function collectEnums(def: Definition, enums: GraphQLEnumMap = {}): GraphQLEnumMap {
|
203 | let info = new TypeInfo(schema);
|
204 | visit(doc, {
|
205 | enter: function(node, key, parent) {
|
206 | info.enter(node);
|
207 | if (node.kind == 'Field') {
|
208 | let field = <Field>node;
|
209 | let name = field.name.value;
|
210 | let type = info.getType();
|
211 | collectEnumsForType(type, enums);
|
212 | }
|
213 |
|
214 | },
|
215 | leave: function(node, key, parent) {
|
216 | info.leave(node);
|
217 | }
|
218 | });
|
219 | return enums;
|
220 | }
|
221 |
|
222 | function collectEnumsForType(type: GraphQLType, seen: GraphQLEnumMap = {}, seenTypes: GraphQLTypeMap = {}): void {
|
223 | if (type instanceof GraphQLEnumType) {
|
224 | seen[type.name] = type;
|
225 | } else if (type instanceof GraphQLList) {
|
226 | collectEnumsForType(type.ofType, seen, seenTypes);
|
227 | } else if (type instanceof GraphQLObjectType ||
|
228 | type instanceof GraphQLInterfaceType ||
|
229 | type instanceof GraphQLInputObjectType) {
|
230 | if (seenTypes[type.name]) {
|
231 | return;
|
232 | } else {
|
233 | seenTypes[type.name] = type;
|
234 | }
|
235 | let fieldMap = type.getFields();
|
236 | for (let fieldName in fieldMap) {
|
237 | let field = fieldMap[fieldName];
|
238 | collectEnumsForType(field.type, seen, seenTypes)
|
239 | }
|
240 | } else if (type instanceof GraphQLNonNull) {
|
241 | collectEnumsForType(type.ofType, seen, seenTypes);
|
242 | }
|
243 | }
|
244 |
|
245 | function walkEnum(enumType: GraphQLEnumType): ElmTypeDecl {
|
246 | console.log(enumType.getValues())
|
247 | return new ElmTypeDecl(enumType.name, enumType.getValues().map(v => v.name[0].toUpperCase() + v.name.substr(1)));
|
248 | }
|
249 |
|
250 | function decoderForEnum(enumType: GraphQLEnumType): ElmFunctionDecl {
|
251 |
|
252 | let decoderTypeName = enumType.name[0].toUpperCase() + enumType.name.substr(1);
|
253 | return new ElmFunctionDecl(enumType.name.toLowerCase() + 'Decoder', [], new ElmTypeName('Decoder ' + decoderTypeName),
|
254 | { expr: 'string |> andThen (\\s ->\n' +
|
255 | ' case s of\n' + enumType.getValues().map(v =>
|
256 | ' "' + v.name + '" -> succeed ' + v.name[0].toUpperCase() + v.name.substr(1)).join('\n') + '\n' +
|
257 | ' _ -> fail "Unknown ' + enumType.name + '")'
|
258 | });
|
259 | }
|
260 |
|
261 | function walkUnion(union: GraphQLUnionType): ElmTypeDecl {
|
262 | union = <GraphQLUnionType>getRootType(union)
|
263 | let types = union.getTypes();
|
264 | let params = types.map((t, i) => alphabet[i]).join(' ');
|
265 | return new ElmTypeDecl(getRootType(union)['name'] + ' ' + params, types.map((t, i) => elmSafeName(t.name) + ' ' + alphabet[i]));
|
266 | }
|
267 |
|
268 | function walkOperationDefinition(def: OperationDefinition, info: TypeInfo): Array<ElmDecl> {
|
269 | info.enter(def);
|
270 | if (!info.getType()) {
|
271 | throw new Error(`GraphQL schema does not define ${def.operation} '${def.name.value}'`);
|
272 | }
|
273 | if (def.operation == 'query' || def.operation == 'mutation') {
|
274 | let decls: Array<ElmDecl> = [];
|
275 |
|
276 | let name: string;
|
277 | if (def.name) {
|
278 | name = def.name.value;
|
279 | } else {
|
280 | name = 'AnonymousQuery';
|
281 | }
|
282 | let resultType = name[0].toUpperCase() + name.substr(1);
|
283 |
|
284 |
|
285 | let [fields, spreads] = walkSelectionSet(def.selectionSet, info);
|
286 |
|
287 | decls.push(new ElmTypeAliasDecl(resultType, new ElmTypeRecord(fields)))
|
288 |
|
289 | let parameters: Array<{name: string, type: ElmType, schemaType: GraphQLType, hasDefault:boolean}> = [];
|
290 | if (def.variableDefinitions) {
|
291 | for (let varDef of def.variableDefinitions) {
|
292 | let name = varDef.variable.name.value;
|
293 | let schemaType = typeFromAST(schema, varDef.type);
|
294 | let type = typeToElm(schemaType);
|
295 | parameters.push({ name, type, schemaType, hasDefault: varDef.defaultValue != null });
|
296 | }
|
297 | }
|
298 | let funcName = name[0].toLowerCase() + name.substr(1);
|
299 |
|
300 |
|
301 | let seenFragments = collectFragments(def);
|
302 |
|
303 |
|
304 | let qFragments = queryFragments(def.selectionSet);
|
305 |
|
306 | let query = '';
|
307 | for (let name in qFragments) {
|
308 | query += print(qFragments[name]) + ' ';
|
309 | }
|
310 |
|
311 | query += print(def);
|
312 | let decodeFuncName = resultType[0].toLowerCase() + resultType.substr(1) + 'Decoder';
|
313 | expose.push(funcName);
|
314 | expose.push(resultType);
|
315 |
|
316 | let resultTypeName = resultType[0].toUpperCase() + resultType.substr(1);
|
317 | let elmContextType = new ElmTypeName("Context");
|
318 | let elmMapperType = new ElmTypeName(`HttpMapper success result`);
|
319 | let elmToMsgType = new ElmTypeName("(result -> msg)");
|
320 | let elmToResultMapType = new ElmTypeName(`(${resultTypeName} -> success)`);
|
321 | let elmParamsType = new ElmTypeRecord(parameters.map(p => new ElmFieldDecl(p.name, p.type)));
|
322 | let elmContext = new ElmParameterDecl('context', elmContextType);
|
323 | let elmMapper = new ElmParameterDecl('mapper', elmMapperType);
|
324 | let elmToMsg = new ElmParameterDecl('toMsg', elmToMsgType);
|
325 | let elmToResultMap = new ElmParameterDecl('mapDecoder', elmToResultMapType);
|
326 | let elmParams = new ElmParameterDecl('params', elmParamsType);
|
327 | let elmParamsDecl = elmParamsType.fields.length > 0 ? [elmContext, elmParams, elmToResultMap, elmMapper, elmToMsg] : [elmContext, elmToResultMap, elmMapper, elmToMsg];
|
328 | let methodParam = def.operation == 'query' ? `"${verb}" ` : '';
|
329 |
|
330 | decls.push(new ElmFunctionDecl(
|
331 | funcName, elmParamsDecl, new ElmTypeName(`Cmd msg`),
|
332 | {
|
333 |
|
334 | expr: `let graphQLQuery = """${query.replace(/\s+/g, ' ')}""" in\n` +
|
335 | ` let graphQLParams =\n` +
|
336 | ` Json.Encode.object\n` +
|
337 | ` [ ` +
|
338 | parameters.map(p => {
|
339 | let encoder: string;
|
340 | if (p.hasDefault) {
|
341 | encoder =`case params.${p.name} of` +
|
342 | `\n Just val -> ${encoderForInputType(p.schemaType, true)} val` +
|
343 | `\n Nothing -> Json.Encode.null`
|
344 | } else {
|
345 | encoder = encoderForInputType(p.schemaType, true, 'params.' + p.name);
|
346 | }
|
347 | return `("${p.name}", ${encoder})`;
|
348 | })
|
349 | .join(`\n , `) + '\n' +
|
350 | ` ]\n` +
|
351 | ` in\n` +
|
352 | ` GraphQL.${def.operation} context ${methodParam}graphQLQuery "${name}" graphQLParams (Json.Decode.map mapDecoder ${decodeFuncName}) mapper toMsg`
|
353 | }
|
354 | ));
|
355 | decls.push(new ElmFunctionDecl(
|
356 | decodeFuncName, [],
|
357 | new ElmTypeName('Decoder ' + resultTypeName),
|
358 | decoderForQuery(def, info, schema, fragmentDefinitionMap, seenFragments) ));
|
359 |
|
360 | info.leave(def);
|
361 | return decls;
|
362 | }
|
363 | }
|
364 |
|
365 | function encoderForInputType(type: GraphQLType, isNonNull?: boolean, value?: string): string {
|
366 | let encoder: string;
|
367 |
|
368 | let isMaybe = false
|
369 | if (type instanceof GraphQLNonNull) {
|
370 | type = type['ofType'];
|
371 | } else {
|
372 | isMaybe = true;
|
373 | }
|
374 |
|
375 | if (type instanceof GraphQLInputObjectType) {
|
376 | let fieldEncoders: Array<string> = [];
|
377 | let fields = type.getFields();
|
378 | for (let name in fields) {
|
379 | let field = fields[name];
|
380 | let valuePath = value + '.' + field.name;
|
381 | fieldEncoders.push(`("${field.name}", ${encoderForInputType(field.type, false, valuePath)})`);
|
382 | }
|
383 | encoder = '(Json.Encode.object [' + fieldEncoders.join(`, `) + '])';
|
384 | } else if (type instanceof GraphQLList) {
|
385 | encoder = '(Json.Encode.list (List.map (\\x -> ' + encoderForInputType(type.ofType, true, 'x') + ') ' + value + '))';
|
386 | } else if (type instanceof GraphQLScalarType) {
|
387 | switch (type.name) {
|
388 | case 'Int': encoder = 'Json.Encode.int ' + value; break;
|
389 | case 'Float': encoder = 'Json.Encode.float ' + value; break;
|
390 | case 'Boolean': encoder = 'Json.Encode.bool ' + value; break;
|
391 | case 'ID':
|
392 | case 'String': encoder = 'Json.Encode.string ' + value; break;
|
393 | }
|
394 | } else {
|
395 | throw new Error('not implemented: ' + type.constructor.name);
|
396 | }
|
397 |
|
398 | if (isMaybe) {
|
399 | encoder = '(maybeEncode ' + encoder + ')'
|
400 | }
|
401 | return encoder;
|
402 | }
|
403 |
|
404 | function walkFragmentDefinition(def: FragmentDefinition, info: TypeInfo): Array<ElmDecl> {
|
405 | info.enter(def);
|
406 |
|
407 | let name = def.name.value;
|
408 |
|
409 | let decls: Array<ElmDecl> = [];
|
410 | let resultType = name[0].toUpperCase() + name.substr(1);
|
411 |
|
412 |
|
413 |
|
414 |
|
415 | let [fields, spreads] = walkSelectionSet(def.selectionSet, info);
|
416 | let type: ElmType = new ElmTypeRecord(fields, 'a')
|
417 | for (let spreadName of spreads) {
|
418 | let typeName = spreadName[0].toUpperCase() + spreadName.substr(1) + '_';
|
419 | type = new ElmTypeApp(typeName, [type]);
|
420 | }
|
421 |
|
422 | decls.push(new ElmTypeAliasDecl(resultType + '_', type, ['a']));
|
423 | decls.push(new ElmTypeAliasDecl(resultType, new ElmTypeApp(resultType + '_', [new ElmTypeRecord([])])));
|
424 |
|
425 | info.leave(def);
|
426 | return decls;
|
427 | }
|
428 |
|
429 | function walkSelectionSet(selSet: SelectionSet, info: TypeInfo): [Array<ElmFieldDecl>, Array<string>, ElmType] {
|
430 | info.enter(selSet);
|
431 | let fields: Array<ElmFieldDecl> = [];
|
432 | let spreads: Array<string> = [];
|
433 |
|
434 | if (getRootType(info.getType()) instanceof GraphQLUnionType) {
|
435 | let type = walkUnionSelectionSet(selSet, info);
|
436 | return [[], [], type];
|
437 | } else {
|
438 | for (let sel of selSet.selections) {
|
439 | if (sel.kind == 'Field') {
|
440 | let field = <Field>sel;
|
441 | fields.push(walkField(field, info));
|
442 | } else if (sel.kind == 'FragmentSpread') {
|
443 | spreads.push((<FragmentSpread>sel).name.value);
|
444 | } else if (sel.kind == 'InlineFragment') {
|
445 | let frag = (<InlineFragment>sel);
|
446 |
|
447 | throw new Error('not implemented: InlineFragment on ' + frag.typeCondition.name.value);
|
448 | }
|
449 | }
|
450 |
|
451 | info.leave(selSet);
|
452 | return [fields, spreads, null];
|
453 | }
|
454 | }
|
455 |
|
456 | function walkUnionSelectionSet(selSet: SelectionSet, info: TypeInfo): ElmType {
|
457 | let union = <GraphQLUnionType>getRootType(info.getType())
|
458 |
|
459 | let typeMap: { [name: string]: ElmType } = {};
|
460 | for (let type of union.getTypes()) {
|
461 | typeMap[type.name] = new ElmTypeRecord([]);
|
462 | }
|
463 |
|
464 | for (let sel of selSet.selections) {
|
465 | if (sel.kind == 'InlineFragment') {
|
466 | let inline = (<InlineFragment>sel);
|
467 |
|
468 | info.enter(inline);
|
469 | let [fields, spreads] = walkSelectionSet(inline.selectionSet, info);
|
470 | info.leave(inline);
|
471 |
|
472 |
|
473 | let type: ElmType = new ElmTypeRecord(fields);
|
474 |
|
475 | for (let spreadName of spreads) {
|
476 | let typeName = spreadName[0].toUpperCase() + spreadName.substr(1) + '_';
|
477 | type = new ElmTypeApp(typeName, [type]);
|
478 | }
|
479 |
|
480 | typeMap[inline.typeCondition.name.value] = type;
|
481 | }
|
482 | }
|
483 |
|
484 | let args: Array<ElmType> = [];
|
485 | for (let name in typeMap) {
|
486 | args.push(typeMap[name]);
|
487 | }
|
488 | return new ElmTypeApp(getRootType(union)['name'], args);
|
489 | }
|
490 |
|
491 | function walkField(field: Field, info: TypeInfo): ElmFieldDecl {
|
492 | info.enter(field);
|
493 |
|
494 | let info_type = info.getType()
|
495 |
|
496 | let name = elmSafeName(field.name.value);
|
497 |
|
498 | if (field.alias) {
|
499 | name = elmSafeName(field.alias.value);
|
500 | }
|
501 |
|
502 | let args = field.arguments;
|
503 |
|
504 |
|
505 | if (field.selectionSet) {
|
506 | let isMaybe = false
|
507 | if (info_type instanceof GraphQLNonNull) {
|
508 | info_type = info_type['ofType']
|
509 | } else {
|
510 | isMaybe = true
|
511 | }
|
512 |
|
513 | let isList = info_type instanceof GraphQLList;
|
514 | let [fields, spreads, union] = walkSelectionSet(field.selectionSet, info);
|
515 |
|
516 | let type: ElmType = union ? union : new ElmTypeRecord(fields);
|
517 |
|
518 | for (let spreadName of spreads) {
|
519 | let typeName = spreadName[0].toUpperCase() + spreadName.substr(1) + '_';
|
520 | type = new ElmTypeApp(typeName, [type]);
|
521 | }
|
522 |
|
523 | if (isList) {
|
524 | type = new ElmTypeApp('List', [type]);
|
525 | }
|
526 |
|
527 | if (isMaybe) {
|
528 | type = new ElmTypeApp('Maybe', [type]);
|
529 | }
|
530 |
|
531 | info.leave(field);
|
532 | return new ElmFieldDecl(name, type)
|
533 | } else {
|
534 | if (!info.getType()) {
|
535 | let errorMessage = `Unknown GraphQL field: ' ${field.name.value}\n${JSON.stringify(info)}\n`
|
536 | throw new Error('Unknown GraphQL field: ' + field.name.value);
|
537 | }
|
538 | let type = typeToElm(info.getType());
|
539 | info.leave(field);
|
540 | return new ElmFieldDecl(name, type)
|
541 | }
|
542 | }
|
543 | return walkQueryDocument(doc, new TypeInfo(schema));
|
544 | }
|
545 |
|
546 | export function getRootType(type: GraphQLType): GraphQLType {
|
547 | if (type instanceof GraphQLList){
|
548 | return getRootType(type['ofType'])
|
549 | } else if (type instanceof GraphQLNonNull){
|
550 | return getRootType(type['ofType'])
|
551 | } else {
|
552 | return type
|
553 | }
|
554 | }
|
555 |
|
556 | export function typeToElm(type: GraphQLType, isNonNull = false): ElmType {
|
557 | let elmType: ElmType;
|
558 |
|
559 | if (type instanceof GraphQLNonNull) {
|
560 | elmType = typeToElm(type.ofType, true);
|
561 | }
|
562 | else if (type instanceof GraphQLScalarType) {
|
563 | switch (type.name) {
|
564 | case 'Int': elmType = new ElmTypeName('Int'); break;
|
565 | case 'Float': elmType = new ElmTypeName('Float'); break;
|
566 | case 'Boolean': elmType = new ElmTypeName('Bool'); break;
|
567 | case 'ID':
|
568 | case 'String': elmType = new ElmTypeName('String'); break;
|
569 | }
|
570 | } else if (type instanceof GraphQLEnumType) {
|
571 | elmType = new ElmTypeName(type.name[0].toUpperCase() + type.name.substr(1));
|
572 | } else if (type instanceof GraphQLList) {
|
573 | elmType = new ElmTypeApp('List', [typeToElm(type.ofType, true)]);
|
574 | } else if (type instanceof GraphQLObjectType ||
|
575 | type instanceof GraphQLInterfaceType ||
|
576 | type instanceof GraphQLInputObjectType) {
|
577 | let fields: Array<ElmFieldDecl> = [];
|
578 | let fieldMap = type.getFields();
|
579 | for (let fieldName in fieldMap) {
|
580 | let field = fieldMap[fieldName];
|
581 | fields.push(new ElmFieldDecl(elmSafeName(fieldName), typeToElm(field.type)))
|
582 | }
|
583 | elmType = new ElmTypeRecord(fields);
|
584 | } else if (type instanceof GraphQLNonNull) {
|
585 | elmType = typeToElm(type.ofType, true);
|
586 | } else {
|
587 | throw new Error('Unexpected: ' + type.constructor.name);
|
588 | }
|
589 |
|
590 | if (!isNonNull && !(type instanceof GraphQLList) && !(type instanceof GraphQLNonNull)) {
|
591 | elmType = new ElmTypeApp('Maybe', [elmType]);
|
592 | }
|
593 | return elmType;
|
594 | }
|
595 |
|
596 | export function elmSafeName(graphQlName: string): string {
|
597 | switch (graphQlName) {
|
598 | case 'type': return "type_";
|
599 | case 'Task': return "Task_";
|
600 | case 'List': return "List_";
|
601 | case 'Http': return "Http_";
|
602 | case 'GraphQL': return "GraphQL_";
|
603 |
|
604 | default: return graphQlName;
|
605 | }
|
606 | }
|