Commit c26c620b authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch 'ajk-globalid-compatibilty' into 'master'

[GraphQL] GlobalID / ID compatibility layer

See merge request gitlab-org/gitlab!36209
parents 761542d3 abb20b6f
...@@ -4,6 +4,7 @@ module Mutations ...@@ -4,6 +4,7 @@ module Mutations
class BaseMutation < GraphQL::Schema::RelayClassicMutation class BaseMutation < GraphQL::Schema::RelayClassicMutation
prepend Gitlab::Graphql::Authorize::AuthorizeResource prepend Gitlab::Graphql::Authorize::AuthorizeResource
prepend Gitlab::Graphql::CopyFieldDescription prepend Gitlab::Graphql::CopyFieldDescription
prepend ::Gitlab::Graphql::GlobalIDCompatibility
ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance' ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance'
......
...@@ -3,13 +3,18 @@ ...@@ -3,13 +3,18 @@
module Mutations module Mutations
module Ci module Ci
class Base < BaseMutation class Base < BaseMutation
argument :id, ::Types::GlobalIDType[::Ci::Pipeline], PipelineID = ::Types::GlobalIDType[::Ci::Pipeline]
argument :id, PipelineID,
required: true, required: true,
description: 'The id of the pipeline to mutate' description: 'The id of the pipeline to mutate'
private private
def find_object(id:) def find_object(id:)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = PipelineID.coerce_isolated_input(id)
GlobalID::Locator.locate(id) GlobalID::Locator.locate(id)
end end
end end
......
...@@ -29,11 +29,18 @@ module Mutations ...@@ -29,11 +29,18 @@ module Mutations
private private
def parameters(**args) def parameters(**args)
args.transform_values { |id| GitlabSchema.find_by_gid(id) }.transform_values(&:sync).tap do |hash| args.transform_values { |id| find_design(id) }.transform_values(&:sync).tap do |hash|
hash.each { |k, design| not_found(args[k]) unless current_user.can?(:read_design, design) } hash.each { |k, design| not_found(args[k]) unless current_user.can?(:read_design, design) }
end end
end end
def find_design(id)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = DesignID.coerce_isolated_input(id)
GitlabSchema.object_from_id(id)
end
def not_found(gid) def not_found(gid)
raise Gitlab::Graphql::Errors::ResourceNotAvailable, "Resource not available: #{gid}" raise Gitlab::Graphql::Errors::ResourceNotAvailable, "Resource not available: #{gid}"
end end
......
...@@ -4,6 +4,7 @@ module Resolvers ...@@ -4,6 +4,7 @@ module Resolvers
class BaseResolver < GraphQL::Schema::Resolver class BaseResolver < GraphQL::Schema::Resolver
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
include ::Gitlab::Graphql::GlobalIDCompatibility
def self.single def self.single
@single ||= Class.new(self) do @single ||= Class.new(self) do
......
# frozen_string_literal: true # frozen_string_literal: true
module GraphQLExtensions
module ScalarExtensions
# Allow ID to unify with GlobalID Types
def ==(other)
if name == 'ID' && other.is_a?(self.class) &&
other.type_class.ancestors.include?(::Types::GlobalIDType)
return true
end
super
end
end
end
::GraphQL::ScalarType.prepend(GraphQLExtensions::ScalarExtensions)
module Types module Types
class GlobalIDType < BaseScalar class GlobalIDType < BaseScalar
graphql_name 'GlobalID' graphql_name 'GlobalID'
......
...@@ -49,8 +49,7 @@ module Types ...@@ -49,8 +49,7 @@ module Types
field :milestone, ::Types::MilestoneType, field :milestone, ::Types::MilestoneType,
null: true, null: true,
description: 'Find a milestone', description: 'Find a milestone' do
resolve: -> (_obj, args, _ctx) { GitlabSchema.find_by_gid(args[:id]) } do
argument :id, ::Types::GlobalIDType[Milestone], argument :id, ::Types::GlobalIDType[Milestone],
required: true, required: true,
description: 'Find a milestone by its ID' description: 'Find a milestone by its ID'
...@@ -86,7 +85,17 @@ module Types ...@@ -86,7 +85,17 @@ module Types
end end
def issue(id:) def issue(id:)
GitlabSchema.object_from_id(id, expected_type: ::Issue) # TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[::Issue].coerce_isolated_input(id)
GitlabSchema.find_by_gid(id)
end
def milestone(id:)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[Milestone].coerce_isolated_input(id)
GitlabSchema.find_by_gid(id)
end end
end end
end end
......
...@@ -49,6 +49,20 @@ See also: ...@@ -49,6 +49,20 @@ See also:
- [Exposing Global IDs](#exposing-global-ids). - [Exposing Global IDs](#exposing-global-ids).
- [Mutation arguments](#object-identifier-arguments). - [Mutation arguments](#object-identifier-arguments).
We have a custom scalar type (`Types::GlobalIDType`) which should be used as the
type of input and output arguments when the value is a `GlobalID`. The benefits
of using this type instead of `ID` are:
- it validates that the value is a `GlobalID`
- it parses it into a `GlobalID` before passing it to user code
- it can be parameterized on the type of the object (e.g.
`GlobalIDType[Project]`) which offers even better validation and security.
Consider using this type for all new arguments and result types. Remember that
it is perfectly possible to parameterize this type with a concern or a
supertype, if you want to accept a wider range of objects (e.g.
`GlobalIDType[Issuable]` vs `GlobalIDType[Issue]`).
## Types ## Types
We use a code-first schema, and we declare what type everything is in Ruby. We use a code-first schema, and we declare what type everything is in Ruby.
......
...@@ -8,8 +8,10 @@ module EE ...@@ -8,8 +8,10 @@ module EE
extend ActiveSupport::Concern extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
EpicID = ::Types::GlobalIDType[::Epic]
prepended do prepended do
argument :epic_id, ::Types::GlobalIDType[::Epic], argument :epic_id, EpicID,
required: false, required: false,
description: 'The ID of the parent epic. NULL when removing the association' description: 'The ID of the parent epic. NULL when removing the association'
end end
...@@ -28,10 +30,12 @@ module EE ...@@ -28,10 +30,12 @@ module EE
override :move_arguments override :move_arguments
def move_arguments(args) def move_arguments(args)
allowed_args = super # TODO: remove this line once the compatibility layer is removed
allowed_args[:epic_id] = args[:epic_id]&.model_id if args.has_key?(:epic_id) # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
coerce_global_id_arguments!(args)
epic_arguments = args.slice(:epic_id).transform_values { |id| id&.model_id }
allowed_args super.merge!(epic_arguments)
end end
end end
end end
......
...@@ -87,7 +87,10 @@ module EE ...@@ -87,7 +87,10 @@ module EE
::Types::DastSiteProfileType, ::Types::DastSiteProfileType,
null: true, null: true,
resolve: -> (obj, args, _ctx) do resolve: -> (obj, args, _ctx) do
DastSiteProfilesFinder.new(project_id: obj.id, id: args[:id].model_id).execute.first # TODO: remove this coercion when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
gid = ::Types::GlobalIDType[::DastSiteProfile].coerce_isolated_input(args[:id])
DastSiteProfilesFinder.new(project_id: obj.id, id: gid.model_id).execute.first
end, end,
description: 'DAST Site Profile associated with the project' do description: 'DAST Site Profile associated with the project' do
argument :id, ::Types::GlobalIDType[::DastSiteProfile], required: true, description: 'ID of the site profile' argument :id, ::Types::GlobalIDType[::DastSiteProfile], required: true, description: 'ID of the site profile'
......
...@@ -8,7 +8,6 @@ module EE ...@@ -8,7 +8,6 @@ module EE
prepended do prepended do
field :iteration, ::Types::IterationType, field :iteration, ::Types::IterationType,
null: true, null: true,
resolve: -> (_obj, args, _ctx) { ::GitlabSchema.find_by_gid(args[:id]) },
description: 'Find an iteration' do description: 'Find an iteration' do
argument :id, ::Types::GlobalIDType[::Iteration], argument :id, ::Types::GlobalIDType[::Iteration],
required: true, required: true,
...@@ -24,8 +23,7 @@ module EE ...@@ -24,8 +23,7 @@ module EE
field :vulnerability, field :vulnerability,
::Types::VulnerabilityType, ::Types::VulnerabilityType,
null: true, null: true,
description: "Find a vulnerability", description: "Find a vulnerability" do
resolve: -> (_obj, args, _ctx) { ::GitlabSchema.find_by_gid(args[:id]) } do
argument :id, ::Types::GlobalIDType[::Vulnerability], argument :id, ::Types::GlobalIDType[::Vulnerability],
required: true, required: true,
description: 'The Global ID of the Vulnerability' description: 'The Global ID of the Vulnerability'
...@@ -54,6 +52,20 @@ module EE ...@@ -54,6 +52,20 @@ module EE
resolver: ::Resolvers::InstanceSecurityDashboardResolver, resolver: ::Resolvers::InstanceSecurityDashboardResolver,
description: 'Fields related to Instance Security Dashboard' description: 'Fields related to Instance Security Dashboard'
end end
def vulnerability(id:)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[::Vulnerability].coerce_isolated_input(id)
::GitlabSchema.find_by_gid(id)
end
def iteration(id:)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[Iteration].coerce_isolated_input(id)
::GitlabSchema.find_by_gid(id)
end
end end
end end
end end
...@@ -8,8 +8,9 @@ module Mutations ...@@ -8,8 +8,9 @@ module Mutations
authorize :create_cluster authorize :create_cluster
argument :cluster_agent_id, ClusterAgentID = ::Types::GlobalIDType[::Clusters::Agent]
::Types::GlobalIDType[::Clusters::Agent],
argument :cluster_agent_id, ClusterAgentID,
required: true, required: true,
description: 'Global ID of the cluster agent that will be associated with the new token' description: 'Global ID of the cluster agent that will be associated with the new token'
...@@ -42,6 +43,9 @@ module Mutations ...@@ -42,6 +43,9 @@ module Mutations
private private
def find_object(id:) def find_object(id:)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ClusterAgentID.coerce_isolated_input(id)
GitlabSchema.find_by_gid(id) GitlabSchema.find_by_gid(id)
end end
end end
......
...@@ -8,8 +8,9 @@ module Mutations ...@@ -8,8 +8,9 @@ module Mutations
authorize :admin_cluster authorize :admin_cluster
argument :id, TokenID = ::Types::GlobalIDType[::Clusters::AgentToken]
::Types::GlobalIDType[::Clusters::AgentToken],
argument :id, TokenID,
required: true, required: true,
description: 'Global ID of the cluster agent token that will be deleted' description: 'Global ID of the cluster agent token that will be deleted'
...@@ -23,6 +24,9 @@ module Mutations ...@@ -23,6 +24,9 @@ module Mutations
private private
def find_object(id:) def find_object(id:)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = TokenID.coerce_isolated_input(id)
GitlabSchema.find_by_gid(id) GitlabSchema.find_by_gid(id)
end end
end end
......
...@@ -8,8 +8,9 @@ module Mutations ...@@ -8,8 +8,9 @@ module Mutations
authorize :admin_cluster authorize :admin_cluster
argument :id, AgentID = ::Types::GlobalIDType[::Clusters::Agent]
::Types::GlobalIDType[::Clusters::Agent],
argument :id, AgentID,
required: true, required: true,
description: 'Global id of the cluster agent that will be deleted' description: 'Global id of the cluster agent that will be deleted'
...@@ -27,6 +28,9 @@ module Mutations ...@@ -27,6 +28,9 @@ module Mutations
private private
def find_object(id:) def find_object(id:)
# TODO: remove this line when the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = AgentID.coerce_isolated_input(id)
GitlabSchema.find_by_gid(id) GitlabSchema.find_by_gid(id)
end end
end end
......
...@@ -30,6 +30,10 @@ module Mutations ...@@ -30,6 +30,10 @@ module Mutations
def resolve(full_path:, dast_site_profile_id:, **args) def resolve(full_path:, dast_site_profile_id:, **args)
project = authorized_find_project!(full_path: full_path) project = authorized_find_project!(full_path: full_path)
# TODO: remove explicit coercion once compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
dast_site_profile_id = ::Types::GlobalIDType[::DastSiteProfile].coerce_isolated_input(dast_site_profile_id)
dast_site_profile = find_dast_site_profile(project: project, dast_site_profile_id: dast_site_profile_id) dast_site_profile = find_dast_site_profile(project: project, dast_site_profile_id: dast_site_profile_id)
dast_site = dast_site_profile.dast_site dast_site = dast_site_profile.dast_site
dast_scanner_profile = find_dast_scanner_profile(project: project, dast_scanner_profile_id: args[:dast_scanner_profile_id]) dast_scanner_profile = find_dast_scanner_profile(project: project, dast_scanner_profile_id: args[:dast_scanner_profile_id])
...@@ -63,6 +67,11 @@ module Mutations ...@@ -63,6 +67,11 @@ module Mutations
def find_dast_scanner_profile(project:, dast_scanner_profile_id:) def find_dast_scanner_profile(project:, dast_scanner_profile_id:)
return unless dast_scanner_profile_id return unless dast_scanner_profile_id
# TODO: remove explicit coercion once compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
dast_scanner_profile_id = ::Types::GlobalIDType[::DastScannerProfile]
.coerce_isolated_input(dast_scanner_profile_id)
project project
.dast_scanner_profiles .dast_scanner_profiles
.find(dast_scanner_profile_id.model_id) .find(dast_scanner_profile_id.model_id)
......
...@@ -7,17 +7,23 @@ module Mutations ...@@ -7,17 +7,23 @@ module Mutations
graphql_name 'DastScannerProfileDelete' graphql_name 'DastScannerProfileDelete'
ScannerProfileID = ::Types::GlobalIDType[::DastScannerProfile]
argument :full_path, GraphQL::ID_TYPE, argument :full_path, GraphQL::ID_TYPE,
required: true, required: true,
description: 'Full path for the project the scanner profile belongs to.' description: 'Full path for the project the scanner profile belongs to.'
argument :id, ::Types::GlobalIDType[::DastScannerProfile], argument :id, ScannerProfileID,
required: true, required: true,
description: 'ID of the scanner profile to be deleted.' description: 'ID of the scanner profile to be deleted.'
authorize :create_on_demand_dast_scan authorize :create_on_demand_dast_scan
def resolve(full_path:, id:) def resolve(full_path:, id:)
# TODO: remove this line once the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ScannerProfileID.coerce_isolated_input(id)
project = authorized_find_project!(full_path: full_path) project = authorized_find_project!(full_path: full_path)
service = ::DastScannerProfiles::DestroyService.new(project, current_user) service = ::DastScannerProfiles::DestroyService.new(project, current_user)
......
...@@ -34,10 +34,14 @@ module Mutations ...@@ -34,10 +34,14 @@ module Mutations
authorize :create_on_demand_dast_scan authorize :create_on_demand_dast_scan
def resolve(full_path:, **service_args) def resolve(full_path:, **service_args)
# TODO: remove this explicit coercion once the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
gid = ::Types::GlobalIDType[::DastScannerProfile].coerce_isolated_input(service_args[:id])
project = authorized_find!(full_path: full_path) project = authorized_find!(full_path: full_path)
service = ::DastScannerProfiles::UpdateService.new(project, current_user) service = ::DastScannerProfiles::UpdateService.new(project, current_user)
result = service.execute({ **service_args, id: service_args[:id].model_id }) result = service.execute({ **service_args, id: gid.model_id })
if result.success? if result.success?
{ id: result.payload.to_global_id, errors: [] } { id: result.payload.to_global_id, errors: [] }
......
...@@ -19,6 +19,10 @@ module Mutations ...@@ -19,6 +19,10 @@ module Mutations
def resolve(full_path:, id:) def resolve(full_path:, id:)
project = authorized_find_project!(full_path: full_path) project = authorized_find_project!(full_path: full_path)
# TODO: remove explicit coercion once compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[::DastSiteProfile].coerce_isolated_input(id)
dast_site_profile = find_dast_site_profile(project: project, global_id: id) dast_site_profile = find_dast_site_profile(project: project, global_id: id)
return { errors: dast_site_profile.errors.full_messages } unless dast_site_profile.destroy return { errors: dast_site_profile.errors.full_messages } unless dast_site_profile.destroy
......
...@@ -29,7 +29,10 @@ module Mutations ...@@ -29,7 +29,10 @@ module Mutations
authorize :create_on_demand_dast_scan authorize :create_on_demand_dast_scan
def resolve(full_path:, **service_args) def resolve(full_path:, id:, **service_args)
# TODO: remove explicit coercion once compatibility layer has been removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
service_args[:id] = ::Types::GlobalIDType[::DastSiteProfile].coerce_isolated_input(id).model_id
project = authorized_find_project!(full_path: full_path) project = authorized_find_project!(full_path: full_path)
service = ::DastSiteProfiles::UpdateService.new(project, current_user) service = ::DastSiteProfiles::UpdateService.new(project, current_user)
......
...@@ -33,6 +33,9 @@ module Mutations ...@@ -33,6 +33,9 @@ module Mutations
end end
def find_object(id:) def find_object(id:)
# TODO: remove this line once the compatibility layer is removed.
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[::Vulnerability].coerce_isolated_input(id)
GitlabSchema.find_by_gid(id) GitlabSchema.find_by_gid(id)
end end
end end
......
...@@ -33,6 +33,9 @@ module Mutations ...@@ -33,6 +33,9 @@ module Mutations
end end
def find_object(id:) def find_object(id:)
# TODO: remove this line once the compatibility layer is removed
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
id = ::Types::GlobalIDType[::Vulnerability].coerce_isolated_input(id)
GitlabSchema.find_by_gid(id) GitlabSchema.find_by_gid(id)
end end
end end
......
...@@ -30,7 +30,7 @@ module DastSiteProfiles ...@@ -30,7 +30,7 @@ module DastSiteProfiles
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def find_dast_site_profile!(id) def find_dast_site_profile!(id)
DastSiteProfilesFinder.new(project_id: project.id, id: id.model_id).execute.first! DastSiteProfilesFinder.new(project_id: project.id, id: id).execute.first!
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
......
...@@ -52,7 +52,7 @@ RSpec.describe Mutations::Clusters::AgentTokens::Create do ...@@ -52,7 +52,7 @@ RSpec.describe Mutations::Clusters::AgentTokens::Create do
subject { mutation.resolve(cluster_agent_id: cluster_agent.id) } subject { mutation.resolve(cluster_agent_id: cluster_agent.id) }
it 'generates an error message when id invalid', :aggregate_failures do it 'generates an error message when id invalid', :aggregate_failures do
expect { subject }.to raise_error(NoMethodError) expect { subject }.to raise_error(::GraphQL::CoercionError)
end end
end end
end end
......
...@@ -44,7 +44,7 @@ RSpec.describe Mutations::Clusters::AgentTokens::Delete do ...@@ -44,7 +44,7 @@ RSpec.describe Mutations::Clusters::AgentTokens::Delete do
let(:global_id) { token.id } let(:global_id) { token.id }
it 'raises an error if the cluster agent id is invalid', :aggregate_failures do it 'raises an error if the cluster agent id is invalid', :aggregate_failures do
expect { subject }.to raise_error(NoMethodError) expect { subject }.to raise_error(::GraphQL::CoercionError)
expect { token.reload }.not_to raise_error expect { token.reload }.not_to raise_error
end end
end end
......
...@@ -24,7 +24,7 @@ RSpec.describe Mutations::Clusters::Agents::Delete do ...@@ -24,7 +24,7 @@ RSpec.describe Mutations::Clusters::Agents::Delete do
context 'without user permissions' do context 'without user permissions' do
it 'fails to delete the cluster agent', :aggregate_failures do it 'fails to delete the cluster agent', :aggregate_failures do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
expect { cluster_agent.reload }.not_to raise_error(ActiveRecord::RecordNotFound) expect { cluster_agent.reload }.not_to raise_error
end end
end end
...@@ -43,8 +43,8 @@ RSpec.describe Mutations::Clusters::Agents::Delete do ...@@ -43,8 +43,8 @@ RSpec.describe Mutations::Clusters::Agents::Delete do
subject { mutation.resolve(id: cluster_agent.id) } subject { mutation.resolve(id: cluster_agent.id) }
it 'raises an error if the cluster agent id is invalid', :aggregate_failures do it 'raises an error if the cluster agent id is invalid', :aggregate_failures do
expect { subject }.to raise_error(NoMethodError) expect { subject }.to raise_error(::GraphQL::CoercionError)
expect { cluster_agent.reload }.not_to raise_error(ActiveRecord::RecordNotFound) expect { cluster_agent.reload }.not_to raise_error
end end
end end
end end
......
...@@ -17,7 +17,7 @@ RSpec.describe DastSiteProfiles::UpdateService do ...@@ -17,7 +17,7 @@ RSpec.describe DastSiteProfiles::UpdateService do
describe '#execute' do describe '#execute' do
subject do subject do
described_class.new(project, user).execute( described_class.new(project, user).execute(
id: dast_site_profile.to_global_id, id: dast_site_profile.id,
profile_name: new_profile_name, profile_name: new_profile_name,
target_url: new_target_url target_url: new_target_url
) )
......
# frozen_string_literal: true
module Gitlab
module Graphql
module GlobalIDCompatibility
# TODO: remove this module once the compatibility layer is no longer needed.
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
def coerce_global_id_arguments!(args)
global_id_arguments = self.class.arguments.values.select do |arg|
arg.type.is_a?(Class) && arg.type <= ::Types::GlobalIDType
end
global_id_arguments.each do |arg|
k = arg.keyword
args[k] &&= arg.type.coerce_isolated_input(args[k])
end
end
end
end
end
...@@ -99,8 +99,6 @@ RSpec.describe Types::GlobalIDType do ...@@ -99,8 +99,6 @@ RSpec.describe Types::GlobalIDType do
end end
describe 'compatibility' do describe 'compatibility' do
# Simplified schema to test compatibility
def query(doc, vars) def query(doc, vars)
GraphQL::Query.new(schema, document: doc, context: {}, variables: vars) GraphQL::Query.new(schema, document: doc, context: {}, variables: vars)
end end
...@@ -112,6 +110,7 @@ RSpec.describe Types::GlobalIDType do ...@@ -112,6 +110,7 @@ RSpec.describe Types::GlobalIDType do
all_types = [::GraphQL::ID_TYPE, ::Types::GlobalIDType, ::Types::GlobalIDType[::Project]] all_types = [::GraphQL::ID_TYPE, ::Types::GlobalIDType, ::Types::GlobalIDType[::Project]]
shared_examples 'a working query' do shared_examples 'a working query' do
# Simplified schema to test compatibility
let!(:schema) do let!(:schema) do
# capture values so they can be closed over # capture values so they can be closed over
arg_type = argument_type arg_type = argument_type
...@@ -135,10 +134,21 @@ RSpec.describe Types::GlobalIDType do ...@@ -135,10 +134,21 @@ RSpec.describe Types::GlobalIDType do
argument :id, arg_type, required: true argument :id, arg_type, required: true
end end
# This is needed so that all types are always registered as input types
field :echo, String, null: true do
argument :id, ::GraphQL::ID_TYPE, required: false
argument :gid, ::Types::GlobalIDType, required: false
argument :pid, ::Types::GlobalIDType[::Project], required: false
end
def project_by_id(id:) def project_by_id(id:)
gid = ::Types::GlobalIDType[::Project].coerce_isolated_input(id) gid = ::Types::GlobalIDType[::Project].coerce_isolated_input(id)
gid.model_class.find(gid.model_id) gid.model_class.find(gid.model_id)
end end
def echo(id: nil, gid: nil, pid: nil)
"id: #{id}, gid: #{gid}, pid: #{pid}"
end
end) end)
end end
end end
...@@ -152,7 +162,7 @@ RSpec.describe Types::GlobalIDType do ...@@ -152,7 +162,7 @@ RSpec.describe Types::GlobalIDType do
end end
end end
context 'when the argument is declared as ID' do context 'when the client declares the argument as ID the actual argument can be any type' do
let(:document) do let(:document) do
<<-GRAPHQL <<-GRAPHQL
query($projectId: ID!){ query($projectId: ID!){
...@@ -163,16 +173,16 @@ RSpec.describe Types::GlobalIDType do ...@@ -163,16 +173,16 @@ RSpec.describe Types::GlobalIDType do
GRAPHQL GRAPHQL
end end
let(:argument_type) { ::GraphQL::ID_TYPE } where(:result_type, :argument_type) do
all_types.flat_map { |arg_type| all_types.zip([arg_type].cycle) }
where(:result_type) { all_types } end
with_them do with_them do
it_behaves_like 'a working query' it_behaves_like 'a working query'
end end
end end
context 'when the argument is declared as GlobalID' do context 'when the client passes the argument as GlobalID' do
let(:document) do let(:document) do
<<-GRAPHQL <<-GRAPHQL
query($projectId: GlobalID!) { query($projectId: GlobalID!) {
...@@ -192,7 +202,7 @@ RSpec.describe Types::GlobalIDType do ...@@ -192,7 +202,7 @@ RSpec.describe Types::GlobalIDType do
end end
end end
context 'when the argument is declared as ProjectID' do context 'when the client passes the argument as ProjectID' do
let(:document) do let(:document) do
<<-GRAPHQL <<-GRAPHQL
query($projectId: ProjectID!) { query($projectId: ProjectID!) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment