abbyy-cloud 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +7 -13
  3. data/.travis.yml +1 -1
  4. data/CHANGELOG.md +11 -0
  5. data/README.md +29 -0
  6. data/abbyy-cloud.gemspec +4 -2
  7. data/lib/abbyy/cloud.rb +14 -26
  8. data/lib/abbyy/cloud/connection.rb +3 -10
  9. data/lib/abbyy/cloud/exceptions/response_error.rb +1 -1
  10. data/lib/abbyy/cloud/models/cost_type.rb +14 -0
  11. data/lib/abbyy/cloud/models/currency.rb +14 -0
  12. data/lib/abbyy/cloud/models/direction.rb +0 -1
  13. data/lib/abbyy/cloud/models/discount.rb +2 -1
  14. data/lib/abbyy/cloud/models/discount_type.rb +19 -0
  15. data/lib/abbyy/cloud/models/engine.rb +0 -2
  16. data/lib/abbyy/cloud/models/error.rb +0 -1
  17. data/lib/abbyy/cloud/models/file_format.rb +19 -0
  18. data/lib/abbyy/cloud/models/file_info.rb +33 -0
  19. data/lib/abbyy/cloud/models/file_link.rb +18 -0
  20. data/lib/abbyy/cloud/models/file_read_error.rb +20 -0
  21. data/lib/abbyy/cloud/models/file_read_status.rb +14 -0
  22. data/lib/abbyy/cloud/models/file_reference.rb +11 -0
  23. data/lib/abbyy/cloud/models/full_order.rb +46 -0
  24. data/lib/abbyy/cloud/models/label.rb +14 -0
  25. data/lib/abbyy/cloud/models/ocr_mode.rb +19 -0
  26. data/lib/abbyy/cloud/models/ocr_quality.rb +18 -0
  27. data/lib/abbyy/cloud/models/ocr_settings.rb +16 -0
  28. data/lib/abbyy/cloud/models/ocr_statistics.rb +13 -0
  29. data/lib/abbyy/cloud/models/ocr_warning.rb +16 -0
  30. data/lib/abbyy/cloud/models/ocr_warning_type.rb +29 -0
  31. data/lib/abbyy/cloud/models/order_statistics.rb +15 -0
  32. data/lib/abbyy/cloud/models/payment_type.rb +14 -0
  33. data/lib/abbyy/cloud/models/price.rb +1 -1
  34. data/lib/abbyy/cloud/models/reading_status.rb +20 -0
  35. data/lib/abbyy/cloud/models/source_segment.rb +0 -1
  36. data/lib/abbyy/cloud/models/source_tag.rb +0 -1
  37. data/lib/abbyy/cloud/models/status.rb +25 -0
  38. data/lib/abbyy/cloud/models/text_statistics.rb +15 -0
  39. data/lib/abbyy/cloud/models/transfer_data.rb +0 -3
  40. data/lib/abbyy/cloud/models/translation.rb +0 -2
  41. data/lib/abbyy/cloud/models/translation_link.rb +18 -0
  42. data/lib/abbyy/cloud/models/translation_segment.rb +0 -1
  43. data/lib/abbyy/cloud/models/translation_type.rb +14 -0
  44. data/lib/abbyy/cloud/models/unit_price.rb +3 -0
  45. data/lib/abbyy/cloud/models/unit_type.rb +14 -0
  46. data/lib/abbyy/cloud/namespaces/files.rb +19 -0
  47. data/lib/abbyy/cloud/namespaces/orders.rb +17 -0
  48. data/lib/abbyy/cloud/namespaces/prices.rb +1 -1
  49. data/lib/abbyy/cloud/operations/base.rb +56 -19
  50. data/lib/abbyy/cloud/operations/download_file.rb +12 -0
  51. data/lib/abbyy/cloud/operations/engines.rb +1 -1
  52. data/lib/abbyy/cloud/operations/find_order.rb +13 -0
  53. data/lib/abbyy/cloud/operations/prices.rb +1 -1
  54. data/lib/abbyy/cloud/operations/submit_order.rb +32 -0
  55. data/lib/abbyy/cloud/operations/translate.rb +1 -1
  56. data/lib/abbyy/cloud/operations/translate_segments.rb +1 -1
  57. data/lib/abbyy/cloud/operations/upload_file.rb +22 -0
  58. data/lib/abbyy/cloud/types.rb +8 -17
  59. data/spec/feature/abbyy/files_download_spec.rb +57 -0
  60. data/spec/feature/abbyy/files_upload_spec.rb +129 -0
  61. data/spec/feature/abbyy/orders_create_spec.rb +245 -0
  62. data/spec/feature/abbyy/orders_find_spec.rb +116 -0
  63. data/spec/{abbyy → unit/abbyy}/cloud/connection_spec.rb +1 -1
  64. data/spec/{abbyy → unit/abbyy}/cloud/models/direction_spec.rb +0 -0
  65. data/spec/{abbyy → unit/abbyy}/cloud/models/discount_spec.rb +0 -0
  66. data/spec/{abbyy → unit/abbyy}/cloud/models/engine_spec.rb +0 -0
  67. data/spec/{abbyy → unit/abbyy}/cloud/models/error_spec.rb +0 -0
  68. data/spec/unit/abbyy/cloud/models/file_link_spec.rb +51 -0
  69. data/spec/unit/abbyy/cloud/models/file_reference_spec.rb +24 -0
  70. data/spec/unit/abbyy/cloud/models/full_order_spec.rb +192 -0
  71. data/spec/{abbyy → unit/abbyy}/cloud/models/locale_spec.rb +0 -0
  72. data/spec/unit/abbyy/cloud/models/order_statistics_spec.rb +15 -0
  73. data/spec/{abbyy → unit/abbyy}/cloud/models/price_spec.rb +0 -8
  74. data/spec/{abbyy → unit/abbyy}/cloud/models/source_segment_spec.rb +0 -0
  75. data/spec/{abbyy → unit/abbyy}/cloud/models/source_tag_spec.rb +0 -0
  76. data/spec/{abbyy → unit/abbyy}/cloud/models/transfer_data_spec.rb +0 -0
  77. data/spec/unit/abbyy/cloud/models/translation_link_spec.rb +66 -0
  78. data/spec/{abbyy → unit/abbyy}/cloud/models/translation_segment_spec.rb +0 -0
  79. data/spec/{abbyy → unit/abbyy}/cloud/models/translation_spec.rb +0 -0
  80. data/spec/{abbyy → unit/abbyy}/cloud/models/unit_price_spec.rb +0 -0
  81. data/spec/{abbyy → unit/abbyy}/cloud/response_error_spec.rb +0 -0
  82. data/spec/{abbyy → unit/abbyy}/cloud/settings_spec.rb +0 -0
  83. data/spec/{abbyy → unit/abbyy}/cloud_spec.rb +0 -0
  84. metadata +114 -37
@@ -21,7 +21,7 @@ class ABBYY::Cloud
21
21
  items = Operations::Prices.new(settings)
22
22
  .call(skip: skip, take: take_now, **opts)
23
23
 
24
- return items if (items.count < take_now) || take_later.zero?
24
+ return items if (items.count < take_now) || take_later&.zero?
25
25
  items + details(skip: skip_later, take: take_later, **opts)
26
26
  end
27
27
  end
@@ -6,7 +6,6 @@
6
6
  class ABBYY::Cloud
7
7
  module Operations
8
8
  class Base
9
- # Helpers to specify concrete operations
10
9
  class << self
11
10
  include Forwardable
12
11
 
@@ -15,14 +14,18 @@ class ABBYY::Cloud
15
14
  @http_method || "Post"
16
15
  end
17
16
 
18
- def path(value = nil)
19
- value ? @path = value : @path
17
+ def path(&block)
18
+ block ? @path = block : @path
20
19
  end
21
20
 
22
21
  def link(value = nil)
23
22
  value ? @link = value : @link
24
23
  end
25
24
 
25
+ def request_type(value = nil)
26
+ value ? (@request_type = value) : (@request_type || :json)
27
+ end
28
+
26
29
  def request_body(struct = nil, &block)
27
30
  provide_struct :@request_body, struct, &block
28
31
  end
@@ -31,6 +34,10 @@ class ABBYY::Cloud
31
34
  provide_struct :@request_query, struct, &block
32
35
  end
33
36
 
37
+ def response_type(value = nil)
38
+ value ? (@response_type = value) : (@response_type || :json)
39
+ end
40
+
34
41
  def response_body(struct = nil, &block)
35
42
  provide_struct :@response_body, struct, &block
36
43
  end
@@ -54,25 +61,39 @@ class ABBYY::Cloud
54
61
 
55
62
  def_delegators :settings, :connection
56
63
  def_delegators :"self.class",
57
- :link,
58
- :http_method,
59
- :path,
60
- :request_body,
61
- :request_query,
62
- :response_body
63
-
64
- def call(**data)
65
- body = prepare_request_body(data)
66
- query = prepare_request_query(data)
67
- res = connection.call(http_method, path, body: body, query: query)
68
-
69
- handle_response_body(res)
64
+ :link, :http_method, :path, :request_type, :request_body,
65
+ :request_query, :response_type, :response_body
66
+
67
+ def call(file = nil, **data)
68
+ mash = Hashie::Mash.new(data)
69
+ url = mash.instance_eval(&path)
70
+ multipart = prepare_multipart(file, **data) if file
71
+ body = prepare_request_body(multipart, data)
72
+ query = prepare_request_query(data)
73
+ headers = prepare_request_headers(multipart)
74
+ res = connection.call http_method, url, body: body,
75
+ query: query,
76
+ headers: headers
77
+ handle_response_body res
70
78
  end
71
79
 
72
80
  private
73
81
 
74
- def prepare_request_body(data)
75
- request_body[data]
82
+ def prepare_multipart(file, content_type: "plain/text", **)
83
+ name = SecureRandom.hex(10)
84
+ data = file.read if file.respond_to?(:read)
85
+ path = Pathname.new(file.path).basename if file.respond_to?(:path)
86
+ part = Part.new(name, data || file, path || "file")
87
+ part.content_type = content_type
88
+ MultipartBody.new [part]
89
+ end
90
+
91
+ def prepare_request_body(multipart, data)
92
+ case request_type
93
+ when :json then JSON(request_body[data].to_h)
94
+ when :file then multipart.to_s << "\r\n"
95
+ else data
96
+ end
76
97
  rescue => error
77
98
  raise ArgumentError.new(link, data, error.message)
78
99
  end
@@ -83,8 +104,24 @@ class ABBYY::Cloud
83
104
  raise ArgumentError.new(link, data, error.message)
84
105
  end
85
106
 
107
+ def prepare_request_headers(multipart)
108
+ headers = {}
109
+ headers["accept"] = "application/json" if response_type == :json
110
+ headers["content-type"] = \
111
+ case request_type
112
+ when :json then "application/json"
113
+ when :file then "multipart/form-data; boundary=#{multipart.boundary}"
114
+ end
115
+
116
+ headers
117
+ end
118
+
86
119
  def handle_response_body(data)
87
- response_body[data]
120
+ case response_type
121
+ when :json then response_body[JSON.parse(data)]
122
+ when :file then StringIO.new(data)
123
+ else data
124
+ end
88
125
  rescue => error
89
126
  raise TypeError.new(link, data, error.message)
90
127
  end
@@ -0,0 +1,12 @@
1
+ require_relative "base"
2
+
3
+ class ABBYY::Cloud
4
+ module Operations
5
+ class DownloadFile < Base
6
+ link "https://api.abbyy.cloud/swagger/ui/index#!/File/File_DownloadFile"
7
+ path { "v0/file/#{id}/#{token}" }
8
+ http_method "get"
9
+ response_type :file
10
+ end
11
+ end
12
+ end
@@ -6,7 +6,7 @@ class ABBYY::Cloud
6
6
  # rubocop: disable Metrics/LineLength
7
7
  link "https://api.abbyy.cloud/swagger/ui/index#!/MachineTranslation/MachineTranslation_Engines"
8
8
  # rubocop: enable Metrics/LineLength
9
- path "v0/mt/engines"
9
+ path { "v0/mt/engines" }
10
10
  http_method "get"
11
11
 
