Created
March 11, 2019 14:52
-
-
Save rmosolgo/3452d2bf98837381df25949498e78e79 to your computer and use it in GitHub Desktop.
A query printer for GraphQL-Ruby that redacts strings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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