Skip to content

Commit ecd4f48

Browse files
committed
Add mount method option sanitizer
1 parent e7f3e4c commit ecd4f48

14 files changed

Lines changed: 408 additions & 2 deletions

lib/graphql_devise.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class InvalidMountOptionsError < GraphqlDevise::Error; end
2323
require 'graphql_devise/user_error'
2424
require 'graphql_devise/detailed_user_error'
2525

26+
require 'graphql_devise/mount_method/option_sanitizer'
2627
require 'graphql_devise/mount_method/options_validator'
2728
require 'graphql_devise/mount_method/queries_preparer'
2829
require 'graphql_devise/mount_method/mutations_preparer'
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
require_relative 'supported_options'
2+
3+
module GraphqlDevise
4+
module MountMethod
5+
class OptionSanitizer
6+
def initialize(options = {}, supported_options = MountMethod::SUPPORTED_OPTIONS)
7+
@options = options
8+
@supported_options = supported_options
9+
end
10+
11+
def call!
12+
@supported_options.each_with_object(Struct.new(*@supported_options.keys).new) do |(key, checker), result|
13+
result[key] = checker.call!(@options[key], key)
14+
end
15+
end
16+
end
17+
end
18+
end
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module GraphqlDevise
2+
module MountMethod
3+
module OptionSanitizers
4+
class ArrayChecker
5+
def initialize(element_type)
6+
@element_type = element_type
7+
@default_value = []
8+
end
9+
10+
def call!(value, key)
11+
return @default_value if value.blank?
12+
13+
unless value.instance_of?(Array)
14+
raise GraphqlDevise::InvalidMountOptionsError, "`#{key}` option has an invalid value. Array expected."
15+
end
16+
17+
unless value.all? { |element| element.instance_of?(@element_type) }
18+
raise GraphqlDevise::InvalidMountOptionsError, "`#{key}` option has invalid elements. #{@element_type} expected."
19+
end
20+
21+
value
22+
end
23+
end
24+
end
25+
end
26+
end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module GraphqlDevise
2+
module MountMethod
3+
module OptionSanitizers
4+
class ClassChecker
5+
def initialize(klass)
6+
@klass = klass
7+
end
8+
9+
def call!(value, key)
10+
return if value.nil?
11+
12+
unless value.instance_of?(Class)
13+
raise GraphqlDevise::InvalidMountOptionsError, "`#{key}` option has an invalid value. Class expected."
14+
end
15+
16+
unless value.ancestors.include?(@klass)
17+
raise GraphqlDevise::InvalidMountOptionsError, "`#{key}` option has an invalid value. #{@klass} expected. Got #{value}."
18+
end
19+
20+
value
21+
end
22+
end
23+
end
24+
end
25+
end
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module GraphqlDevise
2+
module MountMethod
3+
module OptionSanitizers
4+
class HashChecker
5+
def initialize(element_type_array)
6+
@element_type_array = Array(element_type_array)
7+
@default_value = {}
8+
end
9+
10+
def call!(value, key)
11+
return @default_value if value.blank?
12+
13+
unless value.instance_of?(Hash)
14+
raise GraphqlDevise::InvalidMountOptionsError, "`#{key}` option has an invalid value. Hash expected. Got #{value.class}."
15+
end
16+
17+
unless value.all? { |_, element| element.instance_of?(Class) && @element_type_array.any? { |type| element.ancestors.include?(type) } }
18+
raise GraphqlDevise::InvalidMountOptionsError, "`#{key}` option has invalid elements. [#{@element_type_array.join(', ')}] or descendants expected. Got #{value}."
19+
end
20+
21+
value
22+
end
23+
end
24+
end
25+
end
26+
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module GraphqlDevise
2+
module MountMethod
3+
module OptionSanitizers
4+
class StringChecker
5+
def initialize(default_string = nil)
6+
@default_string = default_string
7+
end
8+
9+
def call!(value, key)
10+
return @default_string if value.blank?
11+
12+
unless value.instance_of?(String)
13+
raise GraphqlDevise::InvalidMountOptionsError, "`#{key}` option has an invalid value. String expected."
14+
end
15+
16+
value
17+
end
18+
end
19+
end
20+
end
21+
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
require_relative 'option_sanitizers/array_checker'
2+
require_relative 'option_sanitizers/hash_checker'
3+
require_relative 'option_sanitizers/string_checker'
4+
require_relative 'option_sanitizers/class_checker'
5+
6+
module GraphqlDevise
7+
module MountMethod
8+
SUPPORTED_OPTIONS = {
9+
at: OptionSanitizers::StringChecker.new('/graphql_auth'),
10+
operations: OptionSanitizers::HashChecker.new([GraphQL::Schema::Resolver, GraphQL::Schema::Mutation]),
11+
only: OptionSanitizers::ArrayChecker.new(Symbol),
12+
skip: OptionSanitizers::ArrayChecker.new(Symbol),
13+
additional_queries: OptionSanitizers::HashChecker.new(GraphQL::Schema::Resolver),
14+
additional_mutations: OptionSanitizers::HashChecker.new(GraphQL::Schema::Mutation),
15+
authenticatable_type: OptionSanitizers::ClassChecker.new(GraphQL::Schema::Member)
16+
}.freeze
17+
end
18+
end

