lhs 14.1.1 → 14.2.0

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: 410e4fb105669f81e2f5d2597bfce8e70067ce22
4
- data.tar.gz: 242bab7a1495a4b232b1ef25f99eef6f307c657d
3
+ metadata.gz: e02a2c879954de6557dc437da50cddf53fbcb054
4
+ data.tar.gz: cf5c0881ae67770796703f483d3cdd3939a47425
5
5
  SHA512:
6
- metadata.gz: b36a19677807863f4dafee0c9bc939b89ba922f76b1493610746b0c54a06dc8aa29e3fbed82c4992e1c72591e605c36fc9640680e316294cb733a2b578940ab6
7
- data.tar.gz: 410dbbf82a0172b9604dc2c5ca91a5352a8b095f715a8c9347aff46e51a5699d761d316c0e4c89887924d1d27ef6ad820593b16ca095b780ed4ad8eae66a5efc
6
+ metadata.gz: 5309110a6cc3d2a6afae3562c1297cc25a85959c5bdc3b4b3b28742c0d515aca862dcbf5572691631318818ad7b1cae1c1e7db9b2f8eb9e92c50e1a364c8ff4d
7
+ data.tar.gz: 8bae589cb78ec9f43df90df959aa113e81c9000814ae5d8157e96311e1912fbd169a475dd75a80431145769ca8fe961f1dc447beca8797d1b812fe905da7fd6c
data/README.md CHANGED
@@ -755,7 +755,7 @@ unless user.valid?
755
755
  fail(user.errors[:email])
756
756
  end
757
757
 
758
- user.errors #<LHS::Errors::Base>
758
+ user.errors #<LHS::Problems::Errors>
759
759
  user.errors.include?(:email) # true
760
760
  user.errors[:email] # ['REQUIRED_PROPERTY_VALUE']
761
761
  user.errors.messages # {:email=>["REQUIRED_PROPERTY_VALUE"]}
@@ -844,6 +844,35 @@ lhs.errors.fallback_message
844
844
  ### Know issue with `ActiveModel::Validations`
