kinetic_cafe_error 1.11 → 1.12

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: 3e59520ae4c59e3bd05ea5222fd800317d735457
4
- data.tar.gz: f7e38457bc7b25ec96c4201a7859b55169100a4d
3
+ metadata.gz: ac74dac14f83a0930ff010ec22ee5f38779bc0fb
4
+ data.tar.gz: 2d538e50f54639617655dc5e3d8486bdb66730f2
5
5
  SHA512:
6
- metadata.gz: 2653e7d7e1e214028c91eb1e4ed9ab9249b1cf06f6a24f3fd8f831f07aa942c9259db0a121ac114939705c8eaa0ef21fd752300fd14e1b03109f4018591a09a8
7
- data.tar.gz: 398211ce16cfa01a58dd9a105d025ac7c96b4011d9f0caf2b1451afc3a12240da7cfa31367c76d7b38a02dc8bf1b0a08331bbe562f730c27601a86e737945996
6
+ metadata.gz: a2475041b6851c65005929b3640abd492f625dcee28dee0e7d7c8f57e79c2a0bda44731540a48b483e2e438b65be789381a743c21b06d80c90420e60a800ed77
7
+ data.tar.gz: 8098e1494dc7c9c12c201e71d8cce5cd7ad3d48ae8602e144ddc83026d8f1c384cf9766a8b52f10e54fcf404a4a518d3fd1b6560e23530f7fec36d37e3e7a105
data/History.md CHANGED
@@ -1,3 +1,28 @@
1
+ ### 1.12 / 2016-05-27
2
+
3
+ * 4 major enhancements:
4
+
5
+ * Replace the use of ObjectSpace with a custom system to get a list of the
6
+ children of StandardError. This makes the provided rake tasks compatible
7
+ with JRuby.
8
+
9
+ * Refactor rake tasks into methods and add tests for those methods.
10
+
11
+ * Errors can now be declared in a severity block where their default_severity
12
+ will be different from the default of :error.
13
+
14
+ * Error-declarations can accept a block with other error-declaratons in them,
15
+ allowing for nested error hierarchies.
16
+
17
+ * 1 bug fix:
18
+
19
+ * Use String#new when we need mutable strings.
20
+
21
+ * 1 minor enhancement:
22
+
23
+ * If the controller instance does not respond to the 'respond_to' method,
24
+ then we default to rendering a JSON response.
25
+
1
26
  ### 1.11 / 2016-05-24
2
27
 
3
28
  * 1 minor enhancement:
@@ -27,6 +27,7 @@ lib/kinetic_cafe/error_rspec.rb
27
27
  lib/kinetic_cafe/error_tasks.rake
28
28
  lib/kinetic_cafe/error_tasks.rb
29
29
  lib/kinetic_cafe_error.rb
30
+ test/test_error_tasks.rb
30
31
  test/test_helper.rb
31
32
  test/test_kinetic_cafe_error.rb
32
33
  test/test_kinetic_cafe_error_dsl.rb
@@ -26,7 +26,7 @@ module KineticCafe # :nodoc:
26
26
  # rescue clause and handled there, as is shown in the included
27
27
  # KineticCafe::ErrorHandler controller concern for Rails.
28
28
  class Error < ::StandardError
29
- VERSION = '1.11' # :nodoc:
29
+ VERSION = '1.12' # :nodoc:
30
30
 
31
31
  # Get the KineticCafe::Error functionality.
32
32
  include KineticCafe::ErrorModule
@@ -175,4 +175,4 @@ end
175
175
 
176
176
  require_relative 'error_dsl'
177
177
  require_relative 'error_engine' if defined?(::Rails)
178
- require_relative 'error_tasks' if defined?(::Rake::DSL)
178
+ require_relative 'error_tasks' if defined?(::Rake)
@@ -1,7 +1,9 @@
1
- # frozen_string_literal: false
1
+ # frozen_string_literal: true
2
+
3
+ ##
2
4
  module KineticCafe
3
5
  # Make defining new children of KineticCafe::Error easy. Adds the
