diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-01 15:09:17 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-01 15:09:17 +0300 |
commit | 7b2635a55d4e87431bae752bd44c6fd2d2657b03 (patch) | |
tree | 88182aabb51a167e10f6c3a6d404b2247613047f /spec/support/graphql | |
parent | a7704bf16a51a8c993215a69db17232e3f246b8e (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/support/graphql')
-rw-r--r-- | spec/support/graphql/arguments.rb | 70 | ||||
-rw-r--r-- | spec/support/graphql/field_inspection.rb | 35 | ||||
-rw-r--r-- | spec/support/graphql/field_selection.rb | 73 |
3 files changed, 178 insertions, 0 deletions
diff --git a/spec/support/graphql/arguments.rb b/spec/support/graphql/arguments.rb new file mode 100644 index 00000000000..d8c334c2ca4 --- /dev/null +++ b/spec/support/graphql/arguments.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Graphql + class Arguments + delegate :blank?, :empty?, to: :to_h + + def initialize(values) + @values = values.compact + end + + def to_h + @values + end + + def ==(other) + to_h == other&.to_h + end + + alias_method :eql, :== + + def to_s + return '' if empty? + + @values.map do |name, value| + value_str = as_graphql_literal(value) + + "#{GraphqlHelpers.fieldnamerize(name.to_s)}: #{value_str}" + end.join(", ") + end + + def as_graphql_literal(value) + self.class.as_graphql_literal(value) + end + + # Transform values to GraphQL literal arguments. + # Use symbol for Enum values + def self.as_graphql_literal(value) + case value + when ::Graphql::Arguments then "{#{value}}" + when Array then "[#{value.map { |v| as_graphql_literal(v) }.join(',')}]" + when Hash then "{#{new(value)}}" + when Integer, Float, Symbol then value.to_s + when String then "\"#{value.gsub(/"/, '\\"')}\"" + when nil then 'null' + when true then 'true' + when false then 'false' + else + value.to_graphql_value + end + rescue NoMethodError + raise ArgumentError, "Cannot represent #{value} as GraphQL literal" + end + + def merge(other) + self.class.new(@values.merge(other.to_h)) + end + + def +(other) + if blank? + other + elsif other.blank? + self + elsif other.is_a?(String) + [to_s, other].compact.join(', ') + else + merge(other) + end + end + end +end diff --git a/spec/support/graphql/field_inspection.rb b/spec/support/graphql/field_inspection.rb new file mode 100644 index 00000000000..f39ba751141 --- /dev/null +++ b/spec/support/graphql/field_inspection.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module Graphql + class FieldInspection + def initialize(field) + @field = field + end + + def nested_fields? + !scalar? && !enum? + end + + def scalar? + type.kind.scalar? + end + + def enum? + type.kind.enum? + end + + def type + @type ||= begin + field_type = @field.type.respond_to?(:to_graphql) ? @field.type.to_graphql : @field.type + + # The type could be nested. For example `[GraphQL::STRING_TYPE]`: + # - List + # - String! + # - String + field_type = field_type.of_type while field_type.respond_to?(:of_type) + + field_type + end + end + end +end diff --git a/spec/support/graphql/field_selection.rb b/spec/support/graphql/field_selection.rb new file mode 100644 index 00000000000..e2a3334acac --- /dev/null +++ b/spec/support/graphql/field_selection.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module Graphql + class FieldSelection + delegate :empty?, :blank?, :to_h, to: :selection + delegate :size, to: :paths + + attr_reader :selection + + def initialize(selection = {}) + @selection = selection.to_h + end + + def to_s + serialize_field_selection(selection) + end + + def paths + selection.flat_map do |field, subselection| + paths_in([field], subselection) + end + end + + private + + def paths_in(path, leaves) + return [path] if leaves.nil? + + leaves.to_a.flat_map do |k, v| + paths_in([k], v).map { |tail| path + tail } + end + end + + def serialize_field_selection(hash, level = 0) + indent = ' ' * level + + hash.map do |field, subselection| + if subselection.nil? + "#{indent}#{field}" + else + subfields = serialize_field_selection(subselection, level + 1) + "#{indent}#{field} {\n#{subfields}\n#{indent}}" + end + end.join("\n") + end + + NO_SKIP = ->(_name, _field) { false } + + def self.select_fields(type, skip = NO_SKIP, parent_types = Set.new, max_depth = 3) + return new if max_depth <= 0 + + new(type.fields.flat_map do |name, field| + next [] if skip[name, field] + + inspected = ::Graphql::FieldInspection.new(field) + singular_field_type = inspected.type + + # If field type is the same as parent type, then we're hitting into + # mutual dependency. Break it from infinite recursion + next [] if parent_types.include?(singular_field_type) + + if inspected.nested_fields? + subselection = select_fields(singular_field_type, skip, parent_types | [type], max_depth - 1) + next [] if subselection.empty? + + [[name, subselection.to_h]] + else + [[name, nil]] + end + end) + end + end +end |