GraphQL SDL tips
The Virtualize Message Responder has a Form JSON view that can be constrained to the JSON Schema types defined in a service definition document. This simplifies configuring the response, where the Form JSON view is populated with controls to select and configure the available JSON properties, array items, etc.
What if you are virtualizing a GraphQL endpoint that is not described by a service definition document such as an OpenAPI? Fortunately, it is possible to generate JSON Schema types from a GraphQL Schema Definition Language (SDL) document and reference them from a service definition.
In a provisioning action, create an Extension Tool with the following Groovy script:
import java.io.IOException import java.io.StringWriter import java.util.List import com.fasterxml.jackson.core.JsonFactory import com.fasterxml.jackson.core.JsonGenerator import com.parasoft.api.IOUtil import com.parasoft.api.ScriptingContext import graphql.Scalars import graphql.introspection.Introspection import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLEnumValueDefinition import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLImplementingType import graphql.schema.GraphQLInterfaceType import graphql.schema.GraphQLNamedOutputType import graphql.schema.GraphQLNamedSchemaElement import graphql.schema.GraphQLNamedType import graphql.schema.GraphQLObjectType import graphql.schema.GraphQLOutputType import graphql.schema.GraphQLScalarType import graphql.schema.GraphQLSchema import graphql.schema.GraphQLTypeUtil import graphql.schema.GraphQLUnionType import graphql.schema.idl.SchemaGenerator String convertToJsonSchema(def input, def context) throws IOException { def sdlContent = IOUtil.readTextFile(context.getAbsolutePathFile(input.toString())) GraphQLSchema graphQLSchema = SchemaGenerator.createdMockedSchema(sdlContent) String jsonSchema = dumpSchema(graphQLSchema) return jsonSchema } static String dumpSchema(GraphQLSchema schema) throws IOException { StringWriter sw = new StringWriter() JsonFactory jsonFactory = new JsonFactory() jsonFactory.createGenerator(sw).withCloseable { generator -> generator.useDefaultPrettyPrinter() generator.writeStartObject() generator.writeFieldName('$ref') generator.writeString('#/' + GraphQLSchema.class.getSimpleName()) writeRootType(schema, generator) for (GraphQLNamedSchemaElement type : schema.getAllElementsAsList()) { if (type instanceof GraphQLNamedType && Introspection.isIntrospectionTypes((GraphQLNamedType) type)) { continue } if (type instanceof GraphQLImplementingType) { dumpGraphQLImplementingType((GraphQLImplementingType) type, generator) } else if (type instanceof GraphQLUnionType) { dumpGraphQLUnionType((GraphQLUnionType) type, generator) } else if (type instanceof GraphQLEnumType) { dumpGraphQLEnumType((GraphQLEnumType) type, generator) } } generator.writeEndObject() } return sw.toString() } static void writeRootType(GraphQLSchema schema, JsonGenerator generator) throws IOException { generator.writeFieldName(GraphQLSchema.class.getSimpleName()) generator.writeStartObject() generator.writeFieldName('oneOf') generator.writeStartArray() writeRootOperationType(schema.getQueryType(), generator) writeRootOperationType(schema.getMutationType(), generator) writeRootOperationType(schema.getSubscriptionType(), generator) generator.writeEndArray() generator.writeEndObject() } static void writeRootOperationType(GraphQLObjectType type, JsonGenerator generator) throws IOException { if (type != null) { writeRef(type, generator) } } static void startGraphQLNamedSchemaElement(GraphQLNamedSchemaElement type, JsonGenerator generator) throws IOException { generator.writeFieldName(type.getName()) generator.writeStartObject() String description = type.getDescription() if (description != null && !description.isBlank()) { generator.writeFieldName('description') generator.writeString(description) } } static void dumpGraphQLEnumType(GraphQLEnumType type, JsonGenerator generator) throws IOException { startGraphQLNamedSchemaElement(type, generator) generator.writeFieldName('enum') generator.writeStartArray() for (GraphQLEnumValueDefinition enumValue : type.getValues()) { generator.writeString(enumValue.getName()) } generator.writeEndArray() generator.writeEndObject() } static void dumpGraphQLUnionType(GraphQLUnionType type, JsonGenerator generator) throws IOException { startGraphQLNamedSchemaElement(type, generator) List<GraphQLNamedOutputType> members = type.getTypes() if (!members.isEmpty()) { generator.writeFieldName('oneOf') generator.writeStartArray() for (GraphQLNamedOutputType member : members) { writeRef(member, generator) } generator.writeEndArray() } generator.writeEndObject() } static void dumpGraphQLImplementingType(GraphQLImplementingType type, JsonGenerator generator) throws IOException { startGraphQLNamedSchemaElement(type, generator) List<GraphQLNamedOutputType> interfaces = type.getInterfaces() if (!interfaces.isEmpty()) { generator.writeFieldName('allOf') generator.writeStartArray() for (GraphQLNamedOutputType anInterface : interfaces) { writeRef(anInterface, generator) } generator.writeStartObject() writeProperties(type, interfaces, generator) generator.writeEndObject() generator.writeEndArray() } else { writeProperties(type, interfaces, generator) } generator.writeEndObject() } static void writeProperties(GraphQLImplementingType type, List<GraphQLNamedOutputType> interfaces, JsonGenerator generator) throws IOException { generator.writeFieldName('type') generator.writeString('object') List<GraphQLFieldDefinition> fieldDefinitions = type.getFieldDefinitions() if (!fieldDefinitions.isEmpty()) { generator.writeFieldName('properties') generator.writeStartObject() for (GraphQLFieldDefinition fieldDefinition : fieldDefinitions) { if (!inheritedField(fieldDefinition, interfaces)) { generator.writeFieldName(fieldDefinition.getName()) dumpGraphQLOutputType(fieldDefinition.getType(), generator) } } generator.writeEndObject() } } static boolean inheritedField(GraphQLFieldDefinition fieldDefinition, List<GraphQLNamedOutputType> interfaces) { for (GraphQLNamedOutputType type : interfaces) { if (type instanceof GraphQLInterfaceType) { GraphQLInterfaceType anInterface = (GraphQLInterfaceType) type if (anInterface.getFieldDefinition(fieldDefinition.getName()) != null || inheritedField(fieldDefinition, anInterface.getInterfaces())) { return true } } } return false } static void dumpGraphQLOutputType(GraphQLOutputType type, JsonGenerator generator) throws IOException { if (GraphQLTypeUtil.isNonNull(type)) { dumpGraphQLOutputTypeNullable(GraphQLTypeUtil.unwrapOneAs(type), generator) } else { generator.writeStartObject() generator.writeFieldName('oneOf') generator.writeStartArray() dumpGraphQLOutputTypeNullable(type, generator) generator.writeStartObject() generator.writeFieldName('type') generator.writeString('null') generator.writeEndObject() generator.writeEndArray() generator.writeEndObject() } } static void dumpGraphQLOutputTypeNullable(GraphQLOutputType type, JsonGenerator generator) throws IOException { if (GraphQLTypeUtil.isList(type)) { generator.writeStartObject() generator.writeFieldName('type') generator.writeString('array') generator.writeFieldName('items') dumpGraphQLOutputType(GraphQLTypeUtil.unwrapOneAs(type), generator) generator.writeEndObject() } else if (type instanceof GraphQLScalarType) { dumpGraphQLScalarType((GraphQLScalarType) type, generator) } else if (type instanceof GraphQLNamedOutputType) { writeRef((GraphQLNamedOutputType) type, generator) } else { generator.writeStartObject() generator.writeEndObject() } } static void dumpGraphQLScalarType(GraphQLScalarType type, JsonGenerator generator) throws IOException { generator.writeStartObject() generator.writeFieldName('type') String jsonType = null String format = null if (Scalars.GraphQLBoolean.equals(type)) { jsonType = 'boolean' } else if (Scalars.GraphQLFloat.equals(type)) { jsonType = 'number' format = 'float' } else if (Scalars.GraphQLID.equals(type)) { jsonType = 'string' } else if (Scalars.GraphQLInt.equals(type)) { jsonType = 'integer' format = 'int32' } else if (Scalars.GraphQLString.equals(type)) { jsonType = 'string' } else { switch (type.getName()) { case 'BigInt': jsonType = 'integer' break case 'Byte': jsonType = 'integer' format = 'int8' break case 'Date': jsonType = 'string' format = 'date' break case 'DateTime': jsonType = 'string' format = 'date-time' break case 'Duration': jsonType = 'string' format = 'duration' break case 'EmailAddress': jsonType = 'string' format = 'email' break case 'IPv4': jsonType = 'string' format = 'ipv4' break case 'IPv6': jsonType = 'string' format = 'ipv6' break case 'LocalDate': jsonType = 'string' format = 'date' break case 'LocalEndTime': jsonType = 'string' format = 'time' break case 'LocalTime': jsonType = 'string' format = 'time' break case 'NegativeFloat': jsonType = 'number' break case 'NegativeInt': jsonType = 'integer' break case 'NonNegativeFloat': jsonType = 'number' break case 'NonNegativeInt': jsonType = 'integer' break case 'NonPositiveFloat': jsonType = 'number' break case 'NonPositiveInt': jsonType = 'integer' break case 'Port': jsonType = 'integer' format = 'int16' break case 'PositiveFloat': jsonType = 'number' break case 'PositiveInt': jsonType = 'integer' break case 'RegularExpression': jsonType = 'string' format = 'regex' break case 'SafeInt': jsonType = 'integer' break case 'Time': jsonType = 'string' format = 'time' break case 'Timestamp': jsonType = 'integer' format = 'int64' break case 'URL': jsonType = 'string' format = 'uri' break case 'UUID': jsonType = 'string' format = 'uuid' break default: jsonType = 'string' break } } generator.writeString(jsonType) if (format != null) { generator.writeFieldName('format') generator.writeString(format) } generator.writeEndObject() } static void writeRef(GraphQLNamedOutputType type, JsonGenerator generator) throws IOException { generator.writeStartObject() generator.writeFieldName('$ref') generator.writeString('#/' + type.getName()) generator.writeEndObject() }
In the Method box, choose "convertToJsonSchema". On the Input tab, click File then provide the path to your GraphQL SDL document.
Next, chain a Write File tool to the Extension Tool's "Return Value" output. In the "Target name" field, type a file name for your JSON Schema document (schema.json). In the "Target directory" field, type "." to save the file in the same directory as your provisioning action.
Next, create a contrived service definition document to reference the JSON Schema document.
RAML example:
#%RAML 1.0 title: GraphQL /graphql: get: responses: 200: body: application/json: type: !include schema.json
OpenAPI example:
openapi: 3.0.0 info: version: 1.0.0 title: GraphQL paths: /graphql: get: responses: '200': content: application/json: schema: $ref: "schema.json#/GraphQLSchema"
In a virtual asset, create a Message Responder. On the Definition tab, configure the URL of your contrived service definition. The Response tab will have the Form JSON view automatically configured based on the JSON Schema.
Comments
-
What if you do not have access to the GraphQL SDL document? What if it is difficult to obtain? If the GraphQL endpoint has introspection enabled then you can generate a GraphQL SDL document from an introspection result.
See GraphQL SDL tips on the SOAtest forum for detail.
0