class Graphiti::SchemaDiff

Public Class Methods

new(old, new) click to toggle source
# File lib/graphiti/schema_diff.rb, line 3
def initialize(old, new)
  @old = JSON.parse(old.to_json).deep_symbolize_keys
  @new = JSON.parse(new.to_json).deep_symbolize_keys
  @errors = []
end

Public Instance Methods

compare() click to toggle source
# File lib/graphiti/schema_diff.rb, line 9
def compare
  compare_each if @old != @new
  @errors
end

Private Instance Methods

compare_attribute(resource_name, att_name, old_att, new_att, extra: false) click to toggle source
# File lib/graphiti/schema_diff.rb, line 290
def compare_attribute(resource_name, att_name, old_att, new_att, extra: false)
  prefix = extra ? "extra attribute" : "attribute"

  if old_att[:type] != new_att[:type]
    @errors << "#{resource_name}: #{prefix} #{att_name.inspect} changed type from #{old_att[:type].inspect} to #{new_att[:type].inspect}."
  end

  [:readable, :writable].each do |flag|
    if [true, "guarded"].include?(old_att[flag]) && new_att[flag] == false
      @errors << "#{resource_name}: #{prefix} #{att_name.inspect} changed flag #{flag.inspect} from #{old_att[flag].inspect} to #{new_att[flag].inspect}."
    end

    if new_att[flag] == "guarded" && old_att[flag] == true
      @errors << "#{resource_name}: #{prefix} #{att_name.inspect} changed flag #{flag.inspect} from #{old_att[flag].inspect} to #{new_att[flag].inspect}."
    end
  end
end
compare_attributes(old_resource, new_resource) click to toggle source
# File lib/graphiti/schema_diff.rb, line 52
def compare_attributes(old_resource, new_resource)
  old_resource[:attributes].each_pair do |name, old_att|
    unless (new_att = new_resource[:attributes][name])
      @errors << "#{old_resource[:name]}: attribute #{name.inspect} was removed."
      next
    end

    compare_attribute(old_resource[:name], name, old_att, new_att)
  end
end
compare_defaults(old_resource, new_resource) click to toggle source
# File lib/graphiti/schema_diff.rb, line 63
def compare_defaults(old_resource, new_resource)
  if new_resource[:default_sort] && !old_resource[:default_sort]
    @errors << "#{old_resource[:name]}: default sort added."
  end

  if old_resource[:default_sort] && !new_resource[:default_sort]
    @errors << "#{old_resource[:name]}: default sort removed."
  end

  if new_resource[:default_sort] && old_resource[:default_sort]
    if new_resource[:default_sort] != old_resource[:default_sort]
      @errors << "#{old_resource[:name]}: default sort changed from #{old_resource[:default_sort].inspect} to #{new_resource[:default_sort].inspect}."
    end
  end

  if new_resource[:default_page_size] && !old_resource[:default_page_size]
    @errors << "#{old_resource[:name]}: default page size added."
  end

  if old_resource[:default_page_size] && !new_resource[:default_page_size]
    @errors << "#{old_resource[:name]}: default page size removed."
  end

  if old_resource[:default_page_size] && new_resource[:default_page_size]
    if old_resource[:default_page_size] != new_resource[:default_page_size]
      @errors << "#{old_resource[:name]}: default page size changed from #{old_resource[:default_page_size]} to #{new_resource[:default_page_size]}."
    end
  end
end
compare_each() click to toggle source
# File lib/graphiti/schema_diff.rb, line 16
def compare_each
  compare_resources
  compare_endpoints
  compare_types
end
compare_endpoints() click to toggle source
# File lib/graphiti/schema_diff.rb, line 247
def compare_endpoints
  @old[:endpoints].each_pair do |path, old_endpoint|
    unless (new_endpoint = @new[:endpoints][path])
      @errors << "Endpoint \"#{path}\" was removed."
      next
    end

    old_endpoint[:actions].each_pair do |name, old_action|
      unless (new_action = new_endpoint[:actions][name])
        @errors << "Endpoint \"#{path}\" removed action #{name.inspect}."
        next
      end

      if new_action[:sideload_allowlist] && !old_action[:sideload_allowlist]
        @errors << "Endpoint \"#{path}\" added sideload allowlist."
      end

      if new_action[:sideload_allowlist]
        if new_action[:sideload_allowlist] != old_action[:sideload_allowlist]
          removal = Util::Hash.include_removed? \
            new_action[:sideload_allowlist], old_action[:sideload_allowlist]
          if removal
            @errors << "Endpoint \"#{path}\" had incompatible sideload allowlist. Was #{old_action[:sideload_allowlist].inspect}, now #{new_action[:sideload_allowlist].inspect}."
          end
        end
      end
    end
  end