4
- # #define_error method.
6
+ # #define_error method and #severity block modifier.
5
7
  #
6
8
  # If using when Rack is present, useful variant methodss are provided
7
9
  # matching Rack status symbol codes. These set the default status to the Rack
@@ -78,7 +80,10 @@ module KineticCafe
78
80
  # +i18n_params+:: An array of parameter names that are expected to be
79
81
  # provided for translation. This helps document the
80
82
  # expected translations.
81
- def define_error(options)
83
+ #
84
+ # If a +block+ is provided, errors may be defined as subclasses of the error
85
+ # defined here.
86
+ def define_error(options, &block)
82
87
  fail ArgumentError, 'invalid options' unless options.kind_of?(Hash)
83
88
  fail ArgumentError, 'define what error?' if options.empty?
84
89
 
@@ -91,10 +96,12 @@ module KineticCafe
91
96
  fail ArgumentError, ":key conflicts with class:#{klass}" if options.key?(:key)
92
97
 
93
98
  key = if status.kind_of?(Symbol) || status.kind_of?(String)
94
- "#{klass}_#{KineticCafe::ErrorDSL.namify(status)}"
95
- else
96
- "#{klass}_#{KineticCafe::ErrorDSL.namify(name)}"
97
- end
99
+ "#{klass}_#{KineticCafe::ErrorDSL.namify(status)}"
100
+ else
101
+ "#{klass}_#{KineticCafe::ErrorDSL.namify(name)}"
102
+ end
103
+
104
+ key = String.new(key)
98
105
  else