lib/graphql_devise/rails/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class Mapper
1212

1313
def mount_graphql_devise_for(resource, options = {})
1414
default_operations = GraphqlDevise::DefaultOperations::MUTATIONS.merge(GraphqlDevise::DefaultOperations::QUERIES)
15+
clean_options = GraphqlDevise::MountMethod::OptionSanitizer.new(options).call!
1516

1617
GraphqlDevise::MountMethod::OptionsValidator.new(
1718
[

spec/services/mount_method/mutations_preparer_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
let(:class1) { Class.new(GraphQL::Schema::Mutation) }
1515
let(:class2) { GraphQL::Schema::Mutation }
1616
let(:auth_type) { GraphqlDevise::Types::AuthenticatableType }
17-
let(:mutations) { { mutation_1: class1, mutation_2: class2 } }
17+
let(:mutations) { { mutation1: class1, mutation2: class2 } }
1818

1919
context 'when mutations is *NOT* empty' do
2020
it 'assign gql attibutes to mutations and changes keys using resource map' do
2121
result = subject
2222

23-
expect(result.keys).to contain_exactly(:user_mutation_1, :user_mutation_2)
23+
expect(result.keys).to contain_exactly(:user_mutation1, :user_mutation2)
2424
expect(result.values.map(&:graphql_name)).to contain_exactly(
2525
'UserMutation1', 'UserMutation2'
2626
)
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
require 'spec_helper'
2+
3+
RSpec.describe GraphqlDevise::MountMethod::OptionSanitizer do
4+
subject(:clean_options) { described_class.new(options, supported_options).call! }
5+
6+
describe '#call!' do
7+
let(:supported_options) do
8+
{
9+
my_string: GraphqlDevise::MountMethod::OptionSanitizers::StringChecker.new('default string'),
10+
hash_multiple: GraphqlDevise::MountMethod::OptionSanitizers::HashChecker.new([String, Numeric]),
11+
array: GraphqlDevise::MountMethod::OptionSanitizers::ArrayChecker.new(Symbol),
12+
hash_single: GraphqlDevise::MountMethod::OptionSanitizers::HashChecker.new(Float),
13+
my_class: GraphqlDevise::MountMethod::OptionSanitizers::ClassChecker.new(Numeric)
14+
}
15+
end
16+
17+
context 'when all options are provided and correct' do
18+
let(:options) do
19+
{
20+
my_string: 'non default',
21+
hash_multiple: { first: String, second: Float, third: Float },
22+
array: [:one, :two, :three],
23+
hash_single: { first: Float, second: Float },
24+
my_class: Float
25+
}
26+
end
27+
28+
it 'returns a struct with clean options' do
29+
expect(
30+
my_string: clean_options.my_string,
31+
hash_multiple: clean_options.hash_multiple,
32+
array: clean_options.array,
33+
hash_single: clean_options.hash_single,
34+
my_class: clean_options.my_class
35+
).to match(
36+
my_string: 'non default',
37+
hash_multiple: { first: String, second: Float, third: Float },
38+
array: [:one, :two, :three],
39+
hash_single: { first: Float, second: Float },
40+
my_class: Float
41+
)
42+
end
43+
end
44+
45+
context 'when some options are provided but all correct' do
46+
let(:options) do
47+
{
48+
hash_multiple: { first: String, second: Float, third: Float },
49+
array: [:one, :two, :three],
50+
my_class: Float
51+
}
52+
end
53+
54+
it 'returns a struct with clean options and default values' do
55+
expect(
56+
my_string: clean_options.my_string,
57+
hash_multiple: clean_options.hash_multiple,
58+
array: clean_options.array,
59+
hash_single: clean_options.hash_single,
60+
my_class: clean_options.my_class
61+
).to match(
62+
my_string: 'default string',
63+
hash_multiple: { first: String, second: Float, third: Float },
64+
array: [:one, :two, :three],
65+
hash_single: {},
66+
my_class: Float
67+
)
68+
end
69+
end
70+
71+
context 'when an option provided is invalid' do
72+
let(:options) do
73+
{
74+
hash_multiple: { first: String, second: Float, third: Float },
75+
array: ['not symbol 1', 'not symbol 2'],
76+
my_class: Float
77+
}
78+
end
79+
80+
it 'raises an error' do
81+
expect { clean_options }.to raise_error(GraphqlDevise::InvalidMountOptionsError, '`array` option has invalid elements. Symbol expected.')
82+
end
83+
end
84+
end
85+
end

0 commit comments

Comments
 (0)