Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/main/java/graphql/language/TypeKind.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package graphql.language;

import graphql.Assert;
import graphql.PublicApi;

/**
* And enumeration of the the kind of things that can be in a graphql type system
*/
@PublicApi
public enum TypeKind {

Operation, Object, Interface, Union, Enum, Scalar, InputObject;

public static TypeKind getTypeKind(TypeDefinition def) {
if (def instanceof ObjectTypeDefinition) {
return Object;
}
if (def instanceof InterfaceTypeDefinition) {
return Interface;
}
if (def instanceof UnionTypeDefinition) {
return Union;
}
if (def instanceof ScalarTypeDefinition) {
return Scalar;
}
if (def instanceof EnumTypeDefinition) {
return Enum;
}
if (def instanceof InputObjectTypeDefinition) {
return InputObject;
}
return Assert.assertShouldNeverHappen();
}
}
30 changes: 30 additions & 0 deletions src/main/java/graphql/schema/diff/DiffCategory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package graphql.schema.diff;

import graphql.PublicApi;

/**
* A classification of difference events.
*/
@PublicApi
public enum DiffCategory {
/**
* The new API is missing something compared to the old API
*/
MISSING,
/**
* The new API has become stricter for existing clients than the old API
*/
STRICTER,
/**
* The new API has an invalid structure
*/
INVALID,
/**
* The new API has added something not present in the old API
*/
ADDITION,
/**
* The new API has changed something compared to the old API
*/
DIFFERENT
}
66 changes: 66 additions & 0 deletions src/main/java/graphql/schema/diff/DiffCtx.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package graphql.schema.diff;

import graphql.Internal;
import graphql.schema.diff.reporting.DifferenceReporter;
import graphql.language.Document;
import graphql.language.Type;
import graphql.language.TypeDefinition;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Stack;

/*
* A helper class that represents diff state (eg visited types) as well as helpers
*/
@Internal
class DiffCtx {
final List<String> examinedTypes = new ArrayList<>();
final Stack<String> currentTypes = new Stack<>();
private final DifferenceReporter reporter;
final Document oldDoc;
final Document newDoc;

DiffCtx(DifferenceReporter reporter, Document oldDoc, Document newDoc) {
this.reporter = reporter;
this.oldDoc = oldDoc;
this.newDoc = newDoc;
}

void report(DiffEvent differenceEvent) {
reporter.report(differenceEvent);
}

boolean examiningType(String typeName) {
if (examinedTypes.contains(typeName)) {
return true;
}
examinedTypes.add(typeName);
currentTypes.push(typeName);
return false;
}

void exitType() {
currentTypes.pop();
}

<T extends TypeDefinition> Optional<T> getOldTypeDef(Type type, Class<T> typeDefClass) {
return getType(SchemaDiff.getTypeName(type), typeDefClass, oldDoc);
}

<T extends TypeDefinition> Optional<T> getNewTypeDef(Type type, Class<T> typeDefClass) {
return getType(SchemaDiff.getTypeName(type), typeDefClass, newDoc);
}

private <T extends TypeDefinition> Optional<T> getType(String typeName, Class<T> typeDefClass, Document doc) {
if (typeName == null) {
return Optional.empty();
}
return doc.getDefinitions().stream()
.filter(def -> typeDefClass.isAssignableFrom(def.getClass()))
.map(typeDefClass::cast)
.filter(defT -> defT.getName().equals(typeName))
.findFirst();
}
}
139 changes: 139 additions & 0 deletions src/main/java/graphql/schema/diff/DiffEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package graphql.schema.diff;

import graphql.PublicApi;
import graphql.language.TypeKind;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static java.util.stream.Collectors.toList;

