gorg_service 5.3.1 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/gorg_service.gemspec +3 -1
- data/lib/gorg_service/consumer/errors.rb +29 -3
- data/lib/gorg_service/consumer/listener.rb +17 -14
- data/lib/gorg_service/consumer/message_handler/base.rb +34 -23
- data/lib/gorg_service/event_message.rb +13 -0
- data/lib/gorg_service/log_message.rb +46 -0
- data/lib/gorg_service/message.rb +223 -107
- data/lib/gorg_service/message/formatters.rb +313 -0
- data/lib/gorg_service/message/json_schema.rb +1 -87
- data/lib/gorg_service/reply_message.rb +41 -0
- data/lib/gorg_service/request_message.rb +13 -0
- data/lib/gorg_service/rspec/log_message_handler.rb +4 -4
- data/lib/gorg_service/version.rb +1 -1
- metadata +37 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4176b3c8a8689ae7474eaa5420f3b45c96fbc916
|
4
|
+
data.tar.gz: 0b48b75295d30fbeeabd9245cf22e9d97174a02f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8fe317544a9bf0a1cb7b5583f0489ba734ff982f9d1142527bfc69147d2779ecde58224720138d816fa8eb4e1fce948f78f89d9672765ee67feb36d880bc0b7d
|
7
|
+
data.tar.gz: cec6262709a87326414100b87707f89382f9344be4345c7147bb8403a1040d5b9d9e7ad3615daac78a812026ccf09d4443ae168d9588df792dcf5efcdd721828
|
data/gorg_service.gemspec
CHANGED
@@ -32,8 +32,10 @@ Gem::Specification.new do |spec|
|
|
32
32
|
|
33
33
|
spec.add_development_dependency "bundler", "~> 1.11"
|
34
34
|
spec.add_development_dependency "rake", "~> 10.0"
|
35
|
+
spec.add_development_dependency "yard", "~> 0.8.7"
|
35
36
|
spec.add_development_dependency "rspec", "~> 3.0"
|
36
|
-
spec.add_development_dependency "codeclimate-test-reporter", "~>
|
37
|
+
spec.add_development_dependency "codeclimate-test-reporter", "~> 1.0"
|
38
|
+
spec.add_development_dependency "simplecov", "~> 0.13"
|
37
39
|
spec.add_development_dependency 'bogus', '~> 0.1.6'
|
38
40
|
spec.add_development_dependency 'byebug', '~> 9.0'
|
39
41
|
end
|
@@ -6,10 +6,14 @@ class GorgService
|
|
6
6
|
#Common behavior of failling errors
|
7
7
|
class FailError < StandardError
|
8
8
|
attr_reader :error_raised
|
9
|
+
attr_reader :error_name
|
10
|
+
attr_accessor :gorg_service_message
|
9
11
|
|
10
|
-
def initialize(message = nil, error_raised = nil)
|
12
|
+
def initialize(message = nil, error_raised = nil, gorg_service_message: nil, error_name: nil)
|
11
13
|
@message = message
|
12
14
|
@error_raised = error_raised
|
15
|
+
@gorg_service_message = gorg_service_message
|
16
|
+
@error_name = error_name
|
13
17
|
end
|
14
18
|
|
15
19
|
def message
|
@@ -19,19 +23,41 @@ class GorgService
|
|
19
23
|
def type
|
20
24
|
""
|
21
25
|
end
|
26
|
+
|
27
|
+
def to_log_message
|
28
|
+
@gorg_service_message.log_message(
|
29
|
+
level: self.log_level,
|
30
|
+
error_type: self.type,
|
31
|
+
error_name: @error_name
|
32
|
+
)
|
33
|
+
end
|
22
34
|
end
|
23
35
|
|
24
36
|
#Softfail error : This message should be processed again later
|
25
37
|
class SoftfailError < FailError
|
26
38
|
def type
|
27
|
-
"
|
39
|
+
"softfail"
|
40
|
+
end
|
41
|
+
|
42
|
+
def log_level
|
43
|
+
3
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_log_message
|
47
|
+
r=super
|
48
|
+
r.next_try_in=GorgService.configuration.rabbitmq_deferred_time.to_i
|
49
|
+
r
|
28
50
|
end
|
29
51
|
end
|
30
52
|
|
31
53
|
#Hardfail error : This message is not processable and will never be
|
32
54
|
class HardfailError < FailError
|
33
55
|
def type
|
34
|
-
"
|
56
|
+
"hardfail"
|
57
|
+
end
|
58
|
+
|
59
|
+
def log_level
|
60
|
+
4
|
35
61
|
end
|
36
62
|
end
|
37
63
|
end
|
@@ -64,35 +64,38 @@ class GorgService
|
|
64
64
|
end
|
65
65
|
|
66
66
|
def process_softfail(e, message)
|
67
|
-
message
|
67
|
+
e.gorg_service_message ||= message
|
68
68
|
GorgService.logger.error "SOFTFAIL ERROR : #{e.message}"
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
process_logging(e)
|
70
|
+
message.softfail_count+=1
|
71
|
+
if message.softfail_count.to_i >= @max_attempts
|
72
|
+
GorgService.logger.info " DISCARD MESSAGE : too much soft errors (#{message.softfail_count})"
|
73
|
+
process_hardfail(HardfailError.new("Too Much SoftError : This message reached the limit of softerror (max: #{@max_attempts})", gorg_service_message: message, error_name: e.error_name), message)
|
72
74
|
else
|
73
75
|
send_to_deferred_queue(message)
|
74
76
|
end
|
75
77
|
end
|
76
78
|
|
77
79
|
def process_hardfail(e, message)
|
80
|
+
e.gorg_service_message ||= message
|
78
81
|
GorgService.logger.error "HARDFAIL ERROR : #{e.message}, #{e.error_raised&&e.error_raised.inspect}"
|
79
82
|
GorgService.logger.info " DISCARD MESSAGE"
|
80
|
-
|
81
|
-
message.log_error(e)
|
82
|
-
process_logging(message)
|
83
|
-
end
|
83
|
+
process_logging(e)
|
84
84
|
end
|
85
85
|
|
86
|
-
def process_logging(
|
86
|
+
def process_logging(error)
|
87
|
+
message=error.to_log_message
|
87
88
|
message.routing_key=@log_routing_key
|
88
89
|
GorgService::Producer.new.publish_message(message)
|
89
|
-
#RabbitmqProducer.new.send_raw(message.to_json, @log_routing_key, verbose: true) if @log_routing_key
|
90
90
|
end
|
91
91
|
|
92
|
-
def send_to_deferred_queue(
|
93
|
-
|
94
|
-
|
95
|
-
GorgService.
|
92
|
+
def send_to_deferred_queue(message)
|
93
|
+
|
94
|
+
if @env.delayed_queue_for message.routing_key
|
95
|
+
GorgService::Producer.new.publish_message(message, exchange: @env.delayed_in_exchange)
|
96
|
+
#
|
97
|
+
# @env.delayed_in_exchange.publish(msg.to_json, :routing_key => msg.routing_key)
|
98
|
+
GorgService.logger.info "DEFER MESSAGE : message sent to #{@env.delayed_in_exchange.name} with routing key #{message.routing_key}"
|
96
99
|
else
|
97
100
|
raise "DelayedQueueNotFound"
|
98
101
|
end
|
@@ -32,28 +32,29 @@ class GorgService
|
|
32
32
|
end
|
33
33
|
alias_method :msg, :message
|
34
34
|
|
35
|
-
def reply_with(
|
36
|
-
self.class.reply_to(message
|
35
|
+
def reply_with(*_args, **keyword_args)
|
36
|
+
self.class.reply_to(message,**keyword_args)
|
37
37
|
end
|
38
38
|
|
39
|
-
def raise_hardfail(
|
40
|
-
self.class.raise_hardfail(
|
39
|
+
def raise_hardfail(*args, **keyword_args)
|
40
|
+
self.class.raise_hardfail(*args, **(keyword_args.merge(message:message)))
|
41
41
|
end
|
42
42
|
|
43
|
-
def raise_softfail(
|
44
|
-
self.class.raise_softfail(
|
43
|
+
def raise_softfail(*args, **keyword_args)
|
44
|
+
self.class.raise_softfail(*args, **(keyword_args.merge(message:message)))
|
45
45
|
end
|
46
46
|
|
47
47
|
class << self
|
48
48
|
|
49
|
-
def reply_to(message,data)
|
49
|
+
def reply_to(message, data: {}, status_code: 200, error_type: nil, error_name: nil, next_try_in: nil)
|
50
50
|
if message.expect_reply?
|
51
51
|
|
52
|
-
reply=
|
53
|
-
event: message.reply_routing_key,
|
52
|
+
reply=message.reply_message(
|
54
53
|
data: data,
|
55
|
-
|
56
|
-
|
54
|
+
status_code: status_code,
|
55
|
+
error_type: error_type,
|
56
|
+
error_name: error_name,
|
57
|
+
next_try_in: next_try_in,
|
57
58
|
)
|
58
59
|
|
59
60
|
replier=GorgService::Producer.new
|
@@ -61,28 +62,38 @@ class GorgService
|
|
61
62
|
end
|
62
63
|
end
|
63
64
|
|
64
|
-
def raise_hardfail(error_message,message:nil, error: nil, data: nil)
|
65
|
+
def raise_hardfail(error_message,message:nil, error: nil, data: nil, status_code: 500, error_name: nil)
|
65
66
|
if message
|
66
67
|
reply_to(message,{
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
68
|
+
error_type: 'hardfail',
|
69
|
+
status_code: status_code,
|
70
|
+
error_name: error_name,
|
71
|
+
data:{
|
72
|
+
error_message: error_message,
|
73
|
+
debug_message: error&&error.inspect,
|
74
|
+
error_data: data
|
75
|
+
},
|
76
|
+
|
71
77
|
})
|
72
78
|
end
|
73
|
-
raise HardfailError.new(error_message, error)
|
79
|
+
raise HardfailError.new(error_message, error, gorg_service_message: message, error_name: error_name)
|
74
80
|
end
|
75
81
|
|
76
|
-
def raise_softfail(error_message,message:nil, error: nil, data: nil)
|
82
|
+
def raise_softfail(error_message,message:nil, error: nil, data: nil, status_code: 500, error_name: nil)
|
77
83
|
if message
|
78
84
|
reply_to(message,{
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
85
|
+
error_type: 'softfail',
|
86
|
+
next_try_in: GorgService.configuration.rabbitmq_deferred_time.to_i,
|
87
|
+
status_code: status_code,
|
88
|
+
error_name: error_name,
|
89
|
+
data:{
|
90
|
+
error_message: error_message,
|
91
|
+
debug_message: error&&error.inspect,
|
92
|
+
error_data: data
|
93
|
+
},
|
83
94
|
})
|
84
95
|
end
|
85
|
-
raise SoftfailError.new(error_message, error)
|
96
|
+
raise SoftfailError.new(error_message, error, gorg_service_message: message, error_name: error_name)
|
86
97
|
end
|
87
98
|
|
88
99
|
def handle_error(*errorClasses,&block)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class GorgService
|
2
|
+
class LogMessage<Message
|
3
|
+
# @return [String] Level of the log
|
4
|
+
# 0 => DEBUG
|
5
|
+
# 1 => INFO
|
6
|
+
# 2 => WARNING
|
7
|
+
# 3 => SOFTFAIL
|
8
|
+
# 4 => HARDFAIL
|
9
|
+
attr_accessor :level
|
10
|
+
|
11
|
+
# @return [String] Error type ('hardfail','softfail')
|
12
|
+
attr_accessor :error_type
|
13
|
+
|
14
|
+
# @return [Integer] Time until next attempts in milliseconds
|
15
|
+
attr_accessor :next_try_in
|
16
|
+
|
17
|
+
# @return [String] Name identifying the error
|
18
|
+
attr_accessor :error_name
|
19
|
+
|
20
|
+
def type
|
21
|
+
"log"
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param opts [Hash] Attributes of the message
|
25
|
+
# @option opts [String] :level See {#level}. Default : 1
|
26
|
+
# @option opts [String] :error_type See {#error_type}. Default : nil
|
27
|
+
# @option opts [Integer] :next_try_in See {#next_try_in}. Default : nil
|
28
|
+
# @option opts [String] :error_name See {#error_name}. Default : nil
|
29
|
+
# @see GorgService::Message#initialize
|
30
|
+
def initialize(opts={})
|
31
|
+
super
|
32
|
+
self.level= opts.fetch(:level,1)
|
33
|
+
self.error_type= opts.fetch(:error_type,nil)
|
34
|
+
self.next_try_in= opts.fetch(:next_try_in,nil)
|
35
|
+
self.error_name= opts.fetch(:error_name,nil)
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate
|
39
|
+
self.validation_errors[:level]+=["is null"] unless self.level
|
40
|
+
self.validation_errors[:level]+=["is not in [0,1,2,3,4]"] unless (0..4).to_a.include?(self.level)
|
41
|
+
self.validation_errors[:error_type]+=["is not in (softfail hardfail)"] unless self.error_type && (%w(softfail hardfail).include? self.error_type)
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/lib/gorg_service/message.rb
CHANGED
@@ -8,11 +8,26 @@ require 'securerandom'
|
|
8
8
|
require "gorg_service/message/json_schema"
|
9
9
|
require "gorg_service/message/error_log"
|
10
10
|
|
11
|
+
require "gorg_service/reply_message"
|
12
|
+
require "gorg_service/event_message"
|
13
|
+
require "gorg_service/log_message"
|
14
|
+
require "gorg_service/request_message"
|
15
|
+
|
16
|
+
require "gorg_service/message/formatters"
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
# Message transferred through Bunny. Follow Gorg SOA specification v2.0 ans maintain support for v1.0
|
21
|
+
#
|
22
|
+
# @author Alexandre Narbonne
|
11
23
|
class GorgService
|
12
24
|
class Message
|
13
25
|
|
26
|
+
DEFAULT_SOA_VERSION="1.0"
|
27
|
+
|
14
28
|
class DataValidationError < StandardError
|
15
29
|
|
30
|
+
# @return [Hash] Mapping of invalid data
|
16
31
|
attr_reader :errors
|
17
32
|
|
18
33
|
def initialize(errors)
|
@@ -20,81 +35,165 @@ class GorgService
|
|
20
35
|
end
|
21
36
|
end
|
22
37
|
|
38
|
+
class MessageValidationError < StandardError
|
39
|
+
|
40
|
+
# @return [Hash] Mapping of invalid data
|
41
|
+
attr_reader :errors
|
42
|
+
|
43
|
+
def initialize(errors)
|
44
|
+
@errors=errors
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [String] UUID of the message
|
23
49
|
attr_accessor :id
|
24
|
-
|
25
|
-
|
50
|
+
|
51
|
+
# @return [String] Id of the app which generated the message
|
26
52
|
attr_accessor :sender_id
|
53
|
+
alias_method :sender, :sender_id
|
54
|
+
alias_method :sender=, :sender_id=
|
55
|
+
|
56
|
+
# @return [String] Content type of the payload
|
57
|
+
# @note The only content type officially supported by Gadz.org SOA is 'application/json'
|
27
58
|
attr_accessor :content_type
|
59
|
+
|
60
|
+
# @return [String] Content encoding of the payload
|
61
|
+
# @note The only content encoding officially supported by Gadz.org SOA is 'deflate'
|
28
62
|
attr_accessor :content_encoding
|
29
|
-
attr_accessor :headers
|
30
|
-
attr_accessor :type
|
31
63
|
|
32
|
-
|
64
|
+
# @return [String] The Gadz.org SOA specs version used to generate this message
|
65
|
+
# @note Incomming message without this information should be considered v1.0
|
66
|
+
attr_accessor :soa_version
|
67
|
+
|
68
|
+
# @return [String] Major version of the soa version
|
69
|
+
def soa_version_major
|
70
|
+
(self.soa_version||DEFAULT_SOA_VERSION).split('.')[0]
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [String] identifier of the library used to generate this message, for debug and compatibility purpose
|
74
|
+
attr_accessor :client_library
|
75
|
+
|
76
|
+
# @return [String] Identifier of the admin who triggered the generation of this message
|
77
|
+
# @note In case of autonomous actions, there is no admin_id
|
78
|
+
attr_accessor :admin_id
|
79
|
+
|
80
|
+
# @return [String] Routing of the message
|
33
81
|
attr_accessor :routing_key
|
34
|
-
|
82
|
+
|
83
|
+
# @return [Hash] Data of the payload as an hash
|
84
|
+
# @note for further compatibility, don't assume that data is an hash but check Content-Type
|
35
85
|
attr_accessor :data
|
36
|
-
|
86
|
+
|
87
|
+
# @return [DateTime] Message generation datetime
|
37
88
|
attr_accessor :creation_time
|
38
|
-
attr_accessor :sender
|
39
89
|
|
90
|
+
# @return [String] Name of the exchange used to receive reply
|
91
|
+
attr_accessor :reply_to
|
92
|
+
alias_method :reply_exchange, :reply_to
|
93
|
+
|
94
|
+
# @return [String] UUID of the message the this message refer to (reply or log)
|
95
|
+
attr_accessor :correlation_id
|
96
|
+
|
97
|
+
# @return [nil] Type of message (event,log,request,reply). To be overwritten by children classes
|
98
|
+
def type
|
99
|
+
nil
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Hash] Additional headers
|
103
|
+
attr_accessor :headers
|
104
|
+
|
105
|
+
# @return [Integer] Number of softfails associated to this message
|
106
|
+
attr_accessor :softfail_count
|
107
|
+
|
108
|
+
# @deprecated In Gadz.org Soa v2.0 errors are not store in messages anymore
|
109
|
+
# @return [Array<Message::ErrorLog>] List of errors associated to this message
|
110
|
+
attr_accessor :errors
|
111
|
+
|
112
|
+
# @return [Hash] Mapping of attributes errors in regard of Gadz.org SOA v2
|
113
|
+
attr_accessor :validation_errors
|
114
|
+
def validation_errors
|
115
|
+
@validation_errors||= Hash.new([].freeze)
|
116
|
+
end
|
117
|
+
|
118
|
+
# @deprecated Use {#routing_key} instead. event is no longer a part of GorgSOA specs
|
119
|
+
# @return [String] the name of the event
|
120
|
+
attr_accessor :event
|
121
|
+
def event
|
122
|
+
warn "[DEPRECATION] Message.event is deprecated and will be removed soon. Use id instead (called from #{caller_locations(1,1)[0]})"
|
123
|
+
self.routing_key
|
124
|
+
end
|
125
|
+
def event=(value)
|
126
|
+
warn "[DEPRECATION] Message.event is deprecated and will be removed soon. Use id instead (called from #{caller_locations(1,1)[0]})"
|
127
|
+
self.routing_key=value
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
|
132
|
+
# @deprecated Use {#id} instead. event_id is no longer a part of GorgSOA specs
|
133
|
+
# @return [String] UUID of the message
|
134
|
+
attr_accessor :event_id
|
135
|
+
def event_id
|
136
|
+
warn "[DEPRECATION] Message.event_id is deprecated and will be removed soon. Use id instead (called from #{caller_locations(1,1)[0]})"
|
137
|
+
self.id
|
138
|
+
end
|
139
|
+
def event_id=(value)
|
140
|
+
warn "[DEPRECATION] Message.event_id is deprecated and will be removed soon. Use id instead (called from #{caller_locations(1,1)[0]})"
|
141
|
+
self.id=value
|
142
|
+
end
|
143
|
+
|
144
|
+
# @deprecated In Gadz.org Soa v2.0 errors are not store in messages anymore
|
145
|
+
# @return [Array<Message::ErrorLog>] List of errors associated to this message
|
40
146
|
def errors
|
41
147
|
@errors||=[]
|
42
148
|
end
|
43
149
|
|
44
|
-
|
150
|
+
# @param opts [Hash] Attributes of the message
|
151
|
+
# @option opts [String] :id See {#id}. Default : Random UUID4
|
152
|
+
# @option opts [Array<Message::ErrorLog>] :errors See {#errors}. Default : []
|
153
|
+
# @option opts [DateTime] :creation_time See {#creation_time}. Default : DateTime.now
|
154
|
+
# @option opts [String] :sender See {#sender}. Default : GorgService.configuration.application_id
|
155
|
+
# @option opts [String] :data See {#data}. Default : nil
|
156
|
+
# @option opts [String] :routing_key See {#routing_key}. Default : nil
|
157
|
+
# @option opts [String] :reply_to See {#reply_to}. Default : nil
|
158
|
+
# @option opts [String] :correlation_id See {#correlation_id}. Default : nil
|
159
|
+
# @option opts [String] :content_type See {#content_type}. Default : "application/json"
|
160
|
+
# @option opts [String] :content_encoding See {#content_encoding}. Default : "deflate"
|
161
|
+
# @option opts [String] :headers See {#headers}. Default : {}
|
45
162
|
def initialize(opts={})
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
@content_encoding= opts.fetch(:content_encoding,"deflate")
|
62
|
-
@headers= opts.fetch(:headers,{})
|
63
|
-
@type= opts.fetch(:type,"event")
|
163
|
+
self.id= opts.fetch(:event_id,nil)||opts.fetch(:id,generate_id)
|
164
|
+
|
165
|
+
self.errors= opts.fetch(:errors,[])
|
166
|
+
self.creation_time= opts.fetch(:creation_time,DateTime.now)
|
167
|
+
self.sender= opts.fetch(:sender,application_id)
|
168
|
+
self.data= opts.fetch(:data,nil)
|
169
|
+
self.routing_key= opts.fetch(:routing_key,nil)||opts.fetch(:event,nil)
|
170
|
+
self.softfail_count= opts.fetch(:softfail_count,0)
|
171
|
+
|
172
|
+
self.reply_to= opts.fetch(:reply_to,nil)
|
173
|
+
self.correlation_id= opts.fetch(:correlation_id,nil)
|
174
|
+
self.content_type= opts.fetch(:content_type,"application/json")
|
175
|
+
self.content_encoding= opts.fetch(:content_encoding,"deflate")
|
176
|
+
self.headers= opts.fetch(:headers,{})
|
177
|
+
self.soa_version= opts.fetch(:soa_version, DEFAULT_SOA_VERSION)
|
64
178
|
end
|
65
179
|
|
180
|
+
# @deprecated Please use directly the rendering interface of the formatter
|
181
|
+
# @param formatter [Message::FormatterV1] The formatter to be used. Default : an instance of Message::FormatterV1
|
182
|
+
# @return [Hash] The un-serialized payload of the RabbitMq message
|
66
183
|
def body
|
67
|
-
|
68
|
-
event_uuid: @event_id,
|
69
|
-
event_name: @event,
|
70
|
-
event_sender_id: @sender,
|
71
|
-
event_creation_time: @creation_time,
|
72
|
-
data: @data,
|
73
|
-
}
|
74
|
-
if errors.any?
|
75
|
-
_body[:errors_count]=@errors.count
|
76
|
-
_body[:errors]=@errors.map{|e| e.to_h}
|
77
|
-
end
|
78
|
-
_body
|
184
|
+
Message::Formatter.formatter_for_version(self.soa_version).new(self).body
|
79
185
|
end
|
80
186
|
alias_method :to_h, :body
|
81
187
|
alias_method :payload, :body
|
82
188
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
app_id: sender_id,
|
92
|
-
type: type,
|
93
|
-
message_id: id,
|
94
|
-
}
|
95
|
-
end
|
96
|
-
|
97
|
-
# Generate RabbitMQ message body
|
189
|
+
# @deprecated Please use directly the rendering interface of the formatter
|
190
|
+
# @param formatter [Message::FormatterV1] The formatter to be used. Default : an instance of Message::FormatterV1
|
191
|
+
# @return [Hash] The properties of the RabbitMq message
|
192
|
+
def properties(formatter: Message::FormatterV1.new(self))
|
193
|
+
Message::Formatter.formatter_for_version(self.soa_version).new(self).properties
|
194
|
+
end
|
195
|
+
|
196
|
+
# @return [String] The serialized (JSON) value of the RabbitMQ payload
|
98
197
|
def to_json
|
99
198
|
self.to_h.to_json
|
100
199
|
end
|
@@ -109,22 +208,76 @@ class GorgService
|
|
109
208
|
errors<<e
|
110
209
|
end
|
111
210
|
|
112
|
-
|
113
|
-
reply_to
|
114
|
-
end
|
115
|
-
|
211
|
+
# @return [Boolean] Does this message expect a reply ?
|
116
212
|
def expect_reply?
|
117
213
|
!!reply_to
|
118
214
|
end
|
119
215
|
|
216
|
+
# @return [Stirng] Routing key to use to reply to this message
|
120
217
|
def reply_routing_key
|
121
|
-
|
218
|
+
routing_key.sub('request','reply')
|
219
|
+
end
|
220
|
+
|
221
|
+
# @return [GorgService::ReplyMessage] the response to send
|
222
|
+
|
223
|
+
def reply_message(opts)
|
224
|
+
args= {
|
225
|
+
routing_key: self.reply_routing_key,
|
226
|
+
correlation_id: self.id,
|
227
|
+
soa_version: self.soa_version
|
228
|
+
}.merge(opts)
|
229
|
+
|
230
|
+
GorgService::ReplyMessage.new(args)
|
231
|
+
end
|
232
|
+
|
233
|
+
def log_message(opts)
|
234
|
+
args= {
|
235
|
+
routing_key: self.reply_routing_key,
|
236
|
+
correlation_id: self.id,
|
237
|
+
soa_version: '2.0' #v1 messages generate v2 logs
|
238
|
+
}.merge(opts)
|
239
|
+
|
240
|
+
GorgService::LogMessage.new(args)
|
122
241
|
end
|
123
242
|
|
243
|
+
# Validate the message against rules specified in {https://confluence.gadz.org/display/INFRA/Messages the Gadz.org SOA Message Specification}
|
244
|
+
def validate
|
245
|
+
self.validation_errors= Hash.new([].freeze)
|
246
|
+
|
247
|
+
self.validation_errors[:content_type]+=["is not supported by Gadz.org SOA"] unless ['application/json'].include? self.content_type
|
248
|
+
self.validation_errors[:content_encoding]+=["is not supported by Gadz.org SOA"] unless ['deflate','gzip'].include? self.content_encoding
|
249
|
+
# "gzip" is not officially supported by Gadz.org but I don't feel comfortable blocking it :)
|
250
|
+
|
251
|
+
self.validation_errors[:id]+=["is null"] unless self.id
|
252
|
+
self.validation_errors[:id]+=["is not a UUID"] unless !self.id || /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/.match(self.id)
|
253
|
+
|
254
|
+
self.validation_errors[:correlation_id]+=["is not a UUID"] unless !self.correlation_id || /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/.match()
|
255
|
+
|
256
|
+
self.validation_errors[:creation_time]+=["is not DateTime"] unless self.creation_time.is_a? DateTime
|
257
|
+
|
258
|
+
self.validation_errors[:type]+=["is not in (event, log, request, reply)"] unless %w(event log request reply).include? self.type
|
259
|
+
|
260
|
+
self.validation_errors[:sender]+=["is null"] unless self.sender
|
261
|
+
|
262
|
+
self.validation_errors[:soa_version]+=["is null"] unless self.soa_version
|
263
|
+
|
264
|
+
return self.validation_errors.empty?
|
265
|
+
end
|
266
|
+
alias_method :valid?, :validate
|
267
|
+
|
268
|
+
# @see #validate
|
269
|
+
# @raise [MessageValidationError] Raise an exception containing the errors if the message is invalid
|
270
|
+
def validate!
|
271
|
+
raise MessageValidationError.new(self.errors) unless validate
|
272
|
+
end
|
273
|
+
|
274
|
+
# @param [String,Hash] A JSON Schema usable by {JSON::Validator}
|
275
|
+
# @return [Boolean] True if the message is valid against the provided JSON Schema
|
276
|
+
# @raise [DataValidationError] Validation exception containing errors ( See {DataValidationError#errors})
|
124
277
|
def validate_data_with(schema)
|
125
|
-
|
126
|
-
if
|
127
|
-
raise DataValidationError.new(
|
278
|
+
data_validation_errors=JSON::Validator.fully_validate(schema, self.data)
|
279
|
+
if data_validation_errors.any?
|
280
|
+
raise DataValidationError.new(data_validation_errors)
|
128
281
|
else
|
129
282
|
return true
|
130
283
|
end
|
@@ -134,54 +287,17 @@ class GorgService
|
|
134
287
|
|
135
288
|
### Class methods
|
136
289
|
|
290
|
+
# @param [Hash] delivery_info delivery_info provided by {Bunny}
|
291
|
+
# @param [Hash] properties properties provided by {Bunny}
|
292
|
+
# @param [Hash] body body provided by {Bunny}
|
293
|
+
# @param [Class] formatter_class The formatter to be used to parse the message. Default to {Message::FormatterV1}
|
294
|
+
# @return [GorgService::Message] A kind of GorgService::Message
|
137
295
|
def self.parse(delivery_info, properties, body)
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
JSON::Validator.validate!(GorgService::Message::JSON_SCHEMA,json_body)
|
142
|
-
|
143
|
-
msg=self.new(
|
144
|
-
routing_key: delivery_info[:routing_key],
|
145
|
-
id: properties[:message_id],
|
146
|
-
reply_to: properties[:reply_to],
|
147
|
-
correlation_id: properties[:correlation_id],
|
148
|
-
sender_id: properties[:app_id],
|
149
|
-
content_type: properties[:content_type],
|
150
|
-
content_encoding: properties[:content_encoding],
|
151
|
-
headers: properties[:header],
|
152
|
-
type: properties[:type],
|
153
|
-
|
154
|
-
event_id: json_body["event_uuid"],
|
155
|
-
event: json_body["event_name"],
|
156
|
-
data: convert_keys_to_sym(json_body["data"]),
|
157
|
-
creation_time: json_body["event_creation_time"] && DateTime.parse(json_body["event_creation_time"]),
|
158
|
-
sender: json_body["event_sender_id"],
|
159
|
-
errors: json_body["errors"]&&json_body["errors"].map{|e| GorgService::Message::ErrorLog.parse(e)},
|
160
|
-
)
|
161
|
-
msg
|
162
|
-
rescue JSON::ParserError => e
|
163
|
-
raise GorgService::Consumer::HardfailError.new("Unprocessable message : Unable to parse JSON message body", e)
|
164
|
-
rescue JSON::Schema::ValidationError => e
|
165
|
-
raise GorgService::Consumer::HardfailError.new("Invalid JSON : This message does not respect Gadz.org JSON Schema",e)
|
166
|
-
end
|
296
|
+
formatter_class=Message::Formatter.formatter_for_version(properties.to_h[:headers].to_h["soa-version"]||"1")
|
297
|
+
formatter_class.parse(delivery_info, properties, body)
|
167
298
|
end
|
168
299
|
|
169
|
-
|
170
|
-
def self.convert_keys_to_sym input_hash
|
171
|
-
s2s =
|
172
|
-
lambda do |h|
|
173
|
-
Hash === h ?
|
174
|
-
Hash[
|
175
|
-
h.map do |k, v|
|
176
|
-
[k.respond_to?(:to_sym) ? k.to_sym : k, s2s[v]]
|
177
|
-
end
|
178
|
-
] : h
|
179
|
-
end
|
180
|
-
s2s[input_hash]
|
181
|
-
end
|
182
|
-
|
183
|
-
private
|
184
|
-
|
300
|
+
protected
|
185
301
|
# Generate new id
|
186
302
|
def generate_id
|
187
303
|
SecureRandom.uuid()
|
@@ -0,0 +1,313 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
class GorgService::Message
|
5
|
+
class Formatter
|
6
|
+
|
7
|
+
def initialize(message)
|
8
|
+
@message=message
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
@message
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.formatter_for_version(version)
|
16
|
+
major_version=version.split('.')[0]
|
17
|
+
case major_version
|
18
|
+
when '1'
|
19
|
+
FormatterV1
|
20
|
+
when '2'
|
21
|
+
FormatterV2
|
22
|
+
else
|
23
|
+
raise "Unknown Gorg SOA version"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.convert_keys_to_sym input_hash
|
28
|
+
s2s =
|
29
|
+
lambda do |h|
|
30
|
+
Hash === h ?
|
31
|
+
Hash[
|
32
|
+
h.map do |k, v|
|
33
|
+
[k.respond_to?(:to_sym) ? k.to_sym : k, s2s[v]]
|
34
|
+
end
|
35
|
+
] : h
|
36
|
+
end
|
37
|
+
s2s[input_hash]
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
class FormatterV1 < Formatter
|
43
|
+
|
44
|
+
JSON_SCHEMA_V1 = JSON.parse('{
|
45
|
+
"$schema": "http://json-schema.org/draft-04/schema#",
|
46
|
+
"type": "object",
|
47
|
+
"properties": {
|
48
|
+
"event_name": {
|
49
|
+
"type": "string",
|
50
|
+
"pattern": "^[_a-z]+((\\.)?[_a-z]+)*$",
|
51
|
+
"description": "Event type. Must match the routing key"
|
52
|
+
},
|
53
|
+
"event_uuid": {
|
54
|
+
"type": "string",
|
55
|
+
"description": "The unique identifier of this message as UUID",
|
56
|
+
"pattern": "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
|
57
|
+
},
|
58
|
+
"event_creation_time": {
|
59
|
+
"type": "string",
|
60
|
+
"description": "Creation time in UTC ISO 8601 format",
|
61
|
+
"pattern": "^([\\\+-]?\\\d{4}(?!\\\d{2}\\\b))((-?)((0[1-9]|1[0-2])(\\\3([12]\\\d|0[1-9]|3[01]))?|W([0-4]\\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\\d|[12]\\\d{2}|3([0-5]\\\d|6[1-6])))([T\\\s]((([01]\\\d|2[0-3])((:?)[0-5]\\\d)?|24\\\:?00)([\\\.,]\\\d+(?!:))?)?(\\\17[0-5]\\\d([\\\.,]\\\d+)?)?([zZ]|([\\\+-])([01]\\\d|2[0-3]):?([0-5]\\\d)?)?)?)?$"
|
62
|
+
},
|
63
|
+
"event_sender_id": {
|
64
|
+
"type": "string",
|
65
|
+
"description": "Producer that sent the original message"
|
66
|
+
},
|
67
|
+
"data": {
|
68
|
+
"type": "object",
|
69
|
+
"description": "Data used to process this message"
|
70
|
+
},
|
71
|
+
"errors_count": {
|
72
|
+
"type": "integer",
|
73
|
+
"description": "Helper for counting errors"
|
74
|
+
},
|
75
|
+
"errors": {
|
76
|
+
"type": "array",
|
77
|
+
"items": {
|
78
|
+
"type": "object",
|
79
|
+
"properties": {
|
80
|
+
"error_type": {
|
81
|
+
"enum": [ "debug", "info", "warning", "softerror", "harderror" ],
|
82
|
+
"description": "Type of error."
|
83
|
+
},
|
84
|
+
"error_sender": {
|
85
|
+
"type": "string",
|
86
|
+
"description": "Consummer that sent this error"
|
87
|
+
},
|
88
|
+
"error_code":{
|
89
|
+
"type":"string",
|
90
|
+
"description": "Optionnal error code from the consummer"
|
91
|
+
},
|
92
|
+
"error_uuid":{
|
93
|
+
"type":"string",
|
94
|
+
"description": "The unique identifier of this error as UUID",
|
95
|
+
"pattern": "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
|
96
|
+
},
|
97
|
+
"error_message":{
|
98
|
+
"type":"string",
|
99
|
+
"description": "Error explanation"
|
100
|
+
},
|
101
|
+
"timestamp": {
|
102
|
+
"type": "string",
|
103
|
+
"description": "Time of occuring error in UTC ISO 8601",
|
104
|
+
"pattern": "^([\\\+-]?\\\d{4}(?!\\\d{2}\\\b))((-?)((0[1-9]|1[0-2])(\\\3([12]\\\d|0[1-9]|3[01]))?|W([0-4]\\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\\d|[12]\\\d{2}|3([0-5]\\\d|6[1-6])))([T\\\s]((([01]\\\d|2[0-3])((:?)[0-5]\\\d)?|24\\\:?00)([\\\.,]\\\d+(?!:))?)?(\\\17[0-5]\\\d([\\\.,]\\\d+)?)?([zZ]|([\\\+-])([01]\\\d|2[0-3]):?([0-5]\\\d)?)?)?)?$"
|
105
|
+
},
|
106
|
+
"error_debug": {
|
107
|
+
"type": "object",
|
108
|
+
"description": "Complementary informations for debugging"
|
109
|
+
}
|
110
|
+
},
|
111
|
+
"additionalProperties": false,
|
112
|
+
"required": [
|
113
|
+
"error_type",
|
114
|
+
"error_sender",
|
115
|
+
"timestamp",
|
116
|
+
"error_uuid",
|
117
|
+
"error_message"
|
118
|
+
]
|
119
|
+
}
|
120
|
+
}
|
121
|
+
},
|
122
|
+
"additionalProperties": false,
|
123
|
+
"required": [
|
124
|
+
"event_name",
|
125
|
+
"event_uuid",
|
126
|
+
"event_creation_time",
|
127
|
+
"event_sender_id",
|
128
|
+
"data"
|
129
|
+
]
|
130
|
+
}')
|
131
|
+
|
132
|
+
DEFAULT_HEADERS={
|
133
|
+
"soa-version" => "1.0",
|
134
|
+
"client-library" => "GorgService #{GorgService::VERSION}",
|
135
|
+
}
|
136
|
+
|
137
|
+
def properties
|
138
|
+
{
|
139
|
+
routing_key: message.routing_key,
|
140
|
+
reply_to: message.reply_to,
|
141
|
+
correlation_id: message.correlation_id,
|
142
|
+
content_type: message.content_type,
|
143
|
+
content_encoding: message.content_encoding,
|
144
|
+
headers: DEFAULT_HEADERS.merge(message.headers.to_h).merge(
|
145
|
+
'softfail-count' => message.softfail_count,
|
146
|
+
),
|
147
|
+
app_id: message.sender_id,
|
148
|
+
type: message.type,
|
149
|
+
message_id: message.id,
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
def body
|
154
|
+
b={
|
155
|
+
event_uuid: message.id,
|
156
|
+
event_name: message.routing_key,
|
157
|
+
event_sender_id: message.sender,
|
158
|
+
event_creation_time: message.creation_time.iso8601,
|
159
|
+
data: message.data,
|
160
|
+
}
|
161
|
+
if message.errors.any?
|
162
|
+
b[:errors_count]=message.errors.count
|
163
|
+
b[:errors]=message.errors.map{|e| e.to_h}
|
164
|
+
end
|
165
|
+
b
|
166
|
+
end
|
167
|
+
|
168
|
+
def payload
|
169
|
+
body.to_json
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.parse(delivery_info, properties, body)
|
173
|
+
begin
|
174
|
+
json_body=JSON.parse(body)
|
175
|
+
JSON::Validator.validate!(JSON_SCHEMA_V1, json_body)
|
176
|
+
|
177
|
+
msg=GorgService::Message.new(
|
178
|
+
routing_key: delivery_info[:routing_key],
|
179
|
+
reply_to: properties[:reply_to],
|
180
|
+
correlation_id: properties[:correlation_id],
|
181
|
+
sender_id: properties[:app_id],
|
182
|
+
content_type: properties[:content_type],
|
183
|
+
content_encoding: properties[:content_encoding],
|
184
|
+
headers: properties[:headers],
|
185
|
+
type: properties[:type],
|
186
|
+
|
187
|
+
softfail_count: properties[:headers].to_h.delete('softfail-count'),
|
188
|
+
|
189
|
+
id: json_body["event_uuid"],
|
190
|
+
event_id: json_body["event_uuid"],
|
191
|
+
event: json_body["event_name"],
|
192
|
+
data: convert_keys_to_sym(json_body["data"]),
|
193
|
+
creation_time: json_body["event_creation_time"] && DateTime.parse(json_body["event_creation_time"]),
|
194
|
+
sender: json_body["event_sender_id"],
|
195
|
+
errors: json_body["errors"]&&json_body["errors"].map{|e| GorgService::Message::ErrorLog.parse(e)},
|
196
|
+
)
|
197
|
+
msg
|
198
|
+
rescue JSON::ParserError => e
|
199
|
+
raise GorgService::Consumer::HardfailError.new("Unprocessable message : Unable to parse JSON message body", e)
|
200
|
+
rescue JSON::Schema::ValidationError => e
|
201
|
+
raise GorgService::Consumer::HardfailError.new("Invalid JSON : This message does not respect Gadz.org JSON Schema",e,{})
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
class FormatterV2 < Formatter
|
208
|
+
|
209
|
+
DEFAULT_HEADERS={
|
210
|
+
"soa-version" => "2.0",
|
211
|
+
"client-library" => "GorgService #{GorgService::VERSION}",
|
212
|
+
}
|
213
|
+
|
214
|
+
EXTRA_HEADERS_FOR ={
|
215
|
+
GorgService::ReplyMessage => {
|
216
|
+
'status-code' => :status_code,
|
217
|
+
'error-type' => :error_type,
|
218
|
+
'next-try-in' => :next_try_in,
|
219
|
+
'error-name' => :error_name,
|
220
|
+
},
|
221
|
+
GorgService::LogMessage => {
|
222
|
+
'level' => :level,
|
223
|
+
'error-type' => :error_type,
|
224
|
+
'next-try-in' => :next_try_in,
|
225
|
+
'error-name' => :error_name,
|
226
|
+
},
|
227
|
+
|
228
|
+
}
|
229
|
+
|
230
|
+
def properties
|
231
|
+
|
232
|
+
headers=DEFAULT_HEADERS.merge(message.headers.to_h).merge(
|
233
|
+
'softfail-count' => message.softfail_count,
|
234
|
+
)
|
235
|
+
|
236
|
+
extra_headers=EXTRA_HEADERS_FOR[message.class].to_h.map do|key,method_name|
|
237
|
+
[key,message.public_send(method_name)]
|
238
|
+
end.to_h
|
239
|
+
|
240
|
+
|
241
|
+
headers.merge!(extra_headers)
|
242
|
+
|
243
|
+
{
|
244
|
+
routing_key: message.routing_key,
|
245
|
+
reply_to: message.reply_to,
|
246
|
+
correlation_id: message.correlation_id,
|
247
|
+
content_type: message.content_type,
|
248
|
+
content_encoding: message.content_encoding,
|
249
|
+
headers: headers,
|
250
|
+
app_id: message.sender_id,
|
251
|
+
type: message.type,
|
252
|
+
message_id: message.id,
|
253
|
+
}
|
254
|
+
end
|
255
|
+
|
256
|
+
def body
|
257
|
+
message.data
|
258
|
+
end
|
259
|
+
|
260
|
+
def payload
|
261
|
+
body.to_json
|
262
|
+
end
|
263
|
+
|
264
|
+
def self.parse(delivery_info, properties, body)
|
265
|
+
begin
|
266
|
+
|
267
|
+
type=properties[:type]
|
268
|
+
unless type
|
269
|
+
type=delivery_info[:routing_key].split('.').first
|
270
|
+
end
|
271
|
+
|
272
|
+
type_map={event: GorgService::EventMessage, request: GorgService::RequestMessage, reply: GorgService::ReplyMessage, log: GorgService::LogMessage}
|
273
|
+
klass=type_map[type.to_s.to_sym]
|
274
|
+
|
275
|
+
raise "Unknown type" unless klass
|
276
|
+
|
277
|
+
headers=properties[:headers]||{}
|
278
|
+
|
279
|
+
args={
|
280
|
+
data: convert_keys_to_sym(JSON.parse(body)),
|
281
|
+
id:properties[:message_id],
|
282
|
+
|
283
|
+
creation_time: properties[:timestamp] && DateTime.parse(properties[:timestamp]),
|
284
|
+
sender:properties[:app_id],
|
285
|
+
routing_key: delivery_info[:routing_key],
|
286
|
+
|
287
|
+
reply_to: properties[:reply_to],
|
288
|
+
correlation_id: properties[:correlation_id],
|
289
|
+
content_type: properties[:content_type],
|
290
|
+
content_encoding:properties[:content_encoding],
|
291
|
+
soa_version: headers.delete('soa-version'),
|
292
|
+
|
293
|
+
softfail_count: headers.delete('softfail-count'),
|
294
|
+
|
295
|
+
headers: headers,
|
296
|
+
}
|
297
|
+
|
298
|
+
extra_args=EXTRA_HEADERS_FOR[klass].to_h.map do |key,method_name|
|
299
|
+
[method_name,headers.delete(key)]
|
300
|
+
end.to_h
|
301
|
+
|
302
|
+
args.merge!(extra_args)
|
303
|
+
|
304
|
+
klass.new(args)
|
305
|
+
rescue JSON::ParserError => e
|
306
|
+
raise GorgService::Consumer::HardfailError.new("Unprocessable message : Unable to parse JSON message body", e)
|
307
|
+
rescue JSON::Schema::ValidationError => e
|
308
|
+
raise GorgService::Consumer::HardfailError.new("Invalid JSON : This message does not respect Gadz.org JSON Schema",e)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
end
|
313
|
+
end
|
@@ -6,93 +6,7 @@ require 'json'
|
|
6
6
|
class GorgService
|
7
7
|
class Message
|
8
8
|
|
9
|
-
|
10
|
-
"$schema": "http://json-schema.org/draft-04/schema#",
|
11
|
-
"type": "object",
|
12
|
-
"properties": {
|
13
|
-
"event_name": {
|
14
|
-
"type": "string",
|
15
|
-
"pattern": "^[_a-z]+((\\.)?[_a-z]+)*$",
|
16
|
-
"description": "Event type. Must match the routing key"
|
17
|
-
},
|
18
|
-
"event_uuid": {
|
19
|
-
"type": "string",
|
20
|
-
"description": "The unique identifier of this message as UUID",
|
21
|
-
"pattern": "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
|
22
|
-
},
|
23
|
-
"event_creation_time": {
|
24
|
-
"type": "string",
|
25
|
-
"description": "Creation time in UTC ISO 8601 format",
|
26
|
-
"pattern": "^([\\\+-]?\\\d{4}(?!\\\d{2}\\\b))((-?)((0[1-9]|1[0-2])(\\\3([12]\\\d|0[1-9]|3[01]))?|W([0-4]\\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\\d|[12]\\\d{2}|3([0-5]\\\d|6[1-6])))([T\\\s]((([01]\\\d|2[0-3])((:?)[0-5]\\\d)?|24\\\:?00)([\\\.,]\\\d+(?!:))?)?(\\\17[0-5]\\\d([\\\.,]\\\d+)?)?([zZ]|([\\\+-])([01]\\\d|2[0-3]):?([0-5]\\\d)?)?)?)?$"
|
27
|
-
},
|
28
|
-
"event_sender_id": {
|
29
|
-
"type": "string",
|
30
|
-
"description": "Producer that sent the original message"
|
31
|
-
},
|
32
|
-
"data": {
|
33
|
-
"type": "object",
|
34
|
-
"description": "Data used to process this message"
|
35
|
-
},
|
36
|
-
"errors_count": {
|
37
|
-
"type": "integer",
|
38
|
-
"description": "Helper for counting errors"
|
39
|
-
},
|
40
|
-
"errors": {
|
41
|
-
"type": "array",
|
42
|
-
"items": {
|
43
|
-
"type": "object",
|
44
|
-
"properties": {
|
45
|
-
"error_type": {
|
46
|
-
"enum": [ "debug", "info", "warning", "softerror", "harderror" ],
|
47
|
-
"description": "Type of error."
|
48
|
-
},
|
49
|
-
"error_sender": {
|
50
|
-
"type": "string",
|
51
|
-
"description": "Consummer that sent this error"
|
52
|
-
},
|
53
|
-
"error_code":{
|
54
|
-
"type":"string",
|
55
|
-
"description": "Optionnal error code from the consummer"
|
56
|
-
},
|
57
|
-
"error_uuid":{
|
58
|
-
"type":"string",
|
59
|
-
"description": "The unique identifier of this error as UUID",
|
60
|
-
"pattern": "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"
|
61
|
-
},
|
62
|
-
"error_message":{
|
63
|
-
"type":"string",
|
64
|
-
"description": "Error explanation"
|
65
|
-
},
|
66
|
-
"timestamp": {
|
67
|
-
"type": "string",
|
68
|
-
"description": "Time of occuring error in UTC ISO 8601",
|
69
|
-
"pattern": "^([\\\+-]?\\\d{4}(?!\\\d{2}\\\b))((-?)((0[1-9]|1[0-2])(\\\3([12]\\\d|0[1-9]|3[01]))?|W([0-4]\\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\\d|[12]\\\d{2}|3([0-5]\\\d|6[1-6])))([T\\\s]((([01]\\\d|2[0-3])((:?)[0-5]\\\d)?|24\\\:?00)([\\\.,]\\\d+(?!:))?)?(\\\17[0-5]\\\d([\\\.,]\\\d+)?)?([zZ]|([\\\+-])([01]\\\d|2[0-3]):?([0-5]\\\d)?)?)?)?$"
|
70
|
-
},
|
71
|
-
"error_debug": {
|
72
|
-
"type": "object",
|
73
|
-
"description": "Complementary informations for debugging"
|
74
|
-
}
|
75
|
-
},
|
76
|
-
"additionalProperties": false,
|
77
|
-
"required": [
|
78
|
-
"error_type",
|
79
|
-
"error_sender",
|
80
|
-
"timestamp",
|
81
|
-
"error_uuid",
|
82
|
-
"error_message"
|
83
|
-
]
|
84
|
-
}
|
85
|
-
}
|
86
|
-
},
|
87
|
-
"additionalProperties": false,
|
88
|
-
"required": [
|
89
|
-
"event_name",
|
90
|
-
"event_uuid",
|
91
|
-
"event_creation_time",
|
92
|
-
"event_sender_id",
|
93
|
-
"data"
|
94
|
-
]
|
95
|
-
}')
|
9
|
+
|
96
10
|
|
97
11
|
end
|
98
12
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class GorgService
|
2
|
+
class ReplyMessage<Message
|
3
|
+
|
4
|
+
# @return [String] Error code, HTTP like
|
5
|
+
attr_accessor :status_code
|
6
|
+
|
7
|
+
# @return [String] Error type ('hardfail','softfail')
|
8
|
+
attr_accessor :error_type
|
9
|
+
|
10
|
+
# @return [Integer] Time until next attempts in milliseconds
|
11
|
+
attr_accessor :next_try_in
|
12
|
+
|
13
|
+
# @return [String] Name identifying the error
|
14
|
+
attr_accessor :error_name
|
15
|
+
|
16
|
+
def type
|
17
|
+
"reply"
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param opts [Hash] Attributes of the message
|
21
|
+
# @option opts [String] :status_code See {#status_code}. Default : nil
|
22
|
+
# @option opts [String] :error_type See {#error_type}. Default : nil
|
23
|
+
# @option opts [Integer] :next_try_in See {#next_try_in}. Default : nil
|
24
|
+
# @option opts [String] :error_name See {#error_name}. Default : nil
|
25
|
+
# @see GorgService::Message#initialize
|
26
|
+
def initialize(opts={})
|
27
|
+
super
|
28
|
+
self.status_code= opts.fetch(:status_code,nil)
|
29
|
+
self.error_type= opts.fetch(:error_type,nil)
|
30
|
+
self.next_try_in= opts.fetch(:next_try_in,nil)
|
31
|
+
self.error_name= opts.fetch(:error_name,nil)
|
32
|
+
end
|
33
|
+
|
34
|
+
def validate
|
35
|
+
self.validation_errors[:status_code]+=["is null"] unless self.status_code
|
36
|
+
self.validation_errors[:error_type]+=["is not in (softfail hardfail)"] unless self.error_type && (%w(softfail hardfail).include? self.error_type)
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -9,15 +9,15 @@ class LogMessageHandler < GorgService::Consumer::MessageHandler::Base
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.has_received_error?(type)
|
12
|
-
messages.any?{|m| m.errors.any?{|x| x.type==type}}
|
12
|
+
messages.any?{|m| m.errors.any?{|x| x.type==type}} || messages.any?{|m| m.type=='log'&&m.error_type=type}
|
13
13
|
end
|
14
14
|
|
15
15
|
def self.has_received_hardfail?
|
16
|
-
self.has_received_error?("
|
16
|
+
self.has_received_error?("hardfail")
|
17
17
|
end
|
18
18
|
|
19
|
-
def self.
|
20
|
-
self.has_received_error?("
|
19
|
+
def self.has_received_softfail?
|
20
|
+
self.has_received_error?("softfail")
|
21
21
|
end
|
22
22
|
|
23
23
|
def self.has_received_a_message_with_routing_key?(routing_key)
|
data/lib/gorg_service/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gorg_service
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 6.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexandre Narbonne
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-04-
|
11
|
+
date: 2017-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bunny
|
@@ -86,6 +86,20 @@ dependencies:
|
|
86
86
|
- - "~>"
|
87
87
|
- !ruby/object:Gem::Version
|
88
88
|
version: '10.0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: yard
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: 0.8.7
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 0.8.7
|
89
103
|
- !ruby/object:Gem::Dependency
|
90
104
|
name: rspec
|
91
105
|
requirement: !ruby/object:Gem::Requirement
|
@@ -106,14 +120,28 @@ dependencies:
|
|
106
120
|
requirements:
|
107
121
|
- - "~>"
|
108
122
|
- !ruby/object:Gem::Version
|
109
|
-
version:
|
123
|
+
version: '1.0'
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '1.0'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: simplecov
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0.13'
|
110
138
|
type: :development
|
111
139
|
prerelease: false
|
112
140
|
version_requirements: !ruby/object:Gem::Requirement
|
113
141
|
requirements:
|
114
142
|
- - "~>"
|
115
143
|
- !ruby/object:Gem::Version
|
116
|
-
version: 0.
|
144
|
+
version: '0.13'
|
117
145
|
- !ruby/object:Gem::Dependency
|
118
146
|
name: bogus
|
119
147
|
requirement: !ruby/object:Gem::Requirement
|
@@ -173,11 +201,16 @@ files:
|
|
173
201
|
- lib/gorg_service/consumer/message_handler/reply_handler.rb
|
174
202
|
- lib/gorg_service/consumer/message_handler/request_handler.rb
|
175
203
|
- lib/gorg_service/consumer/message_router.rb
|
204
|
+
- lib/gorg_service/event_message.rb
|
205
|
+
- lib/gorg_service/log_message.rb
|
176
206
|
- lib/gorg_service/message.rb
|
177
207
|
- lib/gorg_service/message/error_log.rb
|
208
|
+
- lib/gorg_service/message/formatters.rb
|
178
209
|
- lib/gorg_service/message/json_schema.rb
|
179
210
|
- lib/gorg_service/producer.rb
|
180
211
|
- lib/gorg_service/rabbitmq_env_builder.rb
|
212
|
+
- lib/gorg_service/reply_message.rb
|
213
|
+
- lib/gorg_service/request_message.rb
|
181
214
|
- lib/gorg_service/rspec/bunny_cleaner.rb
|
182
215
|
- lib/gorg_service/rspec/log_message_handler.rb
|
183
216
|
- lib/gorg_service/version.rb
|