@graphql-codegen/java-apollo-android
Version:
GraphQL Code Generator plugin for generating Java classes for Apollo-Android
190 lines (189 loc) • 8.68 kB
JavaScript
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;
}
}