grape-batch 1.1.1 → 1.1.2
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/grape-batch.gemspec +1 -0
- data/lib/grape/batch/configuration.rb +2 -1
- data/lib/grape/batch/log_subscriber.rb +64 -0
- data/lib/grape/batch/version.rb +1 -1
- data/lib/grape/batch.rb +35 -18
- data/spec/requests_spec.rb +27 -24
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8bbe03da7123e206a98f1665477f2903ccff61aa
|
4
|
+
data.tar.gz: 814d658a2f6300f0b99d966be482bf49adf02f41
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 991c0e72406404557650cda1dbd0303fc0f0b85f701b0570ed8019960223fe64b306af997ce6cb21479932a42d54744bb148433708f458d1326ffa638fb94830
|
7
|
+
data.tar.gz: 0822eda7abe6aaa04bdcb6ee6797549d4a8f8e745a6cb097abdfe5d81059a3da70f92cb74c9d911d4e81032e1a751758edde913585b8d0d3107ad377abf4bef8
|
data/grape-batch.gemspec
CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.add_runtime_dependency 'rack', '>= 1.5'
|
21
21
|
spec.add_runtime_dependency 'grape', '>= 0.7.0'
|
22
22
|
spec.add_runtime_dependency 'multi_json', '>= 1.0'
|
23
|
+
spec.add_runtime_dependency 'activesupport', '>= 4.1.0'
|
23
24
|
|
24
25
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
25
26
|
spec.add_development_dependency 'rake', '~> 10.3.2'
|
@@ -1,12 +1,13 @@
|
|
1
1
|
module Grape
|
2
2
|
module Batch
|
3
3
|
class Configuration
|
4
|
-
attr_accessor :path, :limit, :formatter
|
4
|
+
attr_accessor :path, :limit, :formatter, :logger
|
5
5
|
|
6
6
|
def initialize
|
7
7
|
@path = '/batch'
|
8
8
|
@limit = 10
|
9
9
|
@formatter = Grape::Batch::Response
|
10
|
+
@logger = nil
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Grape
|
2
|
+
module Batch
|
3
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
4
|
+
def dispatch(event)
|
5
|
+
requests = event.payload[:requests]
|
6
|
+
|
7
|
+
if logger.debug?
|
8
|
+
debug_log(requests)
|
9
|
+
else
|
10
|
+
info_log(requests)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def debug_log(requests)
|
17
|
+
logger.info 'grape/batch'
|
18
|
+
|
19
|
+
requests.map do |params|
|
20
|
+
request, response = params
|
21
|
+
logger.info " method=#{request['REQUEST_METHOD']} path=#{request['PATH_INFO']}"
|
22
|
+
|
23
|
+
if request['REQUEST_METHOD'] == 'GET'
|
24
|
+
unless request['QUERY_STRING'].empty?
|
25
|
+
logger.debug " params: #{request['QUERY_STRING'].to_s}"
|
26
|
+
end
|
27
|
+
else
|
28
|
+
if request['rack.input'].respond_to? :string
|
29
|
+
logger.debug " body: #{request['rack.input'].string}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
logger.debug " response: #{response.to_s}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def info_log(requests)
|
37
|
+
messages = []
|
38
|
+
requests.each do |params|
|
39
|
+
request, response = params
|
40
|
+
messages << "method=#{request['REQUEST_METHOD']} path=#{request['PATH_INFO']}"
|
41
|
+
end
|
42
|
+
|
43
|
+
logger.info 'grape/batch ' + messages.join(', ')
|
44
|
+
end
|
45
|
+
|
46
|
+
def logger
|
47
|
+
@logger ||= Grape::Batch.configuration.logger || rails_logger || default_logger
|
48
|
+
end
|
49
|
+
|
50
|
+
def default_logger
|
51
|
+
logger = Logger.new($stdout)
|
52
|
+
logger.level = Logger::INFO
|
53
|
+
logger
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get the Rails logger if it's defined.
|
57
|
+
def rails_logger
|
58
|
+
defined?(::Rails) && ::Rails.respond_to?(:logger) && ::Rails.logger
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Grape::Batch::LogSubscriber.attach_to(:batch)
|
data/lib/grape/batch/version.rb
CHANGED
data/lib/grape/batch.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'grape/batch/log_subscriber'
|
3
|
+
|
4
|
+
require 'grape/batch/version'
|
1
5
|
require 'grape/batch/errors'
|
2
6
|
require 'grape/batch/configuration'
|
3
7
|
require 'grape/batch/parser'
|
4
8
|
require 'grape/batch/response'
|
5
|
-
require 'grape/batch/version'
|
6
9
|
require 'multi_json'
|
7
10
|
|
8
11
|
module Grape
|
@@ -20,7 +23,7 @@ module Grape
|
|
20
23
|
|
21
24
|
def batch_call(env)
|
22
25
|
status = 200
|
23
|
-
headers = {'Content-Type' => 'application/json'}
|
26
|
+
headers = { 'Content-Type' => 'application/json' }
|
24
27
|
|
25
28
|
begin
|
26
29
|
batch_requests = Grape::Batch::Validator::parse(env, Grape::Batch.configuration.limit)
|
@@ -38,30 +41,44 @@ module Grape
|
|
38
41
|
|
39
42
|
def is_batch_request?(env)
|
40
43
|
env['PATH_INFO'].start_with?(Grape::Batch.configuration.path) &&
|
41
|
-
|
42
|
-
|
44
|
+
env['REQUEST_METHOD'] == 'POST' &&
|
45
|
+
env['CONTENT_TYPE'] == 'application/json'
|
43
46
|
end
|
44
47
|
|
45
48
|
def dispatch(env, batch_requests)
|
46
|
-
|
49
|
+
ActiveSupport::Notifications.instrument 'dispatch.batch' do |event|
|
50
|
+
event[:requests] = []
|
47
51
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
+
# iterate
|
53
|
+
batch_requests.map do |request|
|
54
|
+
# init env for Grape resource
|
55
|
+
tmp_env = prepare_tmp_env(env.dup, request)
|
56
|
+
status, headers, response = @app.call(tmp_env)
|
52
57
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
else
|
59
|
-
request_env['rack.input'] = StringIO.new(MultiJson.encode(body))
|
58
|
+
# format response
|
59
|
+
@response_klass::format(status, headers, response).tap do |formatted_response|
|
60
|
+
# log call
|
61
|
+
event[:requests] << [tmp_env, formatted_response]
|
62
|
+
end
|
60
63
|
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def prepare_tmp_env(tmp_env, request)
|
68
|
+
method = request['method']
|
69
|
+
path = request['path']
|
70
|
+
body = request['body'].is_a?(Hash) ? request['body'] : {}
|
61
71
|
|
62
|
-
|
72
|
+
tmp_env.tap do |env|
|
73
|
+
env['REQUEST_METHOD'] = method
|
74
|
+
env['PATH_INFO'] = path
|
63
75
|
|
64
|
-
|
76
|
+
if method == 'GET'
|
77
|
+
env['rack.input'] = StringIO.new('{}')
|
78
|
+
env['QUERY_STRING'] = URI.encode_www_form(body.to_a)
|
79
|
+
else
|
80
|
+
env['rack.input'] = StringIO.new(MultiJson.encode(body))
|
81
|
+
end
|
65
82
|
end
|
66
83
|
end
|
67
84
|
end
|
data/spec/requests_spec.rb
CHANGED
@@ -5,6 +5,10 @@ require 'grape'
|
|
5
5
|
require 'api'
|
6
6
|
|
7
7
|
RSpec.describe Grape::Batch::Base do
|
8
|
+
before(:all) do
|
9
|
+
Grape::Batch.configuration.logger = Logger.new('/dev/null')
|
10
|
+
end
|
11
|
+
|
8
12
|
before :context do
|
9
13
|
@app = Twitter::API.new
|
10
14
|
end
|
@@ -31,15 +35,14 @@ RSpec.describe Grape::Batch::Base do
|
|
31
35
|
|
32
36
|
describe 'GET /failure' do
|
33
37
|
let(:response) { request.get('/api/v1/failure') }
|
34
|
-
|
35
38
|
it { expect(response.status).to eq(503) }
|
36
|
-
it { expect(response.body).to eq(encode({error: 'Failed as expected'})) }
|
39
|
+
it { expect(response.body).to eq(encode({ error: 'Failed as expected' })) }
|
37
40
|
end
|
38
41
|
end
|
39
42
|
|
40
43
|
describe '/batch' do
|
41
44
|
let(:request_body) { nil }
|
42
|
-
let(:response) { request.post('/batch', {'CONTENT_TYPE' => 'application/json', input: request_body}) }
|
45
|
+
let(:response) { request.post('/batch', { 'CONTENT_TYPE' => 'application/json', input: request_body }) }
|
43
46
|
|
44
47
|
context 'with invalid body' do
|
45
48
|
it { expect(response.status).to eq(400) }
|
@@ -74,40 +77,40 @@ RSpec.describe Grape::Batch::Base do
|
|
74
77
|
end
|
75
78
|
|
76
79
|
context "when body['requests'] is not an array" do
|
77
|
-
let(:request_body) { encode({requests: 'request'}) }
|
80
|
+
let(:request_body) { encode({ requests: 'request' }) }
|
78
81
|
it { expect(response.body).to eq("'requests' is not well formatted") }
|
79
82
|
end
|
80
83
|
|
81
84
|
context 'when request limit is exceeded' do
|
82
|
-
let(:request_body) { encode({requests: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]}) }
|
85
|
+
let(:request_body) { encode({ requests: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] }) }
|
83
86
|
it { expect(response.body).to eq('Batch requests limit exceeded') }
|
84
87
|
end
|
85
88
|
|
86
89
|
describe 'method attribute in request object' do
|
87
90
|
context 'method is missing' do
|
88
|
-
let(:request_body) { encode({requests: [{}]}) }
|
91
|
+
let(:request_body) { encode({ requests: [{}] }) }
|
89
92
|
it { expect(response.body).to eq("'method' is missing in one of request objects") }
|
90
93
|
end
|
91
94
|
|
92
95
|
context 'method is not a String' do
|
93
|
-
let(:request_body) { encode({requests: [{method: true}]}) }
|
96
|
+
let(:request_body) { encode({ requests: [{ method: true }] }) }
|
94
97
|
it { expect(response.body).to eq("'method' is invalid in one of request objects") }
|
95
98
|
end
|
96
99
|
|
97
100
|
context 'method is invalid' do
|
98
|
-
let(:request_body) { encode({requests: [{method: 'TRACE'}]}) }
|
101
|
+
let(:request_body) { encode({ requests: [{ method: 'TRACE' }] }) }
|
99
102
|
it { expect(response.body).to eq("'method' is invalid in one of request objects") }
|
100
103
|
end
|
101
104
|
end
|
102
105
|
|
103
106
|
describe 'path attribute in request object' do
|
104
107
|
context 'path is missing' do
|
105
|
-
let(:request_body) { encode({requests: [{method: 'GET'}]}) }
|
108
|
+
let(:request_body) { encode({ requests: [{ method: 'GET' }] }) }
|
106
109
|
it { expect(response.body).to eq("'path' is missing in one of request objects") }
|
107
110
|
end
|
108
111
|
|
109
112
|
context 'path is not a String' do
|
110
|
-
let(:request_body) { encode({requests: [{method: 'GET', path: 123}]}) }
|
113
|
+
let(:request_body) { encode({ requests: [{ method: 'GET', path: 123 }] }) }
|
111
114
|
it { expect(response.body).to eq("'path' is invalid in one of request objects") }
|
112
115
|
end
|
113
116
|
end
|
@@ -115,47 +118,47 @@ RSpec.describe Grape::Batch::Base do
|
|
115
118
|
|
116
119
|
describe 'GET' do
|
117
120
|
context 'with no parameters' do
|
118
|
-
let(:request_body) { encode({requests: [{method: 'GET', path: '/api/v1/hello'}]}) }
|
121
|
+
let(:request_body) { encode({ requests: [{ method: 'GET', path: '/api/v1/hello' }] }) }
|
119
122
|
it { expect(response.status).to eq(200) }
|
120
|
-
it { expect(response.body).to eq(encode([{success: 'world'}])) }
|
123
|
+
it { expect(response.body).to eq(encode([{ success: 'world' }])) }
|
121
124
|
end
|
122
125
|
|
123
126
|
context 'with parameters' do
|
124
|
-
let(:request_body) { encode({requests: [{method: 'GET', path: '/api/v1/user/856'}]}) }
|
127
|
+
let(:request_body) { encode({ requests: [{ method: 'GET', path: '/api/v1/user/856' }] }) }
|
125
128
|
it { expect(response.status).to eq(200) }
|
126
|
-
it { expect(response.body).to eq(encode([{success: 'user 856'}])) }
|
129
|
+
it { expect(response.body).to eq(encode([{ success: 'user 856' }])) }
|
127
130
|
end
|
128
131
|
|
129
132
|
context 'with a body' do
|
130
|
-
let(:request_body) { encode({requests: [{method: 'GET', path: '/api/v1/status', body: {id: 856}}]}) }
|
133
|
+
let(:request_body) { encode({ requests: [{ method: 'GET', path: '/api/v1/status', body: { id: 856 } }] }) }
|
131
134
|
it { expect(response.status).to eq(200) }
|
132
|
-
it { expect(response.body).to eq(encode([{success: 'status 856'}])) }
|
135
|
+
it { expect(response.body).to eq(encode([{ success: 'status 856' }])) }
|
133
136
|
end
|
134
137
|
|
135
138
|
describe '404 errors' do
|
136
|
-
let(:request_body) { encode({requests: [{method: 'GET', path: '/api/v1/unknown'}]}) }
|
139
|
+
let(:request_body) { encode({ requests: [{ method: 'GET', path: '/api/v1/unknown' }] }) }
|
137
140
|
it { expect(response.status).to eq(200) }
|
138
|
-
it { expect(response.body).to eq(encode([{code: 404, error: '/api/v1/unknown not found'}])) }
|
141
|
+
it { expect(response.body).to eq(encode([{ code: 404, error: '/api/v1/unknown not found' }])) }
|
139
142
|
end
|
140
143
|
end
|
141
144
|
|
142
145
|
describe 'POST' do
|
143
146
|
context 'with no parameters' do
|
144
|
-
let(:request_body) { encode({requests: [{method: 'POST', path: '/api/v1/hello'}]}) }
|
147
|
+
let(:request_body) { encode({ requests: [{ method: 'POST', path: '/api/v1/hello' }] }) }
|
145
148
|
it { expect(response.status).to eq(200) }
|
146
|
-
it { expect(response.body).to eq(encode([{success: 'world'}])) }
|
149
|
+
it { expect(response.body).to eq(encode([{ success: 'world' }])) }
|
147
150
|
end
|
148
151
|
|
149
152
|
context 'with a body' do
|
150
|
-
let(:request_body) { encode({requests: [{method: 'POST', path: '/api/v1/status', body: {id: 856}}]}) }
|
153
|
+
let(:request_body) { encode({ requests: [{ method: 'POST', path: '/api/v1/status', body: { id: 856 } }] }) }
|
151
154
|
it { expect(response.status).to eq(200) }
|
152
|
-
it { expect(response.body).to eq(encode([{success: 'status 856'}])) }
|
155
|
+
it { expect(response.body).to eq(encode([{ success: 'status 856' }])) }
|
153
156
|
end
|
154
157
|
end
|
155
158
|
|
156
159
|
describe 'POST' do
|
157
160
|
context 'with multiple requests' do
|
158
|
-
let(:request_body) { encode({requests: [{method: 'POST', path: '/api/v1/hello'}, {method: 'GET', path: '/api/v1/user/856'}]}) }
|
161
|
+
let(:request_body) { encode({ requests: [{ method: 'POST', path: '/api/v1/hello' }, { method: 'GET', path: '/api/v1/user/856' }] }) }
|
159
162
|
it { expect(response.status).to eq(200) }
|
160
163
|
it { expect(decode(response.body).size).to eq(2) }
|
161
164
|
end
|
@@ -173,7 +176,7 @@ RSpec.describe Grape::Batch::Base do
|
|
173
176
|
|
174
177
|
describe '.configure' do
|
175
178
|
before do
|
176
|
-
allow(
|
179
|
+
allow(Grape::Batch).to receive(:configuration) do
|
177
180
|
config = Grape::Batch::Configuration.new
|
178
181
|
config.path = '/custom_path'
|
179
182
|
config.limit = 15
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grape-batch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lionel Oto
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2015-02-17 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
@@ -54,6 +54,20 @@ dependencies:
|
|
54
54
|
- - ">="
|
55
55
|
- !ruby/object:Gem::Version
|
56
56
|
version: '1.0'
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: activesupport
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 4.1.0
|
64
|
+
type: :runtime
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 4.1.0
|
57
71
|
- !ruby/object:Gem::Dependency
|
58
72
|
name: bundler
|
59
73
|
requirement: !ruby/object:Gem::Requirement
|
@@ -132,6 +146,7 @@ files:
|
|
132
146
|
- lib/grape/batch.rb
|
133
147
|
- lib/grape/batch/configuration.rb
|
134
148
|
- lib/grape/batch/errors.rb
|
149
|
+
- lib/grape/batch/log_subscriber.rb
|
135
150
|
- lib/grape/batch/parser.rb
|
136
151
|
- lib/grape/batch/response.rb
|
137
152
|
- lib/grape/batch/version.rb
|