845
845
  If you are using `ActiveModel::Validations` and add errors to the LHS::Record instance - as described above - then those errors will be overwritten by the errors from `ActiveModel::Validations` when using `save` or `valid?`. [Open issue](https://github.com/local-ch/lhs/issues/159)
846
846
 
847
+ ### Non blocking validation errors, so called warnings
848
+
849
+ In some cases, you need non blocking meta information about potential problems with the created record, so called warnings.
850
+
851
+ If the API endpoint implements warnings:
852
+
853
+ ```
854
+ {
855
+ field_warnings: [{
856
+ code: 'WILL_BE_RESIZED',
857
+ path: ['place', 'photos', 0],
858
+ message: 'The image will be resized.'
859
+ }
860
+ }
861
+ ```
862
+
863
+ LHS makes those warnings available:
864
+
865
+ ```ruby
866
+ presence = Presence.options(params: { synchronize: false }).create(
867
+ place: { href: 'http://storage/places/1' }
868
+ )
869
+
870
+ presence.warnings.any? # true
871
+ presence.place.photos[0].warnings.messages.first # 'The photos will be resized'
872
+ ```
873
+
874
+ Warnings behave like [Validation Errors](#Validation) and implements the same interfaces and methods.
875
+
847
876
  ## Pagination
848
877
 
849
878
  LHS supports paginated APIs and it also supports various pagination strategies and by providing configuration possibilities.
data/lib/lhs.rb CHANGED
@@ -15,12 +15,6 @@ module LHS
15
15
  'lhs/data'
16
16
  autoload :Endpoint,
17
17
  'lhs/endpoint'
18
- autoload :Errors,
19
- 'lhs/errors/base'
20
- module Errors
21
- autoload :Nested,
22
- 'lhs/errors/nested'
23
- end
24
18
  autoload :Inspect,
25
19
  'lhs/concerns/inspect'
26
20
  autoload :Item,
@@ -35,6 +29,26 @@ module LHS
35
29
  autoload :Start,
36
30
  'lhs/pagination/start'
37
31
  end
32
+ autoload :Problems,
33
+ 'lhs/problems/base'
34
+ module Problems
35
+ autoload :Base,
36
+ 'lhs/problems/base'
37
+ autoload :Errors,
38
+ 'lhs/problems/errors'
39
+ autoload :Nested,
40
+ 'lhs/problems/nested/base'
41
+ module Nested
42
+ autoload :Base,
43
+ 'lhs/problems/nested/base'
44
+ autoload :Errors,
45
+ 'lhs/problems/nested/errors'
46
+ autoload :Warnings,
47
+ 'lhs/problems/nested/warnings'
48
+ end
49
+ autoload :Warnings,
50
+ 'lhs/problems/warnings'
51
+ end
38
52
  autoload :Proxy,
39
53
  'lhs/proxy'
40
54
  autoload :Record,
@@ -56,7 +56,7 @@ class LHS::Collection < LHS::Proxy
56
56
  record = LHS::Record.for_url(value[:href]) if value.is_a?(Hash) && value[:href]
57
57
  value = enclose_item_in_data(value) if value.is_a?(Hash)
58
58
  return value if METHOD_NAMES_EXLCUDED_FROM_WRAPPING.include?(name.to_s)
59
- wrap_return(value, record, name)
59
+ wrap_return(value, record, name, args)
60
60
  elsif _data._raw.is_a?(Hash)
61
61
  get(name, *args)
62
62
  end
@@ -19,7 +19,7 @@ class LHS::Item < LHS::Proxy
19
19
  apply_default_creation_options(options, url, data)
20
20
  )
21
21
  rescue LHC::Error => e
22
- self.errors = LHS::Errors::Base.new(e.response, record)
22
+ self.errors = LHS::Problems::Errors.new(e.response, record)
23
23
  raise e
24
24
  end
25
25
 
@@ -8,7 +8,7 @@ class LHS::Item < LHS::Proxy
8
8
  def update(params, options = nil)
9
9
  update!(params, options)
10
10
  rescue LHC::Error => e
11
- self.errors = LHS::Errors::Base.new(e.response, record)
11
+ self.errors = LHS::Problems::Errors.new(e.response, record)
12
12
  false
13
13
  end
14
14
 
@@ -16,7 +16,7 @@ class LHS::Item < LHS::Proxy
16
16
  run_validation!(record, options, url, params)
17
17
  true
18
18
  rescue LHC::Error => e
19
- self.errors = LHS::Errors::Base.new(e.response, record)
19
+ self.errors = LHS::Problems::Errors.new(e.response, record)
20
20
  false
21
21
  end
22
22
  alias validate valid?
@@ -67,12 +67,14 @@ class LHS::Proxy
67
67
  wrap_return(collection, record, name)
68
68
  end
69
69
 
70
- # Wraps with record and adds nested errors to data,
70
+ # Wraps with record and adds nested errors/warnings to data,
71
71
  # if errors are existing
72
- def wrap_return(value, record, name)
72
+ def wrap_return(value, record, name, args = nil)
73
+ name = args.first if name == :[]
73
74
  return value unless worth_wrapping?(value)
74
75
  data = value.is_a?(LHS::Data) || value.is_a?(LHS::Record) ? value : LHS::Data.new(value, _data)
75
- data.errors = LHS::Errors::Nested.new(errors, name) if errors
76
+ data.errors = LHS::Problems::Nested::Errors.new(errors, name) if errors.any?
77
+ data.warnings = LHS::Problems::Nested::Warnings.new(warnings, name) if warnings.any?
76
78
  return record.new(data) if record && !value.is_a?(LHS::Record)
77
79
  data
78
80
  end
@@ -0,0 +1,24 @@
1
+ require 'active_support'
2
+
3
+ class LHS::Proxy
4
+
5
+ module Problems
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attr_writer :errors, :warnings
10
+ end
11
+
12
+ def initialize(data)
13
+ super(data)
14
+ end
15
+
16
+ def errors
17
+ @errors ||= LHS::Problems::Errors.new(nil, record)
18
+ end
19
+
20
+ def warnings
21
+ @warnings ||= LHS::Problems::Warnings.new(_raw, record)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,108 @@
1
+ require 'active_support/core_ext/module'
2
+ require 'active_support/core_ext/hash'
3
+
4
+ # Like ActiveModel::Errors
5
+ module LHS::Problems
6
+ class Base
7
+ include Enumerable
8
+
9
+ attr_reader :raw, :messages, :record
10
+
11
+ def include?(attribute)
12
+ messages[attribute].present?
13
+ end
14
+ alias has_key? include?
15
+ alias key? include?
16
+
17
+ def add(attribute, message = :invalid, options = {})
18
+ self[attribute]
19
+ messages[attribute] << generate_message(attribute, message, options)
20
+ end
21
+
22
+ def get(key)
23
+ messages[key]
24
+ end
25
+
26
+ def set(key, message)
27
+ return if message.blank?
28
+ messages[key] = [generate_message(key, message)]
29
+ end
30
+
31
+ delegate :delete, to: :messages
32
+
33
+ def [](attribute)
34
+ get(attribute.to_sym) || messages[attribute] = []
35
+ end
36
+
37
+ def []=(attribute, message)
38
+ self[attribute] << generate_message(attribute, message)
39
+ end
40
+
41
+ def each
42
+ if messages.is_a?(Hash)
43
+ messages.each_key do |attribute|
44
+ self[attribute].each { |message| yield attribute, message }
45
+ end
46
+ elsif messages.is_a?(Array)
47
+ messages.each { |message| yield message }
48
+ end
49
+ end
50
+
51
+ def size
52
+ values.flatten.size
53
+ end
54
+
55
+ def clear
56
+ @raw = nil
57
+ @messages.clear
58
+ end
59
+
60
+ delegate :values, to: :messages
61
+
62
+ delegate :keys, to: :messages
63
+
64
+ def count
65
+ to_a.size
66
+ end
67
+
68
+ def empty?
69
+ all? { |_k, v| v && v.empty? && !v.is_a?(String) }
70
+ end
71
+
72
+ private
73
+
74
+ def add_error(messages, key, value)
75
+ key = key.to_sym
76
+ messages[key] ||= []
77
+ messages[key].push(generate_message(key, value))
78
+ end
79
+
80
+ def generate_message(attribute, message, _options = {})
81
+ problem_type = self.class.name.demodulize.downcase
82
+ find_translated_message(attribute, message, problem_type) || message
83
+ end
84
+
85
+ def find_translated_message(attribute, message, problem_type)
86
+ normalized_attribute = attribute.to_s.underscore
87
+ normalized_message = message.to_s.underscore
88
+ messages = []
89
+ messages = messages_for_record(normalized_attribute, normalized_message, problem_type) if record
90
+ messages.concat([
91
+ ['lhs', problem_type, 'messages', normalized_message],
92
+ ['lhs', problem_type, 'attributes', normalized_attribute, normalized_message],
93
+ ['lhs', problem_type, 'fallback_message']
94
+ ]).detect do |path|
95
+ key = path.join('.')
96
+ return I18n.translate(key) if I18n.exists?(key)
97
+ end
98
+ end
99
+
100
+ def messages_for_record(normalized_attribute, normalized_message, problem_type)
101
+ record_name = record.model_name.name.underscore
102
+ [
103
+ ['lhs', problem_type, 'records', record_name, 'attributes', normalized_attribute, normalized_message],
104
+ ['lhs', problem_type, 'records', record_name, normalized_message]
105
+ ]
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,65 @@
1
+ module LHS::Problems
2
+ class Errors < Base
3
+
4
+ attr_reader :status_code, :message
5
+
6
+ def initialize(response = nil, record = nil)
7
+ @raw = response.body if response
8
+ @record = record
9
+ @messages = messages_from_response(response).with_indifferent_access
10
+ @message = message_from_response(response)
11
+ @status_code = response.code if response
12
+ rescue JSON::ParserError
13
+ @messages = (messages || {}).with_indifferent_access
14
+ @message = 'parse error'
15
+ add_error(@messages, 'body', 'parse error')
16
+ end
17
+
18
+ private
19
+
20
+ def parse_messages(json)
21
+ messages = {}
22
+ fields_to_errors(json, messages) if json['fields']
23
+ field_errors_to_errors(json, messages) if json['field_errors']
24
+ fallback_errors(json, messages) if messages.empty?
25
+ messages
26
+ end
27
+
28
+ def fallback_errors(json, messages)
29
+ if json.present?
30
+ json.each do |key, value|
31
+ add_error(messages, key, value)
32
+ end
33
+ else
34
+ add_error(messages, 'unknown', 'error')
35
+ end
36
+ end
37
+
38
+ def field_errors_to_errors(json, messages)
39
+ json['field_errors'].each do |field_error|
40
+ add_error(messages, field_error['path'].join('.').to_sym, field_error['code'])
41
+ end
42
+ end
43
+
44
+ def fields_to_errors(json, messages)
45
+ json['fields'].each do |field|
46
+ field['details'].each do |detail|
47
+ add_error(messages, field['name'].to_sym, detail['code'])
48
+ end
49
+ end
50
+ end
51
+
52
+ def messages_from_response(response = nil)
53
+ return {} if !response || !response.body.is_a?(String) || response.body.length.zero?
54
+ json = JSON.parse(response.body)
55
+ parse_messages(json)
56
+ end
57
+
58
+ def message_from_response(response = nil)
59
+ return if response.blank?
60
+ raise JSON::ParserError if response.body.blank?
61
+ json = JSON.parse(response.body)
62
+ json['message']
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,52 @@
1
+ module LHS::Problems
2
+ module Nested
3
+ module Base
4
+ # Filters base errors by scope
5
+ # and reduces key by given scope name;
6
+ # returns plain array if end of tree is reached
7
+ def nest(messages, scope = nil)
8
+ scope = translate_rails_to_api_scope(scope)
9
+ return messages unless scope
10
+ messages = messages.select do |key, _|
11
+ key.match(/^#{scope}/)
12
+ end
13
+ # if only one key and this key has no dots, exit with plain
14
+ if reached_leaf?(messages)
15
+ messages.first[1]
16
+ else
17
+ remove_scope(messages, scope)
18
+ end
19
+ end
20
+
21
+ # Identifies if the end of nested errors tree is reached
22
+ def reached_leaf?(messages)
23
+ messages.keys.length == 1 &&
24
+ !messages.first[0].match(/\./)
25
+ end
26
+
27
+ # Removes scope from given messages' key
28
+ def remove_scope(messages, scope)
29
+ messages.each_with_object({}) do |element, hash|
30
+ key = element[0].to_s.gsub(/^#{scope}\./, '')
31
+ hash[key.to_sym] = element[1]
32
+ end
33
+ end
34
+
35
+ # Translates rails like accessors for collections
36
+ # like first, second, last to api error paths
37
+ def translate_rails_to_api_scope(scope)
38
+ case scope
39
+ when :first
40
+ 0
41
+ when :second
42
+ 1
43
+ when :last
44
+ return values.length - 1 if messages.present?
45
+ 0
46
+ else
47
+ scope
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,14 @@
1
+ module LHS::Problems
2
+ module Nested
3
+ class Errors < LHS::Problems::Errors
4
+ include LHS::Problems::Nested::Base
5
+
6
+ def initialize(errors, scope)
7
+ @raw = errors
8
+ @messages = nest(errors.messages, scope)
9
+ @message = errors.message
10
+ @scope = scope
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module LHS::Problems
2
+ module Nested
3
+ class Warnings < LHS::Problems::Warnings
4
+ include LHS::Problems::Nested::Base
5
+
6
+ def initialize(warnings, scope)
7
+ @raw = warnings.raw
8
+ @messages = nest(warnings.messages, scope)
9
+ @scope = scope
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ module LHS::Problems
2
+ class Warnings < Base
3
+
4
+ def initialize(raw, record = nil)
5
+ @raw = raw
6
+ @record = record
7
+ @messages = warnings_from_raw
8
+ end
9
+
10
+ private
11
+
12
+ def warnings_from_raw
13
+ messages = {}
14
+ return messages if !raw.is_a?(Hash) || raw[:field_warnings].blank?
15
+ raw[:field_warnings].each do |field_warning|
16
+ add_error(messages, field_warning[:path].join('.').to_sym, field_warning[:code])
17
+ end
18
+ messages.with_indifferent_access
19
+ end
20
+ end
21
+ end
data/lib/lhs/proxy.rb CHANGED
@@ -5,15 +5,15 @@ class LHS::Proxy
5
5
  'lhs/concerns/proxy/accessors'
6
6
  autoload :Create,
7
7
  'lhs/concerns/proxy/create'
8
- autoload :Errors,
9
- 'lhs/concerns/proxy/errors'
8
+ autoload :Problems,
9
+ 'lhs/concerns/proxy/problems'
10
10
  autoload :Link,
11
11
  'lhs/concerns/proxy/link'
12
12
 
13
13
  include Accessors
14
14
  include Create
15
- include Errors
16
15
  include Link
16
+ include Problems
17
17
 
18
18
  # prevent clashing with attributes of underlying data
19
19
  attr_accessor :_data, :_loaded
data/lib/lhs/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module LHS
2
- VERSION = '14.1.1'
2
+ VERSION = '14.2.0'
3
3
  end
@@ -0,0 +1,52 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHS::Item do
4
+
5
+ before(:each) do
6
+ class Presence < LHS::Record
7
+ endpoint 'http://opm/presence'
8
+ end
9
+ end
10
+
11
+ before(:each) do
12
+ I18n.reload!
13
+ I18n.backend.store_translations(:en, YAML.safe_load(%q{
14
+ lhs:
15
+ warnings:
16
+ records:
17
+ presence:
18
+ will_be_resized: 'The photos will be resized'
19
+ }))
20
+ end
21
+
22
+ it 'provides warnings together with validation errors' do
23
+ stub_request(:post, "http://opm/presence?synchronize=false")
24
+ .to_return(
25
+ body: {
26
+ field_warnings: [{
27
+ code: 'WILL_BE_RESIZED',
28
+ path: ['place', 'photos', 0],
29
+ message: 'The image will be resized.'
30
+ }],
31
+ place: {
32
+ href: 'http://storage/places/1',
33
+ photos: [{
34
+ href: 'http://bin.staticlocal.ch/123',
35
+ width: 10,
36
+ height: 10
37
+ }]
38
+ }
39
+ }.to_json
40
+ )
41
+ presence = Presence.options(params: { synchronize: false }).create(
42
+ place: { href: 'http://storage/places/1' }
43
+ )
44
+ expect(presence.warnings.any?).to eq true
45
+ expect(presence.place.warnings.any?).to eq true
46
+ expect(presence.place.photos.warnings.any?).to eq true
47
+ expect(presence.place.photos[0].warnings.any?).to eq true
48
+ expect(presence.place.photos[0].warnings.messages.first).to eq(
49
+ 'The photos will be resized'
50
+ )
51
+ end
52
+ end
@@ -66,7 +66,7 @@ describe LHS::Record do
66
66
  stub_request(:post, "#{datastore}/content-ads/12345/feedbacks")
67
67
  .to_return(status: 400, body: creation_error.to_json)
68
68
  feedback = Feedback.create(object.merge(campaign_id: '12345'))
69
- expect(feedback.errors).to be_kind_of LHS::Errors::Base
69
+ expect(feedback.errors).to be_kind_of LHS::Problems::Errors
70
70
  end
71
71
 
72
72
  it 'raises an exception when creation failed using create!' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lhs
3
3
  version: !ruby/object:Gem::Version
4
- version: 14.1.1
4
+ version: 14.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - https://github.com/local-ch/lhs/graphs/contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-20 00:00:00.000000000 Z
11
+ date: 2017-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lhc
@@ -212,8 +212,8 @@ files:
212
212
  - lib/lhs/concerns/item/validation.rb
213
213
  - lib/lhs/concerns/proxy/accessors.rb
214
214
  - lib/lhs/concerns/proxy/create.rb
215
- - lib/lhs/concerns/proxy/errors.rb
216
215
  - lib/lhs/concerns/proxy/link.rb
216
+ - lib/lhs/concerns/proxy/problems.rb
217
217
  - lib/lhs/concerns/record/batch.rb
218
218
  - lib/lhs/concerns/record/chainable.rb
219
219
  - lib/lhs/concerns/record/configuration.rb
@@ -234,13 +234,17 @@ files:
234
234
  - lib/lhs/config.rb
235
235
  - lib/lhs/data.rb
236
236
  - lib/lhs/endpoint.rb
237
- - lib/lhs/errors/base.rb
238
- - lib/lhs/errors/nested.rb
239
237
  - lib/lhs/item.rb
240
238
  - lib/lhs/pagination/base.rb
241
239
  - lib/lhs/pagination/offset.rb
242
240
  - lib/lhs/pagination/page.rb
243
241
  - lib/lhs/pagination/start.rb
242
+ - lib/lhs/problems/base.rb
243
+ - lib/lhs/problems/errors.rb
244
+ - lib/lhs/problems/nested/base.rb
245
+ - lib/lhs/problems/nested/errors.rb
246
+ - lib/lhs/problems/nested/warnings.rb
247
+ - lib/lhs/problems/warnings.rb
244
248
  - lib/lhs/proxy.rb
245
249
  - lib/lhs/railtie.rb
246
250
  - lib/lhs/record.rb
@@ -333,6 +337,7 @@ files:
333
337
  - spec/item/translate_errors_spec.rb
334
338
  - spec/item/update_spec.rb
335
339
  - spec/item/validation_spec.rb
340
+ - spec/item/warnings_spec.rb
336
341
  - spec/pagination/pages_left_spec.rb
337
342
  - spec/proxy/create_sub_resource_spec.rb
338
343
  - spec/proxy/load_spec.rb
@@ -420,7 +425,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
420
425
  requirements:
421
426
  - Ruby >= 2.3.0
422
427
  rubyforge_project:
423
- rubygems_version: 2.6.13
428
+ rubygems_version: 2.6.8
424
429
  signing_key:
425
430
  specification_version: 4
426
431
  summary: Rails gem providing an easy, active-record-like interface for http json services
@@ -509,6 +514,7 @@ test_files:
509
514
  - spec/item/translate_errors_spec.rb
510
515
  - spec/item/update_spec.rb
511
516
  - spec/item/validation_spec.rb
517
+ - spec/item/warnings_spec.rb
512
518
  - spec/pagination/pages_left_spec.rb
513
519
  - spec/proxy/create_sub_resource_spec.rb
514
520
  - spec/proxy/load_spec.rb
@@ -1,21 +0,0 @@
1
- require 'active_support'
2
-
3
- class LHS::Proxy
4
-
5
- module Errors
6
- extend ActiveSupport::Concern
7
-
8
- included do
9
- attr_writer :errors
10
- end
11
-
12
- def initialize(data)
13
- super(data)
14
- self.errors = LHS::Errors::Base.new(nil, record)
15
- end
16
-
17
- def errors
18
- @errors ||= LHS::Errors::Base.new(nil, record)
19
- end
20
- end
21
- end
@@ -1,160 +0,0 @@
1
- require 'active_support/core_ext/module'
2
- require 'active_support/core_ext/hash'
3
-
4
- # Like ActiveModel::Errors
5
- module LHS::Errors
6
- class Base
7
- include Enumerable
8
-
9
- attr_reader :messages, :message, :raw, :record, :status_code
10
-
11
- def initialize(response = nil, record = nil)
12
- @raw = response.body if response
13
- @record = record
14
- @messages = messages_from_response(response).with_indifferent_access
15
- @message = message_from_response(response)
16
- @status_code = response.code if response
17
- rescue JSON::ParserError
18
- @messages = (messages || {}).with_indifferent_access
19
- @message = 'parse error'
20
- add_error(@messages, 'body', 'parse error')
21
- end
22
-
23
- def include?(attribute)
24
- messages[attribute].present?
25
- end
26
- alias has_key? include?
27
- alias key? include?
28
-
29
- def add(attribute, message = :invalid, options = {})
30
- self[attribute]
31
- messages[attribute] << generate_message(attribute, message, options)
32
- end
33
-
34
- def get(key)
35
- messages[key]
36
- end
37
-
38
- def set(key, message)
39
- return if message.blank?
40
- messages[key] = [generate_message(key, message)]
41
- end
42
-
43
- delegate :delete, to: :messages
44
-
45
- def [](attribute)
46
- get(attribute.to_sym) || messages[attribute] = []
47
- end
48
-
49
- def []=(attribute, message)
50
- self[attribute] << generate_message(attribute, message)
51
- end
52
-
53
- def each
54
- messages.each_key do |attribute|
55
- self[attribute].each { |message| yield attribute, message }
56
- end
57
- end
58
-
59
- def size
60
- values.flatten.size
61
- end
62
-
63
- def clear
64
- @raw = nil
65
- @messages.clear
66
- end
67
-
68
- delegate :values, to: :messages
69
-
70
- delegate :keys, to: :messages
71
-
72
- def count
73
- to_a.size
74
- end
75
-
76
- def empty?
77
- all? { |_k, v| v && v.empty? && !v.is_a?(String) }
78
- end
79
-
80
- private
81
-
82
- def add_error(messages, key, value)
83
- key = key.to_sym
84
- messages[key] ||= []
85
- messages[key].push(generate_message(key, value))
86
- end
87
-
88
- def generate_message(attribute, message, _options = {})
89
- find_translated_error_message(attribute, message) || message
90
- end
91
-
92
- def find_translated_error_message(attribute, message)
93
- normalized_attribute = attribute.to_s.underscore
94
- normalized_message = message.to_s.underscore
95
- messages = []
96
- messages = messages_for_record(normalized_attribute, normalized_message) if record
97
- messages.concat([
98
- ['lhs', 'errors', 'messages', normalized_message],
99
- ['lhs', 'errors', 'attributes', normalized_attribute, normalized_message],
100
- ['lhs', 'errors', 'fallback_message']
101
- ]).detect do |path|
102
- key = path.join('.')
103
- return I18n.translate(key) if I18n.exists?(key)
104
- end
105
- end
106
-
107
- def messages_for_record(normalized_attribute, normalized_message)
108
- record_name = record.model_name.name.underscore
109
- [
110
- ['lhs', 'errors', 'records', record_name, 'attributes', normalized_attribute, normalized_message],
111
- ['lhs', 'errors', 'records', record_name, normalized_message]
112
- ]
113
- end
114
-
115
- def parse_messages(json)
116
- messages = {}
117
- fields_to_errors(json, messages) if json['fields']
118
- field_errors_to_errors(json, messages) if json['field_errors']
119
- fallback_errors(json, messages) if messages.empty?
120
- messages
121
- end
122
-
123
- def fallback_errors(json, messages)
124
- if json.present?
125
- json.each do |key, value|
126
- add_error(messages, key, value)
127
- end
128
- else
129
- add_error(messages, 'unknown', 'error')
130
- end
131
- end
132
-
133
- def field_errors_to_errors(json, messages)
134
- json['field_errors'].each do |field_error|
135
- add_error(messages, field_error['path'].join('.').to_sym, field_error['code'])
136
- end
137
- end
138
-
139
- def fields_to_errors(json, messages)
140
- json['fields'].each do |field|
141
- field['details'].each do |detail|
142
- add_error(messages, field['name'].to_sym, detail['code'])
143
- end
144
- end
145
- end
146
-
147
- def messages_from_response(response = nil)
148
- return {} if !response || !response.body.is_a?(String) || response.body.length.zero?
149
- json = JSON.parse(response.body)
150
- parse_messages(json)
151
- end
152
-
153
- def message_from_response(response = nil)
154
- return if response.blank?
155
- raise JSON::ParserError if response.body.blank?
156
- json = JSON.parse(response.body)
157
- json['message']
158
- end
159
- end
160
- end
@@ -1,60 +0,0 @@
1
- module LHS::Errors
2
- class Nested < Base
3
-
4
- def initialize(errors, scope)
5
- @raw = errors
6
- @messages = nest(errors.messages, scope)
7
- @message = errors.message
8
- @scope = scope
9
- end
10
-
11
- private
12
-
13
- # Filters base errors by scope
14
- # and reduces key by given scope name;
15
- # returns plain array if end of tree is reached
16
- def nest(messages, scope = nil)
17
- scope = translate_rails_to_api_scope(scope)
18
- return messages unless scope
19
- messages = messages.select do |key, _|
20
- key.match(/^#{scope}/)
21
- end
22
- # if only one key and this key has no dots, exit with plain
23
- if reached_leaf?(messages)
24
- messages.first[1]
25
- else
26
- remove_scope(messages, scope)
27
- end
28
- end
29
-
30
- # Identifies if the end of nested errors tree is reached
31
- def reached_leaf?(messages)
32
- messages.keys.length == 1 &&
33
- !messages.first[0].match(/\./)
34
- end
35
-
36
- # Removes scope from given messages' key
37
- def remove_scope(messages, scope)
38
- messages.each_with_object({}) do |element, hash|
39
- key = element[0].to_s.gsub(/^#{scope}\./, '')
40
- hash[key.to_sym] = element[1]
41
- end
42
- end
43
-
44
- # Translates rails like accessors for collections
45
- # like first, second, last to api error paths
46
- def translate_rails_to_api_scope(scope)
47
- case scope
48
- when :first
49
- 0
50
- when :second
51
- 1
52
- when :last
53
- return values.length - 1 if messages.present?
54
- 0
55
- else
56
- scope
57
- end
58
- end
59
- end
60
- end