Skip to content

Instantly share code, notes, and snippets.

@rmosolgo
Created March 11, 2019 14:52
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rmosolgo/3452d2bf98837381df25949498e78e79 to your computer and use it in GitHub Desktop.
Save rmosolgo/3452d2bf98837381df25949498e78e79 to your computer and use it in GitHub Desktop.
A query printer for GraphQL-Ruby that redacts strings
# frozen_string_literal: true
# QueryPrinter is a custom GraphQL Ruby
# printer used to print sanitized queries. It inlines provided variables
# within the query for facilitate logging and analysis of queries.
# The printer assumes the query is valid.
#
# Since the GraphQL Ruby AST for a GraphQL query doesnt contain any reference
# on the type of fields or arguments, we have to track the current object, field
# and input type while printing the query.
#
# @example Printing a scrubbed string
# printer = QueryPrinter.new(query)
# puts printer.scrubbed
#
class QueryPrinter < GraphQL::Language::Printer
class InvalidQueryError < ArgumentError; end
REDACTED = "<REDACTED>"
def initialize(query)
@query = query
@current_type = nil
@current_field = nil
@current_input_type = nil
end
# @return [String] A scrubbed query string
def scrubbed
raise InvalidQueryError, "Printing requires a valid GraphQL query." unless query.valid? # rubocop:disable GitHub/UsePlatformErrors
print(query.document)
end
def print_node(node, indent: "")
# Replace any strings that aren't IDs or Enum values with REDACTED
if node.is_a?(String) && !(type.kind.enum? || type == GraphQL::ID_TYPE)
GraphQL::Language.serialize(REDACTED)
elsif node.is_a?(Array)
old_input_type = @current_input_type
if @current_input_type && @current_input_type.list?
@current_input_type = @current_input_type.of_type
@current_input_type = @current_input_type.of_type if @current_input_type.non_null?
end
res = super
@current_input_type = old_input_type
res
else
super
end
end
def print_argument(argument)
arg_def = if @current_input_type
@current_input_type.arguments[argument.name]
elsif @current_directive
@current_directive.arguments[argument.name]
else
@current_field.arguments[argument.name]
end
old_input_type = @current_input_type
@current_input_type = arg_def.type.non_null? ? arg_def.type.of_type : arg_def.type
res = super
@current_input_type = old_input_type
res
end
def print_list_type(list_type)
old_input_type = @current_input_type
@current_input_type = old_input_type.of_type
res = super
@current_input_type = old_input_type
res
end
def print_variable_identifier(variable_id)
variable_value = query.provided_variables.with_indifferent_access[variable_id.name]
print_node(VariableInliner.new(variable_value, @current_input_type).to_ast)
end
def print_field(field, indent: "")
@current_field = query.schema.get_field(@current_type, field.name)
old_type = @current_type
@current_type = @current_field.type.unwrap
res = super
@current_type = old_type
res
end
def print_input_object(input_object)
printed_arguments = input_object.arguments.map do |a|
print_argument(a)
end
"{#{printed_arguments.join(", ")}}"
end
def print_inline_fragment(inline_fragment, indent: "")
old_type = @current_type
if inline_fragment.type
@current_type = query.schema.types[inline_fragment.type.name]
end
res = super
@current_type = old_type
res
end
def print_fragment_definition(fragment_def, indent: "")
old_type = @current_type
@current_type = query.schema.types[fragment_def.type.name]
res = super
@current_type = old_type
res
end
def print_directive(directive)
@current_directive = query.schema.directives[directive.name]
res = super
@current_directive = nil
res
end
# Print the operation definition but do not include the variable
# definitions since we will inline them within the query
def print_operation_definition(operation_definition, indent: "")
old_type = @current_type
@current_type = query.schema.public_send(operation_definition.operation_type)
out = "#{indent}#{operation_definition.operation_type}".dup
out << " #{operation_definition.name}" if operation_definition.name
out << print_directives(operation_definition.directives)
out << print_selections(operation_definition.selections, indent: indent)
@current_type = old_type
out
end
private
attr_reader :query
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment