gini-api 0.9.9 → 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f482221a91704b834f741e93bc66ed8d387f673b
4
- data.tar.gz: 7cabfa0aa95303038767caf0058ac02e6684d9cc
3
+ metadata.gz: d81c78fc0e2e77fb6989c207d25f942b4ad950c9
4
+ data.tar.gz: 6d10b26a592452d43ae292620eae7c0d5e41584f
5
5
  SHA512:
6
- metadata.gz: 0819422ce704bd75d4ef24b46ee3566a980453e79819f541312cd6c08a49477f5178c2e7da36eda92c9c541b6dd4b2370ca2e57013c3974ae45477bf7d385c4e
7
- data.tar.gz: b068d3ef18cf8daa7c9514c85c0fd69004e83f65c0f84f6b8bd4189a0589cfcf17b43ce7ab53366ffec32c201eee5b41d979343a9d767e88b4790a8f13144321
6
+ metadata.gz: e792d768ffb748685f4fed691745bc66e3f060dd330d4bf403ffcaea5d3383b265cda4ac3de9e6de84642f026a07a0b974cbefd83c5729069772b9bf395eeb8f
7
+ data.tar.gz: 14a55d19162f5312de7abe9bac09b4ef6ddea5d51b97278d20ef8b853644c6bd020ba476b8033e179bb9cec57e7b8003755c50e23092f28bd49cfa3d976a76f8
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gini-api (0.9.9)
4
+ gini-api (0.9.10)
5
5
  logger
6
6
  oauth2
7
7
 
data/README.md CHANGED
@@ -53,6 +53,11 @@ api.login(username: 'me@example.com', password: 'secret')
53
53
  ```ruby
54
54
  doc = api.upload('/tmp/my_doc.pdf')
55
55
  # => Gini::Api::Document
56
+ fh = File.open('/tmp/scan.pdf', 'r')
57
+ doc = api.upload(fh)
58
+ # => Gini::Api::Document
59
+ doc = api.upload('tmp/my_receipt.pdf', doctype_hint='Receipt')
60
+ # => Gini::Api::Document
56
61
  doc.id
57
62
  # => "123456789-abcd-ef12-000000000000"
58
63
  doc.progress
@@ -148,12 +153,23 @@ doc.extractions.undefinedLabel
148
153
  # => nil
149
154
  ```
150
155
 
156
+ #### Incubator
157
+
158
+ The incubator Gini API is unstable and subject of change. It allows early access to immature features which are still in research or under development. Please refer to the official API documentation for further details.
159
+
160
+ ```ruby
161
+ doc.extractions(incubator=true).amountLiters
162
+ # => {:amountLiters=>{:entity=>"volume", :value=>"25.34:l", :box=>{:top=>1774.0, :left=>674.0, :width=>376.0, :height=>48.0, :page=>1}}
163
+ doc.extractions(incubator=true)[:amountLiters]
164
+ # => "25.34:l"
165
+ ```
166
+
151
167
  ### Submitting feedback
152
168
 
