kinetic_cafe_error 1.1 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: be8bfda8e64ddea25cca49dd080dacfe1b489690
4
- data.tar.gz: ada56cc846a9262dca8941335a32d290d29aa7ff
3
+ metadata.gz: 9f729db0dc8d7bd5d7299f904a9731348d5d22d0
4
+ data.tar.gz: 5f2cca7f8b42a6cfb4cc6cb45b6f4bc8602ac762
5
5
  SHA512:
6
- metadata.gz: 811a51c61090c41d0ec80e7abf28fd3bdeb61852a8031d2c4159487d62562df9a81cf3f30ccfc0c3aa4e41be99f020ee03a6100ce26ef809ca8a1bfd03304dc6
7
- data.tar.gz: 3ba9c1634d770fe886f268c085ddff9c941e5d1cd18b49f723ce71d20fd9bd7fe757825637f607e9aad72c4008aca011e614c16f7b9b88469938ff6e7ea2f0bb
6
+ metadata.gz: 2172f4681a2163199a49c1a099d84a50d6853db13db34e3462df7944239d8922c4e4e25c066ad170d8b82b4331d8ad9a33af00aae8da9b31a6b1e4449d84bb82
7
+ data.tar.gz: a134be803972f943ee5189ac97530f15c5bda8756b146d2c3996d4e89f99a9e72bd34733dc8bdc2a0860b1c92a78619d8064a4767d1859648d330dbbf8f0ea59
data/History.rdoc CHANGED
@@ -1,3 +1,36 @@
1
+ === 1.2 / 2015-06-08
2
+
3
+ * 1 major enhancement
4
+
5
+ * Changed the preferred way of creating an error hierarchy from just
6
+ extending the error class with KineticCafe::ErrorDSL to calling
7
+ KineticCafe.create_hierarchy. Among other options this provides, the
8
+ automatic creation of helper methods and errors based on Rack::Utils status
9
+ codes can be controlled.
10
+
11
+ * 5 minor enhancements
12
+
13
+ * Renamed KineticCafe#header_only? to KineticCafe#header? The old version is
14
+ still present but deprecated. Similarly, the option to
15
+ KineticCafe::ErrorDSL#define_error is now called +header+, but
16
+ +header_only+ also works.
17
+
18
+ * Added an option, +i18n_params+ to KineticCafe::ErrorDSL#define_error, used
19
+ to describe the I18n parameters that are expected to be provided to the
20
+ error for translations. This gets defined as a class method on the new
21
+ error. This should be passed as an array.
22
+
23
+ * Extracted most of the 'magic' functionality to KineticCafe::ErrorModule so
24
+ that useful hierarchies can be generated without inheriting directly from
25
+ KineticCafe::Error.
26
+
27
+ * Added a class method to the Rails controller concern to generate a new
28
+ rescue_from for a non-KineticCafe::Error-derived exception.
29
+
30
+ * Added a pair of rake tasks, kcerror:defined (shows defined errors) and
31
+ kcerror:translations (generates a sample translation key file).
32
+ Automatically inserted for Rails applications.
33
+
1
34
  === 1.1 / 2015-06-05
2
35
 
3
36
  * 7 minor enhancements
data/Manifest.txt CHANGED
@@ -26,8 +26,11 @@ lib/kinetic_cafe/error.rb
26
26
  lib/kinetic_cafe/error/minitest.rb
27
27
  lib/kinetic_cafe/error_dsl.rb
28
28
  lib/kinetic_cafe/error_engine.rb
29
+ lib/kinetic_cafe/error_module.rb
29
30
  lib/kinetic_cafe/error_rspec.rb
31
+ lib/kinetic_cafe/error_tasks.rake
30
32
  lib/kinetic_cafe_error.rb
31
33
  test/test_helper.rb
32
34
  test/test_kinetic_cafe_error.rb
33
35
  test/test_kinetic_cafe_error_dsl.rb
