Skip to content

Commit

Permalink
Merge branch 'github-community-projects:master' into fix_normalize_er…
Browse files Browse the repository at this point in the history
…ror_paths
  • Loading branch information
wuarmin authored Jan 25, 2024
2 parents f9ab00b + 9555929 commit 82d23ac
Show file tree
Hide file tree
Showing 35 changed files with 695 additions and 394 deletions.
26 changes: 14 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,26 @@ jobs:
fail-fast: false
matrix:
ruby_version:
- 2.5.x
- 2.6.x
- '2.7'
- '3.0'
- '3.1'
- '3.2'
graphql_version:
- 1.8.11
- 1.9.12
- "~> 2.1.0"
rails_version:
- "~> 5.2.0"
- "~> 6.0.0"
- "~> 6.1.0"
- "~> 7.0.0"
- "~> 7.1.0"
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Ruby ${{ matrix.ruby_version }}
uses: actions/setup-ruby@v1
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby_version }}
- name: Build and test
run: |
gem install bundler
bundle install --jobs 4 --retry 3
bundle exec rake test
env:
Expand All @@ -37,13 +40,12 @@ jobs:
name: Rubocop
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Ruby
uses: actions/setup-ruby@v1
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.6.x
ruby-version: 3.2
- name: Build and test
run: |
gem install bundler
bundle install --jobs 4 --retry 3
bundle exec rake rubocop
bundle exec rake rubocop
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
Gemfile.lock
pkg
.ruby-version
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require:
- ./lib/rubocop/cop/graphql/heredoc

AllCops:
TargetRubyVersion: 2.3
TargetRubyVersion: 2.7

GraphQL/Heredoc:
Enabled: true
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ graphql_version = ENV["GRAPHQL_VERSION"] == "edge" ? { github: "rmosolgo/graphql
gem "graphql", graphql_version

group :development, :test do
gem "rubocop", "~> 0.62.0"
gem "debug", ">= 1.0.0"
end
10 changes: 5 additions & 5 deletions graphql-client.gemspec
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# frozen_string_literal: true
Gem::Specification.new do |s|
s.name = "graphql-client"
s.version = "0.16.0"
s.version = "0.19.0"
s.summary = "GraphQL Client"
s.description = "A Ruby library for declaring, composing and executing GraphQL queries"
s.homepage = "https://github.com/github/graphql-client"
Expand All @@ -10,15 +10,15 @@ Gem::Specification.new do |s|
s.files = Dir["README.md", "LICENSE", "lib/**/*.rb"]

s.add_dependency "activesupport", ">= 3.0"
s.add_dependency "graphql", "~> 1.8"
s.add_dependency "graphql"

s.add_development_dependency "actionpack", ">= 3.2.22"
s.add_development_dependency "erubi", "~> 1.6"
s.add_development_dependency "erubis", "~> 2.7"
s.add_development_dependency "minitest", "~> 5.9"
s.add_development_dependency "rake", "~> 11.2"
s.add_development_dependency "rubocop-github", "~> 0.10"
s.add_development_dependency "rubocop", "~> 0.55"
s.add_development_dependency "rake", "~> 13.1.0"
s.add_development_dependency "rubocop-github"
s.add_development_dependency "rubocop", "~> 1.57.0"

s.required_ruby_version = ">= 2.1.0"

Expand Down
97 changes: 49 additions & 48 deletions lib/graphql/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require "active_support/inflector"
require "active_support/notifications"
require "graphql"
require "graphql/client/type_stack"
require "graphql/client/collocated_enforcement"
require "graphql/client/definition_variables"
require "graphql/client/definition"
Expand Down Expand Up @@ -49,12 +50,12 @@ def self.load_schema(schema)
when GraphQL::Schema, Class
schema
when Hash
GraphQL::Schema::Loader.load(schema)
GraphQL::Schema.from_introspection(schema)
when String
if schema.end_with?(".json") && File.exist?(schema)
load_schema(File.read(schema))
elsif schema =~ /\A\s*{/
load_schema(JSON.parse(schema))
load_schema(JSON.parse(schema, freeze: true))
end
else
if schema.respond_to?(:execute)
Expand Down Expand Up @@ -97,10 +98,31 @@ def initialize(schema:, execute: nil, enforce_collocated_callers: false)
@document_tracking_enabled = false
@allow_dynamic_queries = false
@enforce_collocated_callers = enforce_collocated_callers

if schema.is_a?(Class)
@possible_types = schema.possible_types
end
@types = Schema.generate(@schema)
end

# A cache of the schema's merged possible types
# @param type_condition [Class, String] a type definition or type name
def possible_types(type_condition = nil)
if type_condition
if defined?(@possible_types)
if type_condition.respond_to?(:graphql_name)
type_condition = type_condition.graphql_name
end
@possible_types[type_condition]
else
@schema.possible_types(type_condition)
end
elsif defined?(@possible_types)
@possible_types
else
@schema.possible_types(type_condition)
end
end

def parse(str, filename = nil, lineno = nil)
if filename.nil? && lineno.nil?
location = caller_locations(1, 1).first
Expand Down Expand Up @@ -169,12 +191,8 @@ def parse(str, filename = nil, lineno = nil)

doc.definitions.each do |node|
if node.name.nil?
if node.respond_to?(:merge) # GraphQL 1.9 +
node_with_name = node.merge(name: "__anonymous__")
doc = doc.replace_child(node, node_with_name)
else
node.name = "__anonymous__"
end
node_with_name = node.merge(name: "__anonymous__")
doc = doc.replace_child(node, node_with_name)
end
end

Expand All @@ -198,24 +216,11 @@ def parse(str, filename = nil, lineno = nil)

definitions = sliced_definitions(document_dependencies, doc, source_location: source_location)

if @document.respond_to?(:merge) # GraphQL 1.9+
visitor = RenameNodeVisitor.new(document_dependencies, definitions: definitions)
visitor.visit
else
name_hook = RenameNodeHook.new(definitions)
visitor = Language::Visitor.new(document_dependencies)
visitor[Language::Nodes::FragmentDefinition].leave << name_hook.method(:rename_node)
visitor[Language::Nodes::OperationDefinition].leave << name_hook.method(:rename_node)
visitor[Language::Nodes::FragmentSpread].leave << name_hook.method(:rename_node)
visitor.visit
end
visitor = RenameNodeVisitor.new(document_dependencies, definitions: definitions)
visitor.visit

if document_tracking_enabled
if @document.respond_to?(:merge) # GraphQL 1.9+
@document = @document.merge(definitions: @document.definitions + doc.definitions)
else
@document.definitions.concat(doc.definitions)
end
@document = @document.merge(definitions: @document.definitions + doc.definitions)
end

if definitions["__anonymous__"]
Expand Down Expand Up @@ -261,27 +266,9 @@ def rename_node(node)
end
end

class RenameNodeHook
def initialize(definitions)
@definitions = definitions
end

def rename_node(node, _parent)
definition = @definitions[node.name]
if definition
node.extend(LazyName)
node._definition = definition
end
end
end

# Public: A wrapper to use the more-efficient `.get_type` when it's available from GraphQL-Ruby (1.10+)
def get_type(type_name)
if @schema.respond_to?(:get_type)
@schema.get_type(type_name)
else
@schema.types[type_name]
end
@schema.get_type(type_name)
end

# Public: Create operation definition from a fragment definition.
Expand Down Expand Up @@ -439,12 +426,26 @@ def sliced_definitions(document_dependencies, doc, source_location:)
end.to_h
end

class GatherNamesVisitor < GraphQL::Language::Visitor
def initialize(node)
@names = []
super
end

attr_reader :names

def on_fragment_spread(node, parent)
@names << node.name
super
end
end

def find_definition_dependencies(node)
names = []
visitor = Language::Visitor.new(node)
visitor[Language::Nodes::FragmentSpread] << -> (node, parent) { names << node.name }
visitor = GatherNamesVisitor.new(node)
visitor.visit
names.uniq
names = visitor.names
names.uniq!
names
end

def deep_freeze_json_object(obj)
Expand Down
35 changes: 21 additions & 14 deletions lib/graphql/client/collocated_enforcement.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class NonCollocatedCallerError < Error; end

# Enforcements collocated object access best practices.
module CollocatedEnforcement
extend self

# Public: Ignore collocated caller enforcement for the scope of the block.
def allow_noncollocated_callers
Thread.current[:query_result_caller_location_ignore] = true
Expand All @@ -20,6 +22,23 @@ def allow_noncollocated_callers
Thread.current[:query_result_caller_location_ignore] = nil
end

def verify_collocated_path(location, path, method = "method")
return yield if Thread.current[:query_result_caller_location_ignore]

if (location.path != path) && !(WHITELISTED_GEM_NAMES.any? { |g| location.path.include?("gems/#{g}") })
error = NonCollocatedCallerError.new("#{method} was called outside of '#{path}' https://git.io/v1syX")
error.set_backtrace(caller(2))
raise error
end

begin
Thread.current[:query_result_caller_location_ignore] = true
yield
ensure
Thread.current[:query_result_caller_location_ignore] = nil
end
end

# Internal: Decorate method with collocated caller enforcement.
#
# mod - Target Module/Class
Expand All @@ -31,21 +50,9 @@ def enforce_collocated_callers(mod, methods, path)
mod.prepend(Module.new do
methods.each do |method|
define_method(method) do |*args, &block|
return super(*args, &block) if Thread.current[:query_result_caller_location_ignore]

locations = caller_locations(1, 1)

if (locations.first.path != path) && !(caller_locations.any? { |cl| WHITELISTED_GEM_NAMES.any? { |g| cl.path.include?("gems/#{g}") } })
error = NonCollocatedCallerError.new("#{method} was called outside of '#{path}' https://git.io/v1syX")
error.set_backtrace(caller(1))
raise error
end

begin
Thread.current[:query_result_caller_location_ignore] = true
location = caller_locations(1, 1)[0]
CollocatedEnforcement.verify_collocated_path(location, path, method) do
super(*args, &block)
ensure
Thread.current[:query_result_caller_location_ignore] = nil
end
end
end
Expand Down
Loading

0 comments on commit 82d23ac

Please sign in to comment.