summaryrefslogtreecommitdiff
path: root/lib/gitlab/graphql
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-06-16 18:25:58 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-06-16 18:25:58 +0000
commita5f4bba440d7f9ea47046a0a561d49adf0a1e6d4 (patch)
treefb69158581673816a8cd895f9d352dcb3c678b1e /lib/gitlab/graphql
parentd16b2e8639e99961de6ddc93909f3bb5c1445ba1 (diff)
downloadgitlab-ce-a5f4bba440d7f9ea47046a0a561d49adf0a1e6d4.tar.gz
Add latest changes from gitlab-org/gitlab@14-0-stable-eev14.0.0-rc42
Diffstat (limited to 'lib/gitlab/graphql')
-rw-r--r--lib/gitlab/graphql/authorize/authorize_resource.rb3
-rw-r--r--lib/gitlab/graphql/deprecation.rb9
-rw-r--r--lib/gitlab/graphql/docs/helper.rb434
-rw-r--r--lib/gitlab/graphql/docs/renderer.rb54
-rw-r--r--lib/gitlab/graphql/docs/templates/default.md.haml224
-rw-r--r--lib/gitlab/graphql/standard_graphql_error.rb10
6 files changed, 18 insertions, 716 deletions
diff --git a/lib/gitlab/graphql/authorize/authorize_resource.rb b/lib/gitlab/graphql/authorize/authorize_resource.rb
index 4d575b964e5..dc49c806398 100644
--- a/lib/gitlab/graphql/authorize/authorize_resource.rb
+++ b/lib/gitlab/graphql/authorize/authorize_resource.rb
@@ -51,14 +51,11 @@ module Gitlab
object
end
- # authorizes the object using the current class authorization.
def authorize!(object)
raise_resource_not_available_error! unless authorized_resource?(object)
end
def authorized_resource?(object)
- # Sanity check. We don't want to accidentally allow a developer to authorize
- # without first adding permissions to authorize against
raise ConfigurationError, "#{self.class.name} has no authorizations" if self.class.authorization.none?
self.class.authorization.ok?(object, current_user)
diff --git a/lib/gitlab/graphql/deprecation.rb b/lib/gitlab/graphql/deprecation.rb
index 8b73eeb4e52..20068758502 100644
--- a/lib/gitlab/graphql/deprecation.rb
+++ b/lib/gitlab/graphql/deprecation.rb
@@ -41,7 +41,7 @@ module Gitlab
parts = [
"#{deprecated_in(format: :markdown)}.",
reason_text,
- replacement.then { |r| "Use: [`#{r}`](##{r.downcase.tr('.', '')})." if r }
+ replacement_markdown.then { |r| "Use: #{r}." if r }
].compact
case context
@@ -52,6 +52,13 @@ module Gitlab
end
end
+ def replacement_markdown
+ return unless replacement.present?
+ return "`#{replacement}`" unless replacement.include?('.') # only fully qualified references can be linked
+
+ "[`#{replacement}`](##{replacement.downcase.tr('.', '')})"
+ end
+
def edit_description(original_description)
@original_description = original_description
return unless original_description
diff --git a/lib/gitlab/graphql/docs/helper.rb b/lib/gitlab/graphql/docs/helper.rb
deleted file mode 100644
index b598b605141..00000000000
--- a/lib/gitlab/graphql/docs/helper.rb
+++ /dev/null
@@ -1,434 +0,0 @@
-# frozen_string_literal: true
-
-return if Rails.env.production?
-
-module Gitlab
- module Graphql
- module Docs
- # We assume a few things about the schema. We use the graphql-ruby gem, which enforces:
- # - All mutations have a single input field named 'input'
- # - All mutations have a payload type, named after themselves
- # - All mutations have an input type, named after themselves
- # If these things change, then some of this code will break. Such places
- # are guarded with an assertion that our assumptions are not violated.
- ViolatedAssumption = Class.new(StandardError)
-
- SUGGESTED_ACTION = <<~MSG
- We expect it to be impossible to violate our assumptions about
- how mutation arguments work.
-
- If that is not the case, then something has probably changed in the
- way we generate our schema, perhaps in the library we use: graphql-ruby
-
- Please ask for help in the #f_graphql or #backend channels.
- MSG
-
- CONNECTION_ARGS = %w[after before first last].to_set
-
- FIELD_HEADER = <<~MD
- #### Fields
-
- | Name | Type | Description |
- | ---- | ---- | ----------- |
- MD
-
- ARG_HEADER = <<~MD
- # Arguments
-
- | Name | Type | Description |
- | ---- | ---- | ----------- |
- MD
-
- CONNECTION_NOTE = <<~MD
- This field returns a [connection](#connections). It accepts the
- four standard [pagination arguments](#connection-pagination-arguments):
- `before: String`, `after: String`, `first: Int`, `last: Int`.
- MD
-
- # Helper with functions to be used by HAML templates
- # This includes graphql-docs gem helpers class.
- # You can check the included module on: https://github.com/gjtorikian/graphql-docs/blob/v1.6.0/lib/graphql-docs/helpers.rb
- module Helper
- include GraphQLDocs::Helpers
- include Gitlab::Utils::StrongMemoize
-
- def auto_generated_comment
- <<-MD.strip_heredoc
- ---
- stage: Plan
- group: Project Management
- info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
- ---
-
- <!---
- This documentation is auto generated by a script.
-
- Please do not edit this file directly, check compile_docs task on lib/tasks/gitlab/graphql.rake.
- --->
- MD
- end
-
- # Template methods:
- # Methods that return chunks of Markdown for insertion into the document
-
- def render_full_field(field, heading_level: 3, owner: nil)
- conn = connection?(field)
- args = field[:arguments].reject { |arg| conn && CONNECTION_ARGS.include?(arg[:name]) }
- arg_owner = [owner, field[:name]]
-
- chunks = [
- render_name_and_description(field, level: heading_level, owner: owner),
- render_return_type(field),
- render_input_type(field),
- render_connection_note(field),
- render_argument_table(heading_level, args, arg_owner),
- render_return_fields(field, owner: owner)
- ]
-
- join(:block, chunks)
- end
-
- def render_argument_table(level, args, owner)
- arg_header = ('#' * level) + ARG_HEADER
- render_field_table(arg_header, args, owner)
- end
-
- def render_name_and_description(object, owner: nil, level: 3)
- content = []
-
- heading = '#' * level
- name = [owner, object[:name]].compact.join('.')
-
- content << "#{heading} `#{name}`"
- content << render_description(object, owner, :block)
-
- join(:block, content)
- end
-
- def render_object_fields(fields, owner:, level_bump: 0)
- return if fields.blank?
-
- (with_args, no_args) = fields.partition { |f| args?(f) }
- type_name = owner[:name] if owner
- header_prefix = '#' * level_bump
- sections = [
- render_simple_fields(no_args, type_name, header_prefix),
- render_fields_with_arguments(with_args, type_name, header_prefix)
- ]
-
- join(:block, sections)
- end
-
- def render_enum_value(enum, value)
- render_row(render_name(value, enum[:name]), render_description(value, enum[:name], :inline))
- end
-
- def render_union_member(member)
- "- [`#{member}`](##{member.downcase})"
- end
-
- # QUERIES:
-
- # Methods that return parts of the schema, or related information:
-
- def connection_object_types
- objects.select { |t| t[:is_edge] || t[:is_connection] }
- end
-
- def object_types
- objects.reject { |t| t[:is_edge] || t[:is_connection] || t[:is_payload] }
- end
-
- def interfaces
- graphql_interface_types.map { |t| t.merge(fields: t[:fields] + t[:connections]) }
- end
-
- def fields_of(type_name)
- graphql_operation_types
- .find { |type| type[:name] == type_name }
- .values_at(:fields, :connections)
- .flatten
- .then { |fields| sorted_by_name(fields) }
- end
-
- # Place the arguments of the input types on the mutation itself.
- # see: `#input_types` - this method must not call `#input_types` to avoid mutual recursion
- def mutations
- @mutations ||= sorted_by_name(graphql_mutation_types).map do |t|
- inputs = t[:input_fields]
- input = inputs.first
- name = t[:name]
-
- assert!(inputs.one?, "Expected exactly 1 input field named #{name}. Found #{inputs.count} instead.")
- assert!(input[:name] == 'input', "Expected the input of #{name} to be named 'input'")
-
- input_type_name = input[:type][:name]
- input_type = graphql_input_object_types.find { |t| t[:name] == input_type_name }
- assert!(input_type.present?, "Cannot find #{input_type_name} for #{name}.input")
-
- arguments = input_type[:input_fields]
- seen_type!(input_type_name)
- t.merge(arguments: arguments)
- end
- end
-
- # We assume that the mutations have been processed first, marking their
- # inputs as `seen_type?`
- def input_types
- mutations # ensure that mutations have seen their inputs first
- graphql_input_object_types.reject { |t| seen_type?(t[:name]) }
- end
-
- # We ignore the built-in enum types, and sort values by name
- def enums
- graphql_enum_types
- .reject { |type| type[:values].empty? }
- .reject { |enum_type| enum_type[:name].start_with?('__') }
- .map { |type| type.merge(values: sorted_by_name(type[:values])) }
- end
-
- private # DO NOT CALL THESE METHODS IN TEMPLATES
-
- # Template methods
-
- def render_return_type(query)
- return unless query[:type] # for example, mutations
-
- "Returns #{render_field_type(query[:type])}."
- end
-
- def render_simple_fields(fields, type_name, header_prefix)
- render_field_table(header_prefix + FIELD_HEADER, fields, type_name)
- end
-
- def render_fields_with_arguments(fields, type_name, header_prefix)
- return if fields.empty?
-
- level = 5 + header_prefix.length
- sections = sorted_by_name(fields).map do |f|
- render_full_field(f, heading_level: level, owner: type_name)
- end
-
- <<~MD.chomp
- #{header_prefix}#### Fields with arguments
-
- #{join(:block, sections)}
- MD
- end
-
- def render_field_table(header, fields, owner)
- return if fields.empty?
-
- fields = sorted_by_name(fields)
- header + join(:table, fields.map { |f| render_field(f, owner) })
- end
-
- def render_field(field, owner)
- render_row(
- render_name(field, owner),
- render_field_type(field[:type]),
- render_description(field, owner, :inline)
- )
- end
-
- def render_return_fields(mutation, owner:)
- fields = mutation[:return_fields]
- return if fields.blank?
-
- name = owner.to_s + mutation[:name]
- render_object_fields(fields, owner: { name: name })
- end
-
- def render_connection_note(field)
- return unless connection?(field)
-
- CONNECTION_NOTE.chomp
- end
-
- def render_row(*values)
- "| #{values.map { |val| val.to_s.squish }.join(' | ')} |"
- end
-
- def render_name(object, owner = nil)
- rendered_name = "`#{object[:name]}`"
- rendered_name += ' **{warning-solid}**' if deprecated?(object, owner)
-
- return rendered_name unless owner
-
- owner = Array.wrap(owner).join('')
- id = (owner + object[:name]).downcase
-
- %(<a id="#{id}"></a>) + rendered_name
- end
-
- # Returns the object description. If the object has been deprecated,
- # the deprecation reason will be returned in place of the description.
- def render_description(object, owner = nil, context = :block)
- if deprecated?(object, owner)
- render_deprecation(object, owner, context)
- else
- render_description_of(object, owner, context)
- end
- end
-
- def deprecated?(object, owner)
- return true if object[:is_deprecated] # only populated for fields, not arguments!
-
- key = [*Array.wrap(owner), object[:name]].join('.')
- deprecations.key?(key)
- end
-
- def render_description_of(object, owner, context = nil)
- desc = if object[:is_edge]
- base = object[:name].chomp('Edge')
- "The edge type for [`#{base}`](##{base.downcase})."
- elsif object[:is_connection]
- base = object[:name].chomp('Connection')
- "The connection type for [`#{base}`](##{base.downcase})."
- else
- object[:description]&.strip
- end
-
- return if desc.blank?
-
- desc += '.' unless desc.ends_with?('.')
- see = doc_reference(object, owner)
- desc += " #{see}" if see
- desc += " (see [Connections](#connections))" if connection?(object) && context != :block
- desc
- end
-
- def doc_reference(object, owner)
- field = schema_field(owner, object[:name]) if owner
- return unless field
-
- ref = field.try(:doc_reference)
- return if ref.blank?
-
- parts = ref.to_a.map do |(title, url)|
- "[#{title.strip}](#{url.strip})"
- end
-
- "See #{parts.join(', ')}."
- end
-
- def render_deprecation(object, owner, context)
- buff = []
- deprecation = schema_deprecation(owner, object[:name])
-
- buff << (deprecation&.original_description || render_description_of(object, owner)) if context == :block
- buff << if deprecation
- deprecation.markdown(context: context)
- else
- "**Deprecated:** #{object[:deprecation_reason]}"
- end
-
- join(context, buff)
- end
-
- def render_field_type(type)
- "[`#{type[:info]}`](##{type[:name].downcase})"
- end
-
- def join(context, chunks)
- chunks.compact!
- return if chunks.blank?
-
- case context
- when :block
- chunks.join("\n\n")
- when :inline
- chunks.join(" ").squish.presence
- when :table
- chunks.join("\n")
- end
- end
-
- # Queries
-
- def sorted_by_name(objects)
- return [] unless objects.present?
-
- objects.sort_by { |o| o[:name] }
- end
-
- def connection?(field)
- type_name = field.dig(:type, :name)
- type_name.present? && type_name.ends_with?('Connection')
- end
-
- # We are ignoring connections and built in types for now,
- # they should be added when queries are generated.
- def objects
- strong_memoize(:objects) do
- mutations = schema.mutation&.fields&.keys&.to_set || []
-
- graphql_object_types
- .reject { |object_type| object_type[:name]["__"] || object_type[:name] == 'Subscription' } # We ignore introspection and subscription types.
- .map do |type|
- name = type[:name]
- type.merge(
- is_edge: name.ends_with?('Edge'),
- is_connection: name.ends_with?('Connection'),
- is_payload: name.ends_with?('Payload') && mutations.include?(name.chomp('Payload').camelcase(:lower)),
- fields: type[:fields] + type[:connections]
- )
- end
- end
- end
-
- def args?(field)
- args = field[:arguments]
- return false if args.blank?
- return true unless connection?(field)
-
- args.any? { |arg| CONNECTION_ARGS.exclude?(arg[:name]) }
- end
-
- # returns the deprecation information for a field or argument
- # See: Gitlab::Graphql::Deprecation
- def schema_deprecation(type_name, field_name)
- key = [*Array.wrap(type_name), field_name].join('.')
- deprecations[key]
- end
-
- def render_input_type(query)
- input_field = query[:input_fields]&.first
- return unless input_field
-
- "Input type: `#{input_field[:type][:name]}`"
- end
-
- def schema_field(type_name, field_name)
- type = schema.types[type_name]
- return unless type && type.kind.fields?
-
- type.fields[field_name]
- end
-
- def deprecations
- strong_memoize(:deprecations) do
- mapping = {}
-
- schema.types.each do |type_name, type|
- next unless type.kind.fields?
-
- type.fields.each do |field_name, field|
- mapping["#{type_name}.#{field_name}"] = field.try(:deprecation)
- field.arguments.each do |arg_name, arg|
- mapping["#{type_name}.#{field_name}.#{arg_name}"] = arg.try(:deprecation)
- end
- end
- end
-
- mapping.compact
- end
- end
-
- def assert!(claim, message)
- raise ViolatedAssumption, "#{message}\n#{SUGGESTED_ACTION}" unless claim
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/graphql/docs/renderer.rb b/lib/gitlab/graphql/docs/renderer.rb
deleted file mode 100644
index ae0898e6198..00000000000
--- a/lib/gitlab/graphql/docs/renderer.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-return if Rails.env.production?
-
-module Gitlab
- module Graphql
- module Docs
- # Gitlab renderer for graphql-docs.
- # Uses HAML templates to parse markdown and generate .md files.
- # It uses graphql-docs helpers and schema parser, more information in https://github.com/gjtorikian/graphql-docs.
- #
- # Arguments:
- # schema - the GraphQL schema definition. For GitLab should be: GitlabSchema
- # output_dir: The folder where the markdown files will be saved
- # template: The path of the haml template to be parsed
- class Renderer
- include Gitlab::Graphql::Docs::Helper
-
- attr_reader :schema
-
- def initialize(schema, output_dir:, template:)
- @output_dir = output_dir
- @template = template
- @layout = Haml::Engine.new(File.read(template))
- @parsed_schema = GraphQLDocs::Parser.new(schema.graphql_definition, {}).parse
- @schema = schema
- @seen = Set.new
- end
-
- def contents
- # Render and remove an extra trailing new line
- @contents ||= @layout.render(self).sub!(/\n(?=\Z)/, '')
- end
-
- def write
- filename = File.join(@output_dir, 'index.md')
-
- FileUtils.mkdir_p(@output_dir)
- File.write(filename, contents)
- end
-
- private
-
- def seen_type?(name)
- @seen.include?(name)
- end
-
- def seen_type!(name)
- @seen << name
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/graphql/docs/templates/default.md.haml b/lib/gitlab/graphql/docs/templates/default.md.haml
deleted file mode 100644
index 7d42fb3a9f8..00000000000
--- a/lib/gitlab/graphql/docs/templates/default.md.haml
+++ /dev/null
@@ -1,224 +0,0 @@
--# haml-lint:disable UnnecessaryStringOutput
-
-= auto_generated_comment
-
-:plain
- # GraphQL API Resources
-
- This documentation is self-generated based on GitLab current GraphQL schema.
-
- The API can be explored interactively using the [GraphiQL IDE](../index.md#graphiql).
-
- Each table below documents a GraphQL type. Types match loosely to models, but not all
- fields and methods on a model are available via GraphQL.
-
- WARNING:
- Fields that are deprecated are marked with **{warning-solid}**.
- Items (fields, enums, etc) that have been removed according to our [deprecation process](../index.md#deprecation-and-removal-process) can be found
- in [Removed Items](../removed_items.md).
-
- <!-- vale off -->
- <!-- Docs linting disabled after this line. -->
- <!-- See https://docs.gitlab.com/ee/development/documentation/testing.html#disable-vale-tests -->
-\
-
-:plain
- ## `Query` type
-
- The `Query` type contains the API's top-level entry points for all executable queries.
-\
-
-- fields_of('Query').each do |field|
- = render_full_field(field, heading_level: 3, owner: 'Query')
- \
-
-:plain
- ## `Mutation` type
-
- The `Mutation` type contains all the mutations you can execute.
-
- All mutations receive their arguments in a single input object named `input`, and all mutations
- support at least a return field `errors` containing a list of error messages.
-
- All input objects may have a `clientMutationId: String` field, identifying the mutation.
-
- For example:
-
- ```graphql
- mutation($id: NoteableID!, $body: String!) {
- createNote(input: { noteableId: $id, body: $body }) {
- errors
- }
- }
- ```
-\
-
-- mutations.each do |field|
- = render_full_field(field, heading_level: 3, owner: 'Mutation')
- \
-
-:plain
- ## Connections
-
- Some types in our schema are `Connection` types - they represent a paginated
- collection of edges between two nodes in the graph. These follow the
- [Relay cursor connections specification](https://relay.dev/graphql/connections.htm).
-
- ### Pagination arguments {#connection-pagination-arguments}
-
- All connection fields support the following pagination arguments:
-
- | Name | Type | Description |
- |------|------|-------------|
- | `after` | [`String`](#string) | Returns the elements in the list that come after the specified cursor. |
- | `before` | [`String`](#string) | Returns the elements in the list that come before the specified cursor. |
- | `first` | [`Int`](#int) | Returns the first _n_ elements from the list. |
- | `last` | [`Int`](#int) | Returns the last _n_ elements from the list. |
-
- Since these arguments are common to all connection fields, they are not repeated for each connection.
-
- ### Connection fields
-
- All connections have at least the following fields:
-
- | Name | Type | Description |
- |------|------|-------------|
- | `pageInfo` | [`PageInfo!`](#pageinfo) | Pagination information. |
- | `edges` | `[edge!]` | The edges. |
- | `nodes` | `[item!]` | The items in the current page. |
-
- The precise type of `Edge` and `Item` depends on the kind of connection. A
- [`ProjectConnection`](#projectconnection) will have nodes that have the type
- [`[Project!]`](#project), and edges that have the type [`ProjectEdge`](#projectedge).
-
- ### Connection types
-
- Some of the types in the schema exist solely to model connections. Each connection
- has a distinct, named type, with a distinct named edge type. These are listed separately
- below.
-\
-
-- connection_object_types.each do |type|
- = render_name_and_description(type, level: 4)
- \
- = render_object_fields(type[:fields], owner: type, level_bump: 1)
- \
-
-:plain
- ## Object types
-
- Object types represent the resources that the GitLab GraphQL API can return.
- They contain _fields_. Each field has its own type, which will either be one of the
- basic GraphQL [scalar types](https://graphql.org/learn/schema/#scalar-types)
- (e.g.: `String` or `Boolean`) or other object types. Fields may have arguments.
- Fields with arguments are exactly like top-level queries, and are listed beneath
- the table of fields for each object type.
-
- For more information, see
- [Object Types and Fields](https://graphql.org/learn/schema/#object-types-and-fields)
- on `graphql.org`.
-\
-
-- object_types.each do |type|
- = render_name_and_description(type)
- \
- = render_object_fields(type[:fields], owner: type)
- \
-
-:plain
- ## Enumeration types
-
- Also called _Enums_, enumeration types are a special kind of scalar that
- is restricted to a particular set of allowed values.
-
- For more information, see
- [Enumeration Types](https://graphql.org/learn/schema/#enumeration-types)
- on `graphql.org`.
-\
-
-- enums.each do |enum|
- = render_name_and_description(enum)
- \
- ~ "| Value | Description |"
- ~ "| ----- | ----------- |"
- - enum[:values].each do |value|
- = render_enum_value(enum, value)
- \
-
-:plain
- ## Scalar types
-
- Scalar values are atomic values, and do not have fields of their own.
- Basic scalars include strings, boolean values, and numbers. This schema also
- defines various custom scalar values, such as types for times and dates.
-
- This schema includes custom scalar types for identifiers, with a specific type for
- each kind of object.
-
- For more information, read about [Scalar Types](https://graphql.org/learn/schema/#scalar-types) on `graphql.org`.
-\
-
-- graphql_scalar_types.each do |type|
- = render_name_and_description(type)
- \
-
-:plain
- ## Abstract types
-
- Abstract types (unions and interfaces) are ways the schema can represent
- values that may be one of several concrete types.
-
- - A [`Union`](https://graphql.org/learn/schema/#union-types) is a set of possible types.
- The types might not have any fields in common.
- - An [`Interface`](https://graphql.org/learn/schema/#interfaces) is a defined set of fields.
- Types may `implement` an interface, which
- guarantees that they have all the fields in the set. A type may implement more than
- one interface.
-
- See the [GraphQL documentation](https://graphql.org/learn/) for more information on using
- abstract types.
-\
-
-:plain
- ### Unions
-\
-
-- graphql_union_types.each do |type|
- = render_name_and_description(type, level: 4)
- \
- One of:
- \
- - type[:possible_types].each do |member|
- = render_union_member(member)
- \
-
-:plain
- ### Interfaces
-\
-
-- interfaces.each do |type|
- = render_name_and_description(type, level: 4)
- \
- Implementations:
- \
- - type[:implemented_by].each do |type_name|
- ~ "- [`#{type_name}`](##{type_name.downcase})"
- \
- = render_object_fields(type[:fields], owner: type, level_bump: 1)
- \
-
-:plain
- ## Input types
-
- Types that may be used as arguments (all scalar types may also
- be used as arguments).
-
- Only general use input types are listed here. For mutation input types,
- see the associated mutation type above.
-\
-
-- input_types.each do |type|
- = render_name_and_description(type)
- \
- = render_argument_table(3, type[:input_fields], type[:name])
- \
diff --git a/lib/gitlab/graphql/standard_graphql_error.rb b/lib/gitlab/graphql/standard_graphql_error.rb
new file mode 100644
index 00000000000..8364c232af2
--- /dev/null
+++ b/lib/gitlab/graphql/standard_graphql_error.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# rubocop:disable Cop/CustomErrorClass
+
+module Gitlab
+ module Graphql
+ class StandardGraphqlError < StandardError
+ end
+ end
+end