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 +4 -4
- data/README.md +30 -1
- data/lib/lhs.rb +20 -6
- data/lib/lhs/collection.rb +1 -1
- data/lib/lhs/concerns/item/save.rb +1 -1
- data/lib/lhs/concerns/item/update.rb +1 -1
- data/lib/lhs/concerns/item/validation.rb +1 -1
- data/lib/lhs/concerns/proxy/accessors.rb +5 -3
- data/lib/lhs/concerns/proxy/problems.rb +24 -0
- data/lib/lhs/problems/base.rb +108 -0
- data/lib/lhs/problems/errors.rb +65 -0
- data/lib/lhs/problems/nested/base.rb +52 -0
- data/lib/lhs/problems/nested/errors.rb +14 -0
- data/lib/lhs/problems/nested/warnings.rb +13 -0
- data/lib/lhs/problems/warnings.rb +21 -0
- data/lib/lhs/proxy.rb +3 -3
- data/lib/lhs/version.rb +1 -1
- data/spec/item/warnings_spec.rb +52 -0
- data/spec/record/create_spec.rb +1 -1
- metadata +12 -6
- data/lib/lhs/concerns/proxy/errors.rb +0 -21
- data/lib/lhs/errors/base.rb +0 -160
- data/lib/lhs/errors/nested.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e02a2c879954de6557dc437da50cddf53fbcb054
|
4
|
+
data.tar.gz: cf5c0881ae67770796703f483d3cdd3939a47425
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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,
|
data/lib/lhs/collection.rb
CHANGED
@@ -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
|
@@ -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
|
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::
|
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 :
|
9
|
-
'lhs/concerns/proxy/
|
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
@@ -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
|
data/spec/record/create_spec.rb
CHANGED
@@ -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
|
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.
|
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-
|
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.
|
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
|
data/lib/lhs/errors/base.rb
DELETED
@@ -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
|
data/lib/lhs/errors/nested.rb
DELETED
@@ -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
|