lib/graphql/language/printer.rb
# frozen_string_literal: true
module GraphQL
module Language
class Printer
# Turn an arbitrary AST node back into a string.
#
# @example Turning a document into a query string
# document = GraphQL.parse(query_string)
# GraphQL::Language::Printer.new.print(document)
# # => "{ ... }"
#
#
# @example Building a custom printer
#
# class MyPrinter < GraphQL::Language::Printer
# def print_argument(arg)
# "#{arg.name}: <HIDDEN>"
# end
# end
#
# MyPrinter.new.print(document)
# # => "mutation { pay(creditCard: <HIDDEN>) { success } }"
#
#
# @param indent [String] Whitespace to add to the printed node
# @return [String] Valid GraphQL for `node`
def print(node, indent: "")
print_node(node, indent: indent)
end
protected
def print_document(document)
document.definitions.map { |d| print_node(d) }.join("\n\n")
end
def print_argument(argument)
"#{argument.name}: #{print_node(argument.value)}".dup
end
def print_directive(directive)
out = "@#{directive.name}".dup
if directive.arguments.any?
out << "(#{directive.arguments.map { |a| print_argument(a) }.join(", ")})"
end
out
end
def print_enum(enum)
"#{enum.name}".dup
end
def print_null_value
"null".dup
end
def print_field(field, indent: "")
out = "#{indent}".dup
out << "#{field.alias}: " if field.alias
out << "#{field.name}"
out << "(#{field.arguments.map { |a| print_argument(a) }.join(", ")})" if field.arguments.any?
out << print_directives(field.directives)
out << print_selections(field.selections, indent: indent)
out
end
def print_fragment_definition(fragment_def, indent: "")
out = "#{indent}fragment #{fragment_def.name}".dup
if fragment_def.type
out << " on #{print_node(fragment_def.type)}"
end
out << print_directives(fragment_def.directives)
out << print_selections(fragment_def.selections, indent: indent)
out
end
def print_fragment_spread(fragment_spread, indent: "")
out = "#{indent}...#{fragment_spread.name}".dup
out << print_directives(fragment_spread.directives)
out
end
def print_inline_fragment(inline_fragment, indent: "")
out = "#{indent}...".dup
if inline_fragment.type
out << " on #{print_node(inline_fragment.type)}"
end
out << print_directives(inline_fragment.directives)
out << print_selections(inline_fragment.selections, indent: indent)
out
end
def print_input_object(input_object)
"{#{input_object.arguments.map { |a| print_argument(a) }.join(", ")}}"
end
def print_list_type(list_type)
"[#{print_node(list_type.of_type)}]".dup
end
def print_non_null_type(non_null_type)
"#{print_node(non_null_type.of_type)}!".dup
end
def print_operation_definition(operation_definition, indent: "")
out = "#{indent}#{operation_definition.operation_type}".dup
out << " #{operation_definition.name}" if operation_definition.name
if operation_definition.variables.any?
out << "(#{operation_definition.variables.map { |v| print_variable_definition(v) }.join(", ")})"
end
out << print_directives(operation_definition.directives)
out << print_selections(operation_definition.selections, indent: indent)
out
end
def print_type_name(type_name)
"#{type_name.name}".dup
end
def print_variable_definition(variable_definition)
out = "$#{variable_definition.name}: #{print_node(variable_definition.type)}".dup
out << " = #{print_node(variable_definition.default_value)}" unless variable_definition.default_value.nil?
out
end
def print_variable_identifier(variable_identifier)
"$#{variable_identifier.name}".dup
end
def print_schema_definition(schema)
has_conventional_names = (schema.query.nil? || schema.query == 'Query') &&
(schema.mutation.nil? || schema.mutation == 'Mutation') &&
(schema.subscription.nil? || schema.subscription == 'Subscription')
if has_conventional_names && schema.directives.empty?
return
end
out = "schema".dup
if schema.directives.any?
schema.directives.each do |dir|
out << "\n "
out << print_node(dir)
end
if !has_conventional_names
out << "\n"
end
end
if !has_conventional_names
if schema.directives.empty?
out << " "
end
out << "{\n"
out << " query: #{schema.query}\n" if schema.query
out << " mutation: #{schema.mutation}\n" if schema.mutation
out << " subscription: #{schema.subscription}\n" if schema.subscription
out << "}"
end
out
end
def print_scalar_type_definition(scalar_type)
out = print_description(scalar_type)
out << "scalar #{scalar_type.name}"
out << print_directives(scalar_type.directives)
end
def print_object_type_definition(object_type)
out = print_description(object_type)
out << "type #{object_type.name}"
out << print_implements(object_type) unless object_type.interfaces.empty?
out << print_directives(object_type.directives)
out << print_field_definitions(object_type.fields)
end
def print_implements(type)
" implements #{type.interfaces.map(&:name).join(" & ")}"
end
def print_input_value_definition(input_value)
out = "#{input_value.name}: #{print_node(input_value.type)}".dup
out << " = #{print_node(input_value.default_value)}" unless input_value.default_value.nil?
out << print_directives(input_value.directives)
end
def print_arguments(arguments, indent: "")
if arguments.all?{ |arg| !arg.description }
return "(#{arguments.map{ |arg| print_input_value_definition(arg) }.join(", ")})"
end
out = "(\n".dup
out << arguments.map.with_index{ |arg, i|
"#{print_description(arg, indent: " " + indent, first_in_block: i == 0)} #{indent}"\
"#{print_input_value_definition(arg)}"
}.join("\n")
out << "\n#{indent})"
end
def print_field_definition(field)
out = field.name.dup
unless field.arguments.empty?
out << print_arguments(field.arguments, indent: " ")
end
out << ": #{print_node(field.type)}"
out << print_directives(field.directives)
end
def print_interface_type_definition(interface_type)
out = print_description(interface_type)
out << "interface #{interface_type.name}"
out << print_implements(interface_type) if interface_type.interfaces.any?
out << print_directives(interface_type.directives)
out << print_field_definitions(interface_type.fields)
end
def print_union_type_definition(union_type)
out = print_description(union_type)
out << "union #{union_type.name}"
out << print_directives(union_type.directives)
out << " = " + union_type.types.map(&:name).join(" | ")
end
def print_enum_type_definition(enum_type)
out = print_description(enum_type)
out << "enum #{enum_type.name}#{print_directives(enum_type.directives)} {\n"
enum_type.values.each.with_index do |value, i|
out << print_description(value, indent: ' ', first_in_block: i == 0)
out << print_enum_value_definition(value)
end
out << "}"
end
def print_enum_value_definition(enum_value)
out = " #{enum_value.name}".dup
out << print_directives(enum_value.directives)
out << "\n"
end
def print_input_object_type_definition(input_object_type)
out = print_description(input_object_type)
out << "input #{input_object_type.name}"
out << print_directives(input_object_type.directives)
if !input_object_type.fields.empty?
out << " {\n"
input_object_type.fields.each.with_index do |field, i|
out << print_description(field, indent: ' ', first_in_block: i == 0)
out << " #{print_input_value_definition(field)}\n"
end
out << "}"
end
out
end
def print_directive_definition(directive)
out = print_description(directive)
out << "directive @#{directive.name}"
if directive.arguments.any?
out << print_arguments(directive.arguments)
end
if directive.repeatable
out << " repeatable"
end
out << " on #{directive.locations.map(&:name).join(' | ')}"
end
def print_description(node, indent: "", first_in_block: true)
return ''.dup unless node.description
description = indent != '' && !first_in_block ? "\n".dup : "".dup
description << GraphQL::Language::BlockString.print(node.description, indent: indent)
end
def print_field_definitions(fields)
if fields.empty?
""
else
out = " {\n".dup
fields.each.with_index do |field, i|
out << print_description(field, indent: ' ', first_in_block: i == 0)
out << " #{print_field_definition(field)}\n"
end
out << "}"
end
end
def print_directives(directives)
if directives.any?
directives.map { |d| " #{print_directive(d)}" }.join
else
""
end
end
def print_selections(selections, indent: "")
if selections.any?
out = " {\n".dup
selections.each do |selection|
out << print_node(selection, indent: indent + " ") << "\n"
end
out << "#{indent}}"
else
""
end
end
def print_node(node, indent: "")
case node
when Nodes::Document
print_document(node)
when Nodes::Argument
print_argument(node)
when Nodes::Directive
print_directive(node)
when Nodes::Enum
print_enum(node)
when Nodes::NullValue
print_null_value
when Nodes::Field
print_field(node, indent: indent)
when Nodes::FragmentDefinition
print_fragment_definition(node, indent: indent)
when Nodes::FragmentSpread
print_fragment_spread(node, indent: indent)
when Nodes::InlineFragment
print_inline_fragment(node, indent: indent)
when Nodes::InputObject
print_input_object(node)
when Nodes::ListType
print_list_type(node)
when Nodes::NonNullType
print_non_null_type(node)
when Nodes::OperationDefinition
print_operation_definition(node, indent: indent)
when Nodes::TypeName
print_type_name(node)
when Nodes::VariableDefinition
print_variable_definition(node)
when Nodes::VariableIdentifier
print_variable_identifier(node)
when Nodes::SchemaDefinition
print_schema_definition(node)
when Nodes::ScalarTypeDefinition
print_scalar_type_definition(node)
when Nodes::ObjectTypeDefinition
print_object_type_definition(node)
when Nodes::InputValueDefinition
print_input_value_definition(node)
when Nodes::FieldDefinition
print_field_definition(node)
when Nodes::InterfaceTypeDefinition
print_interface_type_definition(node)
when Nodes::UnionTypeDefinition
print_union_type_definition(node)
when Nodes::EnumTypeDefinition
print_enum_type_definition(node)
when Nodes::EnumValueDefinition
print_enum_value_definition(node)
when Nodes::InputObjectTypeDefinition
print_input_object_type_definition(node)
when Nodes::DirectiveDefinition
print_directive_definition(node)
when FalseClass, Float, Integer, NilClass, String, TrueClass, Symbol
GraphQL::Language.serialize(node)
when Array
"[#{node.map { |v| print_node(v) }.join(", ")}]".dup
when Hash
"{#{node.map { |k, v| "#{k}: #{print_node(v)}" }.join(", ")}}".dup
else
GraphQL::Language.serialize(node.to_s)
end
end
private
attr_reader :node
end
end
end