kinetic_cafe_error 1.11 → 1.12

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