kinetic_cafe_error 1.1 → 1.2

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.
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