Skip to content

Instantly share code, notes, and snippets.

@myronmarston
Created April 9, 2025 13:40
Show Gist options
  • Save myronmarston/4feeeaa5472096d3c1a09eea8d983046 to your computer and use it in GitHub Desktop.
Save myronmarston/4feeeaa5472096d3c1a09eea8d983046 to your computer and use it in GitHub Desktop.
Script that anonymizes a GraphQL schema
#!/usr/bin/env ruby
require "optparse"
require "graphql"
class SchemaAnonymizer
def initialize
@type_counter = 0
@field_counter = 0
@enum_value_counter = 0
@type_mapping = {}
@field_mapping = {}
@enum_value_mapping = {}
end
def anonymize_schema(schema_file, output_file)
# Read and parse the schema
schema_content = File.read(schema_file)
document = GraphQL.parse(schema_content)
# First pass: collect all type names and create mappings
document.definitions.each do |definition|
case definition
when GraphQL::Language::Nodes::ObjectTypeDefinition,
GraphQL::Language::Nodes::InterfaceTypeDefinition,
GraphQL::Language::Nodes::InputObjectTypeDefinition,
GraphQL::Language::Nodes::ScalarTypeDefinition,
GraphQL::Language::Nodes::UnionTypeDefinition,
GraphQL::Language::Nodes::EnumTypeDefinition
# Create mapping for the type name
get_type_name(definition.name)
end
end
# Process each definition
new_definitions = document.definitions.map do |definition|
case definition
when GraphQL::Language::Nodes::ObjectTypeDefinition,
GraphQL::Language::Nodes::InterfaceTypeDefinition,
GraphQL::Language::Nodes::InputObjectTypeDefinition,
GraphQL::Language::Nodes::ScalarTypeDefinition,
GraphQL::Language::Nodes::UnionTypeDefinition,
GraphQL::Language::Nodes::EnumTypeDefinition
anonymize_type_definition(definition)
else
nil # Skip other definitions (like directives)
end
end.compact
# Convert to string and write
output = new_definitions.map { |d| definition_to_string(d) }.join("\n\n")
File.write(output_file, output)
end
private
def definition_to_string(definition)
case definition
when GraphQL::Language::Nodes::ScalarTypeDefinition
"scalar #{definition.name}"
when GraphQL::Language::Nodes::UnionTypeDefinition
types = definition.types.map(&:name).join(" | ")
"union #{definition.name} = #{types}"
when GraphQL::Language::Nodes::EnumTypeDefinition
values = definition.values.map(&:name).map { |v| " #{v}" }.join("\n")
"enum #{definition.name} {\n#{values}\n}"
when GraphQL::Language::Nodes::InputObjectTypeDefinition
fields = definition.fields.map { |f| input_value_to_string(f, 2) }.join("\n")
"input #{definition.name} {\n#{fields}\n}"
else
interfaces = definition.interfaces&.any? ? " implements #{definition.interfaces.map(&:name).join(" & ")}" : ""
fields = definition.fields.map { |f| field_to_string(f, 2) }.join("\n")
"type #{definition.name}#{interfaces} {\n#{fields}\n}"
end
end
def field_to_string(field, indent)
indentation = " " * indent
args = if field.arguments&.any?
"(#{field.arguments.map { |a| input_value_to_string(a, 0) }.join(", ")})"
else
""
end
"#{indentation}#{field.name}#{args}: #{type_to_string(field.type)}"
end
def input_value_to_string(input_value, indent)
indentation = " " * indent
default = input_value.default_value ? " = #{input_value.default_value}" : ""
"#{indentation}#{input_value.name}: #{type_to_string(input_value.type)}#{default}"
end
def type_to_string(type)
case type
when GraphQL::Language::Nodes::NonNullType
"#{type_to_string(type.of_type)}!"
when GraphQL::Language::Nodes::ListType
"[#{type_to_string(type.of_type)}]"
else
type.name
end
end
def anonymize_type_definition(definition)
case definition
when GraphQL::Language::Nodes::EnumTypeDefinition
anonymize_enum_definition(definition)
when GraphQL::Language::Nodes::UnionTypeDefinition
anonymize_union_definition(definition)
when GraphQL::Language::Nodes::InputObjectTypeDefinition
anonymize_input_object_definition(definition)
when GraphQL::Language::Nodes::ScalarTypeDefinition
anonymize_scalar_definition(definition)
else
anonymize_regular_type_definition(definition)
end
end
def anonymize_regular_type_definition(definition)
# Don't rename the Query type
new_name = definition.name == "Query" ? "Query" : get_type_name(definition.name)
new_fields = definition.fields&.map do |field|
anonymize_field(field)
end
new_interfaces = definition.interfaces&.map do |interface|
GraphQL::Language::Nodes::TypeName.new(
name: get_type_name(interface.name)
)
end
definition.class.new(
name: new_name,
fields: new_fields,
interfaces: new_interfaces,
description: nil,
directives: [] # Remove all directives
)
end
def anonymize_scalar_definition(definition)
GraphQL::Language::Nodes::ScalarTypeDefinition.new(
name: get_type_name(definition.name),
description: nil,
directives: []
)
end
def anonymize_input_object_definition(definition)
new_name = get_type_name(definition.name)
new_fields = definition.fields&.map do |field|
anonymize_input_value(field)
end
GraphQL::Language::Nodes::InputObjectTypeDefinition.new(
name: new_name,
fields: new_fields,
description: nil,
directives: []
)
end
def anonymize_union_definition(definition)
new_types = definition.types.map do |type|
GraphQL::Language::Nodes::TypeName.new(
name: get_type_name(type.name)
)
end
GraphQL::Language::Nodes::UnionTypeDefinition.new(
name: get_type_name(definition.name),
types: new_types,
description: nil,
directives: []
)
end
def anonymize_enum_definition(definition)
new_values = definition.values.map do |value|
anonymize_enum_value(value)
end
GraphQL::Language::Nodes::EnumTypeDefinition.new(
name: get_type_name(definition.name),
values: new_values,
description: nil,
directives: []
)
end
def anonymize_field(field)
new_name = get_field_name(field.name)
new_arguments = if field.respond_to?(:arguments) && field.arguments
field.arguments.map { |arg| anonymize_input_value(arg) }
else
[]
end
GraphQL::Language::Nodes::FieldDefinition.new(
name: new_name,
arguments: new_arguments,
type: anonymize_type_reference(field.type),
description: nil,
directives: []
)
end
def anonymize_input_value(input_value)
GraphQL::Language::Nodes::InputValueDefinition.new(
name: get_field_name(input_value.name),
type: anonymize_type_reference(input_value.type),
default_value: input_value.default_value,
description: nil,
directives: []
)
end
def anonymize_enum_value(value)
new_name = get_enum_value_name(value.name)
GraphQL::Language::Nodes::EnumValueDefinition.new(
name: new_name,
description: nil,
directives: []
)
end
def anonymize_type_reference(type)
case type
when GraphQL::Language::Nodes::NonNullType
GraphQL::Language::Nodes::NonNullType.new(
of_type: anonymize_type_reference(type.of_type)
)
when GraphQL::Language::Nodes::ListType
GraphQL::Language::Nodes::ListType.new(
of_type: anonymize_type_reference(type.of_type)
)
when GraphQL::Language::Nodes::TypeName
GraphQL::Language::Nodes::TypeName.new(
name: get_type_name(type.name)
)
end
end
def get_type_name(original_name)
return original_name if original_name == "Query"
return @type_mapping[original_name] if @type_mapping.key?(original_name)
# Handle built-in scalar types
return original_name if %w[Int Float String Boolean ID].include?(original_name)
@type_counter += 1
@type_mapping[original_name] = "Type#{@type_counter}"
end
def get_field_name(original_name)
@field_mapping[original_name] ||= begin
@field_counter += 1
"field#{@field_counter}"
end
end
def get_enum_value_name(original_name)
@enum_value_mapping[original_name] ||= begin
@enum_value_counter += 1
"ENUM_VALUE#{@enum_value_counter}"
end
end
end
# Parse command line arguments
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: #{$PROGRAM_NAME} SCHEMA_FILE --out OUTPUT_FILE"
opts.on("--out FILE", "Output file for anonymized schema") do |file|
options[:output_file] = file
end
end.parse!
schema_file = ARGV[0]
if schema_file.nil? || options[:output_file].nil?
puts "Error: Both input schema file and output file (--out) are required"
exit 1
end
unless File.exist?(schema_file)
puts "Error: Schema file '#{schema_file}' does not exist"
exit 1
end
begin
anonymizer = SchemaAnonymizer.new
anonymizer.anonymize_schema(schema_file, options[:output_file])
puts "Schema anonymized successfully. Output written to: #{options[:output_file]}"
rescue => e
puts "Error: #{e.message}"
puts e.backtrace
exit 1
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment