1 | import { getBaseTypeNode, indent, indentMultiline } from '@graphql-codegen/visitor-plugin-common';
|
2 | import { JavaDeclarationBlock } from '@graphql-codegen/java-common';
|
3 | import { isScalarType, isInputObjectType, Kind, isEnumType, } from 'graphql';
|
4 | import { Imports } from './imports.js';
|
5 | import { BaseJavaVisitor, SCALAR_TO_WRITER_METHOD } from './base-java-visitor.js';
|
6 | export class InputTypeVisitor extends BaseJavaVisitor {
|
7 | constructor(_schema, rawConfig) {
|
8 | super(_schema, rawConfig, {
|
9 | typePackage: rawConfig.typePackage || 'type',
|
10 | });
|
11 | }
|
12 | getPackage() {
|
13 | return this.config.typePackage;
|
14 | }
|
15 | addInputMembers(cls, fields) {
|
16 | fields.forEach(field => {
|
17 | const type = this.transformType(field.type);
|
18 | const actualType = type.isNonNull ? type.typeToUse : `Input<${type.typeToUse}>`;
|
19 | const annotations = type.isNonNull ? [type.annotation] : [];
|
20 | this._imports.add(Imports[type.annotation]);
|
21 | cls.addClassMember(field.name.value, actualType, null, annotations, 'private', { final: true });
|
22 | cls.addClassMethod(field.name.value, actualType, `return this.${field.name.value};`, [], [type.annotation], 'public');
|
23 | });
|
24 | }
|
25 | addInputCtor(cls, className, fields) {
|
26 | const impl = fields.map(field => `this.${field.name.value} = ${field.name.value};`).join('\n');
|
27 | cls.addClassMethod(className, null, impl, fields.map(f => {
|
28 | const type = this.transformType(f.type);
|
29 | const actualType = type.isNonNull ? type.typeToUse : `Input<${type.typeToUse}>`;
|
30 | this._imports.add(Imports[type.annotation]);
|
31 | return {
|
32 | name: f.name.value,
|
33 | type: actualType,
|
34 | annotations: type.isNonNull ? [type.annotation] : [],
|
35 | };
|
36 | }), [], 'public');
|
37 | }
|
38 | getFieldWriterCall(field, listItemCall = false) {
|
39 | const baseType = getBaseTypeNode(field.type);
|
40 | const schemaType = this._schema.getType(baseType.name.value);
|
41 | const isNonNull = field.type.kind === Kind.NON_NULL_TYPE;
|
42 | let writerMethod = null;
|
43 | if (isScalarType(schemaType)) {
|
44 | writerMethod = SCALAR_TO_WRITER_METHOD[schemaType.name] || 'writeCustom';
|
45 | }
|
46 | else if (isInputObjectType(schemaType)) {
|
47 | return listItemCall
|
48 | ? `writeObject($item.marshaller())`
|
49 | : `writeObject("${field.name.value}", ${field.name.value}.value != null ? ${field.name.value}.value.marshaller() : null)`;
|
50 | }
|
51 | else if (isEnumType(schemaType)) {
|
52 | writerMethod = 'writeString';
|
53 | }
|
54 | return listItemCall
|
55 | ? `${writerMethod}($item)`
|
56 | : `${writerMethod}("${field.name.value}", ${field.name.value}${isNonNull ? '' : '.value'})`;
|
57 | }
|
58 | getFieldWithTypePrefix(field, wrapWith = null, applyNullable = false) {
|
59 | this._imports.add(Imports.Input);
|
60 | const typeToUse = this.getJavaClass(this._schema.getType(getBaseTypeNode(field.type).name.value));
|
61 | const isNonNull = field.type.kind === Kind.NON_NULL_TYPE;
|
62 | const name = field.kind === Kind.INPUT_VALUE_DEFINITION ? field.name.value : field.variable.name.value;
|
63 | if (isNonNull) {
|
64 | this._imports.add(Imports.Nonnull);
|
65 | return `@Nonnull ${typeToUse} ${name}`;
|
66 | }
|
67 | if (wrapWith) {
|
68 | return typeof wrapWith === 'function' ? `${wrapWith(typeToUse)} ${name}` : `${wrapWith}<${typeToUse}> ${name}`;
|
69 | }
|
70 | if (applyNullable) {
|
71 | this._imports.add(Imports.Nullable);
|
72 | }
|
73 | return `${applyNullable ? '@Nullable ' : ''}${typeToUse} ${name}`;
|
74 | }
|
75 | buildFieldsMarshaller(field) {
|
76 | const isNonNull = field.type.kind === Kind.NON_NULL_TYPE;
|
77 | const isArray = field.type.kind === Kind.LIST_TYPE ||
|
78 | (field.type.kind === Kind.NON_NULL_TYPE && field.type.type.kind === Kind.LIST_TYPE);
|
79 | const call = this.getFieldWriterCall(field, isArray);
|
80 | const baseTypeNode = getBaseTypeNode(field.type);
|
81 | const listItemType = this.getJavaClass(this._schema.getType(baseTypeNode.name.value));
|
82 | let result = '';
|
83 |
|
84 | if (isArray) {
|
85 | result = `writer.writeList("${field.name.value}", ${field.name.value}.value != null ? new InputFieldWriter.ListWriter() {
|
86 | @Override
|
87 | public void write(InputFieldWriter.ListItemWriter listItemWriter) throws IOException {
|
88 | for (${listItemType} $item : ${field.name.value}.value) {
|
89 | listItemWriter.${call};
|
90 | }
|
91 | }
|
92 | } : null);`;
|
93 | }
|
94 | else {
|
95 | result = indent(`writer.${call};`);
|
96 | }
|
97 | if (isNonNull) {
|
98 | return result;
|
99 | }
|
100 | return indentMultiline(`if(${field.name.value}.defined) {
|
101 | ${indentMultiline(result)}
|
102 | }`);
|
103 | }
|
104 | buildMarshallerOverride(fields) {
|
105 | this._imports.add(Imports.Override);
|
106 | this._imports.add(Imports.IOException);
|
107 | this._imports.add(Imports.InputFieldWriter);
|
108 | this._imports.add(Imports.InputFieldMarshaller);
|
109 | const allMarshallers = fields.map(field => indentMultiline(this.buildFieldsMarshaller(field), 2));
|
110 | return indentMultiline(`@Override
|
111 | public InputFieldMarshaller marshaller() {
|
112 | return new InputFieldMarshaller() {
|
113 | @Override
|
114 | public void marshal(InputFieldWriter writer) throws IOException {
|
115 | ${allMarshallers.join('\n')}
|
116 | }
|
117 | };
|
118 | }`);
|
119 | }
|
120 | buildBuilderNestedClass(className, fields) {
|
121 | const builderClassName = 'Builder';
|
122 | const privateFields = fields
|
123 | .map(field => {
|
124 | const isArray = field.type.kind === Kind.LIST_TYPE ||
|
125 | (field.type.kind === Kind.NON_NULL_TYPE && field.type.type.kind === Kind.LIST_TYPE);
|
126 | const fieldType = this.getFieldWithTypePrefix(field, v => (!isArray ? `Input<${v}>` : `Input<List<${v}>>`));
|
127 | const isNonNull = field.type.kind === Kind.NON_NULL_TYPE;
|
128 | return `private ${fieldType}${isNonNull ? '' : ' = Input.absent()'};`;
|
129 | })
|
130 | .map(s => indent(s));
|
131 | const setters = fields
|
132 | .map(field => {
|
133 | const isArray = field.type.kind === Kind.LIST_TYPE ||
|
134 | (field.type.kind === Kind.NON_NULL_TYPE && field.type.type.kind === Kind.LIST_TYPE);
|
135 | const fieldType = this.getFieldWithTypePrefix(field, isArray ? 'List' : null);
|
136 | const isNonNull = field.type.kind === Kind.NON_NULL_TYPE;
|
137 | return `\npublic ${builderClassName} ${field.name.value}(${isNonNull ? '' : '@Nullable '}${fieldType}) {
|
138 | this.${field.name.value} = ${isNonNull ? field.name.value : `Input.fromNullable(${field.name.value})`};
|
139 | return this;
|
140 | }`;
|
141 | })
|
142 | .map(s => indentMultiline(s));
|
143 | const nonNullFields = fields
|
144 | .filter(f => f.type.kind === Kind.NON_NULL_TYPE)
|
145 | .map(nnField => {
|
146 | this._imports.add(Imports.Utils);
|
147 | return indent(`Utils.checkNotNull(${nnField.name.value}, "${nnField.name.value} == null");`, 1);
|
148 | });
|
149 | const ctor = '\n' + indent(`${builderClassName}() {}`);
|
150 | const buildFn = indentMultiline(`public ${className} build() {
|
151 | ${nonNullFields.join('\n')}
|
152 | return new ${className}(${fields.map(f => f.name.value).join(', ')});
|
153 | }`);
|
154 | const body = [...privateFields, ctor, ...setters, '', buildFn].join('\n');
|
155 | return indentMultiline(new JavaDeclarationBlock()
|
156 | .withName(builderClassName)
|
157 | .access('public')
|
158 | .final()
|
159 | .static()
|
160 | .withBlock(body)
|
161 | .asKind('class').string);
|
162 | }
|
163 | InputObjectTypeDefinition(node) {
|
164 | const className = node.name.value;
|
165 | this._imports.add(Imports.InputType);
|
166 | this._imports.add(Imports.Generated);
|
167 | const cls = new JavaDeclarationBlock()
|
168 | .annotate([`Generated("Apollo GraphQL")`])
|
169 | .access('public')
|
170 | .final()
|
171 | .asKind('class')
|
172 | .withName(className)
|
173 | .implements(['InputType']);
|
174 | this.addInputMembers(cls, node.fields);
|
175 | this.addInputCtor(cls, className, node.fields);
|
176 | cls.addClassMethod('builder', 'Builder', 'return new Builder();', [], [], 'public', { static: true });
|
177 | const marshallerOverride = this.buildMarshallerOverride(node.fields);
|
178 | const builderClass = this.buildBuilderNestedClass(className, node.fields);
|
179 | const classBlock = [marshallerOverride, '', builderClass].join('\n');
|
180 | cls.withBlock(classBlock);
|
181 | return cls.string;
|
182 | }
|
183 | }
|