12
12
  response_body Types::Array.member(Types::Engine)
@@ -0,0 +1,13 @@
1
+ require_relative "base"
2
+
3
+ class ABBYY::Cloud
4
+ module Operations
5
+ class FindOrder < Base
6
+ link "https://api.abbyy.cloud/swagger/ui/index#!/Order/Order_GetOrder"
7
+ path { "v0/order/#{id}" }
8
+ http_method "get"
9
+
10
+ response_body Models::FullOrder
11
+ end
12
+ end
13
+ end
@@ -6,7 +6,7 @@ class ABBYY::Cloud
6
6
  # rubocop: disable Metrics/LineLength
7
7
  link "https://api.abbyy.cloud/swagger/ui/index#!/Prices/Prices_GetAccountPrices"
8
8
  # rubocop: enable Metrics/LineLength
9
- path "v0/prices/details"
9
+ path { "v0/prices/details" }
10
10
  http_method "get"
11
11
 
12
12
  request_query do
@@ -0,0 +1,32 @@
1
+ require_relative "base"
2
+
3
+ class ABBYY::Cloud
4
+ module Operations
5
+ class SubmitOrder < Base
6
+ # rubocop: disable Metrics/LineLength
7
+ link "https://api.abbyy.cloud/swagger/ui/index#!/Order/Order_SubmitOrder_0"
8
+ # rubocop: enable Metrics/LineLength
9
+ path { "v0/order" }
10
+ http_method "post"
11
+
12
+ request_body do
13
+ attribute :type, Types::TranslationType
14
+ attribute :email, Types::Strict::String.optional
15
+ attribute :contact_culture, Types::Strict::String.optional
16
+ attribute :contact_utc_offset, Types::Coercible::String.optional
17
+ attribute :mt_engine, Types::Strict::String
18
+ attribute :approval_required, Types::Form::Bool
19
+ attribute :is_manual_estimation, Types::Form::Bool
20
+ attribute :cost_type, Types::CostType
21
+ attribute :unit_type, Types::UnitType
22
+ attribute :currency, Types::Currency
23
+ attribute :from, Types::Locale
24
+ attribute :label, Types::Label.optional
25
+ attribute :to, Types::FilledArray.member(Types::Locale)
26
+ attribute :files, Types::FilledArray.member(Types::FileReference)
27
+ end
28
+
29
+ response_body Models::FullOrder
30
+ end
31
+ end
32
+ end
@@ -4,7 +4,7 @@ class ABBYY::Cloud
4
4
  module Operations
5
5
  class Translate < Base
6
6
  link "https://api.abbyy.cloud/swagger/ui/index#!/Order/Order_Translate"
7
- path "v0/order/mt/sync"
7
+ path { "v0/order/mt/sync" }
8
8
  http_method "post"
9
9
 
10
10
  request_body do
@@ -6,7 +6,7 @@ class ABBYY::Cloud
6
6
  # rubocop: disable Metrics/LineLength
7
7
  link "https://api.abbyy.cloud/swagger/ui/index#!/Order/Order_TranslateSegments"
8
8
  # rubocop: enable Metrics/LineLength
9
- path "v1/order/mt/sync"
9
+ path { "v1/order/mt/sync" }
10
10
  http_method "post"
11
11
 
12
12
  request_body do
@@ -0,0 +1,22 @@
1
+ require_relative "base"
2
+
3
+ class ABBYY::Cloud
4
+ module Operations
5
+ class UploadFile < Base
6
+ link "https://api.abbyy.cloud/swagger/ui/index#!/File/File_UploadFile"
7
+ path { "v0/file" }
8
+ http_method "post"
9
+
10
+ request_type :file
11
+
12
+ request_query do
13
+ attribute :quality, Types::OcrQuality.optional
14
+ attribute :synthesisMode, Types::OcrMode.optional
15
+ attribute :exportFormat, Types::FileFormat.optional
16
+ attribute :languages, Types::Array.member(Types::Locale).optional
17
+ end
18
+
19
+ response_body Types::Array.member(Types::FileInfo)
20
+ end
21
+ end
22
+ end
@@ -1,26 +1,17 @@
1
1
  module ABBYY::Cloud::Types