36
+ test/test_kinetic_cafe_error_hierarchy.rb
data/README.rdoc CHANGED
@@ -9,24 +9,111 @@ continuous integration :: {<img src="https://travis-ci.org/KineticCafe/kinetic_c
9
9
  kinetic_cafe_error provides an API-smart error base class and a DSL for
10
10
  defining errors. Under Rails, it also provides a controller concern
11
11
  (KineticCafe::ErrorHandler) that has a useful implementation of +rescue_from+
12
- for KineticCafe::Error types.
12
+ to handle KineticCafe::Error types.
13
+
14
+ Exceptions in a hierarchy can be handled in a uniform manner, including getting
15
+ an I18n translation message with parameters, standard status values, and
16
+ meaningful JSON representations that can be used to establish a standard error
17
+ representations across both clients and servers.
13
18
 
14
19
  == Synopsis
15
20
 
16
- class MyErrorBase < KineticCafe::Error
17
- extend KineticCafe::ErrorDSL
18
-
19
- not_found class: :user # => MyErrorBase::UserNotFound
20
- unauthorized class: :user # => MyErrorBase::UserUnauthorized
21
- forbidden class: :user # => MyErrorBase::UserForbidden
22
- conflict class: :user# => MyErrorBase::UserConflict
21
+ Define a hierarchy with KineticCafe::Error.hierarchy.
22
+
23
+ KineticCafe::Error.hierarchy class: :MyBaseError do
24
+ not_found class: :user # => MyBaseError::UserNotFound
25
+ unauthorized class: :user # => MyBaseError::UserUnauthorized
26
+ forbidden class: :user # => MyBaseError::UserForbidden
27
+ conflict class: :user# => MyBaseError::UserConflict
23
28
  end
24
29
 
30
+ There are a few documented ways to define hierarchies. Examples for handling
31
+ exceptions can be found in the provided Minitest assertions module and the
32
+ RSpec matchers.
33
+
34
+ === Using with Rails
35
+
36
+ When using KineticCafe::Error with Rails, KineticCafe::ErrorEngine is
37
+ automatically injected, which enables the following functionality:
38
+
39
+ * Two rake tasks:
40
+
41
+ * <tt>rake kcerror:defined</tt>, showing the errors defined in the known
42
+ hierarchy.
43
+
44
+ * <tt>rake kcerror:translations[output]</tt>, creating a template translation
45
+ file for all defined errors.
46
+
47
+ * An error view, <tt>kinetic_cafe_error/page</tt>, in ERB, HAML, and Slim
48
+ formats. This also has a partial, <tt>kinetic_cafe_error/_table</tt>. This
49
+ allows KineticCafe::Error classes to be used in HTML contexts as well as JSON
50
+ contexts.
51
+
52
+ * Access to the kinetic_cafe_error translation files for English and French,
53
+ used in logging and in the error view.
54
+
55
+ * A controller concern, KineticCafe::ErrorHandler, that defines a +rescue_from+
56
+ handler for descendants of the KineticCafe::Error class, and a error handler
57
+ generator, #kinetic_cafe_error_handler_for, that sets a +rescue_from+ handler
58
+ for a KineticCafe::Error hierarchy that does not descend from
59
+ KineticCafe::Error itself.
60
+
61
+ #kinetic_cafe_error_handler distinguishes between HTML and JSON contexts.
62
+
63
+ === Using with Minitest
64
+
65
+ KineticCafe::Error provides a number of assertions that can help testing that
66
+ your code returns KineticCafe::Error hierarchies.
67
+
68
+ * #assert_kc_error when used with the return value of +assert_raises+, verifies
69
+ that the captured exception is the expected exception, including parameters.
70
+ Also available as #must_be_kc_error.
71
+
72
+ * assert_kc_error_json when used with a response body, verifies that the
73
+ response is the same as would be generated with the requested error class.
74
+ Also available as #must_be_kc_error_json.
75
+
76
+ * assert_response_kc_error_html works with ActiveSupport::Test; it asserts that
77
+ the +kinetic_cafe_error/page+ template has been rendered and that the
78
+ expected class I18n key is part of the response body. Depends on
79
+ <tt>@response.body</tt> being part of the available test environment.
80
+
81
+ * assert_response_kc_Error works with ActiveSupport::Test and checks
82
+ <tt>@request.format</tt> to determine whether to forward to
83
+ #assert_response_kc_error_html or #assert_kc_error_json.
84
+
85
+ Get access to these with:
86
+
87
+ require 'kinetic_cafe/error/minitest'
88
+
89
+ In your test setup code.
90
+
91
+ === Using with RSpec (Experimental)
92
+
93
+ KineticCafe::Error provides four experimental matchers:
94
+
95
+ * +be_json_for+ verifies that the JSON in the +actual+ string or body match the
96
+ +expected+ data structure.
97
+ * +be_kc_error+ verifies that the error is the expected class and renders
98
+ properly with the same parameters.
99
+ * +be_kc_error_json+ verifies that the JSON provided that the JSON output of the
100
+ +expected+ is generates the same JSON.
101
+ * +be_kc_error_html+ verifies that the response renders the
102
+ +kinetic_cafe_error/page+ template.
103
+
25
104
  == Install
26
105
 
27
- Add kinetic_cafe_error to your gemfile:
106
+ Add kinetic_cafe_error to your Gemfile:
107
+
108
+ gem 'kinetic_cafe_error', '~> 1.2'
109
+
110
+ If not using Rails, install with RubyGems:
111
+
112
+ gem install kinetic_cafe_error
113
+
114
+ And require where needed in your application:
28
115
 
29
- gem 'kinetic_cafe_error', '~> 1.0'
116
+ require 'kinetic_cafe_error'
30
117
 
31
118
  :include: Contributing.rdoc
32
119
  :include: Licence.rdoc
data/Rakefile CHANGED
@@ -64,4 +64,6 @@ namespace :test do
64
64
  end
65
65
  end
66
66
 
67
+ load 'lib/kinetic_cafe/error_tasks.rake'
68
+
67
69
  # vim: syntax=ruby
@@ -7,7 +7,16 @@ module KineticCafe::ErrorHandler
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
- rescue_from KineticCafe::Error, with: :kinetic_cafe_error_handler
10
+ kinetic_cafe_error_handler_for KineticCafe::Error
11
+ end
12
+
13
+ module ClassMethods
14
+ # Create a new +rescue_from+ handler for the specified base class. Useful
15
+ # if the base is not a descendant of KineticCafe::Error, but includes
16
+ # KineticCafe::ErrorHandler.
17
+ def kinetic_cafe_error_handler_for(klass)
18
+ rescue_from klass, with: :kinetic_cafe_error_handler
19
+ end
11
20
  end
12
21
 
13
22
  # This method is called with +error+ when Rails catches a KineticCafe::Error
@@ -19,7 +28,9 @@ module KineticCafe::ErrorHandler
19
28
  # controllers for different behaviour.
20
29
  def kinetic_cafe_error_handler(error)
21
30
  Rails.logger.error(error.message)
22
- Rails.logger.error("^-- caused by: #{error.cause.message}") if error.cause
31
+ if error.cause
32
+ Rails.logger.error(t('kinetic_cafe_error.cause', error.cause.message))
33
+ end
23
34
 
24
35
  respond_to do |format|
25
36
  format.html do
@@ -12,3 +12,5 @@ en-CA:
12
12
  header:
13
13
  status: Status
14
14
  code: Code
15
+ cause: >-
16
+ ^-- caused by: %{message}
@@ -12,3 +12,5 @@ en-UK:
12
12
  header:
13
13
  status: Status
14
14
  code: Code
15
+ cause: >-
16
+ ^-- caused by: %{message}
@@ -12,3 +12,5 @@ en-US:
12
12
  header:
13
13
  status: Status
14
14
  code: Code
15
+ cause: >-
16
+ ^-- caused by: %{message}
@@ -12,3 +12,5 @@ en:
12
12
  header:
13
13
  status: Status
14
14
  code: Code
15
+ cause: >-
16
+ ^-- caused by: %{message}
@@ -12,3 +12,5 @@ fr-CA:
12
12
  header:
13
13
  status: Condition
14
14
  code: Code
15
+ cause: >-
16
+ ^-- causé par: %{message}
@@ -12,3 +12,5 @@ fr:
12
12
  header:
13
13
  status: Condition
14
14
  code: Code
15
+ cause: >-
16
+ ^-- causé par: %{message}
@@ -1,4 +1,6 @@
1
- module KineticCafe #:nodoc:
1
+ require_relative 'error_module'
2
+
3
+ module KineticCafe # :nodoc:
2
4
  # A subclass of StandardError that can render itself as a descriptive JSON
3
5
  # hash, or as a hash that can be passed to a Rails controller +render+
4
6
  # method.
@@ -9,12 +11,10 @@ module KineticCafe #:nodoc:
9
11
  #
10
12
  # == Defining an Error Hierarchy
11
13
  #
12
- # An error hierarchy is defined by subclassing KineticCafe::Error, extending
13
- # it with KineticCafe::ErrorDSL, and defining error subclasses with the DSL.
14
- #
15
- # class MyErrorBase < KineticCafe::Error
16
- # extend KineticCafe::ErrorDSL
14
+ # An error hierarchy is defined by using KineticCafe::Error.hierarchy and
15
+ # defining error subclasses with the DSL.
17
16
  #
17
+ # KineticCafe::Error.hierarchy(class: :MyErrorBase) do
18
18
  # not_found class: :user # => MyErrorBase::UserNotFound
19
19
  # unauthorized class: :user # => MyErrorBase::UserUnauthorized
20
20
  # forbidden class: :user # => MyErrorBase::UserForbidden
@@ -25,147 +25,148 @@ module KineticCafe #:nodoc:
25
25
  # rescue clause and handled there, as is shown in the included
26
26
  # KineticCafe::ErrorHandler controller concern for Rails.
27
27
  class Error < ::StandardError
28
- VERSION = '1.1' # :nodoc:
28
+ VERSION = '1.2' # :nodoc:
29
29
 
30
- # The HTTP status to be returned. If not provided in the constructor, uses
31
- # #default_status.
32
- attr_reader :status
33
- # Extra data relevant to recipients of the exception, provided on
34
- # construction.
35
- attr_reader :extra
36
- # The exception that caused this exception; provided on construction.
37
- attr_reader :cause
30
+ # Get the KineticCafe::Error functionality.
31
+ include KineticCafe::ErrorModule
38
32
 
39
- # Create a new error with the given parameters.
33
+ # Create an error hierarchy using +options+ and the optional +block+. When
34
+ # given, the +block+ will either +yield+ the hierarchy base (if the block
35
+ # accepts arguments) or run with +instance_eval+.
36
+ #
37
+ # If the class does not already include KineticCafe::ErrorModule, it will
38
+ # be included.
39
+ #
40
+ # === Building a Hierarchy
41
+ #
42
+ # A hierarchy using KineticCafe::Error as its base can be created with
43
+ # KineticCafe::Error.hierarchy and no arguments.
44
+ #
45
+ # KineticCafe::Error.hierarchy do
46
+ # not_found class: :user # => KineticCafe::Error::UserNotFound
47
+ # end
48
+ #
49
+ # A hierarchy in a new error class (that descends from KineticCafe::Error)
50
+ # can be created by providing a class name:
51
+ #
52
+ # KineticCafe::Error.hierarchy(class: :MyErrorBase) do
53
+ # not_found class: :user # => MyErrorBase::UserNotFound
54
+ # end
55
+ #
56
+ # The new error class can itself be in a namespace, but the parent
57
+ # namespace must be identified:
58
+ #
59
+ # module My; end
60
+ #
61
+ # KineticCafe::Error.hierarchy(class: :ErrorBase, namespace: My) do
62
+ # not_found class: :user # => My::ErrorBase::UserNotFound
63
+ # end
64
+ #
65
+ # It is also possible to use an explicit descendant easily:
66
+ #
67
+ # module My
68
+ # ErrorBase = Class.new(KineticCafe::Error)
69
+ # end
70
+ #
71
+ # KineticCafe::Error.hierarchy(class: My::ErrorBase) do
72
+ # not_found class: :user # => My::ErrorBase::UserNotFound
73
+ # end
74
+ #
75
+ # === Rack::Utils Errors and Helpers
76
+ #
77
+ # By default, when Rack::Utils is present, KineticCafe::Error will present
78
+ # helper methods and default HTTP status code errors.
79
+ #
80
+ # KineticCafe::Error.hierarchy do
81
+ # not_found class: :user # => KineticCafe::Error::UserNotFound
82
+ # end
83
+ #
84
+ # KineticCafe::Error::UserNotFound.new.status # => :not_found / 404
85
+ # KineticCafe::Error::NotFound.new.status # => :not_found
86
+ #
87
+ # These may be controlled with the option +rack_status+. If provided as
88
+ # +false+, neither will be created:
89
+ #
90
+ # KineticCafe::Error.hierarchy(rack_status: false) do
91
+ # not_found class: :user # => raises NoMethodError
92
+ # end
93
+ #
94
+ # fail KineticCafe::Error::NotFound # => raises NameError
95
+ #
96
+ # These may be controlled individually, as well. Disable the methods:
97
+ #
98
+ # KineticCafe::Error.hierarchy(rack_status: { methods: false }) do
99
+ # not_found class: :user # => raises NoMethodError
100
+ # end
101
+ #
102
+ # fail KineticCafe::Error::NotFound # => works
103
+ #
104
+ # Disable the default error classes:
105
+ #
106
+ # KineticCafe::Error.hierarchy(rack_status: { errors: false }) do
107
+ # not_found class: :user # => KineticCafe::Error::UserNotFound
108
+ # end
109
+ #
110
+ # fail KineticCafe::Error::NotFound # => raises NoMethodError
40
111
  #
41
112
  # === Options
42
113
  #
43
- # +message+:: A message override. This may be provided either as the first
44
- # parameter to the constructor or may be provided as an option.
45
- # A value provided as the first parameter overrides any other
46
- # value.
47
- # +status+:: An override to the HTTP status code to be returned by this
48
- # error by default.
49
- # +i18n_params+:: The parameters to be sent to I18n.translate with the
50
- # #i18n_key.
51
- # +cause+:: The exception that caused this error. Used to wrap an earlier
52
- # exception.
53
- # +extra+:: Extra data to be returned in the API representation of this
54
- # exception.
55
- # +query+:: A hash of parameters, typically from Rails controller +params+
56
- # or model +where+ query. This hash will be converted into a
57
- # string value similar to ActiveSupport#to_query.
58
- #
59
- # Any unmatched options will be added transparently to +i18n_params+.
60
- # Because of this, the following constructors are identical:
61
- #
62
- # KineticCafe::Error.new(i18n_params: { x: 1 })
63
- # KineticCafe::Error.new(x: 1)
64
- #
65
- # :call-seq:
66
- # new(message, options = {})
67
- # new(options)
68
- def initialize(*args)
69
- options = args.last.kind_of?(Hash) ? args.pop.dup : {}
70
- @message = args.shift
71
- @message = options.delete(:message) if @message.nil? || @message.empty?
72
- options.delete(:message)
73
-
74
- @message && @message.freeze
75
-
76
- @status = options.delete(:status) || default_status
77
- @i18n_params = options.delete(:i18n_params) || {}
78
- @extra = options.delete(:extra)
79
- @cause = options.delete(:cause)
80
-
81
- @i18n_params.update(cause: cause.message) if cause
82
-
83
- query = options.delete(:query)
84
- @i18n_params.merge!(query: stringify(query)) if query
85
- @i18n_params.merge!(options)
86
- @i18n_params.freeze
87
- end
88
-
89
- # The message associated with this exception. If not provided, defaults to
90
- # #i18n_message.
91
- def message
92
- @message || i18n_message
93
- end
94
-
95
- # The name of the error class.
96
- def name
97
- @name ||= KineticCafe::ErrorDSL.namify(self.class.name)
98
- end
99
-
100
- # The key used for I18n translation.
101
- def i18n_key
102
- @i18n_key ||= "#{self.class.i18n_key_base}.#{name}".freeze
103
- end
104
- alias_method :code, :i18n_key
114
+ # +class+:: If given, identifies the base class and host namespace of the
115
+ # error hierarchy. Provided as a class, that class is used.
116
+ # Provided as a symbol, creates a new class that descends from
117
+ # KineticCafe::Error.
118
+ # +namespace+:: If +class+ is provided as a symbol, this namespace will be
119
+ # the one where the new error class is created.
120
+ # +rack_status+:: Controls the creation of error-definition helper methods
121
+ # and errors based on Rack::Utils status codes (e.g.,
122
+ # +not_found+). +true+ creates both; +false+ disables both.
123
+ # The values <tt>{ methods: false }</tt> and <tt>{ errors:
124
+ # false }</tt> individually control one.
125
+ def self.hierarchy(options = {}, &block) # :yields base:
126
+ base = options.fetch(:class, self)
127
+
128
+ if base.kind_of?(Symbol)
129
+ ns = options.fetch(:namespace, Object)
130
+ base = if ns.const_defined?(base)
131
+ ns.const_get(base)
132
+ else
133
+ ns.const_set(base, Class.new(self))
134
+ end
135
+ end
105
136
 
106
- # Indicates that this error should *not* have its details rendered to the
107
- # user, but should use the +head+ method.
108
- def header_only?
109
- false
110
- end
137
+ if base.singleton_class < KineticCafe::ErrorDSL
138
+ fail "#{base} is already a root hierarchy"
139
+ end
111
140
 
112
- # Indicates that this error should be rendered to the client, but clients
113
- # are advised *not* to display the message to the user.
114
- def internal?
115
- false
116
- end
141
+ unless base <= ::StandardError
142
+ fail "#{base} cannot root a hierarchy (not a StandardError)"
143
+ end
117
144
 
118
- # The I18n translation of the message. If I18n.translate is defined,
119
- # returns #i18n_key and the I18n parameters.
120
- def i18n_message
121
- @i18n_message ||= if defined?(I18n.translate)
122
- I18n.translate(i18n_key, @i18n_params).freeze
123
- else
124
- [ i18n_key, @i18n_params ].freeze
125
- end
126
- end
145
+ unless base <= KineticCafe::ErrorModule
146
+ base.send(:include, KineticCafe::ErrorModule)
147
+ end
127
148
 
128
- # The details of this error as a hash. Values that are empty or nil are
129
- # omitted.
130
- def api_error(*)
131
- {
132
- message: @message,
133
- status: status,
134
- name: name,
135
- internal: internal?,
136
- i18n_message: i18n_message,
137
- i18n_key: i18n_key,
138
- i18n_params: @i18n_params,
139
- cause: cause && cause.message,
140
- extra: extra
141
- }.delete_if { |_, v| v.nil? || (v.respond_to?(:empty?) && v.empty?) }
142
- end
143
- alias_method :as_json, :api_error
149
+ unless rs_defined = base.respond_to?(:__rack_status)
150
+ base.send :define_singleton_method, :__rack_status do
151
+ options.fetch(:rack_status, { errors: true, methods: true })
152
+ end
153
+ end
144
154
 
145
- # An error result that can be passed as a response body.
146
- def error_result
147
- { error: api_error, message: message }
148
- end
155
+ base.send(:extend, KineticCafe::ErrorDSL)
149
156
 
150
- # A hash that can be passed to the Rails +render+ method with +status+ of
151
- # #status and +layout+ false. The +json+ field is rendered as a hash of
152
- # +error+ (calling #api_error) and +message+ (calling #message).
153
- def json_result
154
- { status: status, json: error_result, layout: false }
155
- end
156
- alias_method :render_json_for_rails, :json_result
157
-
158
- # Nice debugging version of a KineticCafe::Error
159
- def inspect
160
- "#<#{self.class}: name=#{name} status=#{status} " \
161
- "message=#{message.inspect} i18n_key=#{i18n_key} " \
162
- "i18n_params=#{@i18n_params.inspect} extra=#{extra.inspect} " \
163
- "cause=#{cause}>"
164
- end
157
+ if block_given?
158
+ if block.arity > 0
159
+ yield base
160
+ else
161
+ base.instance_eval(&block)
162
+ end
163
+ end
165
164
 
166
- # The base for I18n key resolution.
167
- def self.i18n_key_base
168
- 'kcerrors'.freeze
165
+ base
166
+ ensure
167
+ if base.respond_to?(:__rack_status) && !rs_defined
168
+ base.singleton_class.send :undef_method, :__rack_status
169
+ end
169
170
  end
170
171
 
171
172
  private