end
compare_extra_attributes(old_resource, new_resource) click to toggle source
# File lib/graphiti/schema_diff.rb, line 115
def compare_extra_attributes(old_resource, new_resource)
  old_resource[:extra_attributes].each_pair do |name, old_att|
    unless (new_att = new_resource[:extra_attributes][name])
      @errors << "#{old_resource[:name]}: extra attribute #{name.inspect} was removed."
      next
    end

    compare_attribute(old_resource[:name], name, old_att, new_att, extra: true)
  end
end
compare_filter_group(old_resource, new_resource) click to toggle source
# File lib/graphiti/schema_diff.rb, line 209
def compare_filter_group(old_resource, new_resource)
  if new_resource[:filter_group]
    if old_resource[:filter_group]
      new_names = new_resource[:filter_group][:names]
      old_names = old_resource[:filter_group][:names]
      diff = new_names - old_names
      if !diff.empty? && new_resource[:filter_group][:required] == "all"
        @errors << "#{old_resource[:name]}: all required filter group #{old_names.map(&:to_sym).inspect} added #{"member".pluralize(diff.length)} #{diff.map(&:to_sym).inspect}."
      end

      old_required = old_resource[:filter_group][:required]
      new_required = new_resource[:filter_group][:required]
      if old_required == "any" && new_required == "all"
        @errors << "#{old_resource[:name]}: filter group #{old_names.map(&:to_sym).inspect} moved from required: :any to required: :all"
      end
    else
      @errors << "#{old_resource[:name]}: filter group #{new_resource[:filter_group][:names].map(&:to_sym).inspect} was added."
    end
  end
end
compare_filters(old_resource, new_resource) click to toggle source
# File lib/graphiti/schema_diff.rb, line 149
def compare_filters(old_resource, new_resource)
  old_resource[:filters].each_pair do |name, old_filter|
    unless (new_filter = new_resource[:filters][name])
      @errors << "#{old_resource[:name]}: filter #{name.inspect} was removed."
      next
    end

    if new_filter[:type] != old_filter[:type]
      @errors << "#{old_resource[:name]}: filter #{name.inspect} changed type from #{old_filter[:type].inspect} to #{new_filter[:type].inspect}."
      next
    end

    if new_filter[:single] && !old_filter[:single]
      @errors << "#{old_resource[:name]}: filter #{name.inspect} became singular."
    end

    if new_filter[:dependencies] && !old_filter[:dependencies]
      @errors << "#{old_resource[:name]}: filter #{name.inspect} added dependencies #{new_filter[:dependencies].inspect}."
    end

    if new_filter[:dependencies] && old_filter[:dependencies]
      if new_filter[:dependencies] != old_filter[:dependencies]
        @errors << "#{old_resource[:name]}: filter #{name.inspect} changed dependencies from #{old_filter[:dependencies].inspect} to #{new_filter[:dependencies].inspect}."
      end
    end

    if new_filter[:allow] != old_filter[:allow]
      new = new_filter[:allow] || []
      old = old_filter[:allow] || []
      diff = new - old
      if diff.length > 0
        @errors << "#{old_resource[:name]}: filter #{name.inspect} allowlist went from #{old.inspect} to #{new.inspect}."
      end
    end

    if new_filter[:deny] != old_filter[:deny]
      new = new_filter[:deny] || []
      old = old_filter[:deny] || []
      diff = new - old
      if diff.length > 0
        @errors << "#{old_resource[:name]}: filter #{name.inspect} denylist went from #{old.inspect} to #{new.inspect}."
      end
    end

    if (diff = old_filter[:operators] - new_filter[:operators]).length > 0
      diff.each do |op|
        @errors << "#{old_resource[:name]}: filter #{name.inspect} removed operator #{op.inspect}."
      end
    end

    if new_filter[:required] && !old_filter[:required]
      @errors << "#{old_resource[:name]}: filter #{name.inspect} went from optional to required."
    end

    if new_filter[:guard] && !old_filter[:guard]
      @errors << "#{old_resource[:name]}: filter #{name.inspect} went from unguarded to guarded."
    end
  end
