Skip to content

Commit 67cf039

Browse files
committed
Refactor mount method options validator
1 parent 227b414 commit 67cf039

11 files changed

Lines changed: 313 additions & 23 deletions

.rubocop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Style/MethodCalledOnDoEndBlock:
5959
Enabled: false
6060

6161
Naming/VariableNumber:
62-
Enabled: false
62+
EnforcedColonStyle: normalcase
6363

6464
Style/StringLiterals:
6565
ConsistentQuotesInMultiline: true

lib/graphql_devise.rb

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
require 'graphql'
33
require 'devise_token_auth'
44

5+
module GraphqlDevise
6+
class Error < StandardError; end
7+
8+
class InvalidMountOptionsError < GraphqlDevise::Error; end
9+
end
10+
511
require 'graphql_devise/concerns/controller_methods'
612
require 'graphql_devise/types/authenticatable_type'
713
require 'graphql_devise/types/credential_type'
@@ -17,11 +23,8 @@
1723
require 'graphql_devise/user_error'
1824
require 'graphql_devise/detailed_user_error'
1925

26+
require 'graphql_devise/mount_method/options_validator'
2027
require 'graphql_devise/rails/queries_preparer'
2128
require 'graphql_devise/rails/mutations_preparer'
2229
require 'graphql_devise/rails/operation_checker'
2330
require 'graphql_devise/rails/operation_sanitizer'
24-
25-
module GraphqlDevise
26-
class Error < StandardError; end
27-
end
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
require_relative 'supported_operations_validator'
2+
3+
module GraphqlDevise
4+
module MountMethod
5+
module OptionValidators
6+
class ProvidedOperationsValidator
7+
def initialize(options: {}, supported_operations: {})
8+
@options = options || {}
9+
@supported_operations = supported_operations
10+
end
11+
12+
def validate!
13+
skipped = @options.fetch(:skip, [])
14+
only = @options.fetch(:only, [])
15+
operations = @options.fetch(:operations, {})
16+
supported_keys = @supported_operations.keys
17+
18+
raise_on_invalid_option_type!(:skip, skipped, Array)
19+
raise_on_invalid_option_type!(:only, only, Array)
20+
raise_on_invalid_option_type!(:operations, operations, Hash)
21+
22+
custom = operations.keys
23+
24+
[
25+
SupportedOperationsValidator.new(provided_operations: skipped, key: :skip, supported_operations: supported_keys),
26+
SupportedOperationsValidator.new(provided_operations: only, key: :only, supported_operations: supported_keys),
27+
SupportedOperationsValidator.new(provided_operations: custom, key: :operations, supported_operations: supported_keys)
28+
].each(&:validate!)
29+
end
30+
31+
private
32+
33+
def raise_on_invalid_option_type!(key, value, expected_class)
34+
unless value.is_a?(expected_class)
35+
raise(
36+
GraphqlDevise::InvalidMountOptionsError,
37+
"#{key} option contains value of invalid value. Value must be #{expected_class.name}."
38+
)
39+
end
40+
end
41+
end
42+
end
43+
end
44+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module GraphqlDevise
2+
module MountMethod
3+
module OptionValidators
4+
class SkipOnlyValidator
5+
def initialize(options: {})
6+
@options = options || {}
7+
end
8+
9+
def validate!
10+
if [@options[:skip], @options[:only]].all?(&:present?)
11+
raise(
12+
GraphqlDevise::InvalidMountOptionsError,
13+
"Can't specify both `skip` and `only` options when mounting the route."
14+
)
15+
end
16+
end
17+
end
18+
end
19+
end
20+
end
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module GraphqlDevise
2+
module MountMethod
3+
module OptionValidators
4+
class SupportedOperationsValidator
5+
def initialize(provided_operations: [], supported_operations: [], key:)
6+
@provided_operations = provided_operations
7+
@supported_operations = supported_operations
8+
@key = key
9+
end
10+
11+
def validate!
12+
unsupported_operations = @provided_operations - @supported_operations
13+
14+
if unsupported_operations.present?
15+
raise(
16+
GraphqlDevise::InvalidMountOptionsError,
17+
"#{@key} option contains unsupported operations: \"#{unsupported_operations.join(', ')}\". Check for typos."
18+
)
19+
end
20+
end
21+
end
22+
end
23+
end
24+
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
require_relative 'option_validators/skip_only_validator'
2+
require_relative 'option_validators/provided_operations_validator'
3+
4+
module GraphqlDevise
5+
module MountMethod
6+
class OptionsValidator
7+
def initialize(validators = [])
8+
@validators = validators
9+
end
10+
11+
def validate!
12+
@validators.each(&:validate!)
13+
end
14+
end
15+
end
16+
end

