GQLDefinition.java
package net.morimekta.providence.graphql;
import net.morimekta.providence.PEnumValue;
import net.morimekta.providence.PMessageVariant;
import net.morimekta.providence.PType;
import net.morimekta.providence.descriptor.PContainer;
import net.morimekta.providence.descriptor.PDeclaredDescriptor;
import net.morimekta.providence.descriptor.PDescriptor;
import net.morimekta.providence.descriptor.PEnumDescriptor;
import net.morimekta.providence.descriptor.PField;
import net.morimekta.providence.descriptor.PInterfaceDescriptor;
import net.morimekta.providence.descriptor.PMessageDescriptor;
import net.morimekta.providence.descriptor.PRequirement;
import net.morimekta.providence.descriptor.PService;
import net.morimekta.providence.descriptor.PServiceMethod;
import net.morimekta.providence.descriptor.PStructDescriptor;
import net.morimekta.providence.descriptor.PUnionDescriptor;
import net.morimekta.providence.graphql.gql.GQLScalar;
import net.morimekta.providence.graphql.gql.GQLUtil;
import net.morimekta.providence.graphql.introspection.EnumValue;
import net.morimekta.providence.graphql.introspection.Field;
import net.morimekta.providence.graphql.introspection.InputValue;
import net.morimekta.providence.graphql.introspection.Schema;
import net.morimekta.providence.graphql.introspection.Type;
import net.morimekta.providence.graphql.introspection.TypeKind;
import net.morimekta.util.collect.UnmodifiableList;
import net.morimekta.util.collect.UnmodifiableSet;
import net.morimekta.util.collect.UnmodifiableSortedMap;
import net.morimekta.util.io.IndentedPrintWriter;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
import static net.morimekta.providence.graphql.gql.GQLUtil.toArgumentString;
import static net.morimekta.util.collect.UnmodifiableSet.setOf;
/**
* A GQL service is a collection of zero or more 'queries' and zero
* or more 'mutations'. The query and mutation distinction is meant
* to represent <i>reading</i> and <i>writing</i> operations, but in
* reality they distinguish <i>parallel</i> and <i>serial</i> execution,
* in case the query contains more than one base entry.
*/
@Immutable
public class GQLDefinition {
private final PService query;
private final PService mutation;
private final Map<String, PDescriptor> allTypes;
private final Map<String, PDescriptor> inputTypes;
private final Map<String, PDescriptor> outputTypes;
private final Map<String, Type> introspectionMap;
private final Set<PField<?>> idFields;
private final boolean ignoreMaps;
private final Set<PField<?>> ignoreFields;
private final Set<PDescriptor> ignoreTypes;
private final Set<PUnionDescriptor<?>> asInterface;
private final AtomicReference<String> schema;
private final Schema introspectionSchema;
/**
* Builder for setting up a GraphQL definition.
*/
public static class Builder {
private PService query;
private PService mutation;
private boolean ignoreMaps;
private final Set<PField<?>> idFields;
private final Set<PField<?>> ignoreFields;
private final Set<PDescriptor> ignoreTypes;
private final Set<PUnionDescriptor<?>> asInterface;
private Builder() {
ignoreMaps = false;
idFields = new HashSet<>();
asInterface = new HashSet<>();
ignoreFields = new HashSet<>();
ignoreTypes = new HashSet<>();
}
/**
* Service used for handling queries.
*
* @param query The query service.
* @return The builder.
*/
public Builder query(PService query) {
this.query = query;
return this;
}
/**
* Service used for handling queries.
*
* @param mutation The mutation service.
* @return The builder.
*/
public Builder mutation(PService mutation) {
this.mutation = mutation;
return this;
}
/**
* Transform field from string, binary or container of string or binary
* to ID or ID list.
*
* @param fields The fields to be made into ID fields.
* @return The builder.
*/
public Builder idField(PField<?>... fields) {
for (PField<?> field : fields) {
if (!isValidIdField(field.getDescriptor())) {
throw new IllegalArgumentException(field.getDescriptor().getQualifiedName() + " cannot be handled as ID field.");
}
idFields.add(field);
}
return this;
}
/**
* Handle these unions as interfaces. The union will be ignored in the schema (except for
* input types), and it's interface will be used instead.
*
* @param descriptors Union descriptors to handle as interfaces.
* @return The builder.
*/
public Builder asInterface(PUnionDescriptor<?>... descriptors) {
for (PUnionDescriptor<?> descriptor : descriptors) {
if (descriptor.getImplementing() == null) {
throw new IllegalArgumentException(
"Union " + descriptor.getQualifiedName() + " does not have implementing interface, " +
"so is not allowed as 'union as interface' in graphql.");
}
asInterface.add(descriptor);
}
return this;
}
/**
* Just ignore all maps, instead of failing.
*
* @return The builder.
*/
public Builder ignoreMaps() {
ignoreMaps = true;
return this;
}
/**
* Skip these fields. It will be possible to select / reference the field, but it
* will be skipped from the schema.
*
* @param fields The fields to be ignore / skipped.
* @return The builder.
*/
public Builder ignoreFields(PField<?>... fields) {
Collections.addAll(ignoreFields, fields);
return this;
}
/**
* Setting this will ignore all fields of the given type, or containing the given type
* (list, set, map). Only declared types can be ignored.
*
* @param types Types to be ignored.
* @return The builder.
*/
public Builder ignoreTypes(PDeclaredDescriptor<?>... types) {
Collections.addAll(ignoreTypes, types);
return this;
}
/**
* Build the GraphQL definition.
*
* @return The GQL definition.
*/
public GQLDefinition build() {
if (query == null) {
throw new IllegalStateException("No query defined.");
}
return new GQLDefinition(
query,
mutation,
ignoreMaps,
idFields,
ignoreFields,
ignoreTypes,
asInterface);
}
}
public static Builder builder() {
return new Builder();
}
/**
* Create a graphql definition instance.
*
* @param query The query service. Mandatory.
* @param mutation The mutation service.
* @param idFields Collection if ID fields.
* @deprecated Use Builder.
*/
@Deprecated
public GQLDefinition(@Nonnull PService query,
@Nullable PService mutation,
@Nonnull Collection<PField<?>> idFields) {
this(query, mutation, idFields, setOf());
}
/**
* Create a graphql definition instance.
*
* @param query The query service. Mandatory.
* @param mutation The mutation service.
* @param idFields Collection if ID fields.
* @param asInterface Collection of unions to be
* @deprecated Use Builder.
*/
@Deprecated
public GQLDefinition(@Nonnull PService query,
@Nullable PService mutation,
@Nonnull Collection<PField<?>> idFields,
@Nonnull Collection<PUnionDescriptor<?>> asInterface) {
this(query, mutation, false, idFields, setOf(), setOf(), asInterface);
}
private GQLDefinition(@Nonnull PService query,
@Nullable PService mutation,
boolean ignoreMaps,
@Nonnull Collection<PField<?>> idFields,
@Nonnull Collection<PField<?>> ignoreFields,
@Nonnull Collection<PDescriptor> ignoreTypes,
@Nonnull Collection<PUnionDescriptor<?>> asInterface) {
this.idFields = UnmodifiableSet.copyOf(idFields);
this.asInterface = UnmodifiableSet.copyOf(asInterface);
this.ignoreFields = UnmodifiableSet.copyOf(ignoreFields);
this.ignoreTypes = UnmodifiableSet.copyOf(ignoreTypes);
this.ignoreMaps = ignoreMaps;
for (PUnionDescriptor<?> ui : asInterface) {
if (ui.getImplementing() == null) {
throw new IllegalArgumentException("Union " + ui.getName() + " does not have implementing interface, " +
"so is not allowed as 'union as interface' in graphql.");
}
}
// This should try to preserve order.
LinkedHashMap<String, PDescriptor> typeMap = new LinkedHashMap<>();
LinkedHashMap<String, PDescriptor> inputTypeMap = new LinkedHashMap<>();
for (PServiceMethod method : query.getMethods()) {
registerInputTypes(method.getRequestType(),
typeMap,
inputTypeMap,
this.ignoreMaps,
this.ignoreTypes,
this.ignoreFields,
false);
registerTypes(method.getResponseType(),
typeMap,
inputTypeMap,
this.asInterface,
this.ignoreMaps,
this.ignoreTypes,
this.ignoreFields,
false);
}
if (mutation != null) {
for (PServiceMethod method : mutation.getMethods()) {
registerInputTypes(method.getRequestType(),
typeMap,
inputTypeMap,
this.ignoreMaps,
this.ignoreTypes,
this.ignoreFields,
false);
registerTypes(method.getResponseType(),
typeMap,
inputTypeMap,
this.asInterface,
this.ignoreMaps,
this.ignoreTypes,
this.ignoreFields,
false);
}
}
this.inputTypes = UnmodifiableSortedMap.copyOf(inputTypeMap);
this.outputTypes = UnmodifiableSortedMap.copyOf(typeMap);
registerTypes(Schema.kDescriptor,
typeMap,
inputTypeMap,
setOf(),
false,
setOf(),
setOf(),
true);
Map<String, PDescriptor> allTypes = new TreeMap<>(typeMap);
Map<String, Type> introspection = new TreeMap<>();
ArrayList<PDescriptor> types = new ArrayList<>(typeMap.values());
for (GQLScalar scalar : GQLScalar.values()) {
introspection.put(scalar.introspection.getName(), scalar.introspection);
}
Collections.reverse(types);
types.forEach(descriptor -> buildTypeDefinition(introspection, descriptor, false, false));
types = new ArrayList<>(inputTypeMap.values());
Collections.reverse(types);
types.forEach(descriptor -> {
Type tmp = buildTypeDefinition(introspection, descriptor, true, false);
allTypes.put(tmp.getName(), descriptor);
});
Type queryType = buildServiceDefinition(introspection, query);
Type mutationType = buildServiceDefinition(introspection, mutation);
this.allTypes = UnmodifiableSortedMap.copyOf(allTypes);
this.introspectionMap = UnmodifiableSortedMap.copyOf(introspection);
this.introspectionSchema = Schema
.builder()
.setTypes(introspection
.values()
.stream()
.filter(type -> !type.getName().startsWith("__"))
.collect(Collectors.toList()))
.setQueryType(queryType)
.setMutationType(mutationType)
.build();
this.query = query;
this.mutation = mutation;
this.schema = new AtomicReference<>();
}
/**
* Get query, e.g. for gql queries like this:
*
* <pre>
* {
* hero(id:1001) {
* name
* }
* }
* </pre>
*
* @return The query service.
*/
@Nonnull
public PService getQuery() {
return query;
}
/**
* Get mutation by name, e.g. for gql queries like this:
*
* <pre>
* mutation HeroStore {
* deleteHero(id:1001) {
* name
* }
* }
* </pre>
*
* @return The mutation service.
*/
@Nullable
public PService getMutation() {
return mutation;
}
/**
* If the field should be ignored. This means not allowed in fragments or
* selection sets.
*
* @param field The field to check.
* @return True if the field should be ignored.
*/
public boolean isIgnoredField(PField<?> field) {
return ignoreFields.contains(field) ||
isIgnoredType(field.getDescriptor());
}
/**
* If the type should be ignored.
*
* @param descriptor The type
* @return If the type should be ignored.
*/
public boolean isIgnoredType(PDescriptor descriptor) {
return isIgnoredType(descriptor, ignoreMaps, ignoreTypes);
}
/**
* Get a type used in the GQL service.
*
* @param name The name of the type.
* @return The type description, enum or message.
*/
public PDescriptor getType(@Nonnull String name) {
return allTypes.get(name);
}
/**
* Get the introspection type for a defined type.
* @param name The type name.
* @return Introspection type, or null if not defined in service.
*/
@Nullable
public Type getIntrospectionType(@Nonnull String name) {
return introspectionMap.get(name);
}
/**
* Get introspection type for a given descriptor.
*
* @param descriptor The descriptor to get introspection type for.
* @param isInput If the type should be an input type.
* @return The introspection type.
*/
@Nonnull
public Type getIntrospectionType(@Nonnull PDescriptor descriptor, boolean isInput) {
String name = descriptor.getName();
if (descriptor instanceof PMessageDescriptor &&
isInput) {
name = name + INPUT_TYPE;
}
return Optional.ofNullable(getIntrospectionType(name))
.orElseGet(() -> buildTypeDefinition(
new HashMap<>(introspectionMap), descriptor, isInput, false));
}
/**
* Get a defined schema from the GQL service.
* @return The GQL schema.
*/
public String getSchema() {
return schema.updateAndGet(schema -> {
if (schema == null) {
return buildSchema();
}
return schema;
});
}
/**
* Return the introspection schema for this definition.
*
* @return The schema.
*/
@Nonnull
public Schema getIntrospectionSchema() {
return introspectionSchema;
}
private boolean isIdField(PField<?> field) {
return idFields.contains(field);
}
private boolean isHiddenMethod(PServiceMethod method) {
return method.getName().startsWith("__") ||
(method.getResponseType() != null &&
isHiddenField(method.getResponseType().fieldForId(0)));
}
private boolean isHiddenType(PDescriptor descriptor) {
return descriptor.getName().startsWith("__") ||
isIgnoredType(descriptor);
}
private boolean isHiddenField(PField<?> field) {
return field.getName().startsWith("__") ||
isHiddenType(field.getDescriptor()) ||
isIgnoredField(field);
}
private Type buildServiceDefinition(Map<String, Type> introspection,
PService service) {
if (service == null) return null;
Type._Builder builder = Type.builder();
builder.setName(service.getName());
builder.setKind(TypeKind.OBJECT);
builder.setInterfaces(UnmodifiableList.listOf());
for (PServiceMethod method : service.getMethods()) {
if (isHiddenMethod(method)) {
continue;
}
Field._Builder field = Field.builder();
field.setName(method.getName());
PUnionDescriptor<?> response = method.getResponseType();
PStructDescriptor<?> request = method.getRequestType();
if (response != null) {
PDescriptor desc = response.fieldForId(0).getDescriptor();
field.setType(Type.builder()
.setKind(TypeKind.NON_NULL)
.setOfType(makeTypeReference(buildTypeDefinition(
introspection,
desc,
false,
false))));
} else {
field.setType(GQLScalar.Boolean.introspection);
}
field.setArgs(buildInputValues(introspection, request));
builder.addToFields(field.build());
}
Type type = builder.build();
introspection.put(type.getName(), type);
return type;
}
private String defaultValueString(Object value) {
if (value == null) return null;
return GQLUtil.toArgumentString(value);
}
private List<InputValue> buildInputValues(Map<String, Type> introspection,
PMessageDescriptor<?> arguments) {
List<InputValue> out = new ArrayList<>();
for (PField<?> field : arguments.getFields()) {
if (isHiddenField(field)) {
continue;
}
out.add(InputValue.builder()
.setName(field.getName())
.setType(makeTypeReference(buildTypeDefinition(
introspection,
field.getDescriptor(),
true,
isIdField(field))))
.setDefaultValue(defaultValueString(field.getDefaultValue()))
.build());
}
return UnmodifiableList.copyOf(out);
}
private Field buildFieldSpec(Map<String, Type> introspection,
PField<?> field) {
Field._Builder builder = Field.builder();
builder.setName(field.getName());
if (field.getArgumentsType() != null) {
builder.setArgs(buildInputValues(introspection, field.getArgumentsType()));
}
Type type = makeTypeReference(buildTypeDefinition(
introspection,
field.getDescriptor(),
false,
isIdField(field)));
if (field.getRequirement() == PRequirement.REQUIRED) {
type = Type.builder()
.setKind(TypeKind.NON_NULL)
.setOfType(type)
.build();
}
return builder.setType(type)
.build();
}
private Type makeTypeReference(Type type) {
switch (type.getKind()) {
case ENUM:
case UNION:
case INTERFACE:
case OBJECT:
case INPUT_OBJECT: {
return type.mutate()
.clearInterfaces()
.clearInputFields()
.clearPossibleTypes()
.clearFields()
.clearEnumValues()
.clearDescription()
.build();
}
case LIST:
case NON_NULL: {
return type.mutate()
.setOfType(makeTypeReference(type.getOfType()))
.build();
}
}
return type;
}
private boolean isUnionAsInterface(PDescriptor descriptor) {
return descriptor instanceof PUnionDescriptor &&
asInterface.contains(descriptor) &&
((PUnionDescriptor<?>) descriptor).getImplementing() != null;
}
private Type buildTypeDefinition(Map<String, Type> introspection,
PDescriptor descriptor,
boolean isInput,
boolean isIdField) {
switch (descriptor.getType()) {
case MESSAGE: {
PMessageDescriptor<?> md = (PMessageDescriptor<?>) descriptor;
String name = descriptor.getName();
if (isInput) {
name += INPUT_TYPE;
} else if (isUnionAsInterface(md)) {
return buildTypeDefinition(introspection, requireNonNull(md.getImplementing()), false, isIdField);
}
if (introspection.containsKey(name)) {
return introspection.get(name);
}
Type._Builder builder = Type.builder();
builder.setName(name);
boolean isUnion = false;
if (md.getVariant() == PMessageVariant.INTERFACE) {
builder.setKind(TypeKind.INTERFACE);
builder.setPossibleTypes(UnmodifiableList.listOf());
builder.setFields(UnmodifiableList.listOf());
} else if (isInput) {
builder.setKind(TypeKind.INPUT_OBJECT);
} else if (md.getVariant() == PMessageVariant.UNION &&
md.getImplementing() != null) {
builder.setKind(TypeKind.UNION);
builder.setPossibleTypes(UnmodifiableList.listOf());
isUnion = true;
} else {
builder.setKind(TypeKind.OBJECT);
builder.setFields(UnmodifiableList.listOf());
builder.setInterfaces(UnmodifiableList.listOf());
}
if (md.getImplementing() != null && !isInput) {
builder.addToInterfaces(makeTypeReference(buildTypeDefinition(
introspection,
md.getImplementing(),
false,
false)));
if (introspection.containsKey(name)) {
return introspection.get(name);
}
}
introspection.put(name, builder.build());
if (isUnion) {
for (PField<?> field : md.getFields()) {
if (isHiddenField(field)) {
continue;
}
builder.addToPossibleTypes(makeTypeReference(buildTypeDefinition(
introspection,
field.getDescriptor(),
false,
false)));
}
} else if (isInput) {
builder.setInputFields(buildInputValues(introspection, md));
} else {
for (PField<?> field : md.getFields()) {
if (isHiddenField(field)) {
continue;
}
builder.addToFields(buildFieldSpec(introspection, field));
}
}
introspection.put(name, builder.build());
if (md instanceof PInterfaceDescriptor) {
PInterfaceDescriptor<?> id = (PInterfaceDescriptor<?>) descriptor;
for (PMessageDescriptor<?> pt : id.getPossibleTypes()) {
if (pt.getVariant() != PMessageVariant.UNION) {
builder.addToPossibleTypes(makeTypeReference(buildTypeDefinition(introspection, pt, false, false)));
}
}
}
introspection.put(name, builder.build());
return builder.build();
}
case ENUM: {
Type._Builder builder = Type.builder();
if (introspection.containsKey(descriptor.getName())) {
return introspection.get(descriptor.getName());
}
builder.setName(descriptor.getName());
builder.setKind(TypeKind.ENUM);
builder.mutableEnumValues();
for (PEnumValue<?> value : ((PEnumDescriptor<?>) descriptor).getValues()) {
builder.addToEnumValues(EnumValue.builder()
.setName(value.asString())
.build());
}
introspection.put(descriptor.getName(), builder.build());
return builder.build();
}
case SET:
case LIST: {
Type._Builder builder = Type.builder();
PContainer<?> pc = (PContainer<?>) descriptor;
builder.setKind(TypeKind.LIST);
builder.setOfType(makeTypeReference(buildTypeDefinition(introspection, pc.itemDescriptor(), isInput, isIdField)));
return builder.build();
}
case STRING:
case BINARY:
if (isIdField) {
return GQLScalar.ID.introspection;
}
return GQLScalar.String.introspection;
case VOID:
case BOOL:
return GQLScalar.Boolean.introspection;
case I64:
case I32:
case I16:
case BYTE:
return GQLScalar.Int.introspection;
case DOUBLE:
return GQLScalar.Float.introspection;
}
throw new IllegalStateException("Unsupported type: " + descriptor.getType());
}
private String buildSchema() {
StringWriter out = new StringWriter();
IndentedPrintWriter writer = new IndentedPrintWriter(out, " ", "\n");
writer.append("# Generated for providence graphql")
.newline();
List<PDescriptor> types = new ArrayList<>(outputTypes.values());
for (PDescriptor descriptor : types) {
if (isHiddenType(descriptor)) {
continue;
}
if (descriptor.getType() == PType.ENUM) {
PEnumDescriptor<?> ed = (PEnumDescriptor<?>) descriptor;
writer.formatln("enum %s {", descriptor.getName())
.begin();
for (PEnumValue<?> val : ed.getValues()) {
writer.appendln(val.asString());
}
writer.end()
.appendln("}")
.newline();
} else if (descriptor.getType() == PType.MESSAGE) {
PMessageDescriptor<?> md = (PMessageDescriptor<?>) descriptor;
if (md.getVariant() == PMessageVariant.UNION && md.getImplementing() != null) {
// union Name = Type1 | Type2
writer.formatln("union %s = ", md.getName());
boolean first = true;
for (PField<?> field : md.getFields()) {
if (isHiddenField(field)) {
continue;
}
if (first) {
first = false;
} else {
writer.append(" | ");
}
writer.append(field.getDescriptor().getName());
}
writer.newline();
} else if (md.getVariant() == PMessageVariant.INTERFACE) {
// interface Name { ... }
writer.formatln("interface %s {", md.getName())
.begin();
for (PField<?> field : md.getFields()) {
if (isHiddenField(field)) {
continue;
}
writer.formatln("%s: %s", field.getName(), typeName(field));
}
writer.end()
.appendln("}")
.newline();
} else {
// type
writer.formatln("type %s%s {", md.getName(),
md.getImplementing() != null ? " implements " + typeName(md.getImplementing(), false) : "")
.begin();
for (PField<?> field : md.getFields()) {
if (isHiddenField(field)) {
continue;
}
writer.formatln("%s", field.getName());
if (field.getArgumentsType() != null) {
writer.append("(");
boolean first = true;
for (PField<?> arg : field.getArgumentsType().getFields()) {
if (isHiddenField(field)) {
continue;
}
if (first) {
first = false;
} else {
writer.append(", ");
}
writer.format("%s: %s", arg.getName(), inputTypeName(arg.getDescriptor(), isIdField(arg)));
if (arg.hasDefaultValue()) {
writer.format(" = %s", toArgumentString(arg.getDefaultValue()));
}
}
writer.append(")");
}
writer.format(": %s", typeName(field));
}
writer.end()
.appendln("}")
.newline();
}
}
}
types = new ArrayList<>(inputTypes.values());
for (PDescriptor descriptor : types) {
PMessageDescriptor<?> md = (PMessageDescriptor<?>) descriptor;
// input
writer.formatln("input %s%s {",
md.getName(),
INPUT_TYPE)
.begin();
for (PField<?> field : md.getFields()) {
if (isHiddenField(field)) {
continue;
}
writer.formatln("%s: %s", field.getName(), inputTypeName(field));
}
writer.end()
.appendln("}")
.newline();
}
appendService(writer, query);
if (mutation != null) {
appendService(writer, mutation);
}
writer.appendln("schema {")
.begin()
.formatln("query: %s", query.getName());
if (mutation != null) {
writer.formatln("mutation: %s", mutation.getName());
}
writer.end()
.appendln("}")
.newline();
writer.flush();
return out.toString();
}
private void appendService(IndentedPrintWriter writer, PService service) {
writer.formatln("type %s {", service.getName())
.begin();
boolean firstMethod = true;
for (PServiceMethod method : service.getMethods()) {
if (isHiddenMethod(method)) {
continue;
}
if (firstMethod) {
firstMethod = false;
} else {
writer.newline();
}
writer.appendln(method.getName());
PMessageDescriptor<?> args = method.getRequestType();
writer.append("(");
boolean first = true;
for (PField<?> arg : args.getFields()) {
if (isHiddenField(arg)) {
continue;
}
if (first) {
first = false;
} else {
writer.append(", ");
}
writer.format("%s: %s", arg.getName(), inputTypeName(arg.getDescriptor(), isIdField(arg)));
if (arg.hasDefaultValue()) {
writer.format(" = %s", toArgumentString(arg.getDefaultValue()));
}
}
writer.append("): ");
PUnionDescriptor<?> response = method.getResponseType();
if (response != null) {
PField<?> success = response.fieldForId(0);
if (success.getType() == PType.VOID) {
writer.format("Boolean");
} else {
writer.format("%s!", typeName(success));
}
}
}
writer.end()
.appendln("}")
.newline();
}
private String typeName(PField<?> field) {
return typeName(field.getDescriptor(), isIdField(field)) +
(field.getRequirement() == PRequirement.REQUIRED ? "!" : "");
}
private String typeName(PDescriptor descriptor, boolean idType) {
switch (descriptor.getType()) {
case MESSAGE:
if (isUnionAsInterface(descriptor)) {
return typeName(requireNonNull(((PMessageDescriptor<?>) descriptor).getImplementing()), idType);
}
return descriptor.getName();
case ENUM:
return descriptor.getName();
case LIST:
case SET:
PContainer<?> cd = (PContainer<?>) descriptor;
return "[" + typeName(cd.itemDescriptor(), idType) + "!]";
case VOID:
case BOOL:
return GQLScalar.Boolean.name();
case BYTE:
case I16:
case I32:
case I64:
return GQLScalar.Int.name();
case DOUBLE:
return GQLScalar.Float.name();
case STRING:
case BINARY:
if (idType) {
return GQLScalar.ID.name();
}
return GQLScalar.String.name();
}
throw new UnsupportedOperationException("Not supported type " + descriptor.getQualifiedName());
}
private String inputTypeName(PField<?> field) {
return inputTypeName(field.getDescriptor(), isIdField(field)) +
(field.getRequirement() == PRequirement.REQUIRED ? "!" : "");
}
private String inputTypeName(PDescriptor descriptor, boolean idType) {
if (descriptor.getType() == PType.MESSAGE) {
return descriptor.getName() + INPUT_TYPE;
} else if (descriptor.getType() == PType.SET ||
descriptor.getType() == PType.LIST) {
PContainer<?> cd = (PContainer<?>) descriptor;
return "[" + inputTypeName(cd.itemDescriptor(), idType) + "!]";
}
return typeName(descriptor, idType);
}
private static final String INPUT_TYPE = "InputType";
private static boolean isValidIdField(PDescriptor descriptor) {
if (descriptor.getType() == PType.LIST ||
descriptor.getType() == PType.SET) {
return isValidIdField(((PContainer<?>) descriptor).itemDescriptor());
}
return descriptor.getType() == PType.STRING ||
descriptor.getType() == PType.BINARY;
}
private static boolean isIgnoredType(@Nonnull PDescriptor descriptor,
boolean ignoreMaps,
@Nonnull Collection<PDescriptor> ignoredTypes) {
if (ignoredTypes.contains(descriptor) ||
(ignoreMaps && descriptor.getType() == PType.MAP)) return true;
if (descriptor.getType() == PType.LIST ||
descriptor.getType() == PType.SET) {
return isIgnoredType(((PContainer<?>) descriptor).itemDescriptor(), ignoreMaps, ignoredTypes);
}
return false;
}
private static void registerInputTypes(PMessageDescriptor<?> descriptor,
Map<String, PDescriptor> types,
Map<String, PDescriptor> inputTypes,
boolean ignoreMaps,
Set<PDescriptor> ignoredTypes,
Set<PField<?>> ignoreFields,
boolean registerSelf) {
if (descriptor != null) {
if (descriptor.getVariant() == PMessageVariant.EXCEPTION ||
inputTypes.containsKey(descriptor.getName())) {
return;
}
if (registerSelf) {
inputTypes.put(descriptor.getName(), descriptor);
}
for (PField<?> field : descriptor.getFields()) {
if (field.getName().startsWith("__") ||
ignoreFields.contains(field) ||
isIgnoredType(field.getDescriptor(), ignoreMaps, ignoredTypes)) {
continue;
}
if (field.getType() == PType.ENUM) {
types.put(field.getDescriptor().getName(), field.getDescriptor());
} else if (field.getType() == PType.MESSAGE) {
registerInputTypes((PMessageDescriptor<?>) field.getDescriptor(),
types, inputTypes, ignoreMaps, ignoredTypes, ignoreFields, true);
} else if ((field.getType() == PType.SET ||
field.getType() == PType.LIST)) {
PDescriptor itemType = ((PContainer<?>) field.getDescriptor()).itemDescriptor();
if (itemType.getType() == PType.MESSAGE) {
registerInputTypes((PMessageDescriptor<?>) itemType, types, inputTypes, ignoreMaps, ignoredTypes, ignoreFields, true);
} else if (itemType.getType() == PType.ENUM) {
types.put(itemType.getName(), itemType);
}
}
}
}
}
private static void registerTypes(PMessageDescriptor<?> descriptor,
Map<String, PDescriptor> types,
Map<String, PDescriptor> inputTypes,
Set<PUnionDescriptor<?>> asInterface,
boolean ignoreMaps,
Set<PDescriptor> ignoreTypes,
Set<PField<?>> ignoreFields,
boolean registerSelf) {
if (descriptor != null) {
if (descriptor.getVariant() == PMessageVariant.EXCEPTION ||
types.containsKey(descriptor.getName())) {
return;
}
if (descriptor instanceof PUnionDescriptor &&
asInterface.contains(descriptor)) {
registerSelf = false;
}
if (registerSelf) {
types.put(descriptor.getName(), descriptor);
}
registerTypes(descriptor.getImplementing(),
types,
inputTypes,
asInterface,
ignoreMaps,
ignoreTypes,
ignoreFields,
true);
for (PField<?> field : descriptor.getFields()) {
if (field.getName().startsWith("__") ||
ignoreFields.contains(field) ||
isIgnoredType(field.getDescriptor(), ignoreMaps, ignoreTypes)) {
continue;
}
registerInputTypes(field.getArgumentsType(),
types,
inputTypes,
ignoreMaps,
ignoreTypes,
ignoreFields,
false);
if (field.getType() == PType.ENUM) {
types.put(field.getDescriptor().getName(), field.getDescriptor());
} else if (field.getType() == PType.MESSAGE) {
registerTypes((PMessageDescriptor<?>) field.getDescriptor(),
types,
inputTypes,
asInterface,
ignoreMaps,
ignoreTypes,
ignoreFields,
true);
} else if ((field.getType() == PType.SET ||
field.getType() == PType.LIST)) {
PDescriptor itemType = ((PContainer<?>) field.getDescriptor()).itemDescriptor();
if (itemType.getType() == PType.MESSAGE) {
registerTypes((PMessageDescriptor<?>) itemType,
types,
inputTypes,
asInterface,
ignoreMaps,
ignoreTypes,
ignoreFields,
true);
} else if (itemType.getType() == PType.ENUM) {
types.put(itemType.getName(), itemType);
}
}
}
}
}
}