Skip to content

Commit fade264

Browse files
Merge pull request #78 from graphql-devise/add-mount-method-option-sanitizer
Add mount method option sanitizer
2 parents e7f3e4c + 0753bb2 commit fade264

18 files changed

Lines changed: 449 additions & 75 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: 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 ClassChecker
5+
def initialize(klass)
6+
@klass_array = Array(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 @klass_array.any? { |klass| value.ancestors.include?(klass) }
17+
raise GraphqlDevise::InvalidMountOptionsError,
18+
"`#{key}` option has an invalid value. #{@klass_array.join(', ')} or descendants expected. Got #{value}."
19+
end
20+
21+
value
22+
end
23+
end
24+
end
25+
end
26+
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 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+
value.each { |internal_key, klass| ClassChecker.new(@element_type_array).call!(klass, "#{key} -> #{internal_key}") }
18+
19+
value
20+
end
21+
end
22+
end
23+
end
24+
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

lib/graphql_devise/mount_method/option_validators/provided_operations_validator.rb

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,40 +4,20 @@ module GraphqlDevise
44
module MountMethod
55
module OptionValidators
66
class ProvidedOperationsValidator
7-
def initialize(options: {}, supported_operations: {})
8-
@options = options || {}
7+
def initialize(options:, supported_operations:)
8+
@options = options
99
@supported_operations = supported_operations
1010
end
1111

1212
def validate!
13-
skipped = @options.fetch(:skip, [])
14-
only = @options.fetch(:only, [])
15-
operations = @options.fetch(:operations, {})
1613
supported_keys = @supported_operations.keys
1714

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-
2415
[
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)
16+
SupportedOperationsValidator.new(provided_operations: @options.skip, key: :skip, supported_operations: supported_keys),
17+
SupportedOperationsValidator.new(provided_operations: @options.only, key: :only, supported_operations: supported_keys),
18+
SupportedOperationsValidator.new(provided_operations: @options.operations.keys, key: :operations, supported_operations: supported_keys)
2819
].each(&:validate!)
2920
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
4121
end
4222
end
4323
end

lib/graphql_devise/mount_method/option_validators/skip_only_validator.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ module GraphqlDevise
22
module MountMethod
33
module OptionValidators
44
class SkipOnlyValidator
5-
def initialize(options: {})
6-
@options = options || {}
5+
def initialize(options:)
6+
@options = options
77
end
88

99
def validate!
10-
if [@options[:skip], @options[:only]].all?(&:present?)
10+
if [@options.skip, @options.only].all?(&:present?)
1111
raise(
1212
GraphqlDevise::InvalidMountOptionsError,
1313
"Can't specify both `skip` and `only` options when mounting the route."
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: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,25 @@ 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
[
18-
GraphqlDevise::MountMethod::OptionValidators::SkipOnlyValidator.new(options: options),
19+
GraphqlDevise::MountMethod::OptionValidators::SkipOnlyValidator.new(options: clean_options),
1920
GraphqlDevise::MountMethod::OptionValidators::ProvidedOperationsValidator.new(
20-
options: options, supported_operations: default_operations
21+
options: clean_options, supported_operations: default_operations
2122
)
2223
]
2324
).validate!
2425

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')
31-
mapping_name = resource.underscore.tr('/', '_').to_sym
32-
authenticatable_type = options[:authenticatable_type].presence ||
26+
authenticatable_type = clean_options.authenticatable_type.presence ||
3327
"Types::#{resource}Type".safe_constantize ||
3428
GraphqlDevise::Types::AuthenticatableType
3529

3630
param_operations = {
37-
custom: custom_operations,
38-
only: only_operations,
39-
skipped: skipped_operations
31+
custom: clean_options.operations,
32+
only: clean_options.only,
33+
skipped: clean_options.skip
4034
}
4135

4236
devise_for(
@@ -62,28 +56,30 @@ def mount_graphql_devise_for(resource, options = {})
6256
authenticatable_type: authenticatable_type
6357
)
6458

65-
prepared_mutations.merge(additional_mutations).each do |action, mutation|
59+
all_mutations = prepared_mutations.merge(clean_options.additional_mutations)
60+
all_mutations.each do |action, mutation|
6661
GraphqlDevise::Types::MutationType.field(action, mutation: mutation)
6762
end
6863

69-
if (prepared_mutations.present? || additional_mutations.present?) &&
64+
if all_mutations.present? &&
7065
(Gem::Version.new(GraphQL::VERSION) < Gem::Version.new('1.10.0') || GraphqlDevise::Schema.mutation.nil?)
7166
GraphqlDevise::Schema.mutation(GraphqlDevise::Types::MutationType)
7267
end
7368

74-
prepared_queries.merge(additional_queries).each do |action, resolver|
69+
all_queries = prepared_queries.merge(clean_options.additional_queries)
70+
all_queries.each do |action, resolver|
7571
GraphqlDevise::Types::QueryType.field(action, resolver: resolver)
7672
end
7773

78-
if (prepared_queries.blank? || additional_queries.present?) && GraphqlDevise::Types::QueryType.fields.blank?
74+
if all_queries.present? && GraphqlDevise::Types::QueryType.fields.blank?
7975
GraphqlDevise::Types::QueryType.field(:dummy, resolver: GraphqlDevise::Resolvers::Dummy)
8076
end
8177

8278
Devise.mailer.helper(GraphqlDevise::MailerHelper)
8379

84-
devise_scope mapping_name do
85-
post path, to: 'graphql_devise/graphql#auth'
86-
get path, to: 'graphql_devise/graphql#auth'
80+
devise_scope resource.underscore.tr('/', '_').to_sym do
81+
post clean_options.at, to: 'graphql_devise/graphql#auth'
82+
get clean_options.at, to: 'graphql_devise/graphql#auth'
8783
end
8884
end
8985
end

0 commit comments

Comments
 (0)