153
169
  ```ruby
154
170
  doc.extractions.bic = 'XXXXXXXX'
155
171
  # => 'XXXXXXXX'
156
- doc.extractions.bic = { value: 'XXXXXXXX', :box=>{:top=>2176.0, :left=>2000.0, :width=>173.0, :height=>50.0, :page=>1 }
172
+ doc.extractions.bic = { value: 'XXXXXXXX', :box=>{ :top=>2176.0, :left=>2000.0, :width=>173.0, :height=>50.0, :page=>1 } }
157
173
  # => { value: 'XXXXXXXX', box: { top: 2176.0, left: 2000.0, width: 173.0, height: 50.0, page: 1 }
158
174
  doc.extractions.unknownLabel = 'XXXXXXXX'
159
175
  # => raises Gini::Api::DocumentError
@@ -186,6 +202,19 @@ end
186
202
 
187
203
  Please keep in mind that the amount of availabe instance variables differs depending on the error situation. For details please refer to the ```Gini::Api::Error``` class.
188
204
 
205
+ ## Submitting error reports
206
+
207
+ If the processing result for a document was not satisfactory (e.g. extractions where empty or incorrect), you can create an error report for a document. This allows Gini to analyze and correct the problem that was found. The owner of this document must agree that Gini can use this document for debugging and error analysis. The returned errorId can be used to refer to the reported error towards the Gini support.
208
+
209
+ ```ruby
210
+ doc = api.get('123456789-abcd-ef12-000000000000')
211
+ # => Gini::Api::Document
212
+ doc.report_error()
213
+ # => deadbeef-dead-dead-beef-deadbeeeeef
214
+ doc.report_error(summary='Just a short summary of the problem', description='A detailed description...')
215
+ # => deadbeef-dead-dead-beef-deadbeeeeef
216
+ ```
217
+
189
218
  ## Developers
190
219
 
191
220
  ### Getting started
@@ -25,6 +25,7 @@ module Gini
25
25
  # @option options [String] :api_uri API URI (https://api.gini.net)
26
26
  # @option options [String] :api_version API version to use (v1)
27
27
  # @option options [Logger] :log logger object to use (initialized with STDOUT otherwise)
28
+ # @option options [String] :user_agent HTTP User-Agent (gini-api-ruby/VERSION (Faraday vFaraday::VERSION))
28
29
  #
29
30
  # @example
30
31
  # api = Gini::Api::Client.new(
@@ -42,6 +43,7 @@ module Gini
42
43
  upload_timeout: 90,
43
44
  processing_timeout: 180,
44
45
  log: Logger.new(STDOUT),
46
+ user_agent: "gini-api-ruby/#{VERSION} (Faraday v#{Faraday::VERSION})"
45
47
  }.merge(options)
46
48
 
47
49
  # Ensure mandatory keys are set
@@ -77,6 +79,9 @@ module Gini
77
79
  OAuth2::Response.register_parser(:gini_xml, [version_header(:xml)[:accept]]) do |body|
78
80
  MultiXml.parse(body) rescue body
79
81
  end
82
+ OAuth2::Response.register_parser(:gini_incubator, [version_header(:json, :incubator)[:accept]]) do |body|
83
+ MultiJson.load(body, symbolize_keys: true) rescue body
84
+ end
80
85
  end
81
86
 
82
87
  # Acquire OAuth2 token and popolate @oauth (instance of Gini::Api::OAuth.new)
@@ -106,11 +111,12 @@ module Gini
106
111
  # Version accept header based on @api_version
107
112
  #
108
113
  # @param [Symbol, String] type Expected response type (:xml, :json)
114
+ # @param [Symbol, String] version API version (:v1, :incubator)
109
115
  #
110
116
  # @return [Hash] Return accept header or empty hash
111
117
  #
112
- def version_header(type = @api_type)
113
- { accept: "application/vnd.gini.#{@api_version}+#{type}" }
118
+ def version_header(type = @api_type, version = @api_version)
119
+ { accept: "application/vnd.gini.#{version}+#{type}" }
114
120
  end
115
121
 
116
122
  # Request wrapper that sets URI and accept header
@@ -142,21 +148,24 @@ module Gini
142
148
 
143
149
  # Upload a document
144
150
  #
145
- # @param [String] file path of the document to upload
151
+ # @param [String] file path or open filehandle of the document to upload
152
+ # @param [String] doctype_hint Document type hint to optimize results or get incubator results
146
153
  # @param [Float] interval Interval to poll progress
147
154
  #
148
155
  # @return [Gini::Api::Document] Return Gini::Api::Document object for uploaded document
149
156
  #
150
157
  # @example Upload and wait for completion
151
158
  # doc = api.upload('/tmp/myfile.pdf')
159
+ # @example Upload with doctype hint
160
+ # doc = api.upload('/tmp/myfile.pdf', doctype_hint='Receipt')
152
161
  # @example Upload and monitor progress
153
162
  # doc = api.upload('/tmp/myfile.pdf') { |d| puts "Progress: #{d.progress}" }
154
163
  #
155
- def upload(file, interval = 0.5, &block)
164
+ def upload(file, doctype_hint = nil, interval = 0.5, &block)
156
165
  duration = Hash.new(0)
157
166
 
158
167
  # Document upload
159
- duration[:upload], response = upload_document(file)
168
+ duration[:upload], response = upload_document(file, doctype_hint)
160
169
 
161
170
  # Start polling (0.5s) when document has been uploaded successfully
162
171
  if response.status == 201
@@ -305,16 +314,18 @@ module Gini
305
314
 
306
315
  # Helper to upload document
307
316
  #
308
- # @param [String] file location of document to be uploaded
317
+ # @param [String] file location of document or open filehandle to be uploaded
318
+ # @param [String] doctype_hint Document type hint to optimize results or get incubator results
309
319
  #
310
320
  # @return [Faraday::Response] Response object from upload
311
321
  #
312
- def upload_document(file)
322
+ def upload_document(file, doctype_hint)
313
323
  response = nil
314
324
  duration = Benchmark.realtime do
315
325
  response = upload_connection.post do |req|
316
326
  req.options[:timeout] = @upload_timeout
317
- req.url 'documents/'
327
+ req.url 'documents'
328
+ req.params[:doctype] = doctype_hint if doctype_hint
318
329
  req.headers['Content-Type'] = 'multipart/form-data'
319
330
  req.headers['Authorization'] = "Bearer #{@token.token}"
320
331
  req.headers.merge!(version_header)
@@ -101,10 +101,12 @@ module Gini
101
101
 
102
102
  # Initialize extractions from @_links and return Gini::Api::Extractions object
103
103
  #
104
+ # @param [Boolean] incubator Return experimental extractions
105
+ #
104
106
  # @return [Gini::Api::Document::Extractions] Return Gini::Api::Document::Extractions object for uploaded document
105
107
  #
106
- def extractions
107
- @extractions ||= Gini::Api::Document::Extractions.new(@api, @_links[:extractions])
108
+ def extractions(incubator = false)
109
+ @extractions ||= Gini::Api::Document::Extractions.new(@api, @_links[:extractions], incubator)
108
110
  end
109
111
 
110
112
  # Initialize layout from @_links[:layout] and return Gini::Api::Layout object
@@ -145,6 +147,28 @@ module Gini
145
147
  )
146
148
  end
147
149
  end
150
+
151
+ # Submit error report on document
152
+ #
153
+ # @param [String] summary Short summary on the error found
154
+ # @param [String] description More detailed description of the error found
155
+ #
156
+ # @return [String] Error ID retured from API
157
+ #
158
+ def report_error(summary = nil, description = nil)
159
+ response = @api.request(
160
+ :post,
161
+ "#{@_links[:document]}/errorreport",
162
+ params: { summary: summary, description: description }
163
+ )
164
+ unless response.status == 200
165
+ raise Gini::Api::DocumentError.new(
166
+ "Failed to submit error report for document #{@id} (code=#{response.status})",
167
+ response
168
+ )
169
+ end
170
+ response.parsed[:errorId]
171
+ end
148
172
  end
149
173
  end
150
174
  end
@@ -11,9 +11,17 @@ module Gini
11
11
  #
12
12
  # @param [Gini::Api::Client] api Gini::Api::Client object
13
13
  # @param [String] location Document URL
14
- def initialize(api, location)
15
- @api = api
16
- @location = location
14
+ # @param [Boolean] incubator Return experimental extractions
15
+ #
16
+ def initialize(api, location, incubator = false)
17
+ @api = api
18
+ @location = location
19
+ @incubator = incubator
20
+ @req_opts = {}
21
+
22
+ if incubator
23
+ @req_opts = { headers: @api.version_header(:json, :incubator) }
24
+ end
17
25
 
18
26
  update
19
27
  end
@@ -21,7 +29,7 @@ module Gini
21
29
  # Populate instance variables from fetched extractions
22
30
  #
23
31
  def update
24
- response = @api.request(:get, @location)
32
+ response = @api.request(:get, @location, @req_opts)
25
33
 
26
34
  unless response.status == 200
27
35
  raise Gini::Api::DocumentError.new(
@@ -33,6 +41,14 @@ module Gini
33
41
  # Entire response
34
42
  @raw = response.parsed
35
43
 
44
+ # raise exception if parsing failed
45
+ if response.parsed.nil?
46
+ raise Gini::Api::DocumentError.new(
47
+ "Failed to parse extractions from #{@location}",
48
+ response
49
+ )
50
+ end
51
+
36
52
  response.parsed[:extractions].each do |k,v|
37
53
  instance_variable_set("@#{k}", v)
38
54
  end
@@ -28,6 +28,7 @@ module Gini
28
28
  token_url: '/oauth/token',
29
29
  max_redirects: 0,
30
30
  raise_errors: true,
31
+ connection_opts: { headers: { user_agent: api.user_agent } }
31
32
  )
32
33
 
33
34
  # Verify opts. Prefered authorization methis is auth_code. If no auth_code is present a login from
@@ -1,6 +1,6 @@
1
1
  module Gini
2
2
  module Api
3
3
  # Package version
4
- VERSION = "0.9.9"
4
+ VERSION = '0.9.10'
5
5
  end
6
6
  end
@@ -58,6 +58,11 @@ describe Gini::Api::Client do
58
58
  include(:gini_xml)
59
59
  end
60
60
 
61
+ it do
62
+ expect(OAuth2::Response::PARSERS.keys).to \
63
+ include(:gini_incubator)
64
+ end
65
+
61
66
  end
62
67
 
63
68
  describe '#login' do
@@ -140,6 +145,15 @@ describe Gini::Api::Client do
140
145
 
141
146
  end
142
147
 
148
+ context 'with incubator' do
149
+
150
+ it 'returns accept header with incubator version' do
151
+ expect(api.version_header(:json, :incubator)).to \
152
+ eql({ accept: 'application/vnd.gini.incubator+json' })
153
+ end
154
+
155
+ end
156
+
143
157
  end
144
158
 
145
159
  context 'being logged in' do
@@ -223,6 +237,24 @@ describe Gini::Api::Client do
223
237
 
224
238
  end
225
239
 
240
+ context 'return JSON on incubator request' do
241
+
242
+ let(:header) { 'application/vnd.gini.incubator+json' }
243
+ let(:body) {
244
+ {
245
+ a: 1,
246
+ b: 2
247
+ }.to_json
248
+ }
249
+
250
+ it do
251
+ expect(api.token).to \
252
+ receive(:get).and_return(OAuth2::Response.new(response))
253
+ expect(api.request(:get, '/dummy').parsed).to be_a Hash
254
+ end
255
+
256
+ end
257
+
226
258
  context 'set custom accept header' do
227
259
 
228
260
  let(:header) { 'application/octet-stream' }
@@ -33,8 +33,9 @@ describe Gini::Api::Document::Extractions do
33
33
  )
34
34
  end
35
35
 
36
- let(:header) { 'application/vnd.gini.v1+json' }
37
- let(:location) { 'https://api.gini.net/document/aaa-bbb-ccc/extractions' }
36
+ let(:incubator) { false }
37
+ let(:header) { 'application/vnd.gini.v1+json' }
38
+ let(:location) { 'https://api.gini.net/document/aaa-bbb-ccc/extractions' }
38
39
  let(:response) do
39
40
  double('Response',
40
41
  status: 200,
@@ -68,7 +69,7 @@ describe Gini::Api::Document::Extractions do
68
69
  )
69
70
  end
70
71
 
71
- subject(:extractions) { Gini::Api::Document::Extractions.new(api, location) }
72
+ subject(:extractions) { Gini::Api::Document::Extractions.new(api, location, incubator) }
72
73
 
73
74
  it { should respond_to(:update) }
74
75
  it { should respond_to(:[]) }
@@ -98,6 +99,44 @@ describe Gini::Api::Document::Extractions do
98
99
 
99
100
  end
100
101
 
102
+ context 'failed to parse response' do
103
+
104
+ let(:response) do
105
+ double('Response',
106
+ status: 200,
107
+ headers: {
108
+ 'content-type' => 'vnd/gini.not.supported+json'
109
+ },
110
+ env: {},
111
+ body: {}.to_json
112
+ )
113
+ end
114
+
115
+ it 'raises exception' do
116
+ expect { extractions.update }.to \
117
+ raise_error(Gini::Api::DocumentError, /Failed to parse extractions from #{location}/)
118
+ end
119
+
120
+ end
121
+
122
+ context 'with incubator=true' do
123
+
124
+ let(:extractions) { Gini::Api::Document::Extractions.new(api, location, incubator=true) }
125
+
126
+ it do
127
+ expect(api.token).to receive(:get).with(
128
+ location,
129
+ {
130
+ headers: {
131
+ accept: 'application/vnd.gini.incubator+json'
132
+ }
133
+ }
134
+ ).and_return(OAuth2::Response.new(response))
135
+ expect(extractions.instance_variable_get(:@req_opts)).to eql({ headers: { accept: 'application/vnd.gini.incubator+json' } })
136
+ end
137
+
138
+ end
139
+
101
140
  end
102
141
 
103
142
  describe '#[]' do
@@ -32,6 +32,7 @@ describe Gini::Api::Document do
32
32
  b: 2,
33
33
  progress: 'PENDING',
34
34
  _links: {
35
+ document: location,
35
36
  extractions: "#{location}/extractions",
36
37
  layout: "#{location}/layout",
37
38
  processed: "#{location}/processed"
@@ -50,6 +51,7 @@ describe Gini::Api::Document do
50
51
  it { should respond_to(:pages) }
51
52
  it { should respond_to(:completed?) }
52
53
  it { should respond_to(:successful?) }
54
+ it { should respond_to(:report_error) }
53
55
 
54
56
  it 'does accept duration' do
55
57
  expect(document.duration).to be_nil
@@ -368,4 +370,54 @@ describe Gini::Api::Document do
368
370
 
369
371
  end
370
372
 
373
+ describe '#report_error' do
374
+
375
+ let(:report_response) do
376
+ double('Response', {
377
+ status: 200,
378
+ headers: { 'content-type' => header },
379
+ body: {
380
+ message: "error was reported, please refer to the given error id",
381
+ errorId: "deadbeef-dead-dead-beef-deadbeeeeef",
382
+ }.to_json
383
+ })
384
+ end
385
+
386
+ context 'succeeds and returns error id' do
387
+
388
+ it do
389
+ expect(api.token).to receive(:post).with(
390
+ "#{location}/errorreport",
391
+ {
392
+ headers: { accept: header },
393
+ params: { summary: nil, description: nil }
394
+ }
395
+ ).and_return(OAuth2::Response.new(report_response))
396
+
397
+ expect(document.report_error()).to eql("deadbeef-dead-dead-beef-deadbeeeeef")
398
+ end
399
+
400
+ end
401
+
402
+ context 'fails and raises exception' do
403
+
404
+ let(:report_response) { double('Response', status: 404, body: {}, env: {}) }
405
+
406
+ it do
407
+ expect(api.token).to receive(:post).with(
408
+ "#{location}/errorreport",
409
+ {
410
+ headers: { accept: header },
411
+ params: { summary: nil, description: nil }
412
+ }
413
+ ).and_return(OAuth2::Response.new(report_response))
414
+
415
+ expect { document.report_error() }.to \
416
+ raise_error(Gini::Api::DocumentError, /Failed to submit error report for document/)
417
+ end
418
+
419
+ end
420
+
421
+ end
422
+
371
423
  end
@@ -18,7 +18,8 @@ describe Gini::Api::OAuth do
18
18
  client_id: 'cid',
19
19
  client_secret: 'sec',
20
20
  oauth_site: oauth_site,
21
- oauth_redirect: redirect
21
+ oauth_redirect: redirect,
22
+ user_agent: 'gini-api-ruby/spec'
22
23
  )
23
24
  end
24
25
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gini-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.9
4
+ version: 0.9.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Kerwin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-09 00:00:00.000000000 Z
11
+ date: 2015-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oauth2