
io.basestar.graphql.GraphQLSchemaAdaptor Maven / Gradle / Ivy
package io.basestar.graphql;
/*-
* #%L
* basestar-graphql
* %%
* Copyright (C) 2019 - 2020 Basestar.IO
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import com.google.common.collect.ImmutableList;
import graphql.language.*;
import graphql.schema.idl.TypeDefinitionRegistry;
import io.basestar.schema.*;
import io.basestar.schema.use.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GraphQLSchemaAdaptor {
public static final String INPUT_PREFIX = "Input";
public static final String INPUT_EXPR_PREFIX = "InputExpr";
public static final String ENTRY_PREFIX = "Entry";
public static final String ARRAY_PREFIX = "Array";
public static final String ID_TYPE = "ID";
public static final String STRING_TYPE = "String";
public static final String INT_TYPE = "Int";
public static final String FLOAT_TYPE = "Float";
public static final String BOOLEAN_TYPE = "Boolean";
public static final String INPUT_REF_TYPE = "InputRef";
private final Namespace namespace;
public GraphQLSchemaAdaptor(final Namespace namespace) {
this.namespace = namespace;
}
public TypeDefinitionRegistry typeDefinitionRegistry() {
final TypeDefinitionRegistry registry = new TypeDefinitionRegistry();
final Map> mapTypes = new HashMap<>();
namespace.getSchemas().forEach((k, schema) -> {
registry.add(typeDefinition(schema));
if(schema instanceof InstanceSchema) {
final InstanceSchema instanceSchema = (InstanceSchema)schema;
mapTypes.putAll(mapTypes(instanceSchema));
registry.add(inputTypeDefinition(instanceSchema));
if(schema instanceof ObjectSchema) {
registry.add(inputExpressionTypeDefinition(instanceSchema));
}
}
});
registry.add(queryDefinition());
registry.add(mutationDefinition());
registry.add(InputObjectTypeDefinition.newInputObjectDefinition()
.name(INPUT_REF_TYPE)
.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name(Reserved.ID).type(new NonNullType(new TypeName(ID_TYPE))).build())
.build());
mapTypes.forEach((k, v) -> {
registry.add(mapTypeDefinition(k, v));
registry.add(inputMapTypeDefinition(k, v));
});
// registry.add(batchActionDefinition());
return registry;
}
private ObjectTypeDefinition queryDefinition() {
final ObjectTypeDefinition.Builder builder = ObjectTypeDefinition.newObjectTypeDefinition();
builder.name("Query");
namespace.getSchemas().forEach((schemaName, schema) -> {
if(schema instanceof ObjectSchema) {
final ObjectSchema objectSchema = (ObjectSchema)schema;
builder.fieldDefinition(readDefinition(objectSchema));
builder.fieldDefinition(queryDefinition(objectSchema));
objectSchema.getAllLinks()
.forEach((linkName, link) -> builder.fieldDefinition(queryLinkDefinition(objectSchema, link)));
}
});
return builder.build();
}
public FieldDefinition readDefinition(final Schema> schema) {
final FieldDefinition.Builder builder = FieldDefinition.newFieldDefinition();
builder.name("read" + schema.getName());
builder.type(new TypeName(schema.getName()));
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name(Reserved.ID).type(new NonNullType(new TypeName(ID_TYPE))).build());
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name(Reserved.VERSION).type(new TypeName(STRING_TYPE)).build());
return builder.build();
}
public FieldDefinition queryDefinition(final Schema> schema) {
final FieldDefinition.Builder builder = FieldDefinition.newFieldDefinition();
builder.name("query" + schema.getName());
builder.type(new ListType(new TypeName(schema.getName())));
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name("query").type(new TypeName(STRING_TYPE)).build());
return builder.build();
}
public FieldDefinition queryLinkDefinition(final Schema> schema, final Link link) {
final FieldDefinition.Builder builder = FieldDefinition.newFieldDefinition();
builder.name("query" + schema.getName() + GraphQLUtils.ucFirst(link.getName()));
builder.type(new ListType(new TypeName(link.getSchema().getName())));
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name(Reserved.ID).type(new NonNullType(new TypeName(ID_TYPE))).build());
return builder.build();
}
private ObjectTypeDefinition mutationDefinition() {
final ObjectTypeDefinition.Builder builder = ObjectTypeDefinition.newObjectTypeDefinition();
builder.name("Mutation");
namespace.getSchemas().forEach((k, v) -> {
if(v instanceof ObjectSchema) {
builder.fieldDefinition(createDefinition(v));
builder.fieldDefinition(updateDefinition(v));
builder.fieldDefinition(deleteDefinition(v));
}
});
return builder.build();
}
public FieldDefinition createDefinition(final Schema> schema) {
final FieldDefinition.Builder builder = FieldDefinition.newFieldDefinition();
builder.name("create" + schema.getName());
builder.type(new TypeName(schema.getName()));
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name(Reserved.ID).type(new TypeName(ID_TYPE)).build());
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name("data").type(new TypeName(INPUT_PREFIX + schema.getName())).build());
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name("expressions").type(new TypeName(INPUT_EXPR_PREFIX + schema.getName())).build());
return builder.build();
}
public FieldDefinition updateDefinition(final Schema> schema) {
final FieldDefinition.Builder builder = FieldDefinition.newFieldDefinition();
builder.name("update" + schema.getName());
builder.type(new TypeName(schema.getName()));
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name(Reserved.ID).type(new NonNullType(new TypeName(ID_TYPE))).build());
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name(Reserved.VERSION).type(new TypeName(INT_TYPE)).build());
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name("data").type(new TypeName(INPUT_PREFIX + schema.getName())).build());
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name("expressions").type(new TypeName(INPUT_EXPR_PREFIX + schema.getName())).build());
return builder.build();
}
public FieldDefinition deleteDefinition(final Schema> schema) {
final FieldDefinition.Builder builder = FieldDefinition.newFieldDefinition();
builder.name("delete" + schema.getName());
builder.type(new TypeName(schema.getName()));
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name(Reserved.ID).type(new NonNullType(new TypeName(ID_TYPE))).build());
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name(Reserved.VERSION).type(new TypeName(INT_TYPE)).build());
return builder.build();
}
public InputObjectTypeDefinition inputTypeDefinition(final InstanceSchema schema) {
final InputObjectTypeDefinition.Builder builder = InputObjectTypeDefinition.newInputObjectDefinition();
builder.name(INPUT_PREFIX + schema.getName());
builder.description(description(schema.getDescription()));
schema.getAllProperties()
.forEach((k, v) -> builder.inputValueDefinition(inputValueDefinition(v)));
return builder.build();
}
private SDLDefinition> inputExpressionTypeDefinition(final InstanceSchema schema) {
final InputObjectTypeDefinition.Builder builder = InputObjectTypeDefinition.newInputObjectDefinition();
builder.name(INPUT_EXPR_PREFIX + schema.getName());
builder.description(description(schema.getDescription()));
schema.getAllProperties()
.forEach((k, v) -> builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name(k).type(new TypeName(STRING_TYPE))
.build()));
return builder.build();
}
public InputValueDefinition inputValueDefinition(final Property property) {
final InputValueDefinition.Builder builder = InputValueDefinition.newInputValueDefinition();
builder.name(property.getName());
if(property.getDescription() != null) {
builder.description(new Description(property.getDescription(), null, true));
}
// Cannot use NonNullType because value may come from an expression
final Type> type = inputType(property.getType());
builder.type(type);
return builder.build();
}
public TypeDefinition> typeDefinition(final Schema> schema) {
if (schema instanceof InstanceSchema) {
return typeDefinition((InstanceSchema) schema);
} else if (schema instanceof EnumSchema) {
return typeDefinition((EnumSchema) schema);
} else {
throw new UnsupportedOperationException();
}
}
public TypeDefinition> typeDefinition(final InstanceSchema schema) {
if(schema.isConcrete()) {
final ObjectTypeDefinition.Builder builder = ObjectTypeDefinition.newObjectTypeDefinition();
builder.name(schema.getName());
builder.description(description(schema.getDescription()));
if (schema.getExtend() != null) {
builder.implementz(implementz(schema));
}
fieldDefinitions(schema).forEach(builder::fieldDefinition);
return builder.build();
} else {
final InterfaceTypeDefinition.Builder builder = InterfaceTypeDefinition.newInterfaceTypeDefinition();
builder.name(schema.getName());
builder.description(description(schema.getDescription()));
fieldDefinitions(schema).forEach(builder::definition);
return builder.build();
}
}
@SuppressWarnings("rawtypes")
private List implementz(final InstanceSchema schema) {
final InstanceSchema parent = schema.getExtend();
if(parent != null) {
return ImmutableList.builder()
.addAll(implementz(parent))
.add(new TypeName(parent.getName()))
.build();
} else {
return ImmutableList.of();
}
}
private List fieldDefinitions(final InstanceSchema schema) {
final List fields = new ArrayList<>();
schema.metadataSchema()
.forEach((k, v) -> fields.add(metadataFieldDefinition(k, v)));
schema.getAllProperties()
.forEach((k, v) -> {
if(!v.isAlwaysHidden()) {
fields.add(fieldDefinition(v));
}
});
if(schema instanceof Link.Resolver) {
((Link.Resolver) schema).getAllLinks()
.forEach((k, v) -> {
if(!v.isAlwaysHidden()) {
fields.add(FieldDefinition.newFieldDefinition()
.name(k)
.type(new ListType(new TypeName(v.getSchema().getName())))
.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name("query").type(new TypeName(STRING_TYPE)).build())
.build());
}
});
}
if(schema instanceof Transient.Resolver) {
((Transient.Resolver) schema).getDeclaredTransients()
.forEach((k, v) -> {
// Can only show typed transients
if(!v.isAlwaysHidden() && v.getType() != null) {
fields.add(FieldDefinition.newFieldDefinition()
.name(k)
.type(type(v.getType()))
.build());
}
});
}
return fields;
}
private Description description(final String description) {
if(description != null) {
return new Description(description, null, true);
} else {
return null;
}
}
public EnumTypeDefinition typeDefinition(final EnumSchema schema) {
final EnumTypeDefinition.Builder builder = EnumTypeDefinition.newEnumTypeDefinition();
builder.name(schema.getName());
if(schema.getDescription() != null) {
builder.description(new Description(schema.getDescription(), null, true));
}
schema.getValues().forEach(v -> builder.enumValueDefinition(EnumValueDefinition.newEnumValueDefinition()
.name(v).build()));
return builder.build();
}
public FieldDefinition fieldDefinition(final Property property) {
final FieldDefinition.Builder builder = FieldDefinition.newFieldDefinition();
builder.name(property.getName());
if(property.getDescription() != null) {
builder.description(new Description(property.getDescription(), null, true));
}
final Type> type = type(property.getType());
builder.type(property.isRequired() ? new NonNullType(type) : type);
return builder.build();
}
public FieldDefinition metadataFieldDefinition(final String name, final Use> type) {
final FieldDefinition.Builder builder = FieldDefinition.newFieldDefinition();
builder.name(name);
if(Reserved.ID.equals(name)) {
builder.type(new NonNullType(new TypeName(ID_TYPE)));
} else {
builder.type(new NonNullType(type(type)));
}
return builder.build();
}
private InputObjectTypeDefinition inputMapTypeDefinition(final String name, final Use> type) {
final InputObjectTypeDefinition.Builder builder = InputObjectTypeDefinition.newInputObjectDefinition();
builder.name(INPUT_PREFIX + name);
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name(GraphQLUtils.MAP_KEY).type(new NonNullType(new TypeName(STRING_TYPE))).build());
builder.inputValueDefinition(InputValueDefinition.newInputValueDefinition()
.name(GraphQLUtils.MAP_VALUE).type(inputType(type)).build());
return builder.build();
}
private ObjectTypeDefinition mapTypeDefinition(final String name, final Use> type) {
final ObjectTypeDefinition.Builder builder = ObjectTypeDefinition.newObjectTypeDefinition();
builder.name(name);
builder.fieldDefinition(FieldDefinition.newFieldDefinition()
.name(GraphQLUtils.MAP_KEY).type(new NonNullType(new TypeName(STRING_TYPE))).build());
builder.fieldDefinition(FieldDefinition.newFieldDefinition()
.name(GraphQLUtils.MAP_VALUE).type(type(type)).build());
return builder.build();
}
public Type> type(final Use> type) {
return type.visit(new Use.Visitor>() {
@Override
public Type> visitBoolean(final UseBoolean type) {
return new TypeName(BOOLEAN_TYPE);
}
@Override
public Type> visitInteger(final UseInteger type) {
return new TypeName(INT_TYPE);
}
@Override
public Type> visitNumber(final UseNumber type) {
return new TypeName(FLOAT_TYPE);
}
@Override
public Type> visitString(final UseString type) {
return new TypeName(STRING_TYPE);
}
@Override
public Type> visitEnum(final UseEnum type) {
return new TypeName(type.getSchema().getName());
}
@Override
public Type> visitRef(final UseRef type) {
return new TypeName(type.getSchema().getName());
}
@Override
public Type> visitArray(final UseArray type) {
return new ListType(type.getType().visit(this));
}
@Override
public Type> visitSet(final UseSet type) {
return new ListType(type.getType().visit(this));
}
@Override
public Type> visitMap(final UseMap type) {
return new ListType(new TypeName(mapEntryTypeName(type.getType())));
}
@Override
public Type> visitStruct(final UseStruct type) {
return new TypeName(type.getSchema().getName());
}
@Override
public Type> visitBinary(final UseBinary type) {
return new TypeName(STRING_TYPE);
}
});
}
public Type> inputType(final Use> type) {
return type.visit(new Use.Visitor>() {
@Override
public Type> visitBoolean(final UseBoolean type) {
return new TypeName(BOOLEAN_TYPE);
}
@Override
public Type> visitInteger(final UseInteger type) {
return new TypeName(INT_TYPE);
}
@Override
public Type> visitNumber(final UseNumber type) {
return new TypeName(FLOAT_TYPE);
}
@Override
public Type> visitString(final UseString type) {
return new TypeName(STRING_TYPE);
}
@Override
public Type> visitEnum(final UseEnum type) {
return new TypeName(type.getSchema().getName());
}
@Override
public Type> visitRef(final UseRef type) {
return new TypeName(INPUT_REF_TYPE);
}
@Override
public Type> visitArray(final UseArray type) {
return new ListType(type.getType().visit(this));
}
@Override
public Type> visitSet(final UseSet type) {
return new ListType(type.getType().visit(this));
}
@Override
public Type> visitMap(final UseMap type) {
return new ListType(new TypeName(INPUT_PREFIX + mapEntryTypeName(type.getType())));
}
@Override
public Type> visitStruct(final UseStruct type) {
return new TypeName(INPUT_PREFIX + type.getSchema().getName());
}
@Override
public Type> visitBinary(final UseBinary type) {
return new TypeName(STRING_TYPE);
}
});
}
private String mapEntryTypeName(final Use> type) {
return ENTRY_PREFIX + type.visit(new Use.Visitor() {
@Override
public String visitBoolean(final UseBoolean type) {
return BOOLEAN_TYPE;
}
@Override
public String visitInteger(final UseInteger type) {
return INT_TYPE;
}
@Override
public String visitNumber(final UseNumber type) {
return FLOAT_TYPE;
}
@Override
public String visitString(final UseString type) {
return STRING_TYPE;
}
@Override
public String visitEnum(final UseEnum type) {
return type.getSchema().getName();
}
@Override
public String visitRef(final UseRef type) {
return type.getSchema().getName();
}
@Override
public String visitArray(final UseArray type) {
return ARRAY_PREFIX + type.getType().visit(this);
}
@Override
public String visitSet(final UseSet type) {
return ARRAY_PREFIX + type.getType().visit(this);
}
@Override
public String visitMap(final UseMap type) {
return ENTRY_PREFIX + type.getType().visit(this);
}
@Override
public String visitStruct(final UseStruct type) {
return type.getSchema().getName();
}
@Override
public String visitBinary(final UseBinary type) {
return STRING_TYPE;
}
});
}
private Map> mapTypes(final InstanceSchema schema) {
final Map> mapTypes = new HashMap<>();
schema.getDeclaredProperties().forEach((k, v) -> {
v.getType().visit(new Use.Visitor() {
@Override
public Void visitBoolean(final UseBoolean type) {
return null;
}
@Override
public Void visitInteger(final UseInteger type) {
return null;
}
@Override
public Void visitNumber(final UseNumber type) {
return null;
}
@Override
public Void visitString(final UseString type) {
return null;
}
@Override
public Void visitEnum(final UseEnum type) {
return null;
}
@Override
public Void visitRef(final UseRef type) {
return null;
}
@Override
public Void visitArray(final UseArray type) {
type.getType().visit(this);
return null;
}
@Override
public Void visitSet(final UseSet type) {
type.getType().visit(this);
return null;
}
@Override
public Void visitMap(final UseMap type) {
type.getType().visit(this);
mapTypes.put(mapEntryTypeName(type.getType()), type.getType());
return null;
}
@Override
public Void visitStruct(final UseStruct type) {
return null;
}
@Override
public Void visitBinary(final UseBinary type) {
return null;
}
});
});
return mapTypes;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy