pax_global_header 0000666 0000000 0000000 00000000064 14000335401 0014500 g ustar 00root root 0000000 0000000 52 comment=9100891e180f418697b971152d2d39bbf5584666
ruby-graphql-client-0.16.0/ 0000775 0000000 0000000 00000000000 14000335401 0015455 5 ustar 00root root 0000000 0000000 ruby-graphql-client-0.16.0/LICENSE 0000664 0000000 0000000 00000002040 14000335401 0016456 0 ustar 00root root 0000000 0000000 Copyright (c) 2016 GitHub, Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
ruby-graphql-client-0.16.0/README.md 0000664 0000000 0000000 00000012411 14000335401 0016733 0 ustar 00root root 0000000 0000000 # graphql-client [](https://badge.fury.io/rb/graphql-client) [](https://travis-ci.org/github/graphql-client)
GraphQL Client is a Ruby library for declaring, composing and executing GraphQL queries.
## Usage
### Installation
Add `graphql-client` to your Gemfile and then run `bundle install`.
```ruby
# Gemfile
gem 'graphql-client'
```
### Configuration
Sample configuration for a GraphQL Client to query from the [SWAPI GraphQL Wrapper](https://github.com/graphql/swapi-graphql).
```ruby
require "graphql/client"
require "graphql/client/http"
# Star Wars API example wrapper
module SWAPI
# Configure GraphQL endpoint using the basic HTTP network adapter.
HTTP = GraphQL::Client::HTTP.new("https://example.com/graphql") do
def headers(context)
# Optionally set any HTTP headers
{ "User-Agent": "My Client" }
end
end
# Fetch latest schema on init, this will make a network request
Schema = GraphQL::Client.load_schema(HTTP)
# However, it's smart to dump this to a JSON file and load from disk
#
# Run it from a script or rake task
# GraphQL::Client.dump_schema(SWAPI::HTTP, "path/to/schema.json")
#
# Schema = GraphQL::Client.load_schema("path/to/schema.json")
Client = GraphQL::Client.new(schema: Schema, execute: HTTP)
end
```
### Defining Queries
If you haven't already, [familiarize yourself with the GraphQL query syntax](http://graphql.org/docs/queries/). Queries are declared with the same syntax inside of a `<<-'GRAPHQL'` heredoc. There isn't any special query builder Ruby DSL.
This client library encourages all GraphQL queries to be declared statically and assigned to a Ruby constant.
```ruby
HeroNameQuery = SWAPI::Client.parse <<-'GRAPHQL'
query {
hero {
name
}
}
GRAPHQL
```
Queries can reference variables that are passed in at query execution time.
```ruby
HeroFromEpisodeQuery = SWAPI::Client.parse <<-'GRAPHQL'
query($episode: Episode) {
hero(episode: $episode) {
name
}
}
GRAPHQL
```
Fragments are declared similarly.
```ruby
HumanFragment = SWAPI::Client.parse <<-'GRAPHQL'
fragment on Human {
name
homePlanet
}
GRAPHQL
```
To include a fragment in a query, reference the fragment by constant.
```ruby
HeroNameQuery = SWAPI::Client.parse <<-'GRAPHQL'
{
luke: human(id: "1000") {
...HumanFragment
}
leia: human(id: "1003") {
...HumanFragment
}
}
GRAPHQL
```
This works for namespaced constants.
```ruby
module Hero
Query = SWAPI::Client.parse <<-'GRAPHQL'
{
luke: human(id: "1000") {
...Human::Fragment
}
leia: human(id: "1003") {
...Human::Fragment
}
}
GRAPHQL
end
```
`::` is invalid in regular GraphQL syntax, but `#parse` makes an initial pass on the query string and resolves all the fragment spreads with [`constantize`](http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-constantize).
### Executing queries
Pass the reference of a parsed query definition to `GraphQL::Client#query`. Data is returned back in a wrapped `GraphQL::Client::Schema::ObjectType` struct that provides Ruby-ish accessors.
```ruby
result = SWAPI::Client.query(Hero::Query)
# The raw data is Hash of JSON values
# result["data"]["luke"]["homePlanet"]
# The wrapped result allows to you access data with Ruby methods
result.data.luke.home_planet
```
`GraphQL::Client#query` also accepts variables and context parameters that can be leveraged by the underlying network executor.
```ruby
result = SWAPI::Client.query(Hero::HeroFromEpisodeQuery, variables: {episode: "JEDI"}, context: {user_id: current_user_id})
```
### Rails ERB integration
If you're using Ruby on Rails ERB templates, theres a ERB extension that allows static queries to be defined in the template itself.
In standard Ruby you can simply assign queries and fragments to constants and they'll be available throughout the app. However, the contents of an ERB template is compiled into a Ruby method, and methods can't assign constants. So a new ERB tag was extended to declare static sections that include a GraphQL query.
```erb
<%# app/views/humans/human.html.erb %>
<%graphql
fragment HumanFragment on Human {
name
homePlanet
}
%>
<%= human.name %> lives on <%= human.home_planet %>.
```
These `<%graphql` sections are simply ignored at runtime but make their definitions available through constants. The module namespacing is derived from the `.erb`'s path plus the definition name.
```
>> "views/humans/human".camelize
=> "Views::Humans::Human"
>> Views::Humans::Human::HumanFragment
=> #
```
## Examples
[github/github-graphql-rails-example](https://github.com/github/github-graphql-rails-example) is an example application using this library to implement views on the GitHub GraphQL API.
## Installation
Add `graphql-client` to your app's Gemfile:
```ruby
gem 'graphql-client'
```
## See Also
* [graphql-ruby](https://github.com/rmosolgo/graphql-ruby) gem which implements 80% of what this library provides. ❤️ [@rmosolgo](https://github.com/rmosolgo)
* [Facebook's GraphQL homepage](http://graphql.org/)
* [Facebook's Relay homepage](https://facebook.github.io/relay/)
ruby-graphql-client-0.16.0/graphql-client.gemspec 0000664 0000000 0000000 00000007407 14000335401 0021744 0 ustar 00root root 0000000 0000000 #########################################################
# This file has been automatically generated by gem2tgz #
#########################################################
# -*- encoding: utf-8 -*-
# stub: graphql-client 0.16.0 ruby lib
Gem::Specification.new do |s|
s.name = "graphql-client".freeze
s.version = "0.16.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
s.require_paths = ["lib".freeze]
s.authors = ["GitHub".freeze]
s.date = "2019-10-09"
s.description = "A Ruby library for declaring, composing and executing GraphQL queries".freeze
s.email = "engineering@github.com".freeze
s.files = ["LICENSE".freeze, "README.md".freeze, "lib/graphql/client.rb".freeze, "lib/graphql/client/collocated_enforcement.rb".freeze, "lib/graphql/client/definition.rb".freeze, "lib/graphql/client/definition_variables.rb".freeze, "lib/graphql/client/document_types.rb".freeze, "lib/graphql/client/erb.rb".freeze, "lib/graphql/client/error.rb".freeze, "lib/graphql/client/errors.rb".freeze, "lib/graphql/client/erubi_enhancer.rb".freeze, "lib/graphql/client/erubis.rb".freeze, "lib/graphql/client/erubis_enhancer.rb".freeze, "lib/graphql/client/fragment_definition.rb".freeze, "lib/graphql/client/hash_with_indifferent_access.rb".freeze, "lib/graphql/client/http.rb".freeze, "lib/graphql/client/list.rb".freeze, "lib/graphql/client/log_subscriber.rb".freeze, "lib/graphql/client/operation_definition.rb".freeze, "lib/graphql/client/query_typename.rb".freeze, "lib/graphql/client/railtie.rb".freeze, "lib/graphql/client/response.rb".freeze, "lib/graphql/client/schema.rb".freeze, "lib/graphql/client/schema/base_type.rb".freeze, "lib/graphql/client/schema/enum_type.rb".freeze, "lib/graphql/client/schema/include_directive.rb".freeze, "lib/graphql/client/schema/interface_type.rb".freeze, "lib/graphql/client/schema/list_type.rb".freeze, "lib/graphql/client/schema/non_null_type.rb".freeze, "lib/graphql/client/schema/object_type.rb".freeze, "lib/graphql/client/schema/possible_types.rb".freeze, "lib/graphql/client/schema/scalar_type.rb".freeze, "lib/graphql/client/schema/skip_directive.rb".freeze, "lib/graphql/client/schema/union_type.rb".freeze, "lib/graphql/client/view_module.rb".freeze, "lib/rubocop/cop/graphql/heredoc.rb".freeze, "lib/rubocop/cop/graphql/overfetch.rb".freeze]
s.homepage = "https://github.com/github/graphql-client".freeze
s.licenses = ["MIT".freeze]
s.required_ruby_version = Gem::Requirement.new(">= 2.1.0".freeze)
s.rubygems_version = "3.2.0.rc.2".freeze
s.summary = "GraphQL Client".freeze
if s.respond_to? :specification_version then
s.specification_version = 4
end
if s.respond_to? :add_runtime_dependency then
s.add_development_dependency(%q.freeze, [">= 3.2.22"])
s.add_runtime_dependency(%q.freeze, [">= 3.0"])
s.add_development_dependency(%q.freeze, ["~> 1.6"])
s.add_development_dependency(%q.freeze, ["~> 2.7"])
s.add_runtime_dependency(%q.freeze, ["~> 1.8"])
s.add_development_dependency(%q.freeze, ["~> 5.9"])
s.add_development_dependency(%q.freeze, ["~> 11.2"])
s.add_development_dependency(%q.freeze, ["~> 0.55"])
s.add_development_dependency(%q.freeze, ["~> 0.10"])
else
s.add_dependency(%q.freeze, [">= 3.2.22"])
s.add_dependency(%q.freeze, [">= 3.0"])
s.add_dependency(%q.freeze, ["~> 1.6"])
s.add_dependency(%q.freeze, ["~> 2.7"])
s.add_dependency(%q.freeze, ["~> 1.8"])
s.add_dependency(%q.freeze, ["~> 5.9"])
s.add_dependency(%q.freeze, ["~> 11.2"])
s.add_dependency(%q.freeze, ["~> 0.55"])
s.add_dependency(%q.freeze, ["~> 0.10"])
end
end
ruby-graphql-client-0.16.0/lib/ 0000775 0000000 0000000 00000000000 14000335401 0016223 5 ustar 00root root 0000000 0000000 ruby-graphql-client-0.16.0/lib/graphql/ 0000775 0000000 0000000 00000000000 14000335401 0017661 5 ustar 00root root 0000000 0000000 ruby-graphql-client-0.16.0/lib/graphql/client.rb 0000664 0000000 0000000 00000034165 14000335401 0021475 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "active_support/inflector"
require "active_support/notifications"
require "graphql"
require "graphql/client/collocated_enforcement"
require "graphql/client/definition_variables"
require "graphql/client/definition"
require "graphql/client/error"
require "graphql/client/errors"
require "graphql/client/fragment_definition"
require "graphql/client/operation_definition"
require "graphql/client/query_typename"
require "graphql/client/response"
require "graphql/client/schema"
require "json"
require "delegate"
module GraphQL
# GraphQL Client helps build and execute queries against a GraphQL backend.
#
# A client instance SHOULD be configured with a schema to enable query
# validation. And SHOULD also be configured with a backend "execute" adapter
# to point at a remote GraphQL HTTP service or execute directly against a
# Schema object.
class Client
class DynamicQueryError < Error; end
class NotImplementedError < Error; end
class ValidationError < Error; end
extend CollocatedEnforcement
attr_reader :schema, :execute
attr_reader :types
attr_accessor :document_tracking_enabled
# Public: Check if collocated caller enforcement is enabled.
attr_reader :enforce_collocated_callers
# Deprecated: Allow dynamically generated queries to be passed to
# Client#query.
#
# This ability will eventually be removed in future versions.
attr_accessor :allow_dynamic_queries
def self.load_schema(schema)
case schema
when GraphQL::Schema, Class
schema
when Hash
GraphQL::Schema::Loader.load(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))
end
else
if schema.respond_to?(:execute)
load_schema(dump_schema(schema))
elsif schema.respond_to?(:to_h)
load_schema(schema.to_h)
else
nil
end
end
end
IntrospectionDocument = GraphQL.parse(GraphQL::Introspection::INTROSPECTION_QUERY)
def self.dump_schema(schema, io = nil, context: {})
unless schema.respond_to?(:execute)
raise TypeError, "expected schema to respond to #execute(), but was #{schema.class}"
end
result = schema.execute(
document: IntrospectionDocument,
operation_name: "IntrospectionQuery",
variables: {},
context: context
).to_h
if io
io = File.open(io, "w") if io.is_a?(String)
io.write(JSON.pretty_generate(result))
io.close_write
end
result
end
def initialize(schema:, execute: nil, enforce_collocated_callers: false)
@schema = self.class.load_schema(schema)
@execute = execute
@document = GraphQL::Language::Nodes::Document.new(definitions: [])
@document_tracking_enabled = false
@allow_dynamic_queries = false
@enforce_collocated_callers = enforce_collocated_callers
@types = Schema.generate(@schema)
end
def parse(str, filename = nil, lineno = nil)
if filename.nil? && lineno.nil?
location = caller_locations(1, 1).first
filename = location.path
lineno = location.lineno
end
unless filename.is_a?(String)
raise TypeError, "expected filename to be a String, but was #{filename.class}"
end
unless lineno.is_a?(Integer)
raise TypeError, "expected lineno to be a Integer, but was #{lineno.class}"
end
source_location = [filename, lineno].freeze
definition_dependencies = Set.new
# Replace Ruby constant reference with GraphQL fragment names,
# while populating `definition_dependencies` with
# GraphQL Fragment ASTs which this operation depends on
str = str.gsub(/\.\.\.([a-zA-Z0-9_]+(::[a-zA-Z0-9_]+)*)/) do
match = Regexp.last_match
const_name = match[1]
if str.match(/fragment\s*#{const_name}/)
# It's a fragment _definition_, not a fragment usage
match[0]
else
# It's a fragment spread, so we should load the fragment
# which corresponds to the spread.
# We depend on ActiveSupport to either find the already-loaded
# constant, or to load the constant by name
begin
fragment = ActiveSupport::Inflector.constantize(const_name)
rescue NameError
fragment = nil
end
case fragment
when FragmentDefinition
# We found the fragment definition that this fragment spread belongs to.
# So, register the AST of this fragment in `definition_dependencies`
# and update the query string to valid GraphQL syntax,
# replacing the Ruby constant
definition_dependencies.merge(fragment.document.definitions)
"...#{fragment.definition_name}"
else
if fragment
message = "expected #{const_name} to be a #{FragmentDefinition}, but was a #{fragment.class}."
if fragment.is_a?(Module) && fragment.constants.any?
message += " Did you mean #{fragment}::#{fragment.constants.first}?"
end
else
message = "uninitialized constant #{const_name}"
end
error = ValidationError.new(message)
error.set_backtrace(["#{filename}:#{lineno + match.pre_match.count("\n") + 1}"] + caller)
raise error
end
end
end
doc = GraphQL.parse(str)
document_types = DocumentTypes.analyze_types(self.schema, doc).freeze
doc = QueryTypename.insert_typename_fields(doc, types: document_types)
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
end
end
document_dependencies = Language::Nodes::Document.new(definitions: doc.definitions + definition_dependencies.to_a)
rules = GraphQL::StaticValidation::ALL_RULES - [
GraphQL::StaticValidation::FragmentsAreUsed,
GraphQL::StaticValidation::FieldsHaveAppropriateSelections
]
validator = GraphQL::StaticValidation::Validator.new(schema: self.schema, rules: rules)
query = GraphQL::Query.new(self.schema, document: document_dependencies)
errors = validator.validate(query)
errors.fetch(:errors).each do |error|
error_hash = error.to_h
validation_line = error_hash["locations"][0]["line"]
error = ValidationError.new(error_hash["message"])
error.set_backtrace(["#{filename}:#{lineno + validation_line}"] + caller)
raise error
end
definitions = {}
doc.definitions.each do |node|
sliced_document = Language::DefinitionSlice.slice(document_dependencies, node.name)
definition = Definition.for(
client: self,
ast_node: node,
document: sliced_document,
source_document: doc,
source_location: source_location
)
definitions[node.name] = definition
end
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
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
end
if definitions["__anonymous__"]
definitions["__anonymous__"]
else
Module.new do
definitions.each do |name, definition|
const_set(name, definition)
end
end
end
end
class RenameNodeVisitor < GraphQL::Language::Visitor
def initialize(document, definitions:)
super(document)
@definitions = definitions
end
def on_fragment_definition(node, _parent)
rename_node(node)
super
end
def on_operation_definition(node, _parent)
rename_node(node)
super
end
def on_fragment_spread(node, _parent)
rename_node(node)
super
end
private
def rename_node(node)
definition = @definitions[node.name]
if definition
node.extend(LazyName)
node._definition = definition
end
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
end
# Public: Create operation definition from a fragment definition.
#
# Automatically determines operation variable set.
#
# Examples
#
# FooFragment = Client.parse <<-'GRAPHQL'
# fragment on Mutation {
# updateFoo(id: $id, content: $content)
# }
# GRAPHQL
#
# # mutation($id: ID!, $content: String!) {
# # updateFoo(id: $id, content: $content)
# # }
# FooMutation = Client.create_operation(FooFragment)
#
# fragment - A FragmentDefinition definition.
#
# Returns an OperationDefinition.
def create_operation(fragment, filename = nil, lineno = nil)
unless fragment.is_a?(GraphQL::Client::FragmentDefinition)
raise TypeError, "expected fragment to be a GraphQL::Client::FragmentDefinition, but was #{fragment.class}"
end
if filename.nil? && lineno.nil?
location = caller_locations(1, 1).first
filename = location.path
lineno = location.lineno
end
variables = GraphQL::Client::DefinitionVariables.operation_variables(self.schema, fragment.document, fragment.definition_name)
type_name = fragment.definition_node.type.name
if schema.query && type_name == schema.query.graphql_name
operation_type = "query"
elsif schema.mutation && type_name == schema.mutation.graphql_name
operation_type = "mutation"
elsif schema.subscription && type_name == schema.subscription.graphql_name
operation_type = "subscription"
else
types = [schema.query, schema.mutation, schema.subscription].compact
raise Error, "Fragment must be defined on #{types.map(&:graphql_name).join(", ")}"
end
doc_ast = GraphQL::Language::Nodes::Document.new(definitions: [
GraphQL::Language::Nodes::OperationDefinition.new(
operation_type: operation_type,
variables: variables,
selections: [
GraphQL::Language::Nodes::FragmentSpread.new(name: fragment.name)
]
)
])
parse(doc_ast.to_query_string, filename, lineno)
end
attr_reader :document
def query(definition, variables: {}, context: {})
raise NotImplementedError, "client network execution not configured" unless execute
unless definition.is_a?(OperationDefinition)
raise TypeError, "expected definition to be a #{OperationDefinition.name} but was #{document.class.name}"
end
if allow_dynamic_queries == false && definition.name.nil?
raise DynamicQueryError, "expected definition to be assigned to a static constant https://git.io/vXXSE"
end
variables = deep_stringify_keys(variables)
document = definition.document
operation = definition.definition_node
payload = {
document: document,
operation_name: operation.name,
operation_type: operation.operation_type,
variables: variables,
context: context
}
result = ActiveSupport::Notifications.instrument("query.graphql", payload) do
execute.execute(
document: document,
operation_name: operation.name,
variables: variables,
context: context
)
end
deep_freeze_json_object(result)
data, errors, extensions = result.values_at("data", "errors", "extensions")
errors ||= []
errors = errors.map(&:dup)
GraphQL::Client::Errors.normalize_error_paths(data, errors)
errors.each do |error|
error_payload = payload.merge(message: error["message"], error: error)
ActiveSupport::Notifications.instrument("error.graphql", error_payload)
end
Response.new(
result,
data: definition.new(data, Errors.new(errors, ["data"])),
errors: Errors.new(errors),
extensions: extensions
)
end
# Internal: FragmentSpread and FragmentDefinition extension to allow its
# name to point to a lazily defined Proc instead of a static string.
module LazyName
def name
@_definition.definition_name
end
attr_writer :_definition
end
private
def deep_freeze_json_object(obj)
case obj
when String
obj.freeze
when Array
obj.each { |v| deep_freeze_json_object(v) }
obj.freeze
when Hash
obj.each { |k, v| k.freeze; deep_freeze_json_object(v) }
obj.freeze
end
end
def deep_stringify_keys(obj)
case obj
when Hash
obj.each_with_object({}) do |(k, v), h|
h[k.to_s] = deep_stringify_keys(v)
end
else
obj
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/ 0000775 0000000 0000000 00000000000 14000335401 0021137 5 ustar 00root root 0000000 0000000 ruby-graphql-client-0.16.0/lib/graphql/client/collocated_enforcement.rb 0000664 0000000 0000000 00000003677 14000335401 0026177 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/error"
module GraphQL
class Client
# Collocation will not be enforced if a stack trace includes any of these gems.
WHITELISTED_GEM_NAMES = %w{pry byebug}
# Raised when method is called from outside the expected file scope.
class NonCollocatedCallerError < Error; end
# Enforcements collocated object access best practices.
module CollocatedEnforcement
# Public: Ignore collocated caller enforcement for the scope of the block.
def allow_noncollocated_callers
Thread.current[:query_result_caller_location_ignore] = true
yield
ensure
Thread.current[:query_result_caller_location_ignore] = nil
end
# Internal: Decorate method with collocated caller enforcement.
#
# mod - Target Module/Class
# methods - Array of Symbol method names
# path - String filename to assert calling from
#
# Returns nothing.
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
super(*args, &block)
ensure
Thread.current[:query_result_caller_location_ignore] = nil
end
end
end
end)
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/definition.rb 0000664 0000000 0000000 00000015577 14000335401 0023633 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql"
require "graphql/client/collocated_enforcement"
require "graphql/client/schema/object_type"
require "graphql/client/schema/possible_types"
require "set"
module GraphQL
class Client
# Definitions are constructed by Client.parse and wrap a parsed AST of the
# query string as well as hold references to any external query definition
# dependencies.
#
# Definitions MUST be assigned to a constant.
class Definition < Module
def self.for(ast_node:, **kargs)
case ast_node
when Language::Nodes::OperationDefinition
OperationDefinition.new(ast_node: ast_node, **kargs)
when Language::Nodes::FragmentDefinition
FragmentDefinition.new(ast_node: ast_node, **kargs)
else
raise TypeError, "expected node to be a definition type, but was #{ast_node.class}"
end
end
def initialize(client:, document:, source_document:, ast_node:, source_location:)
@client = client
@document = document
@source_document = source_document
@definition_node = ast_node
@source_location = source_location
definition_type = case ast_node
when GraphQL::Language::Nodes::OperationDefinition
case ast_node.operation_type
when "mutation"
@client.schema.mutation
when "subscription"
@client.schema.subscription
when "query", nil
@client.schema.query
else
raise "Unexpected operation_type: #{ast_node.operation_type}"
end
when GraphQL::Language::Nodes::FragmentDefinition
@client.get_type(ast_node.type.name)
else
raise "Unexpected ast_node: #{ast_node}"
end
@schema_class = client.types.define_class(self, [ast_node], definition_type)
# Clear cache only needed during initialization
@indexes = nil
end
# Internal: Get associated owner GraphQL::Client instance.
attr_reader :client
# Internal root schema class for definition. Returns
# GraphQL::Client::Schema::ObjectType or
# GraphQL::Client::Schema::PossibleTypes.
attr_reader :schema_class
# Internal: Get underlying operation or fragment definition AST node for
# definition.
#
# Returns OperationDefinition or FragmentDefinition object.
attr_reader :definition_node
# Internal: Get original document that created this definition, without
# any additional dependencies.
#
# Returns GraphQL::Language::Nodes::Document.
attr_reader :source_document
# Public: Global name of definition in client document.
#
# Returns a GraphQL safe name of the Ruby constant String.
#
# "Users::UserQuery" #=> "Users__UserQuery"
#
# Returns String.
def definition_name
return @definition_name if defined?(@definition_name)
if name
@definition_name = name.gsub("::", "__").freeze
else
"#{self.class.name}_#{object_id}".gsub("::", "__").freeze
end
end
# Public: Get document with only the definitions needed to perform this
# operation.
#
# Returns GraphQL::Language::Nodes::Document with one OperationDefinition
# and any FragmentDefinition dependencies.
attr_reader :document
# Public: Returns the Ruby source filename and line number containing this
# definition was not defined in Ruby.
#
# Returns Array pair of [String, Fixnum].
attr_reader :source_location
def new(obj, errors = Errors.new)
case schema_class
when GraphQL::Client::Schema::PossibleTypes
case obj
when NilClass
obj
else
cast_object(obj)
end
when GraphQL::Client::Schema::ObjectType
case obj
when NilClass, schema_class
obj
when Hash
schema_class.new(obj, errors)
else
cast_object(obj)
end
else
raise TypeError, "unexpected #{schema_class}"
end
end
# Internal: Nodes AST indexes.
def indexes
@indexes ||= begin
visitor = GraphQL::Language::Visitor.new(document)
definitions = index_node_definitions(visitor)
spreads = index_spreads(visitor)
visitor.visit
{ definitions: definitions, spreads: spreads }
end
end
private
def cast_object(obj)
if obj.class.is_a?(GraphQL::Client::Schema::ObjectType)
unless obj.class._spreads.include?(definition_node.name)
raise TypeError, "#{definition_node.name} is not included in #{obj.class.source_definition.name}"
end
schema_class.cast(obj.to_h, obj.errors)
else
raise TypeError, "unexpected #{obj.class}"
end
end
EMPTY_SET = Set.new.freeze
def index_spreads(visitor)
spreads = {}
on_node = ->(node, _parent) do
node_spreads = flatten_spreads(node).map(&:name)
spreads[node] = node_spreads.empty? ? EMPTY_SET : Set.new(node_spreads).freeze
end
visitor[GraphQL::Language::Nodes::Field] << on_node
visitor[GraphQL::Language::Nodes::FragmentDefinition] << on_node
visitor[GraphQL::Language::Nodes::OperationDefinition] << on_node
spreads
end
def flatten_spreads(node)
spreads = []
node.selections.each do |selection|
case selection
when Language::Nodes::FragmentSpread
spreads << selection
when Language::Nodes::InlineFragment
spreads.concat(flatten_spreads(selection))
else
# Do nothing, not a spread
end
end
spreads
end
def index_node_definitions(visitor)
current_definition = nil
enter_definition = ->(node, _parent) { current_definition = node }
leave_definition = ->(node, _parent) { current_definition = nil }
visitor[GraphQL::Language::Nodes::FragmentDefinition].enter << enter_definition
visitor[GraphQL::Language::Nodes::FragmentDefinition].leave << leave_definition
visitor[GraphQL::Language::Nodes::OperationDefinition].enter << enter_definition
visitor[GraphQL::Language::Nodes::OperationDefinition].leave << leave_definition
definitions = {}
on_node = ->(node, _parent) { definitions[node] = current_definition }
visitor[GraphQL::Language::Nodes::Field] << on_node
visitor[GraphQL::Language::Nodes::FragmentDefinition] << on_node
visitor[GraphQL::Language::Nodes::InlineFragment] << on_node
visitor[GraphQL::Language::Nodes::OperationDefinition] << on_node
definitions
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/definition_variables.rb 0000664 0000000 0000000 00000006032 14000335401 0025645 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql"
module GraphQL
class Client
# Internal: Detect variables used in a definition.
module DefinitionVariables
# Internal: Detect all variables used in a given operation or fragment
# definition.
#
# schema - A GraphQL::Schema
# document - A GraphQL::Language::Nodes::Document to scan
# definition_name - A String definition name. Defaults to anonymous definition.
#
# Returns a Hash[Symbol] to GraphQL::Type objects.
def self.variables(schema, document, definition_name = nil)
unless schema.is_a?(GraphQL::Schema) || (schema.is_a?(Class) && schema < GraphQL::Schema)
raise TypeError, "expected schema to be a GraphQL::Schema, but was #{schema.class}"
end
unless document.is_a?(GraphQL::Language::Nodes::Document)
raise TypeError, "expected document to be a GraphQL::Language::Nodes::Document, but was #{document.class}"
end
sliced_document = GraphQL::Language::DefinitionSlice.slice(document, definition_name)
visitor = GraphQL::Language::Visitor.new(sliced_document)
type_stack = GraphQL::StaticValidation::TypeStack.new(schema, visitor)
variables = {}
visitor[GraphQL::Language::Nodes::VariableIdentifier] << ->(node, parent) do
if definition = type_stack.argument_definitions.last
existing_type = variables[node.name.to_sym]
if existing_type && existing_type.unwrap != definition.type.unwrap
raise GraphQL::Client::ValidationError, "$#{node.name} was already declared as #{existing_type.unwrap}, but was #{definition.type.unwrap}"
elsif !(existing_type && existing_type.kind.non_null?)
variables[node.name.to_sym] = definition.type
end
end
end
visitor.visit
variables
end
# Internal: Detect all variables used in a given operation or fragment
# definition.
#
# schema - A GraphQL::Schema
# document - A GraphQL::Language::Nodes::Document to scan
# definition_name - A String definition name. Defaults to anonymous definition.
#
# Returns a Hash[Symbol] to VariableDefinition objects.
def self.operation_variables(schema, document, definition_name = nil)
variables(schema, document, definition_name).map { |name, type|
GraphQL::Language::Nodes::VariableDefinition.new(name: name.to_s, type: variable_node(type))
}
end
# Internal: Get AST node for GraphQL type.
#
# type - A GraphQL::Type
#
# Returns GraphQL::Language::Nodes::Type.
def self.variable_node(type)
case type.kind.name
when "NON_NULL"
GraphQL::Language::Nodes::NonNullType.new(of_type: variable_node(type.of_type))
when "LIST"
GraphQL::Language::Nodes::ListType.new(of_type: variable_node(type.of_type))
else
GraphQL::Language::Nodes::TypeName.new(name: type.graphql_name)
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/document_types.rb 0000664 0000000 0000000 00000003500 14000335401 0024524 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql"
module GraphQL
class Client
# Internal: Use schema to detect definition and field types.
module DocumentTypes
# Internal: Detect all types used in a given document
#
# schema - A GraphQL::Schema
# document - A GraphQL::Language::Nodes::Document to scan
#
# Returns a Hash[Language::Nodes::Node] to GraphQL::Type objects.
def self.analyze_types(schema, document)
unless schema.is_a?(GraphQL::Schema) || (schema.is_a?(Class) && schema < GraphQL::Schema)
raise TypeError, "expected schema to be a GraphQL::Schema, but was #{schema.class}"
end
unless document.is_a?(GraphQL::Language::Nodes::Document)
raise TypeError, "expected schema to be a GraphQL::Language::Nodes::Document, but was #{document.class}"
end
visitor = GraphQL::Language::Visitor.new(document)
type_stack = GraphQL::StaticValidation::TypeStack.new(schema, visitor)
fields = {}
visitor[GraphQL::Language::Nodes::OperationDefinition] << ->(node, _parent) do
fields[node] = type_stack.object_types.last
end
visitor[GraphQL::Language::Nodes::FragmentDefinition] << ->(node, _parent) do
fields[node] = type_stack.object_types.last
end
visitor[GraphQL::Language::Nodes::InlineFragment] << ->(node, _parent) do
fields[node] = type_stack.object_types.last
end
visitor[GraphQL::Language::Nodes::Field] << ->(node, _parent) do
fields[node] = type_stack.field_definitions.last.type
end
visitor.visit
fields
rescue StandardError => err
if err.is_a?(TypeError)
raise
end
# FIXME: TypeStack my crash on invalid documents
fields
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/erb.rb 0000664 0000000 0000000 00000002506 14000335401 0022237 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "action_view"
module GraphQL
class Client
begin
require "action_view/template/handlers/erb/erubi"
rescue LoadError
require "graphql/client/erubis_enhancer"
# Public: Extended Erubis implementation that supports GraphQL static
# query sections.
#
# <%graphql
# query GetVersion {
# version
# }
# %>
# <%= data.version %>
#
# Configure ActionView's default ERB implementation to use this class.
#
# ActionView::Template::Handlers::ERB.erb_implementation = GraphQL::Client::Erubis
#
class ERB < ActionView::Template::Handlers::Erubis
include ErubisEnhancer
end
else
require "graphql/client/erubi_enhancer"
# Public: Extended Erubis implementation that supports GraphQL static
# query sections.
#
# <%graphql
# query GetVerison {
# version
# }
# %>
# <%= data.version %>
#
# Configure ActionView's default ERB implementation to use this class.
#
# ActionView::Template::Handlers::ERB.erb_implementation = GraphQL::Client::Erubi
#
class ERB < ActionView::Template::Handlers::ERB::Erubi
include ErubiEnhancer
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/error.rb 0000664 0000000 0000000 00000000625 14000335401 0022620 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module GraphQL
class Client
# Public: Abstract base class for all errors raised by GraphQL::Client.
class Error < StandardError
end
class InvariantError < Error
end
class ImplicitlyFetchedFieldError < NoMethodError
end
class UnfetchedFieldError < NoMethodError
end
class UnimplementedFieldError < NoMethodError
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/errors.rb 0000664 0000000 0000000 00000013312 14000335401 0023000 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/hash_with_indifferent_access"
module GraphQL
class Client
# Public: Collection of errors associated with GraphQL object type.
#
# Inspired by ActiveModel::Errors.
class Errors
include Enumerable
# Internal: Normalize GraphQL Error "path" ensuring the path exists.
#
# Records "normalizedPath" value to error object.
#
# data - Hash of response data
# errors - Array of error Hashes
#
# Returns nothing.
def self.normalize_error_paths(data = nil, errors = [])
errors.each do |error|
path = ["data"]
current = data
error.fetch("path", []).each do |key|
break unless current
path << key
current = current[key]
end
error["normalizedPath"] = path
end
errors
end
# Internal: Initialize from collection of errors.
#
# errors - Array of GraphQL Hash error objects
# path - Array of String|Integer fields to data
# all - Boolean flag if all nested errors should be available
def initialize(errors = [], path = [], all = false)
@ast_path = path
@all = all
@raw_errors = errors
end
# Public: Return collection of all nested errors.
#
# data.errors[:node]
# data.errors.all[:node]
#
# Returns Errors collection.
def all
if @all
self
else
self.class.new(@raw_errors, @ast_path, true)
end
end
# Internal: Return collection of errors for a given subfield.
#
# data.errors.filter_by_path("node")
#
# Returns Errors collection.
def filter_by_path(field)
self.class.new(@raw_errors, @ast_path + [field], @all)
end
# Public: Access Hash of error messages.
#
# data.errors.messages["node"]
# data.errors.messages[:node]
#
# Returns HashWithIndifferentAccess.
def messages
return @messages if defined? @messages
messages = {}
details.each do |field, errors|
messages[field] ||= []
errors.each do |error|
messages[field] << error.fetch("message")
end
end
@messages = HashWithIndifferentAccess.new(messages)
end
# Public: Access Hash of error objects.
#
# data.errors.details["node"]
# data.errors.details[:node]
#
# Returns HashWithIndifferentAccess.
def details
return @details if defined? @details
details = {}
@raw_errors.each do |error|
path = error.fetch("normalizedPath", [])
matched_path = @all ? path[0, @ast_path.length] : path[0...-1]
next unless @ast_path == matched_path
field = path[@ast_path.length]
next unless field
details[field] ||= []
details[field] << error
end
@details = HashWithIndifferentAccess.new(details)
end
# Public: When passed a symbol or a name of a field, returns an array of
# errors for the method.
#
# data.errors[:node] # => ["couldn't find node by id"]
# data.errors['node'] # => ["couldn't find node by id"]
#
# Returns Array of errors.
def [](key)
messages.fetch(key, [])
end
# Public: Iterates through each error key, value pair in the error
# messages hash. Yields the field and the error for that attribute. If the
# field has more than one error message, yields once for each error
# message.
def each
return enum_for(:each) unless block_given?
messages.each_key do |field|
messages[field].each { |error| yield field, error }
end
end
# Public: Check if there are any errors on a given field.
#
# data.errors.messages # => {"node"=>["couldn't find node by id", "unauthorized"]}
# data.errors.include?("node") # => true
# data.errors.include?("version") # => false
#
# Returns true if the error messages include an error for the given field,
# otherwise false.
def include?(field)
self[field].any?
end
alias has_key? include?
alias key? include?
# Public: Count the number of errors on object.
#
# data.errors.messages # => {"node"=>["couldn't find node by id", "unauthorized"]}
# data.errors.size # => 2
#
# Returns the number of error messages.
def size
values.flatten.size
end
alias count size
# Public: Check if there are no errors on object.
#
# data.errors.messages # => {"node"=>["couldn't find node by id"]}
# data.errors.empty? # => false
#
# Returns true if no errors are found, otherwise false.
def empty?
size.zero?
end
alias blank? empty?
# Public: Returns all message keys.
#
# data.errors.messages # => {"node"=>["couldn't find node by id"]}
# data.errors.values # => ["node"]
#
# Returns Array of String field names.
def keys
messages.keys
end
# Public: Returns all message values.
#
# data.errors.messages # => {"node"=>["couldn't find node by id"]}
# data.errors.values # => [["couldn't find node by id"]]
#
# Returns Array of Array String messages.
def values
messages.values
end
# Public: Display console friendly representation of errors collection.
#
# Returns String.
def inspect
"#<#{self.class} @messages=#{messages.inspect} @details=#{details.inspect}>"
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/erubi_enhancer.rb 0000664 0000000 0000000 00000001000 14000335401 0024424 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module GraphQL
class Client
# Public: Erubi enhancer that adds support for GraphQL static query sections.
#
# <%graphql
# query GetVersion {
# version
# }
# %>
# <%= data.version %>
#
module ErubiEnhancer
# Internal: Extend Erubi handler to simply ignore <%graphql sections.
def initialize(input, *args)
input = input.gsub(/<%graphql/, "<%#")
super(input, *args)
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/erubis.rb 0000664 0000000 0000000 00000000206 14000335401 0022753 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/erb"
module GraphQL
class Client
Erubis = GraphQL::Client::ERB
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/erubis_enhancer.rb 0000664 0000000 0000000 00000001002 14000335401 0024611 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module GraphQL
class Client
# Public: Erubis enhancer that adds support for GraphQL static query sections.
#
# <%graphql
# query GetVersion {
# version
# }
# %>
# <%= data.version %>
#
module ErubisEnhancer
# Internal: Extend Erubis handler to simply ignore <%graphql sections.
def convert_input(src, input)
input = input.gsub(/<%graphql/, "<%#")
super(src, input)
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/fragment_definition.rb 0000664 0000000 0000000 00000000567 14000335401 0025507 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/definition"
module GraphQL
class Client
# Specific fragment definition subtype.
class FragmentDefinition < Definition
def new(obj, *args)
if obj.is_a?(Hash)
raise TypeError, "constructing fragment wrapper from Hash is deprecated"
end
super
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/hash_with_indifferent_access.rb 0000664 0000000 0000000 00000002623 14000335401 0027343 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "active_support/inflector"
require "forwardable"
module GraphQL
class Client
# Public: Implements a read only hash where keys can be accessed by
# strings, symbols, snake or camel case.
#
# Also see ActiveSupport::HashWithIndifferentAccess.
class HashWithIndifferentAccess
extend Forwardable
include Enumerable
def initialize(hash = {})
@hash = hash
@aliases = {}
hash.each_key do |key|
if key.is_a?(String)
key_alias = ActiveSupport::Inflector.underscore(key)
@aliases[key_alias] = key if key != key_alias
end
end
freeze
end
def_delegators :@hash, :each, :empty?, :inspect, :keys, :length, :size, :to_h, :to_hash, :values
def [](key)
@hash[convert_value(key)]
end
def fetch(key, *args, &block)
@hash.fetch(convert_value(key), *args, &block)
end
def key?(key)
@hash.key?(convert_value(key))
end
alias include? key?
alias has_key? key?
alias member? key?
def each_key(&block)
@hash.each_key { |key| yield convert_value(key) }
end
private
def convert_value(key)
case key
when String, Symbol
key = key.to_s
@aliases.fetch(key, key)
else
key
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/http.rb 0000664 0000000 0000000 00000005734 14000335401 0022454 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "json"
require "net/http"
require "uri"
module GraphQL
class Client
# Public: Basic HTTP network adapter.
#
# GraphQL::Client::Client.new(
# execute: GraphQL::Client::HTTP.new("http://graphql-swapi.parseapp.com/")
# )
#
# Assumes GraphQL endpoint follows the express-graphql endpoint conventions.
# https://github.com/graphql/express-graphql#http-usage
#
# Production applications should consider implementing there own network
# adapter. This class exists for trivial stock usage and allows for minimal
# request header configuration.
class HTTP
# Public: Create HTTP adapter instance for a single GraphQL endpoint.
#
# GraphQL::Client::HTTP.new("http://graphql-swapi.parseapp.com/") do
# def headers(context)
# { "User-Agent": "My Client" }
# end
# end
#
# uri - String endpoint URI
# block - Optional block to configure class
def initialize(uri, &block)
@uri = URI.parse(uri)
singleton_class.class_eval(&block) if block_given?
end
# Public: Parsed endpoint URI
#
# Returns URI.
attr_reader :uri
# Public: Extension point for subclasses to set custom request headers.
#
# Returns Hash of String header names and values.
def headers(_context)
{}
end
# Public: Make an HTTP request for GraphQL query.
#
# Implements Client's "execute" adapter interface.
#
# document - The Query GraphQL::Language::Nodes::Document
# operation_name - The String operation definition name
# variables - Hash of query variables
# context - An arbitrary Hash of values which you can access
#
# Returns { "data" => ... , "errors" => ... } Hash.
def execute(document:, operation_name: nil, variables: {}, context: {})
request = Net::HTTP::Post.new(uri.request_uri)
request.basic_auth(uri.user, uri.password) if uri.user || uri.password
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
headers(context).each { |name, value| request[name] = value }
body = {}
body["query"] = document.to_query_string
body["variables"] = variables if variables.any?
body["operationName"] = operation_name if operation_name
request.body = JSON.generate(body)
response = connection.request(request)
case response
when Net::HTTPOK, Net::HTTPBadRequest
JSON.parse(response.body)
else
{ "errors" => [{ "message" => "#{response.code} #{response.message}" }] }
end
end
# Public: Extension point for subclasses to customize the Net:HTTP client
#
# Returns a Net::HTTP object
def connection
Net::HTTP.new(uri.host, uri.port).tap do |client|
client.use_ssl = uri.scheme == "https"
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/list.rb 0000664 0000000 0000000 00000000704 14000335401 0022440 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/errors"
module GraphQL
class Client
# Public: Array wrapper for value returned from GraphQL List.
class List < Array
def initialize(values, errors = Errors.new)
super(values)
@errors = errors
freeze
end
# Public: Return errors associated with list of data.
#
# Returns Errors collection.
attr_reader :errors
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/log_subscriber.rb 0000664 0000000 0000000 00000002175 14000335401 0024475 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "active_support/log_subscriber"
module GraphQL
class Client
# Public: Logger for "*.graphql" notification events.
#
# Logs GraphQL queries to Rails logger.
#
# UsersController::ShowQuery QUERY (123ms)
# UsersController::UpdateMutation MUTATION (456ms)
#
# Enable GraphQL Client query logging.
#
# require "graphql/client/log_subscriber"
# GraphQL::Client::LogSubscriber.attach_to :graphql
#
class LogSubscriber < ActiveSupport::LogSubscriber
def query(event)
logger.info do
name = event.payload[:operation_name].gsub("__", "::")
type = event.payload[:operation_type].upcase
color("#{name} #{type} (#{event.duration.round(1)}ms)", nil, true)
end
logger.debug do
event.payload[:document].to_query_string
end
end
def error(event)
logger.error do
name = event.payload[:operation_name].gsub("__", "::")
message = event.payload[:message]
color("#{name} ERROR: #{message}", nil, true)
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/operation_definition.rb 0000664 0000000 0000000 00000000520 14000335401 0025671 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/definition"
module GraphQL
class Client
# Specific operation definition subtype for queries, mutations or
# subscriptions.
class OperationDefinition < Definition
# Public: Alias for definition name.
alias operation_name definition_name
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/query_typename.rb 0000664 0000000 0000000 00000007633 14000335401 0024544 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql"
require "graphql/client/document_types"
module GraphQL
class Client
# Internal: Insert __typename field selections into query.
module QueryTypename
# Internal: Insert __typename field selections into query.
#
# Skips known types when schema is provided.
#
# document - GraphQL::Language::Nodes::Document to modify
# schema - Optional Map of GraphQL::Language::Nodes::Node to GraphQL::Type
#
# Returns the document with `__typename` added to it
if GraphQL::Language::Nodes::AbstractNode.method_defined?(:merge)
# GraphQL 1.9 introduces a new visitor class
# and doesn't expose writer methods for node attributes.
# So, use the node mutation API instead.
class InsertTypenameVisitor < GraphQL::Language::Visitor
def initialize(document, types:)
@types = types
super(document)
end
def add_typename(node, parent)
type = @types[node]
type = type && type.unwrap
if (node.selections.any? && (type.nil? || type.kind.interface? || type.kind.union?)) ||
(node.selections.none? && (type && type.kind.object?))
names = QueryTypename.node_flatten_selections(node.selections).map { |s| s.respond_to?(:name) ? s.name : nil }
names = Set.new(names.compact)
if names.include?("__typename")
yield(node, parent)
else
node_with_typename = node.merge(selections: [GraphQL::Language::Nodes::Field.new(name: "__typename")] + node.selections)
yield(node_with_typename, parent)
end
else
yield(node, parent)
end
end
def on_operation_definition(node, parent)
add_typename(node, parent) { |n, p| super(n, p) }
end
def on_field(node, parent)
add_typename(node, parent) { |n, p| super(n, p) }
end
def on_fragment_definition(node, parent)
add_typename(node, parent) { |n, p| super(n, p) }
end
end
def self.insert_typename_fields(document, types: {})
visitor = InsertTypenameVisitor.new(document, types: types)
visitor.visit
visitor.result
end
else
def self.insert_typename_fields(document, types: {})
on_selections = ->(node, _parent) do
type = types[node]
if node.selections.any?
case type && type.unwrap
when NilClass, GraphQL::InterfaceType, GraphQL::UnionType
names = node_flatten_selections(node.selections).map { |s| s.respond_to?(:name) ? s.name : nil }
names = Set.new(names.compact)
unless names.include?("__typename")
node.selections = [GraphQL::Language::Nodes::Field.new(name: "__typename")] + node.selections
end
end
elsif type && type.unwrap.is_a?(GraphQL::ObjectType)
node.selections = [GraphQL::Language::Nodes::Field.new(name: "__typename")]
end
end
visitor = GraphQL::Language::Visitor.new(document)
visitor[GraphQL::Language::Nodes::Field].leave << on_selections
visitor[GraphQL::Language::Nodes::FragmentDefinition].leave << on_selections
visitor[GraphQL::Language::Nodes::OperationDefinition].leave << on_selections
visitor.visit
document
end
end
def self.node_flatten_selections(selections)
selections.flat_map do |selection|
case selection
when GraphQL::Language::Nodes::Field
selection
when GraphQL::Language::Nodes::InlineFragment
node_flatten_selections(selection.selections)
else
[]
end
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/railtie.rb 0000664 0000000 0000000 00000002727 14000335401 0023125 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql"
require "graphql/client"
require "rails/railtie"
module GraphQL
class Client
# Optional Rails configuration for GraphQL::Client.
#
# Simply require this file to activate in the application.
#
# # config/application.rb
# require "graphql/client/railtie"
#
class Railtie < Rails::Railtie
config.graphql = ActiveSupport::OrderedOptions.new
config.graphql.client = GraphQL::Client.new
initializer "graphql.configure_log_subscriber" do |_app|
require "graphql/client/log_subscriber"
GraphQL::Client::LogSubscriber.attach_to :graphql
end
initializer "graphql.configure_erb_implementation" do |_app|
require "graphql/client/erb"
ActionView::Template::Handlers::ERB.erb_implementation = GraphQL::Client::ERB
end
initializer "graphql.configure_views_namespace" do |app|
require "graphql/client/view_module"
path = app.paths["app/views"].first
# TODO: Accessing config.graphql.client during the initialization
# process seems error prone. The application may reassign
# config.graphql.client after this block is executed.
client = config.graphql.client
config.watchable_dirs[path] = [:erb]
Object.const_set(:Views, Module.new do
extend GraphQL::Client::ViewModule
self.path = path
self.client = client
end)
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/response.rb 0000664 0000000 0000000 00000002242 14000335401 0023322 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/errors"
module GraphQL
class Client
# Public: Abstract base class for GraphQL responses.
#
# https://facebook.github.io/graphql/#sec-Response-Format
class Response
# Public: Original JSON response hash returned from server.
#
# Returns Hash.
attr_reader :original_hash
alias_method :to_h, :original_hash
# Public: Wrapped ObjectType of data returned from the server.
#
# https://facebook.github.io/graphql/#sec-Data
#
# Returns instance of ObjectType subclass.
attr_reader :data
# Public: Get partial failures from response.
#
# https://facebook.github.io/graphql/#sec-Errors
#
# Returns Errors collection object with zero or more errors.
attr_reader :errors
# Public: Hash of server specific extension metadata.
attr_reader :extensions
# Internal: Initialize base class.
def initialize(hash, data: nil, errors: Errors.new, extensions: {})
@original_hash = hash
@data = data
@errors = errors
@extensions = extensions
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/schema.rb 0000664 0000000 0000000 00000007400 14000335401 0022725 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql"
require "graphql/client/schema/enum_type"
require "graphql/client/schema/include_directive"
require "graphql/client/schema/interface_type"
require "graphql/client/schema/list_type"
require "graphql/client/schema/non_null_type"
require "graphql/client/schema/object_type"
require "graphql/client/schema/scalar_type"
require "graphql/client/schema/skip_directive"
require "graphql/client/schema/union_type"
module GraphQL
class Client
module Schema
module ClassMethods
def define_class(definition, ast_nodes, type)
type_class = case type.kind.name
when "NON_NULL"
define_class(definition, ast_nodes, type.of_type).to_non_null_type
when "LIST"
define_class(definition, ast_nodes, type.of_type).to_list_type
else
get_class(type.graphql_name).define_class(definition, ast_nodes)
end
ast_nodes.each do |ast_node|
ast_node.directives.each do |directive|
if directive = self.directives[directive.name.to_sym]
type_class = directive.new(type_class)
end
end
end
type_class
end
def get_class(type_name)
const_get(normalize_type_name(type_name))
end
def set_class(type_name, klass)
class_name = normalize_type_name(type_name)
if constants.include?(class_name.to_sym)
raise ArgumentError,
"Can't define #{class_name} to represent type #{type_name} " \
"because it's already defined"
end
const_set(class_name, klass)
end
DIRECTIVES = { include: IncludeDirective,
skip: SkipDirective }.freeze
def directives
DIRECTIVES
end
private
def normalize_type_name(type_name)
/\A[A-Z]/.match?(type_name) ? type_name : type_name.camelize
end
end
def self.generate(schema)
mod = Module.new
mod.extend ClassMethods
cache = {}
schema.types.each do |name, type|
next if name.start_with?("__")
if klass = class_for(schema, type, cache)
klass.schema_module = mod
mod.set_class(name, klass)
end
end
mod
end
def self.class_for(schema, type, cache)
return cache[type] if cache[type]
case type.kind.name
when "INPUT_OBJECT"
nil
when "SCALAR"
cache[type] = ScalarType.new(type)
when "ENUM"
cache[type] = EnumType.new(type)
when "LIST"
cache[type] = class_for(schema, type.of_type, cache).to_list_type
when "NON_NULL"
cache[type] = class_for(schema, type.of_type, cache).to_non_null_type
when "UNION"
klass = cache[type] = UnionType.new(type)
type.possible_types.each do |possible_type|
possible_klass = class_for(schema, possible_type, cache)
possible_klass.send :include, klass
end
klass
when "INTERFACE"
cache[type] = InterfaceType.new(type)
when "OBJECT"
klass = cache[type] = ObjectType.new(type)
type.interfaces.each do |interface|
klass.send :include, class_for(schema, interface, cache)
end
# Legacy objects have `.all_fields`
all_fields = type.respond_to?(:all_fields) ? type.all_fields : type.fields.values
all_fields.each do |field|
klass.fields[field.name.to_sym] = class_for(schema, field.type, cache)
end
klass
else
raise TypeError, "unexpected #{type.class} (#{type.inspect})"
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/schema/ 0000775 0000000 0000000 00000000000 14000335401 0022377 5 ustar 00root root 0000000 0000000 ruby-graphql-client-0.16.0/lib/graphql/client/schema/base_type.rb 0000664 0000000 0000000 00000001770 14000335401 0024704 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
module GraphQL
class Client
module Schema
module BaseType
# Public: Get associated GraphQL::BaseType with for this class.
attr_reader :type
# Internal: Get owner schema Module container.
attr_accessor :schema_module
# Internal: Cast JSON value to wrapped value.
#
# value - JSON value
# errors - Errors instance
#
# Returns BaseType instance.
def cast(value, errors)
raise NotImplementedError, "subclasses must implement #cast(value, errors)"
end
# Internal: Get non-nullable wrapper of this type class.
#
# Returns NonNullType instance.
def to_non_null_type
@null_type ||= NonNullType.new(self)
end
# Internal: Get list wrapper of this type class.
#
# Returns ListType instance.
def to_list_type
@list_type ||= ListType.new(self)
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/schema/enum_type.rb 0000664 0000000 0000000 00000004764 14000335401 0024744 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/error"
require "graphql/client/schema/base_type"
module GraphQL
class Client
module Schema
class EnumType < Module
include BaseType
class EnumValue < String
def initialize(obj, enum_value, enum)
super(obj)
@enum_value = enum_value
@enum = enum
end
def respond_to_missing?(method_name, include_private = false)
if method_name[-1] == "?" && @enum.include?(method_name[0..-2])
true
else
super
end
end
def method_missing(method_name, *args)
if method_name[-1] == "?"
queried_value = method_name[0..-2]
if @enum.include?(queried_value)
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0)" unless args.empty?
return @enum_value == queried_value
end
end
super
end
end
# Internal: Construct enum wrapper from another GraphQL::EnumType.
#
# type - GraphQL::EnumType instance
def initialize(type)
unless type.kind.enum?
raise "expected type to be an Enum, but was #{type.class}"
end
@type = type
@values = {}
all_values = type.values.keys
comparison_set = all_values.map { |s| -s.downcase }.to_set
all_values.each do |value|
str = EnumValue.new(-value, -value.downcase, comparison_set).freeze
const_set(value, str) if value =~ /^[A-Z]/
@values[str.to_s] = str
end
@values.freeze
end
def define_class(definition, ast_nodes)
self
end
def [](value)
@values[value]
end
# Internal: Cast JSON value to the enumeration's corresponding constant string instance
# with the convenience predicate methods.
#
# values - JSON value
# errors - Errors instance
#
# Returns String or nil.
def cast(value, _errors = nil)
case value
when String
raise Error, "unexpected enum value #{value}" unless @values.key?(value)
@values[value]
when NilClass
value
else
raise InvariantError, "expected value to be a String, but was #{value.class}"
end
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/schema/include_directive.rb 0000664 0000000 0000000 00000001753 14000335401 0026413 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/schema/base_type"
module GraphQL
class Client
module Schema
class IncludeDirective
include BaseType
# Internal: Construct list wrapper from other BaseType.
#
# of_klass - BaseType instance
def initialize(of_klass)
unless of_klass.is_a?(BaseType)
raise TypeError, "expected #{of_klass.inspect} to be a #{BaseType}"
end
@of_klass = of_klass
end
# Internal: Get wrapped klass.
#
# Returns BaseType instance.
attr_reader :of_klass
# Internal: Cast JSON value to wrapped value.
#
# values - JSON value
# errors - Errors instance
#
# Returns List instance or nil.
def cast(value, errors)
case value
when NilClass
nil
else
of_klass.cast(value, errors)
end
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/schema/interface_type.rb 0000664 0000000 0000000 00000001525 14000335401 0025730 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/schema/possible_types"
module GraphQL
class Client
module Schema
class InterfaceType < Module
include BaseType
def initialize(type)
unless type.kind.interface?
raise "expected type to be an Interface, but was #{type.class}"
end
@type = type
end
def new(types)
PossibleTypes.new(type, types)
end
def define_class(definition, ast_nodes)
possible_type_names = definition.client.schema.possible_types(type).map(&:graphql_name)
possible_types = possible_type_names.map { |concrete_type_name|
schema_module.get_class(concrete_type_name).define_class(definition, ast_nodes)
}
new(possible_types)
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/schema/list_type.rb 0000664 0000000 0000000 00000002621 14000335401 0024741 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/error"
require "graphql/client/list"
require "graphql/client/schema/base_type"
module GraphQL
class Client
module Schema
class ListType
include BaseType
# Internal: Construct list wrapper from other BaseType.
#
# of_klass - BaseType instance
def initialize(of_klass)
unless of_klass.is_a?(BaseType)
raise TypeError, "expected #{of_klass.inspect} to be a #{BaseType}"
end
@of_klass = of_klass
end
# Internal: Get wrapped klass.
#
# Returns BaseType instance.
attr_reader :of_klass
# Internal: Cast JSON value to wrapped value.
#
# values - JSON value
# errors - Errors instance
#
# Returns List instance or nil.
def cast(values, errors)
case values
when Array
List.new(values.each_with_index.map { |e, idx|
of_klass.cast(e, errors.filter_by_path(idx))
}, errors)
when NilClass
nil
else
raise InvariantError, "expected value to be a list, but was #{values.class}"
end
end
# Internal: Get list wrapper of this type class.
#
# Returns ListType instance.
def to_list_type
self
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/schema/non_null_type.rb 0000664 0000000 0000000 00000002370 14000335401 0025613 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/error"
require "graphql/client/schema/base_type"
module GraphQL
class Client
module Schema
class NonNullType
include BaseType
# Internal: Construct non-nullable wrapper from other BaseType.
#
# of_klass - BaseType instance
def initialize(of_klass)
unless of_klass.is_a?(BaseType)
raise TypeError, "expected #{of_klass.inspect} to be a #{BaseType}"
end
@of_klass = of_klass
end
# Internal: Get wrapped klass.
#
# Returns BaseType instance.
attr_reader :of_klass
# Internal: Cast JSON value to wrapped value.
#
# value - JSON value
# errors - Errors instance
#
# Returns BaseType instance.
def cast(value, errors)
case value
when NilClass
raise InvariantError, "expected value to be non-nullable, but was nil"
else
of_klass.cast(value, errors)
end
end
# Internal: Get non-nullable wrapper of this type class.
#
# Returns NonNullType instance.
def to_non_null_type
self
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/schema/object_type.rb 0000664 0000000 0000000 00000021240 14000335401 0025232 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "active_support/inflector"
require "graphql/client/error"
require "graphql/client/errors"
require "graphql/client/schema/base_type"
require "graphql/client/schema/possible_types"
module GraphQL
class Client
module Schema
module ObjectType
def self.new(type, fields = {})
Class.new(ObjectClass) do
extend BaseType
extend ObjectType
define_singleton_method(:type) { type }
define_singleton_method(:fields) { fields }
end
end
def define_class(definition, ast_nodes)
# First, gather all the ast nodes representing a certain selection, by name.
# We gather AST nodes into arrays so that multiple selections can be grouped, for example:
#
# {
# f1 { a b }
# f1 { b c }
# }
#
# should be treated like `f1 { a b c }`
field_nodes = {}
ast_nodes.each do |ast_node|
ast_node.selections.each do |selected_ast_node|
gather_selections(field_nodes, definition, selected_ast_node)
end
end
# After gathering all the nodes by name, prepare to create methods and classes for them.
field_classes = {}
field_nodes.each do |result_name, field_ast_nodes|
# `result_name` might be an alias, so make sure to get the proper name
field_name = field_ast_nodes.first.name
field_definition = definition.client.schema.get_field(type.graphql_name, field_name)
field_return_type = field_definition.type
field_classes[result_name.to_sym] = schema_module.define_class(definition, field_ast_nodes, field_return_type)
end
klass = Class.new(self)
klass.define_fields(field_classes)
klass.instance_variable_set(:@source_definition, definition)
klass.instance_variable_set(:@_spreads, definition.indexes[:spreads][ast_nodes.first])
if definition.client.enforce_collocated_callers
keys = field_classes.keys.map { |key| ActiveSupport::Inflector.underscore(key) }
Client.enforce_collocated_callers(klass, keys, definition.source_location[0])
end
klass
end
PREDICATE_CACHE = Hash.new { |h, name|
h[name] = -> { @data[name] ? true : false }
}
METHOD_CACHE = Hash.new { |h, key|
h[key] = -> {
name = key.to_s
type = self.class::FIELDS[key]
@casted_data.fetch(name) do
@casted_data[name] = type.cast(@data[name], @errors.filter_by_path(name))
end
}
}
MODULE_CACHE = Hash.new do |h, fields|
h[fields] = Module.new do
fields.each do |name|
GraphQL::Client::Schema::ObjectType.define_cached_field(name, self)
end
end
end
FIELDS_CACHE = Hash.new { |h, k| h[k] = k }
def define_fields(fields)
const_set :FIELDS, FIELDS_CACHE[fields]
mod = MODULE_CACHE[fields.keys.sort]
include mod
end
def self.define_cached_field(name, ctx)
key = name
name = -name.to_s
method_name = ActiveSupport::Inflector.underscore(name)
ctx.send(:define_method, method_name, &METHOD_CACHE[key])
ctx.send(:define_method, "#{method_name}?", &PREDICATE_CACHE[name])
end
def define_field(name, type)
name = name.to_s
method_name = ActiveSupport::Inflector.underscore(name)
define_method(method_name) do
@casted_data.fetch(name) do
@casted_data[name] = type.cast(@data[name], @errors.filter_by_path(name))
end
end
define_method("#{method_name}?") do
@data[name] ? true : false
end
end
def cast(value, errors)
case value
when Hash
new(value, errors)
when NilClass
nil
else
raise InvariantError, "expected value to be a Hash, but was #{value.class}"
end
end
private
# Given an AST selection on this object, gather it into `fields` if it applies.
# If it's a fragment, continue recursively checking the selections on the fragment.
def gather_selections(fields, definition, selected_ast_node)
case selected_ast_node
when GraphQL::Language::Nodes::InlineFragment
continue_selection = if selected_ast_node.type.nil?
true
else
schema = definition.client.schema
type_condition = definition.client.get_type(selected_ast_node.type.name)
applicable_types = schema.possible_types(type_condition)
# continue if this object type is one of the types matching the fragment condition
applicable_types.include?(type)
end
if continue_selection
selected_ast_node.selections.each do |next_selected_ast_node|
gather_selections(fields, definition, next_selected_ast_node)
end
end
when GraphQL::Language::Nodes::FragmentSpread
fragment_definition = definition.document.definitions.find do |defn|
defn.is_a?(GraphQL::Language::Nodes::FragmentDefinition) && defn.name == selected_ast_node.name
end
schema = definition.client.schema
type_condition = definition.client.get_type(fragment_definition.type.name)
applicable_types = schema.possible_types(type_condition)
# continue if this object type is one of the types matching the fragment condition
continue_selection = applicable_types.include?(type)
if continue_selection
fragment_definition.selections.each do |next_selected_ast_node|
gather_selections(fields, definition, next_selected_ast_node)
end
end
when GraphQL::Language::Nodes::Field
operation_definition_for_field = definition.indexes[:definitions][selected_ast_node]
# Ignore fields defined in other documents.
if definition.source_document.definitions.include?(operation_definition_for_field)
field_method_name = selected_ast_node.alias || selected_ast_node.name
ast_nodes = fields[field_method_name] ||= []
ast_nodes << selected_ast_node
end
else
raise "Unexpected selection node: #{selected_ast_node}"
end
end
end
class ObjectClass
module ClassMethods
attr_reader :source_definition
attr_reader :_spreads
end
extend ClassMethods
def initialize(data = {}, errors = Errors.new)
@data = data
@casted_data = {}
@errors = errors
end
# Public: Returns the raw response data
#
# Returns Hash
def to_h
@data
end
# Public: Return errors associated with data.
#
# Returns Errors collection.
attr_reader :errors
def method_missing(*args)
super
rescue NoMethodError => e
type = self.class.type
if ActiveSupport::Inflector.underscore(e.name.to_s) != e.name.to_s
raise e
end
all_fields = type.respond_to?(:all_fields) ? type.all_fields : type.fields.values
field = all_fields.find do |f|
f.name == e.name.to_s || ActiveSupport::Inflector.underscore(f.name) == e.name.to_s
end
unless field
raise UnimplementedFieldError, "undefined field `#{e.name}' on #{type.graphql_name} type. https://git.io/v1y3m"
end
if @data.key?(field.name)
error_class = ImplicitlyFetchedFieldError
message = "implicitly fetched field `#{field.name}' on #{type} type. https://git.io/v1yGL"
else
error_class = UnfetchedFieldError
message = "unfetched field `#{field.name}' on #{type} type. https://git.io/v1y3U"
end
raise error_class, message
end
def inspect
parent = self.class.ancestors.select { |m| m.is_a?(ObjectType) }.last
ivars = @data.map { |key, value|
if value.is_a?(Hash) || value.is_a?(Array)
"#{key}=..."
else
"#{key}=#{value.inspect}"
end
}
buf = "#<#{parent.name}".dup
buf << " " << ivars.join(" ") if ivars.any?
buf << ">"
buf
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/schema/possible_types.rb 0000664 0000000 0000000 00000002767 14000335401 0026004 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/error"
require "graphql/client/schema/base_type"
require "graphql/client/schema/object_type"
module GraphQL
class Client
module Schema
class PossibleTypes
include BaseType
def initialize(type, types)
@type = type
unless types.is_a?(Enumerable)
raise TypeError, "expected types to be Enumerable, but was #{types.class}"
end
@possible_types = {}
types.each do |klass|
unless klass.is_a?(ObjectType)
raise TypeError, "expected type to be #{ObjectType}, but was #{type.class}"
end
@possible_types[klass.type.graphql_name] = klass
end
end
attr_reader :possible_types
# Internal: Cast JSON value to wrapped value.
#
# value - JSON value
# errors - Errors instance
#
# Returns BaseType instance.
def cast(value, errors)
case value
when Hash
typename = value["__typename"]
if type = possible_types[typename]
type.cast(value, errors)
else
raise InvariantError, "expected value to be one of (#{possible_types.keys.join(", ")}), but was #{typename.inspect}"
end
when NilClass
nil
else
raise InvariantError, "expected value to be a Hash, but was #{value.class}"
end
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/schema/scalar_type.rb 0000664 0000000 0000000 00000002100 14000335401 0025223 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/schema/base_type"
module GraphQL
class Client
module Schema
class ScalarType
include BaseType
# Internal: Construct type wrapper from another GraphQL::BaseType.
#
# type - GraphQL::BaseType instance
def initialize(type)
unless type.kind.scalar?
raise "expected type to be a Scalar, but was #{type.class}"
end
@type = type
end
def define_class(definition, ast_nodes)
self
end
# Internal: Cast raw JSON value to Ruby scalar object.
#
# value - JSON value
# errors - Errors instance
#
# Returns casted Object.
def cast(value, _errors = nil)
case value
when NilClass
nil
else
if type.respond_to?(:coerce_isolated_input)
type.coerce_isolated_input(value)
else
type.coerce_input(value)
end
end
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/schema/skip_directive.rb 0000664 0000000 0000000 00000001750 14000335401 0025733 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/schema/base_type"
module GraphQL
class Client
module Schema
class SkipDirective
include BaseType
# Internal: Construct list wrapper from other BaseType.
#
# of_klass - BaseType instance
def initialize(of_klass)
unless of_klass.is_a?(BaseType)
raise TypeError, "expected #{of_klass.inspect} to be a #{BaseType}"
end
@of_klass = of_klass
end
# Internal: Get wrapped klass.
#
# Returns BaseType instance.
attr_reader :of_klass
# Internal: Cast JSON value to wrapped value.
#
# values - JSON value
# errors - Errors instance
#
# Returns List instance or nil.
def cast(value, errors)
case value
when NilClass
nil
else
of_klass.cast(value, errors)
end
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/schema/union_type.rb 0000664 0000000 0000000 00000001510 14000335401 0025112 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "graphql/client/schema/possible_types"
module GraphQL
class Client
module Schema
class UnionType < Module
include BaseType
def initialize(type)
unless type.kind.union?
raise "expected type to be a Union, but was #{type.class}"
end
@type = type
end
def new(types)
PossibleTypes.new(type, types)
end
def define_class(definition, ast_nodes)
possible_type_names = definition.client.schema.possible_types(type).map(&:graphql_name)
possible_types = possible_type_names.map { |concrete_type_name|
schema_module.get_class(concrete_type_name).define_class(definition, ast_nodes)
}
new(possible_types)
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/graphql/client/view_module.rb 0000664 0000000 0000000 00000010737 14000335401 0024013 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "active_support/dependencies"
require "active_support/inflector"
require "graphql/client/erubis_enhancer"
module GraphQL
class Client
# Allows a magic namespace to map to app/views/**/*.erb files to retrieve
# statically defined GraphQL definitions.
#
# # app/views/users/show.html.erb
# <%grapql
# fragment UserFragment on User { }
# %>
#
# # Loads graphql section from app/views/users/show.html.erb
# Views::Users::Show::UserFragment
#
module ViewModule
attr_accessor :client
# Public: Extract GraphQL section from ERB template.
#
# src - String ERB text
#
# Returns String GraphQL query and line number or nil or no section was
# defined.
def self.extract_graphql_section(src)
query_string = src.scan(/<%graphql([^%]+)%>/).flatten.first
return nil unless query_string
[query_string, Regexp.last_match.pre_match.count("\n") + 1]
end
# Public: Eager load module and all subdependencies.
#
# Use in production when cache_classes is true.
#
# Traverses all app/views/**/*.erb and loads all static constants defined in
# ERB files.
#
# Examples
#
# Views.eager_load!
#
# Returns nothing.
def eager_load!
return unless File.directory?(load_path)
Dir.entries(load_path).sort.each do |entry|
next if entry == "." || entry == ".."
name = entry.sub(/(\.\w+)+$/, "").camelize.to_sym
if ViewModule.valid_constant_name?(name)
mod = const_defined?(name, false) ? const_get(name) : load_and_set_module(name)
mod.eager_load! if mod
end
end
nil
end
# Internal: Check if name is a valid Ruby constant identifier.
#
# name - String or Symbol constant name
#
# Examples
#
# valid_constant_name?("Foo") #=> true
# valid_constant_name?("404") #=> false
#
# Returns true if name is a valid constant, otherwise false if name would
# result in a "NameError: wrong constant name".
def self.valid_constant_name?(name)
name.to_s =~ /^[A-Z][a-zA-Z0-9_]*$/
end
# Public: Directory to retrieve nested GraphQL definitions from.
#
# Returns absolute String path under app/views.
attr_accessor :load_path
alias_method :path=, :load_path=
alias_method :path, :load_path
# Public: if this module was defined by a view
#
# Returns absolute String path under app/views.
attr_accessor :source_path
# Internal: Initialize new module for constant name and load ERB statics.
#
# name - String or Symbol constant name.
#
# Examples
#
# Views::Users.load_module(:Profile)
# Views::Users::Profile.load_module(:Show)
#
# Returns new Module implementing Loadable concern.
def load_module(name)
pathname = ActiveSupport::Inflector.underscore(name.to_s)
path = Dir[File.join(load_path, "{#{pathname},_#{pathname}}{.*}")].sort.map { |fn| File.expand_path(fn) }.first
return if !path || File.extname(path) != ".erb"
contents = File.read(path)
query, lineno = ViewModule.extract_graphql_section(contents)
return unless query
mod = client.parse(query, path, lineno)
mod.extend(ViewModule)
mod.load_path = File.join(load_path, pathname)
mod.source_path = path
mod.client = client
mod
end
def placeholder_module(name)
dirname = File.join(load_path, ActiveSupport::Inflector.underscore(name.to_s))
return nil unless Dir.exist?(dirname)
Module.new.tap do |mod|
mod.extend(ViewModule)
mod.load_path = dirname
mod.client = client
end
end
def load_and_set_module(name)
placeholder = placeholder_module(name)
const_set(name, placeholder) if placeholder
mod = load_module(name)
return placeholder unless mod
remove_const(name) if placeholder
const_set(name, mod)
mod.unloadable
mod
end
# Public: Implement constant missing hook to autoload View ERB statics.
#
# name - String or Symbol constant name
#
# Returns module or raises NameError if missing.
def const_missing(name)
load_and_set_module(name) || super
end
end
end
end
ruby-graphql-client-0.16.0/lib/rubocop/ 0000775 0000000 0000000 00000000000 14000335401 0017674 5 ustar 00root root 0000000 0000000 ruby-graphql-client-0.16.0/lib/rubocop/cop/ 0000775 0000000 0000000 00000000000 14000335401 0020455 5 ustar 00root root 0000000 0000000 ruby-graphql-client-0.16.0/lib/rubocop/cop/graphql/ 0000775 0000000 0000000 00000000000 14000335401 0022113 5 ustar 00root root 0000000 0000000 ruby-graphql-client-0.16.0/lib/rubocop/cop/graphql/heredoc.rb 0000664 0000000 0000000 00000002023 14000335401 0024046 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "rubocop"
module RuboCop
module Cop
module GraphQL
# Public: Cop for enforcing non-interpolated GRAPHQL heredocs.
class Heredoc < Cop
def on_dstr(node)
check_str(node)
end
def on_str(node)
check_str(node)
end
def check_str(node)
return unless node.location.is_a?(Parser::Source::Map::Heredoc)
return unless node.location.expression.source =~ /^<<(-|~)?GRAPHQL/
node.each_child_node(:begin) do |begin_node|
add_offense(begin_node, location: :expression, message: "Do not interpolate variables into GraphQL queries, " \
"used variables instead.")
end
add_offense(node, location: :expression, message: "GraphQL heredocs should be quoted. <<-'GRAPHQL'")
end
def autocorrect(node)
->(corrector) do
corrector.replace(node.location.expression, "<<-'GRAPHQL'")
end
end
end
end
end
end
ruby-graphql-client-0.16.0/lib/rubocop/cop/graphql/overfetch.rb 0000664 0000000 0000000 00000004656 14000335401 0024440 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "active_support/inflector"
require "graphql"
require "graphql/client/view_module"
require "rubocop"
module RuboCop
module Cop
module GraphQL
# Public: Rubocop for catching overfetched fields in ERB templates.
class Overfetch < Cop
if defined?(RangeHelp)
# rubocop 0.53 moved the #source_range method into this module
include RangeHelp
end
def_node_search :send_methods, "({send csend block_pass} ...)"
def investigate(processed_source)
erb = File.read(processed_source.buffer.name)
query, = ::GraphQL::Client::ViewModule.extract_graphql_section(erb)
return unless query
aliases = {}
fields = {}
ranges = {}
# TODO: Use GraphQL client parser
document = ::GraphQL.parse(query.gsub(/::/, "__"))
visitor = ::GraphQL::Language::Visitor.new(document)
visitor[::GraphQL::Language::Nodes::Field] << ->(node, _parent) do
name = node.alias || node.name
fields[name] ||= 0
field_aliases(name).each { |n| (aliases[n] ||= []) << name }
ranges[name] ||= source_range(processed_source.buffer, node.line, 0)
end
visitor.visit
send_methods(processed_source.ast).each do |node|
method_names = method_names_for(*node)
method_names.each do |method_name|
aliases.fetch(method_name, []).each do |field_name|
fields[field_name] += 1
end
end
end
fields.each do |field, count|
next if count > 0
add_offense(nil, location: ranges[field], message: "GraphQL field '#{field}' query but was not used in template.")
end
end
def field_aliases(name)
names = Set.new
names << name
names << "#{name}?"
names << underscore_name = ActiveSupport::Inflector.underscore(name)
names << "#{underscore_name}?"
names
end
def method_names_for(*node)
receiver, method_name, *_args = node
method_names = []
method_names << method_name if method_name
# add field accesses like `nodes.map(&:field)`
method_names.concat(receiver.children) if receiver && receiver.sym_type?
method_names.map!(&:to_s)
end
end
end
end
end