graphql_devise 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/Appraisals +8 -0
  4. data/CHANGELOG.md +17 -0
  5. data/graphql_devise.gemspec +3 -3
  6. data/lib/graphql_devise.rb +20 -4
  7. data/lib/graphql_devise/default_operations/mutations.rb +20 -0
  8. data/lib/graphql_devise/default_operations/resolvers.rb +12 -0
  9. data/lib/graphql_devise/mount_method/operation_preparer.rb +41 -0
  10. data/lib/graphql_devise/mount_method/operation_preparers/custom_operation_preparer.rb +24 -0
  11. data/lib/graphql_devise/mount_method/operation_preparers/default_operation_preparer.rb +32 -0
  12. data/lib/graphql_devise/mount_method/operation_preparers/gql_name_setter.rb +23 -0
  13. data/lib/graphql_devise/mount_method/operation_preparers/mutation_field_setter.rb +17 -0
  14. data/lib/graphql_devise/mount_method/operation_preparers/resolver_type_setter.rb +17 -0
  15. data/lib/graphql_devise/mount_method/operation_preparers/resource_name_setter.rb +17 -0
  16. data/lib/graphql_devise/mount_method/operation_sanitizer.rb +29 -0
  17. data/lib/graphql_devise/mount_method/option_sanitizer.rb +18 -0
  18. data/lib/graphql_devise/mount_method/option_sanitizers/array_checker.rb +26 -0
  19. data/lib/graphql_devise/mount_method/option_sanitizers/class_checker.rb +26 -0
  20. data/lib/graphql_devise/mount_method/option_sanitizers/hash_checker.rb +24 -0
  21. data/lib/graphql_devise/mount_method/option_sanitizers/string_checker.rb +21 -0
  22. data/lib/graphql_devise/mount_method/option_validators/provided_operations_validator.rb +24 -0
  23. data/lib/graphql_devise/mount_method/option_validators/skip_only_validator.rb +20 -0
  24. data/lib/graphql_devise/mount_method/option_validators/supported_operations_validator.rb +24 -0
  25. data/lib/graphql_devise/mount_method/options_validator.rb +16 -0
  26. data/lib/graphql_devise/mount_method/supported_options.rb +18 -0
  27. data/{app/graphql → lib}/graphql_devise/mutations/base.rb +0 -0
  28. data/{app/graphql → lib}/graphql_devise/mutations/login.rb +0 -0
  29. data/{app/graphql → lib}/graphql_devise/mutations/logout.rb +0 -0
  30. data/{app/graphql → lib}/graphql_devise/mutations/resend_confirmation.rb +0 -0
  31. data/{app/graphql → lib}/graphql_devise/mutations/send_password_reset.rb +0 -0
  32. data/{app/graphql → lib}/graphql_devise/mutations/sign_up.rb +0 -0
  33. data/{app/graphql → lib}/graphql_devise/mutations/update_password.rb +0 -0
  34. data/lib/graphql_devise/rails/routes.rb +57 -85
  35. data/{app/graphql → lib}/graphql_devise/resolvers/base.rb +0 -0
  36. data/{app/graphql → lib}/graphql_devise/resolvers/check_password_token.rb +0 -0
  37. data/{app/graphql → lib}/graphql_devise/resolvers/confirm_account.rb +0 -0
  38. data/{app/graphql → lib}/graphql_devise/resolvers/dummy.rb +0 -0
  39. data/{app/graphql → lib}/graphql_devise/types/authenticatable_type.rb +0 -0
  40. data/{app/graphql → lib}/graphql_devise/types/credential_type.rb +0 -0
  41. data/{app/graphql → lib}/graphql_devise/types/mutation_type.rb +0 -0
  42. data/{app/graphql → lib}/graphql_devise/types/query_type.rb +0 -0
  43. data/lib/graphql_devise/version.rb +1 -1
  44. data/spec/services/mount_method/operation_preparer_spec.rb +30 -0
  45. data/spec/services/mount_method/operation_preparers/custom_operation_preparer_spec.rb +43 -0
  46. data/spec/services/mount_method/operation_preparers/default_operation_preparer_spec.rb +60 -0
  47. data/spec/services/mount_method/operation_preparers/gql_name_setter_spec.rb +16 -0
  48. data/spec/services/mount_method/operation_preparers/mutation_field_setter_spec.rb +16 -0
  49. data/spec/services/mount_method/operation_preparers/resolver_type_setter_spec.rb +16 -0
  50. data/spec/services/mount_method/operation_preparers/resource_name_setter_spec.rb +14 -0
  51. data/spec/services/mount_method/operation_sanitizer_spec.rb +33 -0
  52. data/spec/services/mount_method/option_sanitizer_spec.rb +85 -0
  53. data/spec/services/mount_method/option_sanitizers/array_checker_spec.rb +38 -0
  54. data/spec/services/mount_method/option_sanitizers/class_checker_spec.rb +44 -0
  55. data/spec/services/mount_method/option_sanitizers/hash_checker_spec.rb +85 -0
  56. data/spec/services/mount_method/option_sanitizers/string_checker_spec.rb +30 -0
  57. data/spec/services/mount_method/option_validators/provided_operations_validator_spec.rb +57 -0
  58. data/spec/services/mount_method/option_validators/skip_only_validator_spec.rb +31 -0
  59. data/spec/services/mount_method/option_validators/supported_operations_validator_spec.rb +35 -0
  60. data/spec/services/mount_method/options_validator_spec.rb +36 -0
  61. metadata +79 -39
  62. data/gemfiles/.bundle/config +0 -2
  63. data/gemfiles/rails4.2_graphql1.8.gemfile +0 -10
  64. data/gemfiles/rails5.0_graphql1.8.gemfile +0 -11
  65. data/gemfiles/rails5.0_graphql1.9.gemfile +0 -9
  66. data/gemfiles/rails5.1_graphql1.8.gemfile +0 -11
  67. data/gemfiles/rails5.1_graphql1.9.gemfile +0 -9
  68. data/gemfiles/rails5.2_graphql1.10.gemfile +0 -9
  69. data/gemfiles/rails5.2_graphql1.8.gemfile +0 -11
  70. data/gemfiles/rails5.2_graphql1.9.gemfile +0 -9
  71. data/gemfiles/rails6.0_graphql1.10.gemfile +0 -10
  72. data/gemfiles/rails6.0_graphql1.8.gemfile +0 -10
  73. data/gemfiles/rails6.0_graphql1.9.gemfile +0 -10
  74. data/gemfiles/rails6.0_graphql_edge.gemfile +0 -11
  75. data/gemfiles/rails_edge_graphql_edge.gemfile +0 -11
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,24 @@
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
+ supported_keys = @supported_operations.keys
14
+
15
+ [
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)
19
+ ].each(&:validate!)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,111 +1,83 @@
1
1
  module ActionDispatch::Routing
2
2
  class Mapper
3
- def mount_graphql_devise_for(resource, opts = {})
4
- custom_operations = opts.fetch(:operations, {})
5
- skipped_operations = opts.fetch(:skip, [])
6
- only_operations = opts.fetch(:only, [])
7
- additional_mutations = opts.fetch(:additional_mutations, {})
8
- additional_queries = opts.fetch(:additional_queries, {})
9
-
10
- if [skipped_operations, only_operations].all?(&:any?)
11
- raise GraphqlDevise::Error, "Can't specify both `skip` and `only` options when mounting the route."
12
- end
13
-
14
- default_mutations = {
15
- login: GraphqlDevise::Mutations::Login,
16
- logout: GraphqlDevise::Mutations::Logout,
17
- sign_up: GraphqlDevise::Mutations::SignUp,
18
- update_password: GraphqlDevise::Mutations::UpdatePassword,
19
- send_password_reset: GraphqlDevise::Mutations::SendPasswordReset,
20
- resend_confirmation: GraphqlDevise::Mutations::ResendConfirmation
21
- }.freeze
22
- default_queries = {
23
- confirm_account: GraphqlDevise::Resolvers::ConfirmAccount,
24
- check_password_token: GraphqlDevise::Resolvers::CheckPasswordToken
25
- }
26
- supported_operations = default_mutations.keys + default_queries.keys
27
-
28
- unless skipped_operations.all? { |skipped| supported_operations.include?(skipped) }
29
- raise GraphqlDevise::Error, 'Trying to skip a non supported operation. Check for typos.'
30
- end
31
- unless only_operations.all? { |only| supported_operations.include?(only) }
32
- raise GraphqlDevise::Error, 'One of the `only` operations is not supported. Check for typos.'
33
- end
34
-
35
- path = opts.fetch(:at, '/graphql_auth')
36
- mapping_name = resource.underscore.tr('/', '_').to_sym
3
+ DEVISE_OPERATIONS = [
4
+ :sessions,
5
+ :registrations,
6
+ :passwords,
7
+ :confirmations,
8
+ :omniauth_callbacks,
9
+ :unlocks,
10
+ :invitations
11
+ ].freeze
12
+
13
+ def mount_graphql_devise_for(resource, options = {})
14
+ default_operations = GraphqlDevise::DefaultOperations::MUTATIONS.merge(GraphqlDevise::DefaultOperations::QUERIES)
15
+
16
+ # clean_options responds to all keys defined in GraphqlDevise::MountMethod::SUPPORTED_OPTIONS
17
+ clean_options = GraphqlDevise::MountMethod::OptionSanitizer.new(options).call!
18
+
19
+ GraphqlDevise::MountMethod::OptionsValidator.new(
20
+ [
21
+ GraphqlDevise::MountMethod::OptionValidators::SkipOnlyValidator.new(options: clean_options),
22
+ GraphqlDevise::MountMethod::OptionValidators::ProvidedOperationsValidator.new(
23
+ options: clean_options, supported_operations: default_operations
24
+ )
25
+ ]
26
+ ).validate!
27
+
28
+ authenticatable_type = clean_options.authenticatable_type.presence ||
29
+ "Types::#{resource}Type".safe_constantize ||
30
+ GraphqlDevise::Types::AuthenticatableType
37
31
 
38
32
  devise_for(
39
33
  resource.pluralize.underscore.tr('/', '_').to_sym,
40
34
  module: :devise,
41
35
  class_name: resource,
42
- skip: [:sessions, :registrations, :passwords, :confirmations, :omniauth_callbacks, :unlocks, :invitations]
36
+ skip: DEVISE_OPERATIONS
43
37
  )
44
38
 
45
- authenticatable_type = opts[:authenticatable_type] ||
46
- "Types::#{resource}Type".safe_constantize ||
47
- GraphqlDevise::Types::AuthenticatableType
48
-
49
- used_mutations = if only_operations.present?
50
- default_mutations.slice(*only_operations)
51
- else
52
- default_mutations.except(*skipped_operations)
53
- end
54
- used_mutations.each do |action, mutation|
55
- used_mutation = if custom_operations[action].present?
56
- custom_operations[action]
57
- else
58
- new_mutation = Class.new(mutation)
59
- new_mutation.graphql_name("#{resource.gsub('::', '')}#{action.to_s.camelize(:upper)}")
60
- new_mutation.field(:authenticatable, authenticatable_type, null: true)
61
-
62
- new_mutation
63
- end
64
- used_mutation.instance_variable_set(:@resource_name, mapping_name)
65
-
66
- GraphqlDevise::Types::MutationType.field("#{mapping_name}_#{action}", mutation: used_mutation)
67
- end
68
- additional_mutations.each do |action, mutation|
39
+ prepared_mutations = GraphqlDevise::MountMethod::OperationPreparer.new(
40
+ resource: resource,
41
+ custom: clean_options.operations,
42
+ additional_operations: clean_options.additional_mutations,
43
+ preparer: GraphqlDevise::MountMethod::OperationPreparers::MutationFieldSetter.new(authenticatable_type),
44
+ selected_operations: GraphqlDevise::MountMethod::OperationSanitizer.call(
45
+ default: GraphqlDevise::DefaultOperations::MUTATIONS, only: clean_options.only, skipped: clean_options.skip
46
+ )
47
+ ).call
48
+
49
+ prepared_mutations.each do |action, mutation|
69
50
  GraphqlDevise::Types::MutationType.field(action, mutation: mutation)
70
51
  end
71
52
 
72
- if (used_mutations.present? || additional_mutations.present?) &&
73
- (Gem::Version.new(GraphQL::VERSION) <= Gem::Version.new('1.10.0') || GraphqlDevise::Schema.mutation.nil?)
53
+ if prepared_mutations.present? &&
54
+ (Gem::Version.new(GraphQL::VERSION) < Gem::Version.new('1.10.0') || GraphqlDevise::Schema.mutation.nil?)
74
55
  GraphqlDevise::Schema.mutation(GraphqlDevise::Types::MutationType)
75
56
  end
76
57
 
77
- used_queries = if only_operations.present?
78
- default_queries.slice(*only_operations)
79
- else
80
- default_queries.except(*skipped_operations)
81
- end
82
- used_queries.each do |action, query|
83
- used_query = if custom_operations[action].present?
84
- custom_operations[action]
85
- else
86
- new_query = Class.new(query)
87
- new_query.graphql_name("#{resource.gsub('::', '')}#{action.to_s.camelize(:upper)}")
88
- new_query.type(authenticatable_type, null: true)
89
-
90
- new_query
91
- end
92
- used_query.instance_variable_set(:@resource_name, mapping_name)
93
-
94
- GraphqlDevise::Types::QueryType.field("#{mapping_name}_#{action}", resolver: used_query)
95
- end
96
- additional_queries.each do |action, resolver|
58
+ prepared_queries = GraphqlDevise::MountMethod::OperationPreparer.new(
59
+ resource: resource,
60
+ custom: clean_options.operations,
61
+ additional_operations: clean_options.additional_queries,
62
+ preparer: GraphqlDevise::MountMethod::OperationPreparers::ResolverTypeSetter.new(authenticatable_type),
63
+ selected_operations: GraphqlDevise::MountMethod::OperationSanitizer.call(
64
+ default: GraphqlDevise::DefaultOperations::QUERIES, only: clean_options.only, skipped: clean_options.skip
65
+ )
66
+ ).call
67
+
68
+ prepared_queries.each do |action, resolver|
97
69
  GraphqlDevise::Types::QueryType.field(action, resolver: resolver)
98
70
  end
99
71
 
100
- if (used_queries.blank? || additional_queries.present?) && GraphqlDevise::Types::QueryType.fields.blank?
72
+ if prepared_queries.blank? && GraphqlDevise::Types::QueryType.fields.blank?
101
73
  GraphqlDevise::Types::QueryType.field(:dummy, resolver: GraphqlDevise::Resolvers::Dummy)
102
74
  end
103
75
 
104
76
  Devise.mailer.helper(GraphqlDevise::MailerHelper)
105
77
 
106
- devise_scope mapping_name do
107
- post path, to: 'graphql_devise/graphql#auth'
108
- get path, to: 'graphql_devise/graphql#auth'
78
+ devise_scope resource.underscore.tr('/', '_').to_sym do
79
+ post clean_options.at, to: 'graphql_devise/graphql#auth'
80
+ get clean_options.at, to: 'graphql_devise/graphql#auth'
109
81
  end
110
82
  end
111
83
  end
@@ -1,3 +1,3 @@
1
1
  module GraphqlDevise
2
- VERSION = '0.10.1'.freeze
2
+ VERSION = '0.11.0'.freeze
3
3
  end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe GraphqlDevise::MountMethod::OperationPreparer do
4
+ describe '#call' do
5
+ subject(:prepared_operations) do
6
+ described_class.new(
7
+ resource: resource,
8
+ selected_operations: selected,
9
+ preparer: preparer,
10
+ custom: custom,
11
+ additional_operations: additional
12
+ ).call
13
+ end
14
+
15
+ let(:logout_class) { Class.new(GraphQL::Schema::Resolver) }
16
+ let(:resource) { 'User' }
17
+ let(:selected) { { login: double(:login_default), logout: logout_class } }
18
+ let(:preparer) { double(:preparer, call: logout_class) }
19
+ let(:custom) { { login: double(:custom_login, graphql_name: nil) } }
20
+ let(:additional) { { user_additional: double(:user_additional) } }
21
+
22
+ it 'is expected to return all provided operation keys' do
23
+ expect(prepared_operations.keys).to contain_exactly(
24
+ :user_login,
25
+ :user_logout,
26
+ :user_additional
27
+ )
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe GraphqlDevise::MountMethod::OperationPreparers::CustomOperationPreparer do
4
+ describe '#call' do
5
+ subject(:prepared) { described_class.new(selected_keys: selected_keys, custom_operations: operations, mapping_name: mapping_name).call }
6
+
7
+ let(:login_operation) { double(:confirm_operation, graphql_name: nil) }
8
+ let(:logout_operation) { double(:sign_up_operation, graphql_name: nil) }
9
+ let(:mapping_name) { 'user' }
10
+ let(:operations) { { login: login_operation, logout: logout_operation, invalid: double(:invalid) } }
11
+ let(:selected_keys) { [:login, :logout, :sign_up, :confirm] }
12
+
13
+ it 'returns only those operations with no custom operation provided' do
14
+ expect(prepared.keys).to contain_exactly(:user_login, :user_logout)
15
+ end
16
+
17
+ it 'prepares custom operations' do
18
+ expect(login_operation).to receive(:graphql_name).with('UserLogin')
19
+ expect(logout_operation).to receive(:graphql_name).with('UserLogout')
20
+
21
+ prepared
22
+
23
+ expect(login_operation.instance_variable_get(:@resource_name)).to eq(:user)
24
+ expect(logout_operation.instance_variable_get(:@resource_name)).to eq(:user)
25
+ end
26
+
27
+ context 'when no selected keys are provided' do
28
+ let(:selected_keys) { [] }
29
+
30
+ it 'returns no operations' do
31
+ expect(prepared).to eq({})
32
+ end
33
+ end
34
+
35
+ context 'when no custom operations are provided' do
36
+ let(:operations) { {} }
37
+
38
+ it 'returns no operations' do
39
+ expect(prepared).to eq({})
40
+ end
41
+ end
42
+ end
43
+ end