2
2
  include Dry::Types.module
3
3
 
4
- VERSIONS = [0].freeze
5
- UNIT_TYPES = %w(Chars Words Pages Documents).freeze
6
- DISCOUNT_TYPES = \
7
- %w(TMTextMatch TMTaggedTextMatch TMHalfContextMatch TMFullContextMatch)
8
- .freeze
9
-
10
4
  # Gem-specific primitive types
11
- Version = Coercible::Int.constrained(included_in: VERSIONS)
12
- UnitType = Strict::String.constrained(included_in: UNIT_TYPES)
13
- DiscountType = Strict::String.constrained(included_in: DISCOUNT_TYPES)
14
- Currency = Strict::String.constrained(format: /\A[A-Z]{3}\z/)
5
+ FilledArray = Array.constrained(min_size: 1)
15
6
 
16
- # Registers new coercible type from a struct class
17
- def self.register_type(struct, as: nil, constructor: :new)
18
- type_name = Inflecto.underscore(as || struct.name.split("::").last)
19
- definition = Dry::Types::Definition.new(struct).constructor do |value|
7
+ # Registers new coercible type
8
+ def self.register_type(klass, as: nil, constructor: :new)
9
+ type_name = Inflecto.underscore(as || klass.name.split("::").last)
10
+ definition = Dry::Types::Definition.new(klass).constructor do |value|
20
11
  case value
21
- when nil then raise
22
- when struct then value
23
- else struct.send(constructor, value)
12
+ when nil then raise
13
+ when klass then value
14
+ else klass.send(constructor, value)
24
15
  end
25
16
  end
26
17
 