end
compare_relationships(old_resource, new_resource) click to toggle source
# File lib/graphiti/schema_diff.rb, line 93
def compare_relationships(old_resource, new_resource)
  old_resource[:relationships].each_pair do |name, old_rel|
    unless (new_rel = new_resource[:relationships][name])
      @errors << "#{old_resource[:name]}: relationship #{name.inspect} was removed."
      next
    end

    if new_rel[:single] && !old_rel[:single]
      @errors << "#{old_resource[:name]}: relationship #{name.inspect} became single: true."
      next
    end

    if new_rel[:resource] != old_rel[:resource]
      @errors << "#{old_resource[:name]}: relationship #{name.inspect} changed resource from #{old_rel[:resource]} to #{new_rel[:resource]}."
    end

    if new_rel[:type] != old_rel[:type]
      @errors << "#{old_resource[:name]}: relationship #{name.inspect} changed type from #{old_rel[:type].inspect} to #{new_rel[:type].inspect}."
    end
  end
end
compare_resource(old_resource, new_resource) { || ... } click to toggle source
# File lib/graphiti/schema_diff.rb, line 40
def compare_resource(old_resource, new_resource)
  unless new_resource
    @errors << "#{old_resource[:name]} was removed."
    return
  end

  if old_resource[:type] != new_resource[:type]
    @errors << "#{old_resource[:name]} changed type from #{old_resource[:type].inspect} to #{new_resource[:type].inspect}."
  end
  yield
end
compare_resources() click to toggle source
# File lib/graphiti/schema_diff.rb, line 22
def compare_resources
  @old[:resources].each_with_index do |r, index|
    new_resource = @new[:resources].find { |n| n[:name] == r[:name] }
    next if new_resource.try(:[], :remote) || r.try(:[], :remote)

    compare_resource(r, new_resource) do
      compare_attributes(r, new_resource)
      compare_defaults(r, new_resource)
      compare_extra_attributes(r, new_resource)
      compare_sorts(r, new_resource)
      compare_filters(r, new_resource)
      compare_filter_group(r, new_resource)
      compare_stats(r, new_resource)
      compare_relationships(r, new_resource)
    end
  end
end
compare_sorts(old_resource, new_resource) click to toggle source
# File lib/graphiti/schema_diff.rb, line 126
def compare_sorts(old_resource, new_resource)
  old_resource[:sorts].each_pair do |name, old_sort|
    unless (new_sort = new_resource[:sorts][name])
      @errors << "#{old_resource[:name]}: sort #{name.inspect} was removed."
      next
    end

    if new_sort[:guard] && !old_sort[:guard]
      @errors << "#{old_resource[:name]}: sort #{name.inspect} became guarded."
    end

    if new_sort[:only] && !old_sort[:only]
      @errors << "#{old_resource[:name]}: sort #{name.inspect} now limited to only #{new_sort[:only].to_sym.inspect}."
    end

    if new_sort[:only] && old_sort[:only]
      if new_sort[:only] != old_sort[:only]
        @errors << "#{old_resource[:name]}: sort #{name.inspect} was limited to only #{old_sort[:only].to_sym.inspect}, now limited to only #{new_sort[:only].to_sym.inspect}."
      end
    end
  end
end
compare_stats(old_resource, new_resource) click to toggle source
# File lib/graphiti/schema_diff.rb, line 230
def compare_stats(old_resource, new_resource)
  return unless old_resource.key?(:stats)

  old_resource[:stats].each_pair do |name, old_calculations|
    new_calculations = new_resource[:stats][name]
    if new_calculations
      old_calculations.each do |calc|
        unless new_calculations.include?(calc)
          @errors << "#{old_resource[:name]}: calculation #{calc.to_sym.inspect} was removed from stat #{name.inspect}."
        end
      end
    else
      @errors << "#{old_resource[:name]}: stat #{name.inspect} was removed."
    end
  end
end
compare_types() click to toggle source
# File lib/graphiti/schema_diff.rb, line 277
def compare_types
  @old[:types].each_pair do |name, old_type|
    unless (new_type = @new[:types][name])
      @errors << "Type #{name.inspect} was removed."
      next
    end

    if new_type[:kind] != old_type[:kind]
      @errors << "Type #{name.inspect} changed kind from #{old_type[:kind].inspect} to #{new_type[:kind].inspect}."
    end
  end
end