stockboy 0.5.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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +5 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +12 -0
- data/Guardfile +10 -0
- data/LICENSE +21 -0
- data/README.md +293 -0
- data/Rakefile +30 -0
- data/lib/stockboy.rb +80 -0
- data/lib/stockboy/attribute.rb +11 -0
- data/lib/stockboy/attribute_map.rb +74 -0
- data/lib/stockboy/candidate_record.rb +130 -0
- data/lib/stockboy/configuration.rb +62 -0
- data/lib/stockboy/configurator.rb +176 -0
- data/lib/stockboy/dsl.rb +68 -0
- data/lib/stockboy/exceptions.rb +3 -0
- data/lib/stockboy/filter.rb +58 -0
- data/lib/stockboy/filter_chain.rb +41 -0
- data/lib/stockboy/filters.rb +11 -0
- data/lib/stockboy/filters/missing_email.rb +37 -0
- data/lib/stockboy/job.rb +241 -0
- data/lib/stockboy/mapped_record.rb +59 -0
- data/lib/stockboy/provider.rb +238 -0
- data/lib/stockboy/providers.rb +11 -0
- data/lib/stockboy/providers/file.rb +135 -0
- data/lib/stockboy/providers/ftp.rb +205 -0
- data/lib/stockboy/providers/http.rb +123 -0
- data/lib/stockboy/providers/imap.rb +290 -0
- data/lib/stockboy/providers/soap.rb +120 -0
- data/lib/stockboy/railtie.rb +28 -0
- data/lib/stockboy/reader.rb +59 -0
- data/lib/stockboy/readers.rb +11 -0
- data/lib/stockboy/readers/csv.rb +115 -0
- data/lib/stockboy/readers/fixed_width.rb +121 -0
- data/lib/stockboy/readers/spreadsheet.rb +144 -0
- data/lib/stockboy/readers/xml.rb +155 -0
- data/lib/stockboy/registry.rb +42 -0
- data/lib/stockboy/source_record.rb +43 -0
- data/lib/stockboy/string_pool.rb +35 -0
- data/lib/stockboy/template_file.rb +44 -0
- data/lib/stockboy/translations.rb +70 -0
- data/lib/stockboy/translations/boolean.rb +58 -0
- data/lib/stockboy/translations/date.rb +41 -0
- data/lib/stockboy/translations/decimal.rb +33 -0
- data/lib/stockboy/translations/default_empty_string.rb +38 -0
- data/lib/stockboy/translations/default_false.rb +41 -0
- data/lib/stockboy/translations/default_nil.rb +38 -0
- data/lib/stockboy/translations/default_true.rb +41 -0
- data/lib/stockboy/translations/default_zero.rb +41 -0
- data/lib/stockboy/translations/integer.rb +33 -0
- data/lib/stockboy/translations/string.rb +33 -0
- data/lib/stockboy/translations/time.rb +41 -0
- data/lib/stockboy/translations/uk_date.rb +51 -0
- data/lib/stockboy/translations/us_date.rb +51 -0
- data/lib/stockboy/translator.rb +66 -0
- data/lib/stockboy/version.rb +3 -0
- data/spec/fixtures/.gitkeep +0 -0
- data/spec/fixtures/files/a_garbage.csv +1 -0
- data/spec/fixtures/files/test_data-20120101.csv +1 -0
- data/spec/fixtures/files/test_data-20120202.csv +1 -0
- data/spec/fixtures/files/z_garbage.csv +1 -0
- data/spec/fixtures/jobs/test_job.rb +1 -0
- data/spec/fixtures/soap/get_list/fault.xml +8 -0
- data/spec/fixtures/soap/get_list/success.xml +18 -0
- data/spec/fixtures/spreadsheets/test_data.xls +0 -0
- data/spec/fixtures/spreadsheets/test_row_options.xls +0 -0
- data/spec/fixtures/xml/body.xml +14 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/stockboy/attribute_map_spec.rb +59 -0
- data/spec/stockboy/attribute_spec.rb +11 -0
- data/spec/stockboy/candidate_record_spec.rb +150 -0
- data/spec/stockboy/configuration_spec.rb +28 -0
- data/spec/stockboy/configurator_spec.rb +127 -0
- data/spec/stockboy/filter_chain_spec.rb +40 -0
- data/spec/stockboy/filter_spec.rb +41 -0
- data/spec/stockboy/filters/missing_email_spec.rb +26 -0
- data/spec/stockboy/filters_spec.rb +38 -0
- data/spec/stockboy/job_spec.rb +238 -0
- data/spec/stockboy/mapped_record_spec.rb +30 -0
- data/spec/stockboy/provider_spec.rb +34 -0
- data/spec/stockboy/providers/file_spec.rb +116 -0
- data/spec/stockboy/providers/ftp_spec.rb +143 -0
- data/spec/stockboy/providers/http_spec.rb +94 -0
- data/spec/stockboy/providers/imap_spec.rb +76 -0
- data/spec/stockboy/providers/soap_spec.rb +107 -0
- data/spec/stockboy/providers_spec.rb +38 -0
- data/spec/stockboy/readers/csv_spec.rb +68 -0
- data/spec/stockboy/readers/fixed_width_spec.rb +52 -0
- data/spec/stockboy/readers/spreadsheet_spec.rb +121 -0
- data/spec/stockboy/readers/xml_spec.rb +94 -0
- data/spec/stockboy/readers_spec.rb +30 -0
- data/spec/stockboy/source_record_spec.rb +19 -0
- data/spec/stockboy/template_file_spec.rb +30 -0
- data/spec/stockboy/translations/boolean_spec.rb +48 -0
- data/spec/stockboy/translations/date_spec.rb +38 -0
- data/spec/stockboy/translations/decimal_spec.rb +23 -0
- data/spec/stockboy/translations/default_empty_string_spec.rb +32 -0
- data/spec/stockboy/translations/default_false_spec.rb +25 -0
- data/spec/stockboy/translations/default_nil_spec.rb +32 -0
- data/spec/stockboy/translations/default_true_spec.rb +25 -0
- data/spec/stockboy/translations/default_zero_spec.rb +32 -0
- data/spec/stockboy/translations/integer_spec.rb +22 -0
- data/spec/stockboy/translations/string_spec.rb +22 -0
- data/spec/stockboy/translations/time_spec.rb +27 -0
- data/spec/stockboy/translations/uk_date_spec.rb +37 -0
- data/spec/stockboy/translations/us_date_spec.rb +37 -0
- data/spec/stockboy/translations_spec.rb +55 -0
- data/spec/stockboy/translator_spec.rb +27 -0
- data/stockboy.gemspec +32 -0
- metadata +305 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
require 'stockboy/provider'
|
|
2
|
+
require 'httpi'
|
|
3
|
+
|
|
4
|
+
module Stockboy::Providers
|
|
5
|
+
|
|
6
|
+
# Fetches data from an HTTP endpoint
|
|
7
|
+
#
|
|
8
|
+
# == Job template DSL
|
|
9
|
+
#
|
|
10
|
+
# provider :http do
|
|
11
|
+
# get "http://example.com/api/things"
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
class HTTP < Stockboy::Provider
|
|
15
|
+
|
|
16
|
+
# @!group Options
|
|
17
|
+
|
|
18
|
+
# Shorthand for +:method+ and +:uri+ using HTTP GET
|
|
19
|
+
#
|
|
20
|
+
# @!attribute [rw] get
|
|
21
|
+
# @return [String]
|
|
22
|
+
# @example
|
|
23
|
+
# get 'http://example.com/api/things'
|
|
24
|
+
#
|
|
25
|
+
dsl_attr :get, attr_writer: false
|
|
26
|
+
|
|
27
|
+
# Shorthand for +:method+ and +:uri+ using HTTP POST
|
|
28
|
+
#
|
|
29
|
+
# @!attribute [rw] post
|
|
30
|
+
# @return [String]
|
|
31
|
+
# @example
|
|
32
|
+
# post 'http://example.com/api/search'
|
|
33
|
+
#
|
|
34
|
+
dsl_attr :post, attr_writer: false
|
|
35
|
+
|
|
36
|
+
# HTTP method: +:get+ or +:post+
|
|
37
|
+
#
|
|
38
|
+
# @!attribute [rw] method
|
|
39
|
+
# @return [Symbol]
|
|
40
|
+
# @example
|
|
41
|
+
# method :post
|
|
42
|
+
#
|
|
43
|
+
dsl_attr :method, attr_writer: false
|
|
44
|
+
|
|
45
|
+
def uri
|
|
46
|
+
return nil if @uri.nil? || @uri.empty?
|
|
47
|
+
URI(@uri).tap { |u| u.query = URI.encode_www_form(@query) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def uri=(uri)
|
|
51
|
+
@uri = uri
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# HTTP host and path to the data resource
|
|
55
|
+
#
|
|
56
|
+
# @!attribute [rw] uri
|
|
57
|
+
# @return [String]
|
|
58
|
+
# @example
|
|
59
|
+
# uri 'http://example.com/api/things'
|
|
60
|
+
#
|
|
61
|
+
dsl_attr :uri, attr_accessor: false, alias: :url
|
|
62
|
+
|
|
63
|
+
# Hash of query options
|
|
64
|
+
#
|
|
65
|
+
# @!attribute [rw] query
|
|
66
|
+
# @return [Hash]
|
|
67
|
+
# @example
|
|
68
|
+
# query start: 1, limit: 100
|
|
69
|
+
#
|
|
70
|
+
dsl_attr :query
|
|
71
|
+
|
|
72
|
+
def method=(http_method)
|
|
73
|
+
return @method = nil unless %w(get post).include? http_method.to_s.downcase
|
|
74
|
+
@method = http_method.to_s.downcase.to_sym
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def get=(uri)
|
|
78
|
+
@method = :get
|
|
79
|
+
@uri = uri
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def post=(uri)
|
|
83
|
+
@method = :post
|
|
84
|
+
@uri = uri
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @!endgroup
|
|
88
|
+
|
|
89
|
+
# Initialize an HTTP provider
|
|
90
|
+
#
|
|
91
|
+
def initialize(opts={}, &block)
|
|
92
|
+
super(opts, &block)
|
|
93
|
+
self.uri = opts[:uri]
|
|
94
|
+
self.method = opts[:method] || :get
|
|
95
|
+
self.query = opts[:query] || Hash.new
|
|
96
|
+
DSL.new(self).instance_eval(&block) if block_given?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def client
|
|
100
|
+
return HTTPI unless block_given?
|
|
101
|
+
yield HTTPI
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def validate
|
|
107
|
+
errors.add_on_blank [:uri, :method]
|
|
108
|
+
errors.empty?
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def fetch_data
|
|
112
|
+
request = HTTPI::Request.new
|
|
113
|
+
request.url = uri
|
|
114
|
+
response = HTTPI.send(method, request)
|
|
115
|
+
if response.error?
|
|
116
|
+
errors.add :response, "HTTP respone error: #{response.code}"
|
|
117
|
+
else
|
|
118
|
+
@data = response.body
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
require 'stockboy/provider'
|
|
2
|
+
require 'net/imap'
|
|
3
|
+
require 'mail'
|
|
4
|
+
|
|
5
|
+
module Stockboy::Providers
|
|
6
|
+
|
|
7
|
+
# Read data from a file attachment in IMAP email
|
|
8
|
+
#
|
|
9
|
+
# == Job template DSL
|
|
10
|
+
#
|
|
11
|
+
# provider :imap do
|
|
12
|
+
# host "imap.example.com"
|
|
13
|
+
# username "arthur@example.com"
|
|
14
|
+
# password "424242"
|
|
15
|
+
# mailbox "INBOX"
|
|
16
|
+
# subject "Daily Report"
|
|
17
|
+
# since Date.today
|
|
18
|
+
# file_name /report-[0-9]+\.csv/
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
class IMAP < Stockboy::Provider
|
|
22
|
+
|
|
23
|
+
# Corresponds to %v mode in +DateTime#strftime+
|
|
24
|
+
VMS_DATE = /\A\d{2}-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\d{2}\z/i
|
|
25
|
+
|
|
26
|
+
# @!group Options
|
|
27
|
+
|
|
28
|
+
# Host name or IP address for IMAP server connection
|
|
29
|
+
#
|
|
30
|
+
# @!attribute [rw] host
|
|
31
|
+
# @return [String]
|
|
32
|
+
# @example
|
|
33
|
+
# host "imap.example.com"
|
|
34
|
+
#
|
|
35
|
+
dsl_attr :host
|
|
36
|
+
|
|
37
|
+
# User name for connection credentials
|
|
38
|
+
#
|
|
39
|
+
# @!attribute [rw] username
|
|
40
|
+
# @return [String]
|
|
41
|
+
# @example
|
|
42
|
+
# username "arthur@example.com"
|
|
43
|
+
#
|
|
44
|
+
dsl_attr :username
|
|
45
|
+
|
|
46
|
+
# Password for connection credentials
|
|
47
|
+
#
|
|
48
|
+
# @!attribute [rw] password
|
|
49
|
+
# @return [String]
|
|
50
|
+
# @example
|
|
51
|
+
# password "424242"
|
|
52
|
+
#
|
|
53
|
+
dsl_attr :password
|
|
54
|
+
|
|
55
|
+
# Where to look for email on the server (usually "INBOX")
|
|
56
|
+
#
|
|
57
|
+
# @!attribute [rw] mailbox
|
|
58
|
+
# @return [String]
|
|
59
|
+
# @example
|
|
60
|
+
# mailbox "INBOX"
|
|
61
|
+
#
|
|
62
|
+
dsl_attr :mailbox
|
|
63
|
+
|
|
64
|
+
# Substring to find contained in matching email subject
|
|
65
|
+
#
|
|
66
|
+
# @!attribute [rw] subject
|
|
67
|
+
# @return [String]
|
|
68
|
+
# @example
|
|
69
|
+
# subject "Daily Report"
|
|
70
|
+
#
|
|
71
|
+
dsl_attr :subject
|
|
72
|
+
|
|
73
|
+
# Email address of the sender
|
|
74
|
+
#
|
|
75
|
+
# @!attribute [rw] from
|
|
76
|
+
# @return [String]
|
|
77
|
+
# @example
|
|
78
|
+
# from "sender+12345@example.com"
|
|
79
|
+
#
|
|
80
|
+
dsl_attr :from
|
|
81
|
+
|
|
82
|
+
# Minimum time sent for matching email
|
|
83
|
+
#
|
|
84
|
+
# @!attribute [rw] since
|
|
85
|
+
# @return [String]
|
|
86
|
+
# @example
|
|
87
|
+
# since Date.today
|
|
88
|
+
#
|
|
89
|
+
dsl_attr :since, alias: :newer_than
|
|
90
|
+
|
|
91
|
+
# Key-value tokens for IMAP search options
|
|
92
|
+
#
|
|
93
|
+
# @!attribute [rw] search
|
|
94
|
+
# @return [String]
|
|
95
|
+
# @example
|
|
96
|
+
# search ['FLAGGED', 'BODY', 'Report attached']
|
|
97
|
+
#
|
|
98
|
+
dsl_attr :search
|
|
99
|
+
|
|
100
|
+
# Name or pattern for matching attachment files. First matching attachment
|
|
101
|
+
# is picked, or the first attachment if not specified.
|
|
102
|
+
#
|
|
103
|
+
# @!attribute [rw] attachment
|
|
104
|
+
# @return [String, Regexp]
|
|
105
|
+
# @example
|
|
106
|
+
# attachment "daily-report.csv"
|
|
107
|
+
# attachment /daily-report-[0-9]+.csv/
|
|
108
|
+
#
|
|
109
|
+
dsl_attr :attachment
|
|
110
|
+
|
|
111
|
+
# Method for choosing which email message to process from potential
|
|
112
|
+
# matches. Default is last by date sent.
|
|
113
|
+
#
|
|
114
|
+
# @!attribute [rw] pick
|
|
115
|
+
# @return [Symbol, Proc]
|
|
116
|
+
# @example
|
|
117
|
+
# pick :last
|
|
118
|
+
# pick :first
|
|
119
|
+
# pick ->(list) {
|
|
120
|
+
# list.max_by { |msgid| client.fetch(msgid, 'SENTON').to_i }
|
|
121
|
+
# }
|
|
122
|
+
#
|
|
123
|
+
dsl_attr :pick
|
|
124
|
+
|
|
125
|
+
# @!endgroup
|
|
126
|
+
|
|
127
|
+
# Library for connection, defaults to +Net::IMAP+
|
|
128
|
+
#
|
|
129
|
+
# @!attribute [rw] imap_client
|
|
130
|
+
#
|
|
131
|
+
def self.imap_client
|
|
132
|
+
@imap_client ||= Net::IMAP
|
|
133
|
+
end
|
|
134
|
+
class << self
|
|
135
|
+
attr_writer :imap_client
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Initialize a new IMAP reader
|
|
139
|
+
#
|
|
140
|
+
def initialize(opts={}, &block)
|
|
141
|
+
super(opts, &block)
|
|
142
|
+
@host = opts[:host]
|
|
143
|
+
@username = opts[:username]
|
|
144
|
+
@password = opts[:password]
|
|
145
|
+
@mailbox = opts[:mailbox]
|
|
146
|
+
@subject = opts[:subject]
|
|
147
|
+
@from = opts[:from]
|
|
148
|
+
@since = opts[:since]
|
|
149
|
+
@search = opts[:search]
|
|
150
|
+
@attachment = opts[:attachment]
|
|
151
|
+
@file_smaller = opts[:file_smaller]
|
|
152
|
+
@file_larger = opts[:file_larger]
|
|
153
|
+
@pick = opts[:pick] || :last
|
|
154
|
+
DSL.new(self).instance_eval(&block) if block_given?
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def client
|
|
158
|
+
raise(ArgumentError, "no block given") unless block_given?
|
|
159
|
+
return yield @open_client if @open_client
|
|
160
|
+
|
|
161
|
+
@open_client = ::Net::IMAP.new(host).tap do |i|
|
|
162
|
+
i.login(username, password)
|
|
163
|
+
i.examine(mailbox)
|
|
164
|
+
end
|
|
165
|
+
yield @open_client
|
|
166
|
+
client.disconnect
|
|
167
|
+
@open_client = nil
|
|
168
|
+
rescue ::Net::IMAP::Error => e
|
|
169
|
+
errors.add :response, "IMAP connection error"
|
|
170
|
+
client.disconnect
|
|
171
|
+
@open_client = nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def delete_data
|
|
175
|
+
raise Stockboy::OutOfSequence, "must confirm #matching_message or calling #data" unless picked_matching_message?
|
|
176
|
+
|
|
177
|
+
logger.info "Deleting message #{username}:#{host} message_uid: #{matching_message}"
|
|
178
|
+
client do |imap|
|
|
179
|
+
imap.uid_store(matching_message, "+FLAGS", [:Deleted])
|
|
180
|
+
imap.expunge
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def matching_message
|
|
185
|
+
return @matching_message if @matching_message
|
|
186
|
+
keys = fetch_imap_message_keys
|
|
187
|
+
@matching_message = pick_from(keys) unless keys.empty?
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def clear
|
|
191
|
+
super
|
|
192
|
+
@matching_message = nil
|
|
193
|
+
@data_time = nil
|
|
194
|
+
@data_size = nil
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
private
|
|
198
|
+
|
|
199
|
+
def fetch_data
|
|
200
|
+
client do |imap|
|
|
201
|
+
return false unless matching_message
|
|
202
|
+
mail = ::Mail.new(imap.fetch(matching_message, 'RFC822')[0].attr['RFC822'])
|
|
203
|
+
if part = mail.attachments.detect { |part| validate_attachment(part) }
|
|
204
|
+
validate_file(part.decoded)
|
|
205
|
+
if valid?
|
|
206
|
+
logger.info "Getting file from #{username}:#{host} message_uid #{matching_message}"
|
|
207
|
+
@data = part.decoded
|
|
208
|
+
@data_time = normalize_imap_datetime(mail.date)
|
|
209
|
+
logger.info "Got file from #{username}:#{host} message_uid #{matching_message}"
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
!@data.nil?
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def validate
|
|
217
|
+
errors.add_on_blank [:host, :username, :password]
|
|
218
|
+
errors.empty?
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def fetch_imap_message_keys
|
|
222
|
+
client { |imap| imap.sort(['DATE'], search_keys, 'UTF-8') }
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def picked_matching_message?
|
|
226
|
+
!!@matching_message
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def validate_attachment(part)
|
|
230
|
+
case attachment
|
|
231
|
+
when String
|
|
232
|
+
part.filename == attachment
|
|
233
|
+
when Regexp
|
|
234
|
+
part.filename =~ attachment
|
|
235
|
+
else
|
|
236
|
+
true
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def search_keys
|
|
241
|
+
keys = []
|
|
242
|
+
keys.concat ['SUBJECT', subject] if subject
|
|
243
|
+
keys.concat ['FROM', from] if from
|
|
244
|
+
keys.concat ['SINCE', date_format(since)] if since
|
|
245
|
+
keys.concat search if search
|
|
246
|
+
keys
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def date_format(value)
|
|
250
|
+
case value
|
|
251
|
+
when Date, Time, DateTime
|
|
252
|
+
value.strftime('%v')
|
|
253
|
+
when Numeric
|
|
254
|
+
Time.at(value).strftime('%v')
|
|
255
|
+
when String
|
|
256
|
+
return value if value =~ VMS_DATE
|
|
257
|
+
Date.parse(value).strftime('%v')
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# If activesupport is loaded, it mucks with DateTime#to_time to return
|
|
262
|
+
# self when it has a utc_offset. Handle both to always return a Time.utc.
|
|
263
|
+
#
|
|
264
|
+
def normalize_imap_datetime(datetime)
|
|
265
|
+
datetime.respond_to?(:getutc) ?
|
|
266
|
+
datetime.getutc.to_time : datetime.to_time.utc
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def validate_file(data_file)
|
|
270
|
+
return errors.add :response, "No matching attachments" unless data_file
|
|
271
|
+
validate_file_smaller(data_file)
|
|
272
|
+
validate_file_larger(data_file)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def validate_file_smaller(data_file)
|
|
276
|
+
@data_size ||= data_file.bytesize
|
|
277
|
+
if file_smaller && @data_size > file_smaller
|
|
278
|
+
errors.add :response, "File size larger than #{file_smaller}"
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def validate_file_larger(data_file)
|
|
283
|
+
@data_size ||= data_file.bytesize
|
|
284
|
+
if file_larger && @data_size < file_larger
|
|
285
|
+
errors.add :response, "File size smaller than #{file_larger}"
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
require 'stockboy/provider'
|
|
2
|
+
require 'stockboy/string_pool'
|
|
3
|
+
require 'savon'
|
|
4
|
+
|
|
5
|
+
module Stockboy::Providers
|
|
6
|
+
|
|
7
|
+
# Fetch data from a SOAP endpoint
|
|
8
|
+
#
|
|
9
|
+
# Backed by Savon gem, see savon for full configuration options: extra
|
|
10
|
+
# options are passed through.
|
|
11
|
+
#
|
|
12
|
+
class SOAP < Stockboy::Provider
|
|
13
|
+
include Stockboy::StringPool
|
|
14
|
+
|
|
15
|
+
# @!group Options
|
|
16
|
+
#
|
|
17
|
+
# These options correspond to Savon client options
|
|
18
|
+
|
|
19
|
+
# URL with the WSDL document
|
|
20
|
+
#
|
|
21
|
+
# @!attribute [rw] wsdl
|
|
22
|
+
# @return [String]
|
|
23
|
+
# @example
|
|
24
|
+
# wsdl "http://example.com/api/soap?wsdl"
|
|
25
|
+
#
|
|
26
|
+
dsl_attr :wsdl
|
|
27
|
+
|
|
28
|
+
# The name of the request, see your SOAP documentation
|
|
29
|
+
#
|
|
30
|
+
# @!attribute [rw] request
|
|
31
|
+
# @return [String]
|
|
32
|
+
# @example
|
|
33
|
+
# request "allItemsDetails"
|
|
34
|
+
#
|
|
35
|
+
dsl_attr :request
|
|
36
|
+
|
|
37
|
+
# @return [String]
|
|
38
|
+
# @!attribute [rw] namespace
|
|
39
|
+
# Optional if specified in WSDL
|
|
40
|
+
#
|
|
41
|
+
dsl_attr :namespace
|
|
42
|
+
|
|
43
|
+
# @return [String]
|
|
44
|
+
# @!attribute [rw] namespace_id
|
|
45
|
+
# Optional if specified in WSDL
|
|
46
|
+
#
|
|
47
|
+
dsl_attr :namespace_id
|
|
48
|
+
|
|
49
|
+
# @return [String]
|
|
50
|
+
# @!attribute [rw] endpoint
|
|
51
|
+
# Optional if specified in WSDL
|
|
52
|
+
#
|
|
53
|
+
dsl_attr :endpoint
|
|
54
|
+
|
|
55
|
+
# Hash of message options passed in the request, often includes
|
|
56
|
+
# credentials and query options.
|
|
57
|
+
#
|
|
58
|
+
# @!attribute [rw] message
|
|
59
|
+
# @return [Hash]
|
|
60
|
+
# @example
|
|
61
|
+
# message "clientId" => "12345", "updatedSince" => "2012-12-12"
|
|
62
|
+
#
|
|
63
|
+
dsl_attr :message
|
|
64
|
+
|
|
65
|
+
# Hash of optional HTTP request headers
|
|
66
|
+
#
|
|
67
|
+
# @!attribute [rw] headers
|
|
68
|
+
# @return [Hash]
|
|
69
|
+
# @example
|
|
70
|
+
# headers "X-ClientKey" => "12345"
|
|
71
|
+
#
|
|
72
|
+
dsl_attr :headers
|
|
73
|
+
|
|
74
|
+
# @!endgroup
|
|
75
|
+
|
|
76
|
+
# Initialize a new SOAP provider
|
|
77
|
+
#
|
|
78
|
+
def initialize(opts={}, &block)
|
|
79
|
+
super
|
|
80
|
+
DSL.new(self).instance_eval(&block) if block_given?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Connection object to the configured SOAP endpoint
|
|
84
|
+
#
|
|
85
|
+
# @return [Savon::Client]
|
|
86
|
+
#
|
|
87
|
+
def client
|
|
88
|
+
@client ||= Savon.client(client_options)
|
|
89
|
+
return @client unless block_given?
|
|
90
|
+
yield @client
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def client_options
|
|
96
|
+
opts = if wsdl
|
|
97
|
+
{wsdl: wsdl}
|
|
98
|
+
elsif endpoint
|
|
99
|
+
{endpoint: endpoint}
|
|
100
|
+
end
|
|
101
|
+
opts[:convert_response_tags_to] = ->(tag) { string_pool(tag) }
|
|
102
|
+
opts[:namespace] = namespace if namespace
|
|
103
|
+
opts[:namespace_identifier] = namespace_id if namespace_id
|
|
104
|
+
opts[:headers] = headers if headers
|
|
105
|
+
opts
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def validate
|
|
109
|
+
errors.add_on_blank(:endpoint) unless wsdl
|
|
110
|
+
errors.blank?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def fetch_data
|
|
114
|
+
with_string_pool do
|
|
115
|
+
@data = client.call(@request, message: message).body
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
end
|
|
120
|
+
end
|