/**
* This represents the events that the {@link SchemaDiff} outputs.
*/
@PublicApi
public class DiffEvent {

private final DiffLevel level;
private final DiffCategory category;
private final TypeKind typeOfType;
private final String typeName;
private final String fieldName;
private final String reasonMsg;
private final List<String> components;

DiffEvent(DiffLevel level, DiffCategory category, String typeName, String fieldName, TypeKind typeOfType, String reasonMsg, List<String> components) {
this.level = level;
this.category = category;
this.typeName = typeName;
this.fieldName = fieldName;
this.typeOfType = typeOfType;
this.reasonMsg = reasonMsg;
this.components = components;
}

public String getTypeName() {
return typeName;
}

public TypeKind getTypeKind() {
return typeOfType;
}

public String getReasonMsg() {
return reasonMsg;
}

public DiffLevel getLevel() {
return level;
}

public String getFieldName() {
return fieldName;
}

public DiffCategory getCategory() {
return category;
}

public List<String> getComponents() {
return new ArrayList<>(components);
}

@Override
public String toString() {
return "DifferenceEvent{" +
" reasonMsg='" + reasonMsg + '\'' +
", level=" + level +
", category=" + category +
", typeName='" + typeName + '\'' +
", typeKind=" + typeOfType +
", fieldName=" + fieldName +
'}';
}

public static Builder newInfo() {
return new Builder().level(DiffLevel.INFO);
}

public static Builder apiDanger() {
return new Builder().level(DiffLevel.DANGEROUS);
}

public static Builder apiBreakage() {
return new Builder().level(DiffLevel.BREAKING);
}


public static class Builder {

DiffCategory category;
DiffLevel level;
String typeName;
TypeKind typeOfType;
String reasonMsg;
String fieldName;
List<String> components = new ArrayList<>();

public Builder level(DiffLevel level) {
this.level = level;
return this;
}


public Builder typeName(String typeName) {
this.typeName = typeName;
return this;
}

public Builder fieldName(String fieldName) {
this.fieldName = fieldName;
return this;
}

public Builder typeKind(TypeKind typeOfType) {
this.typeOfType = typeOfType;
return this;
}

public Builder category(DiffCategory category) {
this.category = category;
return this;
}

public Builder reasonMsg(String format, Object... args) {
this.reasonMsg = String.format(format, args);
return this;
}

public Builder components(Object... args) {
components.addAll(Arrays.stream(args).map(String::valueOf).collect(toList()));
return this;
}

public DiffEvent build() {
return new DiffEvent(level, category, typeName, fieldName, typeOfType, reasonMsg, components);
}
}
}
22 changes: 22 additions & 0 deletions src/main/java/graphql/schema/diff/DiffLevel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package graphql.schema.diff;

import graphql.PublicApi;

/**
* This is the level of difference between graphql APIs
*/
@PublicApi
public enum DiffLevel {
/**
* A simple info object coming out of the difference engine
*/
INFO,
/**
* The new API has made a breaking change
*/
BREAKING,
/**
* The new API has made a dangerous (but non breaking) change
*/
DANGEROUS
}
75 changes: 75 additions & 0 deletions src/main/java/graphql/schema/diff/DiffSet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package graphql.schema.diff;

import graphql.Assert;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.PublicApi;
import graphql.introspection.IntrospectionQuery;
import graphql.schema.GraphQLSchema;

import java.util.Map;

/**
* Represents 2 schemas that can be diffed. The {@link SchemaDiff} code
* assumes that that schemas to be diffed are the result of a
* {@link graphql.introspection.IntrospectionQuery}.
*/
@PublicApi
public class DiffSet {

private final Map<String, Object> introspectionOld;
private final Map<String, Object> introspectionNew;

public DiffSet(Map<String, Object> introspectionOld, Map<String, Object> introspectionNew) {
this.introspectionOld = introspectionOld;
this.introspectionNew = introspectionNew;
}

/**
* @return the old API as an introspection result
*/
public Map<String, Object> getOld() {
return introspectionOld;
}

/**
* @return the new API as an introspection result
*/
public Map<String, Object> getNew() {
return introspectionNew;
}


/**
* Creates a diff set out of the result of 2 introspection queries.
*
* @param introspectionOld the older introspection query
* @param introspectionNew the newer introspection query
*
* @return a diff set representing them
*/
public static DiffSet diffSet(Map<String, Object> introspectionOld, Map<String, Object> introspectionNew) {
return new DiffSet(introspectionOld, introspectionNew);
}

/**
* Creates a diff set out of the result of 2 schemas.
*
* @param schemaOld the older schema
* @param schemaNew the newer schema
*
* @return a diff set representing them
*/
public static DiffSet diffSet(GraphQLSchema schemaOld, GraphQLSchema schemaNew) {
Map<String, Object> introspectionOld = introspect(schemaOld);
Map<String, Object> introspectionNew = introspect(schemaNew);
return diffSet(introspectionOld, introspectionNew);
}

private static Map<String, Object> introspect(GraphQLSchema schema) {
GraphQL gql = GraphQL.newGraphQL(schema).build();
ExecutionResult result = gql.execute(IntrospectionQuery.INTROSPECTION_QUERY);
Assert.assertTrue(result.getErrors().size() == 0, "The schema has errors during Introspection");
return result.getData();
}
}
Loading