graphql.schema.SchemaTransformer Maven / Gradle / Ivy
package graphql.schema;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import graphql.PublicApi;
import graphql.collect.ImmutableKit;
import graphql.util.Breadcrumb;
import graphql.util.NodeAdapter;
import graphql.util.NodeLocation;
import graphql.util.NodeZipper;
import graphql.util.TraversalControl;
import graphql.util.Traverser;
import graphql.util.TraverserContext;
import graphql.util.TraverserVisitor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import static graphql.Assert.assertNotEmpty;
import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertShouldNeverHappen;
import static graphql.schema.GraphQLSchemaElementAdapter.SCHEMA_ELEMENT_ADAPTER;
import static graphql.schema.SchemaElementChildrenContainer.newSchemaElementChildrenContainer;
import static graphql.schema.impl.StronglyConnectedComponentsTopologicallySorted.getStronglyConnectedComponentsTopologicallySorted;
import static graphql.util.NodeZipper.ModificationType.DELETE;
import static graphql.util.NodeZipper.ModificationType.REPLACE;
import static graphql.util.TraversalControl.CONTINUE;
import static java.lang.String.format;
/**
* Transforms a {@link GraphQLSchema} object by calling bac on a provided visitor.
*
* To change a {@link GraphQLSchemaElement} node in the schema you need
* to return {@link GraphQLTypeVisitor#changeNode(TraverserContext, GraphQLSchemaElement)}
* which instructs the schema transformer to change that element upon leaving that
* visitor method.
*
* {@code
* public TraversalControl visitGraphQLObjectType(GraphQLObjectType objectType, TraverserContext context) {
* GraphQLObjectType newObjectType = mkSomeNewNode(objectType);
* return changeNode(context, newObjectType);
* }
* }
*
*
* To delete an element use {@link GraphQLTypeVisitor#deleteNode(TraverserContext)}
*
* {@code
* public TraversalControl visitGraphQLObjectType(GraphQLObjectType objectType, TraverserContext context) {
* return deleteNode(context, objectType);
* }
* }
*
*
* To insert elements use either {@link GraphQLTypeVisitor#insertAfter(TraverserContext, GraphQLSchemaElement)} or
* {@link GraphQLTypeVisitor#insertBefore(TraverserContext, GraphQLSchemaElement)}
* which will insert the new node before or after the current node being visited
*
* {@code
* public TraversalControl visitGraphQLObjectType(GraphQLObjectType objectType, TraverserContext context) {
* GraphQLObjectType newObjectType = mkSomeNewNode();
* return insertAfter(context, newObjectType);
* }
* }
*
*/
@PublicApi
public class SchemaTransformer {
/**
* Transforms a GraphQLSchema and returns a new GraphQLSchema object.
*
* @param schema the schema to transform
* @param visitor the visitor call back
*
* @return a new GraphQLSchema instance.
*/
public static GraphQLSchema transformSchema(GraphQLSchema schema, GraphQLTypeVisitor visitor) {
SchemaTransformer schemaTransformer = new SchemaTransformer();
return schemaTransformer.transform(schema, visitor);
}
/**
* Transforms a GraphQLSchema and returns a new GraphQLSchema object.
*
* @param schema the schema to transform
* @param visitor the visitor call back
* @param postTransformation a callback that can be as a final step to the schema
*
* @return a new GraphQLSchema instance.
*/
public static GraphQLSchema transformSchema(GraphQLSchema schema, GraphQLTypeVisitor visitor, Consumer postTransformation) {
SchemaTransformer schemaTransformer = new SchemaTransformer();
return schemaTransformer.transform(schema, visitor, postTransformation);
}
/**
* Transforms a {@link GraphQLSchemaElement} and returns a new element.
*
* @param schemaElement the schema element to transform
* @param visitor the visitor call back
* @param for two
*
* @return a new GraphQLSchemaElement instance.
*/
public static T transformSchema(final T schemaElement, GraphQLTypeVisitor visitor) {
SchemaTransformer schemaTransformer = new SchemaTransformer();
return schemaTransformer.transform(schemaElement, visitor);
}
public GraphQLSchema transform(final GraphQLSchema schema, GraphQLTypeVisitor visitor) {
return (GraphQLSchema) transformImpl(schema, null, visitor, null);
}
public GraphQLSchema transform(final GraphQLSchema schema, GraphQLTypeVisitor visitor, Consumer postTransformation) {
return (GraphQLSchema) transformImpl(schema, null, visitor, postTransformation);
}
public T transform(final T schemaElement, GraphQLTypeVisitor visitor) {
//noinspection unchecked
return (T) transformImpl(null, schemaElement, visitor, null);
}
private Object transformImpl(final GraphQLSchema schema, GraphQLSchemaElement schemaElement, GraphQLTypeVisitor visitor, Consumer postTransformation) {
DummyRoot dummyRoot;
GraphQLCodeRegistry.Builder codeRegistry = null;
if (schema != null) {
dummyRoot = new DummyRoot(schema);
codeRegistry = GraphQLCodeRegistry.newCodeRegistry(schema.getCodeRegistry());
} else {
dummyRoot = new DummyRoot(schemaElement);
}
final Map changedTypes = new LinkedHashMap<>();
final Map typeReferences = new LinkedHashMap<>();
// first pass - general transformation
boolean schemaChanged = traverseAndTransform(dummyRoot, changedTypes, typeReferences, visitor, codeRegistry, schema);
// if we have changed any named elements AND we have type references referring to them then
// we need to make a second pass to replace these type references to the new names
if (!changedTypes.isEmpty()) {
boolean hasTypeRefsForChangedTypes = changedTypes.keySet().stream().anyMatch(typeReferences::containsKey);
if (hasTypeRefsForChangedTypes) {
replaceTypeReferences(dummyRoot, schema, codeRegistry, changedTypes);
}
}
if (schema != null) {
GraphQLSchema graphQLSchema = schema;
if (schemaChanged || codeRegistry.hasChanged()) {
graphQLSchema = dummyRoot.rebuildSchema(codeRegistry);
if (postTransformation != null) {
graphQLSchema = graphQLSchema.transform(postTransformation);
}
}
return graphQLSchema;
} else {
return dummyRoot.schemaElement;
}
}
private void replaceTypeReferences(DummyRoot dummyRoot, GraphQLSchema schema, GraphQLCodeRegistry.Builder codeRegistry, Map changedTypes) {
GraphQLTypeVisitor typeRefVisitor = new GraphQLTypeVisitorStub() {
@Override
public TraversalControl visitGraphQLTypeReference(GraphQLTypeReference typeRef, TraverserContext context) {
GraphQLNamedType graphQLNamedType = changedTypes.get(typeRef.getName());
if (graphQLNamedType != null) {
typeRef = GraphQLTypeReference.typeRef(graphQLNamedType.getName());
return changeNode(context, typeRef);
}
return CONTINUE;
}
};
traverseAndTransform(dummyRoot, new HashMap<>(), new HashMap<>(), typeRefVisitor, codeRegistry, schema);
}
private boolean traverseAndTransform(DummyRoot dummyRoot, Map changedTypes, Map typeReferences, GraphQLTypeVisitor visitor, GraphQLCodeRegistry.Builder codeRegistry, GraphQLSchema schema) {
List> zippers = new LinkedList<>();
Map> zipperByNodeAfterTraversing = new LinkedHashMap<>();
Map> zipperByOriginalNode = new LinkedHashMap<>();
Map, List>>> breadcrumbsByZipper = new LinkedHashMap<>();
Map> reverseDependencies = new LinkedHashMap<>();
Map> typeRefReverseDependencies = new LinkedHashMap<>();
TraverserVisitor nodeTraverserVisitor = new TraverserVisitor<>() {
@Override
public TraversalControl enter(TraverserContext context) {
GraphQLSchemaElement currentSchemaElement = context.thisNode();
if (currentSchemaElement == dummyRoot) {
return TraversalControl.CONTINUE;
}
if (currentSchemaElement instanceof GraphQLTypeReference) {
GraphQLTypeReference typeRef = (GraphQLTypeReference) currentSchemaElement;
typeReferences.put(typeRef.getName(), typeRef);
}
NodeZipper nodeZipper = new NodeZipper<>(currentSchemaElement, context.getBreadcrumbs(), SCHEMA_ELEMENT_ADAPTER);
context.setVar(NodeZipper.class, nodeZipper);
context.setVar(NodeAdapter.class, SCHEMA_ELEMENT_ADAPTER);
int zippersBefore = zippers.size();
TraversalControl result = currentSchemaElement.accept(context, visitor);
// detection if the node was changed
if (zippersBefore + 1 == zippers.size()) {
nodeZipper = zippers.get(zippers.size() - 1);
if (context.originalThisNode() instanceof GraphQLNamedType && context.isChanged()) {
GraphQLNamedType originalNamedType = (GraphQLNamedType) context.originalThisNode();
GraphQLNamedType changedNamedType = (GraphQLNamedType) context.thisNode();
if (!originalNamedType.getName().equals(changedNamedType.getName())) {
changedTypes.put(originalNamedType.getName(), changedNamedType);
}
}
}
zipperByOriginalNode.put(context.originalThisNode(), nodeZipper);
if (context.isDeleted()) {
zipperByNodeAfterTraversing.put(context.originalThisNode(), nodeZipper);
} else {
zipperByNodeAfterTraversing.put(context.thisNode(), nodeZipper);
}
breadcrumbsByZipper.put(nodeZipper, new ArrayList<>());
breadcrumbsByZipper.get(nodeZipper).add(context.getBreadcrumbs());
if (nodeZipper.getModificationType() != NodeZipper.ModificationType.DELETE) {
reverseDependencies.computeIfAbsent(context.thisNode(), ign -> new ArrayList<>()).add(context.getParentNode());
if (context.originalThisNode() instanceof GraphQLTypeReference) {
String typeName = ((GraphQLTypeReference) context.originalThisNode()).getName();
typeRefReverseDependencies.computeIfAbsent(typeName, ign -> new ArrayList<>()).add(context.getParentNode());
}
}
return result;
}
@Override
public TraversalControl leave(TraverserContext context) {
return TraversalControl.CONTINUE;
}
@Override
public TraversalControl backRef(TraverserContext context) {
NodeZipper zipper = zipperByOriginalNode.get(context.thisNode());
breadcrumbsByZipper.get(zipper).add(context.getBreadcrumbs());
if (zipper.getModificationType() == DELETE) {
return CONTINUE;
}
visitor.visitBackRef(context);
List reverseDependenciesForCurNode = reverseDependencies.get(zipper.getCurNode());
assertNotNull(reverseDependenciesForCurNode);
reverseDependenciesForCurNode.add(context.getParentNode());
return TraversalControl.CONTINUE;
}
};
Traverser traverser = Traverser.depthFirstWithNamedChildren(SCHEMA_ELEMENT_ADAPTER::getNamedChildren, zippers, null);
if (codeRegistry != null) {
traverser.rootVar(GraphQLCodeRegistry.Builder.class, codeRegistry);
}
if (schema != null) {
traverser.rootVar(GraphQLSchema.class, schema);
}
traverser.traverse(dummyRoot, nodeTraverserVisitor);
List> stronglyConnectedTopologicallySorted = getStronglyConnectedComponentsTopologicallySorted(reverseDependencies, typeRefReverseDependencies);
return zipUpToDummyRoot(zippers, stronglyConnectedTopologicallySorted, breadcrumbsByZipper, zipperByNodeAfterTraversing);
}
private static class RelevantZippersAndBreadcrumbs {
final Multimap> zipperByParent = LinkedHashMultimap.create();
final Set> relevantZippers;
final Map, List>>> breadcrumbsByZipper;
public RelevantZippersAndBreadcrumbs(List> relevantZippers,
Map, List>>> breadcrumbsByZipper) {
this.relevantZippers = new LinkedHashSet<>(relevantZippers);
this.breadcrumbsByZipper = breadcrumbsByZipper;
for (NodeZipper zipper : relevantZippers) {
for (List> breadcrumbs : breadcrumbsByZipper.get(zipper)) {
zipperByParent.put(breadcrumbs.get(0).getNode(), zipper);
}
}
}
public boolean isRelevantZipper(NodeZipper zipper) {
return relevantZippers.contains(zipper);
}
public Collection> zippersWithParent(GraphQLSchemaElement parent) {
return zipperByParent.get(parent);
}
public void removeRelevantZipper(NodeZipper zipper) {
relevantZippers.remove(zipper);
}
public List>> getBreadcrumbs(NodeZipper zipper) {
return breadcrumbsByZipper.get(zipper);
}
public void updateZipper(NodeZipper currentZipper,
NodeZipper newZipper) {
// the current zipper is not always relevant, meaning this has no effect sometimes
relevantZippers.remove(currentZipper);
relevantZippers.add(newZipper);
List>> currentBreadcrumbs = breadcrumbsByZipper.get(currentZipper);
assertNotNull(currentBreadcrumbs, "No breadcrumbs found for zipper %s", currentZipper);
for (List> breadcrumbs : currentBreadcrumbs) {
GraphQLSchemaElement parent = breadcrumbs.get(0).getNode();
zipperByParent.remove(parent, currentZipper);
zipperByParent.put(parent, newZipper);
}
breadcrumbsByZipper.remove(currentZipper);
breadcrumbsByZipper.put(newZipper, currentBreadcrumbs);
}
}
private boolean zipUpToDummyRoot(List> zippers,
List> stronglyConnectedTopologicallySorted,
Map, List>>> breadcrumbsByZipper,
Map> nodeToZipper) {
if (zippers.size() == 0) {
return false;
}
RelevantZippersAndBreadcrumbs relevantZippers = new RelevantZippersAndBreadcrumbs(zippers, breadcrumbsByZipper);
for (int i = stronglyConnectedTopologicallySorted.size() - 1; i >= 0; i--) {
List scc = stronglyConnectedTopologicallySorted.get(i);
// performance relevant: we avoid calling zipperWithSameParent twice
// for SCC of size one.
if (scc.size() > 1) {
boolean sccChanged = false;
List unchangedSccElements = new ArrayList<>();
for (GraphQLSchemaElement element : scc) {
// if the current element itself has a zipper it is changed
if (relevantZippers.isRelevantZipper(nodeToZipper.get(element))) {
sccChanged = true;
continue;
}
// if the current element is changed via "moveUp" it is changed
Map, Breadcrumb> zipperWithSameParent = zipperWithSameParent(element, relevantZippers, false);
if (zipperWithSameParent.size() > 0) {
sccChanged = true;
} else {
unchangedSccElements.add(element);
}
}
if (!sccChanged) {
continue;
}
// we need to change all elements inside the current SCC
for (GraphQLSchemaElement element : unchangedSccElements) {
NodeZipper currentZipper = nodeToZipper.get(element);
NodeZipper newZipper = currentZipper.withNewNode(element.copy());
nodeToZipper.put(element, newZipper);
relevantZippers.updateZipper(currentZipper, newZipper);
}
}
for (int j = scc.size() - 1; j >= 0; j--) {
GraphQLSchemaElement element = scc.get(j);
Map, Breadcrumb> zipperWithSameParent = zipperWithSameParent(element, relevantZippers, true);
// this means we have a node which doesn't need to be changed
if (zipperWithSameParent.size() == 0) {
continue;
}
NodeZipper newZipper = moveUp(element, zipperWithSameParent);
if (element instanceof DummyRoot) {
// this means we have updated the dummy root and we are done (dummy root is a special as it gets updated in place, see Implementation of DummyRoot)
break;
}
NodeZipper curZipperForElement = nodeToZipper.get(element);
assertNotNull(curZipperForElement, "curZipperForElement is null for parentNode %s", element);
relevantZippers.updateZipper(curZipperForElement, newZipper);
}
}
return true;
}
private Map, Breadcrumb> zipperWithSameParent(
GraphQLSchemaElement parent,
RelevantZippersAndBreadcrumbs relevantZippers,
boolean cleanup) {
Map, Breadcrumb> result = new LinkedHashMap<>();
Collection> zippersWithParent = relevantZippers.zippersWithParent(parent);
Iterator> zippersIter = zippersWithParent.iterator();
outer:
while (zippersIter.hasNext()) {
NodeZipper zipper = zippersIter.next();
List>> listOfBreadcrumbsList = assertNotNull(relevantZippers.getBreadcrumbs(zipper));
for (int i = 0; i < listOfBreadcrumbsList.size(); i++) {
List> path = listOfBreadcrumbsList.get(i);
if (path.get(0).getNode() == parent) {
result.put(zipper, path.get(0));
if (cleanup) {
// remove breadcrumb we just used
listOfBreadcrumbsList.remove(i);
if (listOfBreadcrumbsList.size() == 0) {
// if there are no breadcrumbs left for this zipper it is safe to remove
relevantZippers.removeRelevantZipper(zipper);
}
}
continue outer;
}
}
}
return result;
}
private NodeZipper moveUp(
GraphQLSchemaElement parent,
Map, Breadcrumb> sameParentsZipper) {
Set> sameParent = sameParentsZipper.keySet();
assertNotEmpty(sameParent, () -> "expected at least one zipper");
Map> childrenMap = new HashMap<>(SCHEMA_ELEMENT_ADAPTER.getNamedChildren(parent));
Map indexCorrection = new HashMap<>();
List zipperWithOneParents = new ArrayList<>();
for (NodeZipper zipper : sameParent) {
Breadcrumb breadcrumb = sameParentsZipper.get(zipper);
zipperWithOneParents.add(new ZipperWithOneParent(zipper, breadcrumb));
}
zipperWithOneParents.sort((zipperWithOneParent1, zipperWithOneParent2) -> {
NodeZipper zipper1 = zipperWithOneParent1.zipper;
NodeZipper zipper2 = zipperWithOneParent2.zipper;
Breadcrumb breadcrumb1 = zipperWithOneParent1.parent;
Breadcrumb breadcrumb2 = zipperWithOneParent2.parent;
int index1 = breadcrumb1.getLocation().getIndex();
int index2 = breadcrumb2.getLocation().getIndex();
if (index1 != index2) {
return Integer.compare(index1, index2);
}
NodeZipper.ModificationType modificationType1 = zipper1.getModificationType();
NodeZipper.ModificationType modificationType2 = zipper2.getModificationType();
// same index can never be deleted and changed at the same time
if (modificationType1 == modificationType2) {
return 0;
}
// always first replacing the node
if (modificationType1 == REPLACE) {
return -1;
}
// and then INSERT_BEFORE before INSERT_AFTER
return modificationType1 == NodeZipper.ModificationType.INSERT_BEFORE ? -1 : 1;
});
for (ZipperWithOneParent zipperWithOneParent : zipperWithOneParents) {
NodeZipper zipper = zipperWithOneParent.zipper;
Breadcrumb breadcrumb = zipperWithOneParent.parent;
NodeLocation location = breadcrumb.getLocation();
Integer ixDiff = indexCorrection.getOrDefault(location.getName(), 0);
int ix = location.getIndex() + ixDiff;
String name = location.getName();
List childList = new ArrayList<>(childrenMap.get(name));
switch (zipper.getModificationType()) {
case REPLACE:
childList.set(ix, zipper.getCurNode());
break;
case DELETE:
childList.remove(ix);
indexCorrection.put(name, ixDiff - 1);
break;
case INSERT_BEFORE:
childList.add(ix, zipper.getCurNode());
indexCorrection.put(name, ixDiff + 1);
break;
case INSERT_AFTER:
childList.add(ix + 1, zipper.getCurNode());
indexCorrection.put(name, ixDiff + 1);
break;
}
childrenMap.put(name, childList);
}
GraphQLSchemaElement newNode = SCHEMA_ELEMENT_ADAPTER.withNewChildren(parent, childrenMap);
final List> oldBreadcrumbs = sameParent.iterator().next().getBreadcrumbs();
List> newBreadcrumbs;
if (oldBreadcrumbs.size() > 1) {
newBreadcrumbs = oldBreadcrumbs.subList(1, oldBreadcrumbs.size());
} else {
newBreadcrumbs = ImmutableKit.emptyList();
}
return new NodeZipper<>(newNode, newBreadcrumbs, SCHEMA_ELEMENT_ADAPTER);
}
private static class ZipperWithOneParent {
public NodeZipper zipper;
public Breadcrumb parent;
public ZipperWithOneParent(NodeZipper zipper, Breadcrumb parent) {
this.zipper = zipper;
this.parent = parent;
}
}
// artificial schema element which serves as root element for the transformation
private static class DummyRoot implements GraphQLSchemaElement {
static final String QUERY = "query";
static final String MUTATION = "mutation";
static final String SUBSCRIPTION = "subscription";
static final String ADD_TYPES = "addTypes";
static final String DIRECTIVES = "directives";
static final String SCHEMA_DIRECTIVES = "schemaDirectives";
static final String SCHEMA_APPLIED_DIRECTIVES = "schemaAppliedDirectives";
static final String INTROSPECTION = "introspection";
static final String SCHEMA_ELEMENT = "schemaElement";
GraphQLSchema schema;
GraphQLObjectType query;
GraphQLObjectType mutation;
GraphQLObjectType subscription;
GraphQLObjectType introspectionSchemaType;
Set additionalTypes;
Set directives;
Set schemaDirectives;
Set schemaAppliedDirectives;
GraphQLSchemaElement schemaElement;
DummyRoot(GraphQLSchema schema) {
this.schema = schema;
query = schema.getQueryType();
mutation = schema.isSupportingMutations() ? schema.getMutationType() : null;
subscription = schema.isSupportingSubscriptions() ? schema.getSubscriptionType() : null;
additionalTypes = schema.getAdditionalTypes();
schemaDirectives = new LinkedHashSet<>(schema.getSchemaDirectives());
schemaAppliedDirectives = new LinkedHashSet<>(schema.getSchemaAppliedDirectives());
directives = new LinkedHashSet<>(schema.getDirectives());
introspectionSchemaType = schema.getIntrospectionSchemaType();
}
DummyRoot(GraphQLSchemaElement schemaElement) {
this.schemaElement = schemaElement;
}
@Override
public GraphQLSchemaElement copy() {
return assertShouldNeverHappen();
}
@Override
public List getChildren() {
return assertShouldNeverHappen();
}
@Override
public SchemaElementChildrenContainer getChildrenWithTypeReferences() {
SchemaElementChildrenContainer.Builder builder = newSchemaElementChildrenContainer();
if (schemaElement != null) {
builder.child(SCHEMA_ELEMENT, schemaElement);
} else {
builder.child(QUERY, query);
if (schema.isSupportingMutations()) {
builder.child(MUTATION, mutation);
}
if (schema.isSupportingSubscriptions()) {
builder.child(SUBSCRIPTION, subscription);
}
builder.children(ADD_TYPES, additionalTypes);
builder.children(DIRECTIVES, directives);
builder.children(SCHEMA_DIRECTIVES, schemaDirectives);
builder.children(SCHEMA_APPLIED_DIRECTIVES, schemaAppliedDirectives);
builder.child(INTROSPECTION, introspectionSchemaType);
}
return builder.build();
}
@Override
public GraphQLSchemaElement withNewChildren(SchemaElementChildrenContainer newChildren) {
if (this.schemaElement != null) {
this.schemaElement = newChildren.getChildOrNull(SCHEMA_ELEMENT);
return this;
}
// special hack: we don't create a new dummy root, but we simply update it
query = newChildren.getChildOrNull(QUERY);
mutation = newChildren.getChildOrNull(MUTATION);
subscription = newChildren.getChildOrNull(SUBSCRIPTION);
introspectionSchemaType = newChildren.getChildOrNull(INTROSPECTION);
additionalTypes = new LinkedHashSet<>(newChildren.getChildren(ADD_TYPES));
directives = new LinkedHashSet<>(newChildren.getChildren(DIRECTIVES));
schemaDirectives = new LinkedHashSet<>(newChildren.getChildren(SCHEMA_DIRECTIVES));
schemaAppliedDirectives = new LinkedHashSet<>(newChildren.getChildren(SCHEMA_APPLIED_DIRECTIVES));
return this;
}
@Override
public TraversalControl accept(TraverserContext context, GraphQLTypeVisitor visitor) {
return assertShouldNeverHappen();
}
public GraphQLSchema rebuildSchema(GraphQLCodeRegistry.Builder codeRegistry) {
return GraphQLSchema.newSchema()
.query(this.query)
.mutation(this.mutation)
.subscription(this.subscription)
.additionalTypes(this.additionalTypes)
.additionalDirectives(this.directives)
.introspectionSchemaType(this.introspectionSchemaType)
.withSchemaDirectives(this.schemaDirectives)
.withSchemaAppliedDirectives(this.schemaAppliedDirectives)
.codeRegistry(codeRegistry.build())
.description(schema.getDescription())
.build();
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy