daredevil 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +18 -0
  4. data/config/locales/daredevil.yml +14 -0
  5. data/lib/daredevil.rb +60 -0
  6. data/lib/daredevil/configuration.rb +22 -0
  7. data/lib/daredevil/engine.rb +6 -0
  8. data/lib/daredevil/errors.rb +41 -0
  9. data/lib/daredevil/responder.rb +126 -0
  10. data/lib/daredevil/responder/actions.rb +92 -0
  11. data/lib/daredevil/responder/responses.rb +30 -0
  12. data/lib/daredevil/responder/sanitizers.rb +18 -0
  13. data/lib/daredevil/utils/rack_helper.rb +13 -0
  14. data/lib/daredevil/version.rb +3 -0
  15. data/test/daredevil_test.rb +15 -0
  16. data/test/dummy/README.rdoc +28 -0
  17. data/test/dummy/Rakefile +6 -0
  18. data/test/dummy/app/controllers/application_controller.rb +4 -0
  19. data/test/dummy/app/controllers/posts_controller.rb +44 -0
  20. data/test/dummy/app/models/application_record.rb +3 -0
  21. data/test/dummy/app/models/post.rb +3 -0
  22. data/test/dummy/app/serializers/kustom_post_serializer.rb +3 -0
  23. data/test/dummy/app/serializers/post_serializer.rb +3 -0
  24. data/test/dummy/app/views/posts/_post.json.jbuilder +1 -0
  25. data/test/dummy/app/views/posts/index.json.jbuilder +5 -0
  26. data/test/dummy/app/views/posts/show.json.jbuilder +4 -0
  27. data/test/dummy/bin/bundle +3 -0
  28. data/test/dummy/bin/rails +4 -0
  29. data/test/dummy/bin/rake +4 -0
  30. data/test/dummy/bin/setup +29 -0
  31. data/test/dummy/config.ru +4 -0
  32. data/test/dummy/config/application.rb +14 -0
  33. data/test/dummy/config/boot.rb +5 -0
  34. data/test/dummy/config/database.yml +25 -0
  35. data/test/dummy/config/environment.rb +5 -0
  36. data/test/dummy/config/environments/development.rb +41 -0
  37. data/test/dummy/config/environments/test.rb +42 -0
  38. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  39. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  40. data/test/dummy/config/initializers/session_store.rb +3 -0
  41. data/test/dummy/config/initializers/to_time_preserves_timezone.rb +10 -0
  42. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  43. data/test/dummy/config/locales/en.yml +23 -0
  44. data/test/dummy/config/routes.rb +3 -0
  45. data/test/dummy/config/secrets.yml +22 -0
  46. data/test/dummy/db/development.sqlite3 +0 -0
  47. data/test/dummy/db/migrate/20170713181802_create_posts.rb +8 -0
  48. data/test/dummy/db/schema.rb +20 -0
  49. data/test/dummy/db/test.sqlite3 +0 -0
  50. data/test/dummy/log/development.log +12878 -0
  51. data/test/dummy/log/test.log +5 -0
  52. data/test/factories/posts.rb +6 -0
  53. data/test/post_jbuilder_test.rb +27 -0
  54. data/test/post_test.rb +77 -0
  55. data/test/reports/TEST-DaredevilTest.xml +9 -0
  56. data/test/reports/TEST-PostJbuilderTest.xml +7 -0
  57. data/test/reports/TEST-PostTest.xml +17 -0
  58. data/test/test_helper.rb +20 -0
  59. metadata +174 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5623a1c5349dd39821379641f01f69c99f8b15ae
4
+ data.tar.gz: fac4157ad72ed070c87415203c454966e033d501
5
+ SHA512:
6
+ metadata.gz: 96b82a00fed7c5fa4e50da77aa0214b1547565fee8746c5d8d4ee9317a80b76ffe4289445ac8dd2db08a6a91739ae82a6af8fd6efbc502259e975a39ee2db4be
7
+ data.tar.gz: 689b7b1d628f8a15a5037f2e72f7442ba88c3d9a914e7ade1d4d4b4a79a3be88639d0d7ed0788cd73659cf88c0a924130ac355dd58a55536ae1d05202ce8a387
@@ -0,0 +1,20 @@
1
+ Copyright 2017 ProctorU
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ require 'rake/testtask'
10
+
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs << 'lib'
13
+ t.libs << 'test'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = false
16
+ end
17
+
18
+ task default: :test
@@ -0,0 +1,14 @@
1
+ en:
2
+ daredevil:
3
+ errors:
4
+ not_found:
5
+ reason: Not found
6
+ forbidden:
7
+ reason: Unauthorized
8
+ unprocessable_entity:
9
+ reason: Unprocessable Entity
10
+ conflict:
11
+ reason: Invalid Attribute
12
+ parameter_missing:
13
+ reason: Missing Parameter
14
+ detail: Please supply the %{parameter} param
@@ -0,0 +1,60 @@
1
+ require 'active_support'
2
+ require 'daredevil/engine'
3
+ require 'daredevil/errors'
4
+ require 'daredevil/responder'
5
+
6
+ module Daredevil
7
+ def self.included(base)
8
+ base.rescue_from ActiveRecord::RecordNotFound, with: :record_not_found!
9
+ base.rescue_from ActionController::ParameterMissing, with: :parameter_missing!
10
+ redefine_authorization(base)
11
+ end
12
+
13
+ def self.redefine_authorization(base)
14
+ return unless base.instance_methods.include?(:authenticate_user_from_token!)
15
+
16
+ base.class_eval do
17
+ alias_method(:_authenticate_from_token!, :authenticate_user_from_token!)
18
+
19
+ define_method :authenticate_user_from_token! do
20
+ result = catch(:warden) { _authenticate_from_token! }
21
+
22
+ return unless result
23
+ respond_with_error(:unauthorized)
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def respond_with_error(error_type, resource = nil)
31
+ case error_type
32
+ when :unauthorized
33
+ Responder.new(nil, controller: self, status: :forbidden).unauthorized
34
+ when :not_found
35
+ Responder.new(resource, controller: self, status: :not_found).not_found
36
+ when :parameter_missing
37
+ Responder.new(resource, controller: self, status: :unprocessable_entity).
38
+ parameter_missing
39
+ end
40
+ end
41
+
42
+ def respond_with(resource, options = {})
43
+ options = {
44
+ namespace: self.class.parent,
45
+ status: :ok,
46
+ params: params,
47
+ controller: self
48
+ }.merge(options)
49
+
50
+ Responder.new(resource, options).respond!
51
+ end
52
+
53
+ def record_not_found!(error)
54
+ respond_with_error(:not_found, error)
55
+ end
56
+
57
+ def parameter_missing!(error)
58
+ respond_with_error(:parameter_missing, error)
59
+ end
60
+ end
@@ -0,0 +1,22 @@
1
+ require 'active_support/configurable'
2
+
3
+ module Daredevil
4
+ def self.configure(&block)
5
+ yield @config ||= Daredevil::Configuration.new
6
+ end
7
+
8
+ # Global settings for Daredevil
9
+ def self.config
10
+ @config
11
+ end
12
+
13
+ class Configuration
14
+ include ActiveSupport::Configurable
15
+
16
+ config_accessor :responder_type
17
+ end
18
+
19
+ configure do |config|
20
+ config.responder_type = :jbuilder
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ module Daredevil
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Daredevil
4
+ config.autoload_paths += Dir["#{config.root}/lib/**/"]
5
+ end
6
+ end
@@ -0,0 +1,41 @@
1
+ require_relative 'utils/rack_helper'
2
+
3
+ module Daredevil
4
+ module Errors
5
+ class UnknownHTTPStatus < StandardError
6
+ include Utils::RackHelper
7
+
8
+ attr_reader :status
9
+
10
+ def initialize(status)
11
+ @status = status
12
+ super(message)
13
+ end
14
+
15
+ def message
16
+ "Unknown HTTP status code '#{status}'.\n"\
17
+ "Available status codes and symbols are: #{statuses}"
18
+ end
19
+
20
+ private
21
+
22
+ def statuses
23
+ (status_symbols + status_codes).join(', ')
24
+ end
25
+ end
26
+
27
+ class UnknownAction < StandardError
28
+ attr_reader :action
29
+
30
+ def initialize(action)
31
+ @action = action
32
+ super(message)
33
+ end
34
+
35
+ def message
36
+ "Unknown controller action '#{action}'.\n"\
37
+ "Accepted actions are #{Daredevil::Responder::ACTIONS.join(', ')}"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,126 @@
1
+ require 'daredevil/configuration'
2
+ require 'daredevil/responder/actions'
3
+ require 'daredevil/responder/responses'
4
+ require 'daredevil/responder/sanitizers'
5
+
6
+ module Daredevil
7
+ class Responder
8
+ include Actions
9
+ include Responses
10
+ include Sanitizers
11
+
12
+ attr_accessor :errors
13
+ attr_accessor :status
14
+ attr_reader :resource
15
+ attr_reader :options
16
+ attr_reader :params
17
+ attr_reader :controller
18
+ attr_reader :namespace
19
+
20
+ def initialize(resource, options = {})
21
+ @resource = resource
22
+ @options = options
23
+ self.status = @options[:status]
24
+ @params = @options[:params]
25
+ @controller = @options[:controller]
26
+ @namespace = @options[:namespace]
27
+ end
28
+
29
+ def respond!
30
+ render_response
31
+ end
32
+
33
+ def render_response
34
+ return send("respond_to_#{action}_action") if action.in?(ACTIONS)
35
+ raise(Daredevil::Errors::UnknownAction, action)
36
+ end
37
+
38
+ def render_error
39
+ controller.render(error_render_options)
40
+ end
41
+
42
+ private
43
+
44
+ def status=(status)
45
+ @status = Sanitizers.status_symbol(status)
46
+ end
47
+
48
+ def status_code
49
+ Rack::Utils::SYMBOL_TO_STATUS_CODE[status]
50
+ end
51
+
52
+ def action
53
+ params[:action]
54
+ end
55
+
56
+ def render_error
57
+ controller.render(error_render_options)
58
+ end
59
+
60
+ def error_render_options
61
+ render_options.merge(
62
+ json: {
63
+ status: status_code,
64
+ message: general_error_message
65
+ }.tap do |added_errors|
66
+ added_errors_obj(added_errors)
67
+ added_resource(added_errors)
68
+ added_message(added_errors)
69
+ end
70
+ )
71
+ end
72
+
73
+ def render_options
74
+ {
75
+ status: status,
76
+ content_type: 'application/vnd.api+json'
77
+ }
78
+ end
79
+
80
+ def error_response
81
+ return if errors
82
+ resource.errors.inject([]) do |new_errors, (attribute, message)|
83
+ new_errors << {
84
+ resource: resource_name.titleize,
85
+ field: attribute,
86
+ reason: message,
87
+ detail: resource.errors.full_message(attribute, message)
88
+ }
89
+ end
90
+ end
91
+
92
+ def resource_name
93
+ return resource.object.class.name if resource.respond_to?(:decorated?) &&
94
+ resource.decorated?
95
+ resource.class.name
96
+ end
97
+
98
+ def general_error_message
99
+ I18n.t('daredevil.errors.conflict.reason')
100
+ end
101
+
102
+ def added_errors_obj(error_obj)
103
+ error_obj[:errors] = error_response unless errors
104
+ end
105
+
106
+ def added_resource(error_obj)
107
+ return unless errors
108
+ error_obj[:resource] = resource_model_or_param
109
+ error_obj[:detail] = errors[:detail]
110
+ end
111
+
112
+ def resource_model_or_param
113
+ return resource.model if resource.try(:model).present?
114
+ resource_param
115
+ end
116
+
117
+ def resource_param
118
+ resource.param if
119
+ resource.class.name.eql?('ActionController::ParameterMissing')
120
+ end
121
+
122
+ def added_message(error_obj)
123
+ error_obj[:message] = errors[:reason] if errors
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,92 @@
1
+ module Daredevil
2
+ class Responder
3
+ module Actions
4
+ ACTIONS = %w(index show create update destroy).freeze
5
+
6
+ %w(index show).each do |method|
7
+ define_method("respond_to_#{method}_action") do
8
+ self.status ||= :ok
9
+ render_resource
10
+ end
11
+ end
12
+
13
+ def respond_to_create_action
14
+ if resource.persisted? && resource.valid?
15
+ self.status ||= :created
16
+ render_resource
17
+ else
18
+ self.status ||= :conflict
19
+ render_error
20
+ end
21
+ end
22
+
23
+ def respond_to_update_action
24
+ if resource.valid?
25
+ self.status ||= :ok
26
+ render_resource
27
+ else
28
+ self.status ||= :conflict
29
+ render_error
30
+ end
31
+ end
32
+
33
+ def respond_to_destroy_action
34
+ self.status ||= :no_content
35
+ controller.head(status, render_options)
36
+ end
37
+
38
+ def render_resource
39
+ controller.render(resource_render_options)
40
+ end
41
+
42
+ def resource_render_options
43
+ self.send(:"#{Daredevil.config.responder_type.to_s}_resource_render_options")
44
+ end
45
+
46
+ def jbuilder_resource_render_options
47
+ render_options
48
+ end
49
+
50
+ def serializers_resource_render_options
51
+ serializer_key = relation? ? :each_serializer : :serializer
52
+
53
+ render_options.merge(
54
+ json: resource,
55
+ serializer_key => serializer_class
56
+ )
57
+ end
58
+
59
+ def serializer_class
60
+ options[:serializer] ||=
61
+ [
62
+ namespace,
63
+ "#{resource_class}Serializer"
64
+ ].compact.join('::').safe_constantize
65
+ options[:serializer] ||= "#{resource_class}Serializer".safe_constantize
66
+ end
67
+
68
+ def resource_class
69
+ return resource.model if relation?
70
+ resource.class
71
+ end
72
+
73
+ def relation?
74
+ resource.is_a?(ActiveRecord::Relation)
75
+ end
76
+
77
+ def responder_type
78
+ supported_responders.each do |type, klass|
79
+ return type.to_sym if defined?(klass.safe_constantize).eql?('constant')
80
+ end
81
+ end
82
+
83
+ def supported_responders
84
+ {
85
+ jbuilder: 'Jbuilder',
86
+ serializers: 'ActiveModel::Serializers',
87
+ rabl: 'RABL'
88
+ }
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,30 @@
1
+ module Daredevil
2
+ class Responder
3
+ module Responses
4
+ def not_found
5
+ self.errors = {
6
+ reason: I18n.t('daredevil.errors.not_found.reason')
7
+ }
8
+ render_error
9
+ end
10
+
11
+ def parameter_missing
12
+ self.errors = {
13
+ reason: I18n.t('daredevil.errors.parameter_missing.reason'),
14
+ detail: I18n.t(
15
+ 'daredevil.errors.parameter_missing.detail',
16
+ parameter: resource.param
17
+ )
18
+ }
19
+ render_error
20
+ end
21
+
22
+ def unauthorized
23
+ self.errors = {
24
+ reason: I18n.t('daredevil.errors.forbidden.reason')
25
+ }
26
+ render_error
27
+ end
28
+ end
29
+ end
30
+ end