lhs 14.1.1 → 14.2.0

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