99
106
  key = options.fetch(:key) {
100
107
  fail ArgumentError, 'one of :key or :class must be provided'
@@ -115,7 +122,7 @@ module KineticCafe
115
122
  i18n_key = "#{i18n_key_base}.#{key}"
116
123
 
117
124
  if const_defined?(error_name)
118
- message = "key:#{key} already exists as #{error_name}"
125
+ message = String.new("key:#{key} already exists as #{error_name}")
119
126
  message << " with class:#{klass}" if klass
120
127
  fail ArgumentError, message
121
128
  end
@@ -141,27 +148,52 @@ module KineticCafe
141
148
  status ||= defined?(Rack::Utils) && :bad_request || 400
142
149
  status.freeze
143
150
 
144
- severity ||= :error
151
+ if status
152
+ error.send :define_method, :default_status, -> { status }
153
+ error.send :private, :default_status
154
+ error.send :define_singleton_method, :default_status, -> { status }
155
+ end
156
+
157
+ severity ||= @severity || :error
145
158
  severity.freeze
146
159
 
147
- error.send :define_method, :default_status, -> { status } if status
148
- error.send :define_method, :default_severity, -> { severity } if severity
149
- error.send :private, :default_status
160
+ if severity
161
+ error.send :define_method, :default_severity, -> { severity }
162
+ error.send :define_singleton_method, :default_severity, -> { severity }
163
+ end
164
+
165
+ error.instance_exec(&block) if block
150
166
 
151
167
  const_set(error_name, error)
152
168
  end
153
169
 
154
- ##
155
- def self.included(_mod)
170
+ # Temporarily override the default severity with +value+ for errors defined in
171
+ # the provided +block+.
172
+ def severity(value, &block)
173
+ old_severity, @severity = @severity, value
174
+ fail 'Severity must have a block' unless block
175
+ instance_exec(&block)
176
+ ensure
177
+ @severity = old_severity
178
+ end
179
+
180
+ # Keep track of subclasses of errors using ErrorDSL.
181
+ def inherited(subclass) #:nodoc:
182
+ super
183
+ KineticCafe::ErrorDSL.inheritors << subclass
184
+ end
185
+
186
+ def self.included(_mod) # :nodoc:
156
187
  fail "#{self} cannot be included"
157
188
  end
158
189
 
159
- ##
160
- def self.extended(base)
190
+ def self.extended(base) # :nodoc:
161
191
  unless base < ::StandardError
162
192
  fail "#{self} cannot extend #{base} (not a StandardError)"
163
193
  end
164
194
 
195
+ inheritors << base
196
+
165
197
  rack_status = base.__rack_status if base.respond_to?(:__rack_status)
166
198
 
167
199
  case rack_status
@@ -178,8 +210,8 @@ module KineticCafe
178
210
  # Make the Rack names safe to use
179
211
  name = name.to_s.gsub(/[^[:word:]]/, '_').squeeze('_').to_sym
180
212
  if rack_status.fetch(:methods, true)
181
- base.singleton_class.send :define_method, name do |options = {}|
182
- define_error(options.merge(status: name))
213
+ base.singleton_class.send :define_method, name do |options = {}, &block|
214
+ define_error(options.merge(status: name), &block)
183
215
  end
184
216
  end
185
217
 
@@ -189,5 +221,9 @@ module KineticCafe
189
221
  end
190
222
  end
191
223
  end
224
+
225
+ def self.inheritors # :nodoc:
226
+ @inheritors ||= []
227
+ end
192
228
  end
193
229
  end
@@ -1,102 +1,144 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'kinetic_cafe_error'
4
+
3
5
  module KineticCafe
4
6
  # Tasks to assist with managing kinetic_cafe_error classes in Rake.
5
7
  module ErrorTasks #:nodoc:
6
- extend Rake::DSL
8
+ require 'rake' unless defined?(::Rake)
9
+
10
+ extend ::Rake::DSL
7
11
  end
8
12
  end
9
13
 
10
- ##
11
- module KineticCafe::ErrorTasks #:nodoc:
12
- require 'kinetic_cafe_error'
13
-
14
- namespace :kcerror do
15
- desc 'Show defined errors.'
16
- task :defined, [ :params ] => 'kcerror:find' do |_, args|
17
- display = ->(root, prefix: '', show_params: false) {
18
- line = "#{prefix}- #{root}"
19
-
20
- if !root.i18n_params.empty? && show_params
21
- line << ' (' << root.i18n_params.join(', ') << ')'
22
- end
23
-
24
- puts line
25
-
26
- if @descendants[root]
27
- sorted = @descendants[root].sort_by(&:to_s)
28
- sorted.each do |child|
29
- s = (child == sorted.last) ? '`' : '|'
30
- display.(
31
- child,
32
- prefix: "#{prefix.tr('|`', ' ')} #{s}",
33
- show_params: show_params
34
- )
35
- end
36
- end
14
+ class << KineticCafe::ErrorTasks
15
+ def print_defined(descendants = @descendants, output: $stdout, params: false)
16
+ if descendants[::StandardError].nil? || descendants[::StandardError].empty?
17
+ output.puts 'No defined errors.'
18
+ else
19
+ descendants[::StandardError].sort_by(&:to_s).each { |d|
20
+ display_error_class(descendants, d, output: output, params: params)
37
21
  }
22
+ end
23
+ end
38
24
 
39
- show_params = args.params =~ /^y/i
25
+ def print_translation_yaml(descendants = @descendants, output: $stdout)
26
+ output.puts build_translation_yaml(descendants)
27
+ end
40
28
 
41
- if @descendants[StandardError]
42
- @descendants[StandardError].sort_by(&:to_s).each { |d|
43
- display.(d, show_params: show_params)
44
- }
45
- else
46
- puts 'No defined errors.'
29
+ private
30
+
31
+ def display_error_class(descendants, root, output: $stdout, prefix: '', params: false)
32
+ line = String.new("#{prefix}- #{root}")
33
+
34
+ if !root.i18n_params.empty? && params
35
+ line << ' (' << root.i18n_params.join(', ') << ')'
36
+ end
37
+
38
+ output.puts line
39
+
40
+ if descendants[root]
41
+ sorted = descendants[root].sort_by(&:to_s)
42
+ sorted.each do |child|
43
+ s = (child == sorted.last) ? '`' : '|'
44
+ display_error_class(
45
+ descendants,
46
+ child,
47
+ output: output,
48
+ prefix: "#{prefix.tr('|`', ' ')} #{s}",
49
+ params: params
50
+ )
47
51
  end
48
52
  end
53
+ end
49
54
 
50
- desc 'Generate a sample translation key file.'
51
- task :translations, [ :output ] => 'kcerror:find' do |_, args|
52
- translations = {}
53
- traverse = ->(root) {
54
- translation = (translations[root.i18n_key_base] ||= {})
55
- name = KineticCafe::ErrorDSL.namify(root)
56
-
57
- params = root.i18n_params.map { |param| "%{#{param}}" }.join(' ')
58
-
59
- if params.empty?
60
- translation[name] = "Translation for #{name} with no params."
61
- else
62
- translation[name] = "Translation for #{name} with #{params}."
63
- end
64
-
65
- if @descendants[root]
66
- @descendants[root].sort_by(&:to_s).each { |child| traverse.(child) }
67
- end
68
- }
55
+ def build_error_hierarchy(error_class_list)
56
+ Hash.new { |h, k| h[k] = [] }.tap do |hierarchy|
57
+ error_class_list.each do |error_class|
58
+ hierarchy[error_class.superclass] << error_class
59
+ end
60
+
61
+ hierarchy[::StandardError].push(
62
+ *(
63
+ hierarchy.keys.select { |r| r.superclass == ::StandardError } -
64
+ hierarchy[::StandardError]
65
+ )
66
+ )
67
+ end
68
+ end
69
+
70
+ def find_with_error_dsl_inheritors
71
+ @descendants = build_error_hierarchy(KineticCafe::ErrorDSL.inheritors)
72
+ end
69
73
 
70
- if @descendants[StandardError]
71
- @descendants[StandardError].sort_by(&:to_s).each do |d| traverse.(d) end
74
+ def collect_translations(descendants, translations, root)
75
+ translation = (translations[root.i18n_key_base] ||= {})
76
+ name = KineticCafe::ErrorDSL.namify(root)
72
77
 
73
- # Normal YAML dump does not match the pattern that we want to compare
74
- # against. Therefore, we are going to write this file manually.
75
- text = 'kc:'
78
+ params = root.i18n_params.map { |param| "%{#{param}}" }.join(' ')
76
79
 
77
- translations.keys.sort.each do |group|
78
- text << "\n #{group}:"
80
+ translation[name] = if params.empty?
81
+ "Translation for #{name} with no params."
82
+ else
83
+ "Translation for #{name} with #{params}."
84
+ end
79
85
 
80
- translations[group].keys.sort.each { |k|
81
- text << "\n #{k}: >-\n #{translations[group][k]}"
82
- }
83
- end
86
+ if descendants[root]
87
+ descendants[root].sort_by(&:to_s).each do |child|
88
+ collect_translations(descendants, translations, child)
89
+ end
90
+ end
91
+ end
84
92
 
85
- if args.output
86
- File.open(args.output, 'w') { |f| f.puts text }
87
- else
88
- puts text
89
- end
93
+ def find_translations(descendants)
94
+ {}.tap do |translations|
95
+ descendants[::StandardError].sort_by(&:to_s).each do |d|
96
+ collect_translations(descendants, translations, d)
90
97
  end
91
98
  end
99
+ end
92
100
 
93
- task :find do
94
- @descendants = {}
95
- ObjectSpace.each_object(Class) do |k|
96
- next unless k.singleton_class < KineticCafe::ErrorDSL
101
+ def build_translation_yaml(descendants)
102
+ translations = find_translations(descendants)
103
+ return if translations.empty?
104
+
105
+ # Normal YAML dump does not match the pattern that we want to compare
106
+ # against. Therefore, we are going to write this file manually.
107
+ String.new('kc:').tap do |text|
108
+ translations.keys.sort.each do |group|
109
+ text << "\n #{group}:"
97
110
 
98
- (@descendants[k.superclass] ||= []) << k
111
+ translations[group].keys.sort.each { |k|
112
+ text << "\n #{k}: >-\n #{translations[group][k]}"
113
+ }
99
114
  end
100
115
  end
101
116
  end
117
+
118
+ def save_translation_yaml(filename, descendants = @descendants)
119
+ File.open(filename, 'w') { |f| print_translation_yaml(descendants, output: f) }
120
+ end
121
+ end
122
+
123
+ ##
124
+ module KineticCafe::ErrorTasks #:nodoc:
125
+ namespace :kcerror do
126
+ desc 'Show defined errors.'
127
+ task :defined, [ :params ] => 'kcerror:find' do |_, args|
128
+ print_defined(params: args.params =~ /^y/i)
129
+ end
130
+
131
+ desc 'Generate a sample translation key file.'
132
+ task :translations, [ :output ] => 'kcerror:find' do |_, args|
133
+ if args.output
134
+ save_translation_yaml(args.output)
135
+ else
136
+ print_translation_yaml
137
+ end
138
+ end
139
+
140
+ task :find do
141
+ find_with_error_dsl_inheritors
142
+ end
143
+ end
102
144
  end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+ require 'kinetic_cafe/error_tasks'
5
+
6
+ describe KineticCafe::ErrorTasks do
7
+ def setup
8
+ Object.const_set(:My, Module.new)
9
+ KineticCafe::Error.hierarchy(class: :Base, namespace: My, rack_status: { errors: false }) do
10
+ define_error class: :child, status: :not_found, i18n_params: %i(query)
11
+ define_error key: :other_child, status: :bad_request do
12
+ define_error key: :sub_child, status: :im_a_teapot
13
+ end
14
+ end
15
+ end
16
+
17
+ def teardown
18
+ Object.send(:remove_const, :My)
19
+ end
20
+
21
+ describe '.print_defined' do
22
+ describe 'when no StandardError descendants' do
23
+ it 'prints no defined errors' do
24
+ io = StringIO.new
25
+ KineticCafe::ErrorTasks.print_defined({}, { output: io })
26
+
27
+ assert_match(/No defined errors./, io.string)
28
+ end
29
+ end
30
+
31
+ describe 'when data' do
32
+ it 'prints out the classes involved, sorted' do
33
+ io = StringIO.new
34
+
35
+ classes = [
36
+ My::Base,
37
+ My::Base::ChildNotFound, My::Base::OtherChild,
38
+ My::Base::OtherChild::SubChild
39
+ ]
40
+ descendants = KineticCafe::ErrorTasks.send(:build_error_hierarchy, classes)
41
+ KineticCafe::ErrorTasks.print_defined(descendants, output: io)
42
+
43
+ expected = [
44
+ 'KineticCafe::Error',
45
+ 'My::Base',
46
+ 'My::Base::ChildNotFound',
47
+ 'My::Base::OtherChild',
48
+ 'My::Base::OtherChild::SubChild'
49
+ ]
50
+ actual = io.string.scan(/ \w.+\n/).map(&:strip)
51
+
52
+ assert_equal expected, actual
53
+ end
54
+ end
55
+ end
56
+
57
+ describe '.print_translation_yaml' do
58
+ describe 'when no StandardError descendant' do
59
+ it 'prints no defined errors' do
60
+ io = StringIO.new
61
+ KineticCafe::ErrorTasks.print_defined({}, { output: io })
62
+
63
+ assert_match(/No defined errors./, io.string)
64
+ end
65
+ end
66
+
67
+ describe 'when data' do
68
+ it 'prints the correct data in YAML format' do
69
+ io = StringIO.new
70
+
71
+ classes = [
72
+ My::Base,
73
+ My::Base::ChildNotFound, My::Base::OtherChild,
74
+ My::Base::OtherChild::SubChild
75
+ ]
76
+ descendants = KineticCafe::ErrorTasks.send(:build_error_hierarchy, classes)
77
+ KineticCafe::ErrorTasks.print_translation_yaml(descendants, output: io)
78
+
79
+ expected = { 'kc' =>
80
+ { 'kcerrors' =>
81
+ {
82
+ 'base' => 'Translation for base with no params.',
83
+ 'child_not_found' => 'Translation for child_not_found with %{query}.',
84
+ 'error' => 'Translation for error with no params.',
85
+ 'other_child' => 'Translation for other_child with no params.',
86
+ 'sub_child' => 'Translation for sub_child with no params.'
87
+ } } }
88
+
89
+ actual = YAML.load(io.string)
90
+
91
+ assert_equal expected, actual
92
+ end
93
+ end
94
+ end
95
+ end
@@ -8,6 +8,7 @@ require 'minitest/moar'
8
8
  require 'minitest/stub_const'
9
9
  require 'minitest-bonus-assertions'
10
10
  require 'rack/test'
11
+ require 'yaml'
11
12
  require 'kinetic_cafe_error'
12
13
 
13
14
  puts "Testing with Rack.release #{Rack.release}"
@@ -36,6 +36,34 @@ describe KineticCafe::ErrorDSL do
36
36
  assert base.respond_to?(:define_error)
37
37
  end
38
38
 
39
+ describe '#severity' do
40
+ describe 'fails when' do
41
+ it 'is not passed a block' do
42
+ ex = assert_raises RuntimeError do
43
+ base.severity(:foo)
44
+ end
45
+
46
+ assert_match(/must have a block/, ex.message)
47
+ end
48
+ end
49
+
50
+ it 'creates any errors in its block with the given severity, then reverts to old severity' do
51
+ severity = :test_level
52
+
53
+ base.severity(severity) do
54
+ not_found key: :foo
55
+ not_found key: :bar
56
+ end
57
+
58
+ assert_equal severity, base::Foo.default_severity
59
+ assert_equal severity, base::Bar.default_severity
60
+
61
+ base.define_error key: :child
62
+
63
+ refute_equal severity, base::Child.default_severity
64
+ end
65
+ end
66
+
39
67
  describe '#define_error' do
40
68
  describe 'fails when' do
41
69
  it 'given bad options' do
@@ -113,6 +141,18 @@ describe KineticCafe::ErrorDSL do
113
141
  end
114
142
  end
115
143
 
144
+ describe 'when nested definitions' do
145
+ it 'creates a nested error' do
146
+ base.define_error(key: :child) do
147
+ not_found key: :foo
148
+ end
149
+
150
+ assert base::Child::Foo < base::Child
151
+
152
+ assert base::Child::Foo.respond_to?(:default_severity)
153
+ end
154
+ end
155
+
116
156
  describe 'class-based definition' do
117
157
  describe 'without status' do
118
158
  before do
@@ -84,6 +84,10 @@ describe KineticCafe::Error, '.hierarchy' do
84
84
  KineticCafe::Error.hierarchy(class: My)
85
85
  end
86
86
 
87
+ it 'does not subclass KineticCafe::Error' do
88
+ refute My < KineticCafe::Error
89
+ end
90
+
87
91
  it 'has been extended with KineticCafe::ErrorDSL' do
88
92
  assert My.singleton_class < KineticCafe::ErrorDSL
89
93
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kinetic_cafe_error
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.11'
4
+ version: '1.12'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Austin Ziegler
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-05-24 00:00:00.000000000 Z
13
+ date: 2016-05-27 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: minitest
@@ -364,6 +364,7 @@ files:
364
364
  - lib/kinetic_cafe/error_tasks.rake
365
365
  - lib/kinetic_cafe/error_tasks.rb
366
366
  - lib/kinetic_cafe_error.rb
367
+ - test/test_error_tasks.rb
367
368
  - test/test_helper.rb
368
369
  - test/test_kinetic_cafe_error.rb
369
370
  - test/test_kinetic_cafe_error_dsl.rb
@@ -390,7 +391,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
390
391
  version: '0'
391
392
  requirements: []
392
393
  rubyforge_project:
393
- rubygems_version: 2.6.4
394
+ rubygems_version: 2.5.1
394
395
  signing_key:
395
396
  specification_version: 4
396
397
  summary: kinetic_cafe_error provides an API-smart error base class and a DSL for defining