lib/graphql_devise/rails/routes.rb

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,35 @@ class Mapper
1010
:invitations
1111
].freeze
1212

13-
def mount_graphql_devise_for(resource, opts = {})
14-
custom_operations = opts.fetch(:operations, {})
15-
skipped_operations = opts.fetch(:skip, [])
16-
only_operations = opts.fetch(:only, [])
17-
additional_mutations = opts.fetch(:additional_mutations, {})
18-
additional_queries = opts.fetch(:additional_queries, {})
19-
path = opts.fetch(:at, '/graphql_auth')
13+
def mount_graphql_devise_for(resource, options = {})
14+
default_operations = GraphqlDevise::DefaultOperations::MUTATIONS.merge(GraphqlDevise::DefaultOperations::QUERIES)
15+
16+
GraphqlDevise::MountMethod::OptionsValidator.new(
17+
[
18+
GraphqlDevise::MountMethod::OptionValidators::SkipOnlyValidator.new(options: options),
19+
GraphqlDevise::MountMethod::OptionValidators::ProvidedOperationsValidator.new(
20+
options: options, supported_operations: default_operations
21+
)
22+
]
23+
).validate!
24+
25+
custom_operations = options.fetch(:operations, {})
26+
skipped_operations = options.fetch(:skip, [])
27+
only_operations = options.fetch(:only, [])
28+
additional_mutations = options.fetch(:additional_mutations, {})
29+
additional_queries = options.fetch(:additional_queries, {})
30+
path = options.fetch(:at, '/graphql_auth')
2031
mapping_name = resource.underscore.tr('/', '_').to_sym
21-
authenticatable_type = opts[:authenticatable_type].presence ||
22-
"Types::#{resource}Type".safe_constantize ||
23-
GraphqlDevise::Types::AuthenticatableType
24-
param_operations = {
32+
authenticatable_type = options[:authenticatable_type].presence ||
33+
"Types::#{resource}Type".safe_constantize ||
34+
GraphqlDevise::Types::AuthenticatableType
35+
36+
param_operations = {
2537
custom: custom_operations,
2638
only: only_operations,
2739
skipped: skipped_operations
2840
}
2941

30-
GraphqlDevise::OperationChecker.call(
31-
mutations: GraphqlDevise::DefaultOperations::MUTATIONS,
32-
queries: GraphqlDevise::DefaultOperations::QUERIES,
33-
**param_operations
34-
)
35-
3642
devise_for(
3743
resource.pluralize.underscore.tr('/', '_').to_sym,
3844
module: :devise,
@@ -61,7 +67,7 @@ def mount_graphql_devise_for(resource, opts = {})
6167
end
6268

6369
if (prepared_mutations.present? || additional_mutations.present?) &&
64-
(Gem::Version.new(GraphQL::VERSION) <= Gem::Version.new('1.10.0') || GraphqlDevise::Schema.mutation.nil?)
70+
(Gem::Version.new(GraphQL::VERSION) <= Gem::Version.new('1.10.0') || GraphqlDevise::Schema.mutation.nil?)
6571
GraphqlDevise::Schema.mutation(GraphqlDevise::Types::MutationType)
6672
end
6773

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
require 'spec_helper'
2+
3+
RSpec.describe GraphqlDevise::MountMethod::OptionValidators::ProvidedOperationsValidator do
4+
describe '#validate!' do
5+
subject { -> { described_class.new(options: provided_operations, supported_operations: supported_operations).validate! } }
6+
7+
let(:supported_operations) { { operation1: 'irrelevant', operation2: 'irrelevant', operation3: 'irrelevant' } }
8+
9+
context 'when skip option is provided' do
10+
let(:provided_operations) { { skip: skipped } }
11+
12+
context 'when all skipped are supported' do
13+
let(:skipped) { [:operation2, :operation3] }
14+
15+
it { is_expected.not_to raise_error }
16+
end
17+
18+
context 'when skipped contains unsupported operations' do
19+
let(:skipped) { [:operation2, :operation3, :invalid] }
20+
21+
it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'skip option contains unsupported operations: "invalid". Check for typos.') }
22+
end
23+
24+
context 'when provided skip value is not an array' do
25+
let(:skipped) { 'Invalid value' }
26+
27+
it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'skip option contains value of invalid value. Value must be Array.') }
28+
end
29+
end
30+
31+
context 'when only option is provided' do
32+
let(:provided_operations) { { only: only } }
33+
34+
context 'when all only are supported' do
35+
let(:only) { [:operation2, :operation3] }
36+
37+
it { is_expected.not_to raise_error }
38+
end
39+
40+
context 'when only contains unsupported operations' do
41+
let(:only) { [:operation2, :operation3, :invalid] }
42+
43+
it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'only option contains unsupported operations: "invalid". Check for typos.') }
44+
end
45+
46+
context 'when provided only value is not an array' do
47+
let(:only) { 'Invalid value' }
48+
49+
it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'only option contains value of invalid value. Value must be Array.') }
50+
end
51+
end
52+
53+
context 'when operations option is provided' do
54+
let(:provided_operations) { { operations: operations } }
55+
56+
context 'when all operations are supported' do
57+
let(:operations) { { operation2: 'irrelevant', operation3: 'irrelevant' } }
58+
59+
it { is_expected.not_to raise_error }
60+
end
61+
62+
context 'when operations contains unsupported operations' do
63+
let(:operations) { { operation2: 'irrelevant', operation3: 'irrelevant', invalid: 'invalid' } }
64+
65+
it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'operations option contains unsupported operations: "invalid". Check for typos.') }
66+
end
67+
68+
context 'when provided operations value is not a hash' do
69+
let(:operations) { [:one, :two, :three] }
70+
71+
it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'operations option contains value of invalid value. Value must be Hash.') }
72+
end
73+
end
74+
end
75+
end
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
require 'spec_helper'
2+
3+
RSpec.describe GraphqlDevise::MountMethod::OptionValidators::SkipOnlyValidator do
4+
describe '#validate!' do
5+
subject { -> { described_class.new(options: options).validate! } }
6+
7+
context 'when only `only` key is set' do
8+
let(:options) { { only: 'Irrelevant value' } }
9+
10+
it { is_expected.not_to raise_error }
11+
end
12+
13+
context 'when only `skip` key is set' do
14+
let(:options) { { skip: 'Irrelevant value' } }
15+
16+
it { is_expected.not_to raise_error }
17+
end
18+
19+
context 'when `skip` and `only` keys are set' do
20+
let(:options) { { only: 'Irrelevant value', skip: 'irrelevant for specs' } }
21+
22+
it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, "Can't specify both `skip` and `only` options when mounting the route.") }
23+
end
24+
25+
context 'when neither `skip` nor `only are set`' do
26+
let(:options) { { irrelevant_option: 'Irrelevant value', another_option: 'irrelevant for specs' } }
27+
28+
it { is_expected.not_to raise_error }
29+
end
30+
end
31+
end
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
require 'spec_helper'
2+
3+
RSpec.describe GraphqlDevise::MountMethod::OptionValidators::SupportedOperationsValidator do
4+
describe '#validate!' do
5+
subject { -> { described_class.new(provided_operations: provided_operations, supported_operations: supported_operations, key: key).validate! } }
6+
7+
let(:supported_operations) { [:operation1, :operation2, :operation3] }
8+
let(:key) { :only }
9+
10+
context 'when custom operations are all supported' do
11+
let(:provided_operations) { [:operation2, :operation3] }
12+
13+
it { is_expected.not_to raise_error }
14+
end
15+
16+
context 'when no operations are provided' do
17+
let(:provided_operations) { [] }
18+
19+
it { is_expected.not_to raise_error }
20+
end
21+
22+
context 'when default_operations are empty' do
23+
let(:supported_operations) { [] }
24+
let(:provided_operations) { [:invalid] }
25+
26+
it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'only option contains unsupported operations: "invalid". Check for typos.') }
27+
end
28+
29+
context 'when not all custom operations are supported' do
30+
let(:provided_operations) { [:operation2, :operation3, :unsupported] }
31+
32+
it { is_expected.to raise_error(GraphqlDevise::InvalidMountOptionsError, 'only option contains unsupported operations: "unsupported". Check for typos.') }
33+
end
34+
end
35+
end

0 commit comments

Comments
 (0)