graphql_devise 0.10.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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