@@ -0,0 +1,57 @@
1
+ RSpec.describe "files.download" do
2
+ subject { client.files.download(options) }
3
+
4
+ let(:client) { ABBYY::Cloud.new(id: "foo", token: "bar") }
5
+ let(:path) { "https://api.abbyy.cloud/v0/file/baz/qux" }
6
+ let(:options) { { id: "baz", token: "qux" } }
7
+
8
+ before do
9
+ stub_request(:get, path)
10
+ .with(basic_auth: %w(foo bar))
11
+ .to_return(status: 200, body: "Hello world!")
12
+ end
13
+
14
+ context "with valid params" do
15
+ it "sends a request to ABBYY Cloud API" do
16
+ subject
17
+ expect(a_request(:get, path)).to have_been_made
18
+ end
19
+
20
+ it "returns an IO" do
21
+ expect(subject).to be_kind_of StringIO
22
+ expect(subject.read).to eq "Hello world!"
23
+ end
24
+ end
25
+
26
+ context "without id:" do
27
+ before { options.delete(:id) }
28
+
29
+ it "raises ArgumentError without sending a request" do
30
+ expect { subject }.to raise_error(ArgumentError)
31
+ expect(a_request(:any, //)).not_to have_been_made
32
+ end
33
+ end
34
+
35
+ context "without token:" do
36
+ before { options.delete(:token) }
37
+
38
+ it "raises ArgumentError without sending a request" do
39
+ expect { subject }.to raise_error(ArgumentError)
40
+ expect(a_request(:any, //)).not_to have_been_made
41
+ end
42
+ end
43
+
44
+ context "when API responded with error:" do
45
+ before do
46
+ stub_request(:get, path)
47
+ .with(basic_auth: %w(foo bar))
48
+ .to_return status: 500,
49
+ headers: { "Content-Type" => "application/json" },
50
+ body: "Server error"
51
+ end
52
+
53
+ it "raises ResponseError" do
54
+ expect { subject }.to raise_error(ABBYY::Cloud::ResponseError)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,129 @@
1
+ RSpec.describe "files.upload" do
2
+ subject do
3
+ client.files.upload file,
4
+ synthesis_mode: synthesis_mode,
5
+ export_format: export_format,
6
+ languages: languages,
7
+ quality: quality
8
+ end
9
+
10
+ let(:client) { ABBYY::Cloud.new(id: "foo", token: "bar") }
11
+ let(:path) { %r{https://api.abbyy.cloud/v0/file} }
12
+ let(:file) { StringIO.new("Hello world!") }
13
+ let(:export_format) { "Doc" }
14
+ let(:quality) { "Fast" }
15
+ let(:synthesis_mode) { "EditableCopy" }
16
+ let(:languages) { %w(en) }
17
+ let(:response) do
18
+ {
19
+ id: "foo",
20
+ token: "bar",
21
+ name: "data.xml",
22
+ mime: "application/xml",
23
+ is_recognizable: true,
24
+ expected_languages: %w(en),
25
+ ocr_settings: {
26
+ format: "Doc",
27
+ quality: "Fast",
28
+ mode: "EditableCopy"
29
+ },
30
+ statistics: {
31
+ language: "en",
32
+ chars_count: 0,
33
+ words_count: 0,
34
+ pages_count: 0
35
+ },
36
+ ocr_statistics: {
37
+ exported_pages: 0,
38
+ total_characters: 0,
39
+ uncertain_characters: 0,
40
+ success_part: 0
41
+ },
42
+ created: "2016-09-05T08:33:03.567Z",
43
+ processed: "2016-09-05T08:33:03.567Z",
44
+ deleted: "2016-09-05T08:33:03.567Z",
45
+ reading_progress: 0,
46
+ reading_status: "New",
47
+ ocr_warnings: [
48
+ {
49
+ warning_type: "BrightnessIsTooHigh",
50
+ page_number: 0,
51
+ dpi: 0,
52
+ language_count: 0,
53
+ full_warning_message: "Uwaga!"
54
+ }
55
+ ],
56
+ error: "Unsupported",
57
+ is_deleted: true
58
+ }
59
+ end
60
+
61
+ before do
62
+ stub_request(:post, path)
63
+ .with(basic_auth: %w(foo bar))
64
+ .to_return status: 200,
65
+ headers: { "Content-Type" => "application/json" },
66
+ body: JSON([response])
67
+ end
68
+
69
+ context "with valid params" do
70
+ it "sends a request to ABBYY Cloud API" do
71
+ subject
72
+ expect(a_request(:post, path)).to have_been_made
73
+ end
74
+
75
+ it "returns array of file info" do
76
+ expect(subject.first).to be_kind_of ABBYY::Cloud::Models::FileInfo
77
+ end
78
+ end
79
+
80
+ context "with wrong export_format:" do
81
+ let(:export_format) { "Unknown" }
82
+
83
+ it "raises ArgumentError without sending a request" do
84
+ expect { subject }.to raise_error(ABBYY::Cloud::ArgumentError)
85
+ expect(a_request(:any, //)).not_to have_been_made
86
+ end
87
+ end
88
+
89
+ context "with wrong quality:" do
90
+ let(:quality) { "Unknown" }
91
+
92
+ it "raises ArgumentError without sending a request" do
93
+ expect { subject }.to raise_error(ABBYY::Cloud::ArgumentError)
94
+ expect(a_request(:any, //)).not_to have_been_made
95
+ end
96
+ end
97
+
98
+ context "with wrong synthesis_mode:" do
99
+ let(:synthesis_mode) { "Unknown" }
100
+
101
+ it "raises ArgumentError without sending a request" do
102
+ expect { subject }.to raise_error(ABBYY::Cloud::ArgumentError)
103
+ expect(a_request(:any, //)).not_to have_been_made
104
+ end
105
+ end
106
+
107
+ context "with wrong languages:" do
108
+ let(:languages) { "foo" }
109
+
110
+ it "raises ArgumentError without sending a request" do
111
+ expect { subject }.to raise_error(ABBYY::Cloud::ArgumentError)
112
+ expect(a_request(:any, //)).not_to have_been_made
113
+ end
114
+ end
115
+
116
+ context "when API responded with error:" do
117
+ before do
118
+ stub_request(:post, path)
119
+ .with(basic_auth: %w(foo bar))
120
+ .to_return status: 500,
121
+ headers: { "Content-Type" => "application/json" },
122
+ body: "Server error"
123
+ end
124
+
125
+ it "raises ResponseError" do
126
+ expect { subject }.to raise_error(ABBYY::Cloud::ResponseError)
127
+ end
128
+ end
129
+ end