UNPKG

@graphql-codegen/java-apollo-android

Version:

GraphQL Code Generator plugin for generating Java classes for Apollo-Android

190 lines (189 loc) • 8.68 kB
import { isEnumType, isInputObjectType, isScalarType, Kind, } from 'graphql'; import { JavaDeclarationBlock } from '@graphql-codegen/java-common'; import { getBaseTypeNode, indent, indentMultiline } from '@graphql-codegen/visitor-plugin-common'; import { BaseJavaVisitor, SCALAR_TO_WRITER_METHOD } from './base-java-visitor.js'; import { Imports } from './imports.js'; export class InputTypeVisitor extends BaseJavaVisitor { constructor(_schema, rawConfig) { super(_schema, rawConfig, { typePackage: rawConfig.typePackage || 'type', }); } getPackage() { return this.config.typePackage; } addInputMembers(cls, fields) { fields.forEach(field => { const type = this.transformType(field.type); const actualType = type.isNonNull ? type.typeToUse : `Input<${type.typeToUse}>`; const annotations = type.isNonNull ? [type.annotation] : []; this._imports.add(Imports[type.annotation]); cls.addClassMember(field.name.value, actualType, null, annotations, 'private', { final: true, }); cls.addClassMethod(field.name.value, actualType, `return this.${field.name.value};`, [], [type.annotation], 'public'); }); } addInputCtor(cls, className, fields) { const impl = fields.map(field => `this.${field.name.value} = ${field.name.value};`).join('\n'); cls.addClassMethod(className, null, impl, fields.map(f => { const type = this.transformType(f.type); const actualType = type.isNonNull ? type.typeToUse : `Input<${type.typeToUse}>`; this._imports.add(Imports[type.annotation]); return { name: f.name.value, type: actualType, annotations: type.isNonNull ? [type.annotation] : [], }; }), [], 'public'); } getFieldWriterCall(field, listItemCall = false) { const baseType = getBaseTypeNode(field.type); const schemaType = this._schema.getType(baseType.name.value); const isNonNull = field.type.kind === Kind.NON_NULL_TYPE; let writerMethod = null; if (isScalarType(schemaType)) { writerMethod = SCALAR_TO_WRITER_METHOD[schemaType.name] || 'writeCustom'; } else if (isInputObjectType(schemaType)) { return listItemCall ? `writeObject($item.marshaller())` : `writeObject("${field.name.value}", ${field.name.value}.value != null ? ${field.name.value}.value.marshaller() : null)`; } else if (isEnumType(schemaType)) { writerMethod = 'writeString'; } return listItemCall ? `${writerMethod}($item)` : `${writerMethod}("${field.name.value}", ${field.name.value}${isNonNull ? '' : '.value'})`; } getFieldWithTypePrefix(field, wrapWith = null, applyNullable = false) { this._imports.add(Imports.Input); const typeToUse = this.getJavaClass(this._schema.getType(getBaseTypeNode(field.type).name.value)); const isNonNull = field.type.kind === Kind.NON_NULL_TYPE; const name = field.kind === Kind.INPUT_VALUE_DEFINITION ? field.name.value : field.variable.name.value; if (isNonNull) { this._imports.add(Imports.Nonnull); return `@Nonnull ${typeToUse} ${name}`; } if (wrapWith) { return typeof wrapWith === 'function' ? `${wrapWith(typeToUse)} ${name}` : `${wrapWith}<${typeToUse}> ${name}`; } if (applyNullable) { this._imports.add(Imports.Nullable); } return `${applyNullable ? '@Nullable ' : ''}${typeToUse} ${name}`; } buildFieldsMarshaller(field) { const isNonNull = field.type.kind === Kind.NON_NULL_TYPE; const isArray = field.type.kind === Kind.LIST_TYPE || (field.type.kind === Kind.NON_NULL_TYPE && field.type.type.kind === Kind.LIST_TYPE); const call = this.getFieldWriterCall(field, isArray); const baseTypeNode = getBaseTypeNode(field.type); const listItemType = this.getJavaClass(this._schema.getType(baseTypeNode.name.value)); let result = ''; // TODO: Refactor if (isArray) { result = `writer.writeList("${field.name.value}", ${field.name.value}.value != null ? new InputFieldWriter.ListWriter() { @Override public void write(InputFieldWriter.ListItemWriter listItemWriter) throws IOException { for (${listItemType} $item : ${field.name.value}.value) { listItemWriter.${call}; } } } : null);`; } else { result = indent(`writer.${call};`); } if (isNonNull) { return result; } return indentMultiline(`if(${field.name.value}.defined) { ${indentMultiline(result)} }`); } buildMarshallerOverride(fields) { this._imports.add(Imports.Override); this._imports.add(Imports.IOException); this._imports.add(Imports.InputFieldWriter); this._imports.add(Imports.InputFieldMarshaller); const allMarshallers = fields.map(field => indentMultiline(this.buildFieldsMarshaller(field), 2)); return indentMultiline(`@Override public InputFieldMarshaller marshaller() { return new InputFieldMarshaller() { @Override public void marshal(InputFieldWriter writer) throws IOException { ${allMarshallers.join('\n')} } }; }`); } buildBuilderNestedClass(className, fields) { const builderClassName = 'Builder'; const privateFields = fields .map(field => { const isArray = field.type.kind === Kind.LIST_TYPE || (field.type.kind === Kind.NON_NULL_TYPE && field.type.type.kind === Kind.LIST_TYPE); const fieldType = this.getFieldWithTypePrefix(field, v => !isArray ? `Input<${v}>` : `Input<List<${v}>>`); const isNonNull = field.type.kind === Kind.NON_NULL_TYPE; return `private ${fieldType}${isNonNull ? '' : ' = Input.absent()'};`; }) .map(s => indent(s)); const setters = fields .map(field => { const isArray = field.type.kind === Kind.LIST_TYPE || (field.type.kind === Kind.NON_NULL_TYPE && field.type.type.kind === Kind.LIST_TYPE); const fieldType = this.getFieldWithTypePrefix(field, isArray ? 'List' : null); const isNonNull = field.type.kind === Kind.NON_NULL_TYPE; return `\npublic ${builderClassName} ${field.name.value}(${isNonNull ? '' : '@Nullable '}${fieldType}) { this.${field.name.value} = ${isNonNull ? field.name.value : `Input.fromNullable(${field.name.value})`}; return this; }`; }) .map(s => indentMultiline(s)); const nonNullFields = fields .filter(f => f.type.kind === Kind.NON_NULL_TYPE) .map(nnField => { this._imports.add(Imports.Utils); return indent(`Utils.checkNotNull(${nnField.name.value}, "${nnField.name.value} == null");`, 1); }); const ctor = '\n' + indent(`${builderClassName}() {}`); const buildFn = indentMultiline(`public ${className} build() { ${nonNullFields.join('\n')} return new ${className}(${fields.map(f => f.name.value).join(', ')}); }`); const body = [...privateFields, ctor, ...setters, '', buildFn].join('\n'); return indentMultiline(new JavaDeclarationBlock() .withName(builderClassName) .access('public') .final() .static() .withBlock(body) .asKind('class').string); } InputObjectTypeDefinition(node) { const className = node.name.value; this._imports.add(Imports.InputType); this._imports.add(Imports.Generated); const cls = new JavaDeclarationBlock() .annotate([`Generated("Apollo GraphQL")`]) .access('public') .final() .asKind('class') .withName(className) .implements(['InputType']); this.addInputMembers(cls, node.fields); this.addInputCtor(cls, className, node.fields); cls.addClassMethod('builder', 'Builder', 'return new Builder();', [], [], 'public', { static: true, }); const marshallerOverride = this.buildMarshallerOverride(node.fields); const builderClass = this.buildBuilderNestedClass(className, node.fields); const classBlock = [marshallerOverride, '', builderClass].join('\n'); cls.withBlock(classBlock); return cls.string; } }