uploadcare-ruby 1.2.2 → 2.0.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.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +19 -5
  5. data/CHANGELOG.md +12 -30
  6. data/README.md +149 -72
  7. data/Rakefile +1 -1
  8. data/UPGRADE_NOTES.md +36 -0
  9. data/lib/uploadcare.rb +16 -22
  10. data/lib/uploadcare/api.rb +3 -1
  11. data/lib/uploadcare/api/file_list_api.rb +15 -4
  12. data/lib/uploadcare/api/file_storage_api.rb +34 -0
  13. data/lib/uploadcare/api/group_list_api.rb +13 -4
  14. data/lib/uploadcare/api/raw_api.rb +10 -16
  15. data/lib/uploadcare/api/uploading_api.rb +45 -84
  16. data/lib/uploadcare/api/uploading_api/upload_params.rb +72 -0
  17. data/lib/uploadcare/api/validators/file_list_options_validator.rb +73 -0
  18. data/lib/uploadcare/api/validators/group_list_options_validator.rb +49 -0
  19. data/lib/uploadcare/resources/file_list.rb +6 -33
  20. data/lib/uploadcare/resources/group_list.rb +6 -23
  21. data/lib/uploadcare/resources/resource_list.rb +83 -0
  22. data/lib/uploadcare/rest/connections/api_connection.rb +25 -3
  23. data/lib/uploadcare/rest/connections/upload_connection.rb +3 -2
  24. data/lib/uploadcare/version.rb +1 -1
  25. data/spec/api/file_list_api_spec.rb +95 -0
  26. data/spec/api/file_storage_api_spec.rb +88 -0
  27. data/spec/api/group_list_api_spec.rb +59 -0
  28. data/spec/api/raw_api_spec.rb +12 -12
  29. data/spec/api/uploading_api/upload_params_spec.rb +99 -0
  30. data/spec/api/uploading_api_spec.rb +59 -0
  31. data/spec/resources/file_list_spec.rb +13 -52
  32. data/spec/resources/file_spec.rb +6 -3
  33. data/spec/resources/group_list_spec.rb +15 -20
  34. data/spec/rest/api_connection_spec.rb +1 -1
  35. data/spec/shared/resource_list.rb +188 -0
  36. data/spec/spec_helper.rb +11 -1
  37. data/spec/uploadcare_spec.rb +9 -32
  38. data/spec/utils/parser_spec.rb +34 -36
  39. data/uploadcare-ruby.gemspec +7 -6
  40. metadata +52 -37
  41. data/lib/uploadcare/utils/user_agent.rb +0 -44
  42. data/spec/uploading/uploading_multiple_spec.rb +0 -43
  43. data/spec/uploading/uploading_spec.rb +0 -40
  44. data/spec/utils/user_agent_spec.rb +0 -46
@@ -0,0 +1,49 @@
1
+ module Uploadcare
2
+ module Validators
3
+
4
+ class GroupListOptionsValidator
5
+ SUPPORTED_KEYS = [:from, :ordering, :limit]
6
+
7
+ def initialize(options)
8
+ @options = options
9
+ end
10
+
11
+ def validate
12
+ check_for_unsupported_keys(@options)
13
+
14
+ validate_limit(@options[:limit])
15
+ validate_ordering(@options[:ordering])
16
+ validate_from(@options[:from])
17
+ end
18
+
19
+ private
20
+
21
+ def check_for_unsupported_keys(options)
22
+ unsupported_keys = options.keys.reject{|k,_| SUPPORTED_KEYS.include?(k)}
23
+ error("Unknown options: #{unsupported_keys}") if unsupported_keys.any?
24
+ end
25
+
26
+ def validate_ordering(ordering)
27
+ return if !ordering || ordering =~ /^-?datetime_created$/
28
+ error("Unknown value for :ordering option: #{ordering.inspect}")
29
+ end
30
+
31
+ def validate_from(from)
32
+ return if from.nil? || from.to_s =~ /^\d{4}-\d{2}-\d{2}T\d{2}.*/
33
+ error(":from value should be a DateTime or an iso8601 string, "\
34
+ "#{from.inspect} given")
35
+ end
36
+
37
+ def validate_limit(limit)
38
+ return if limit.nil? || (limit.is_a?(Integer) && (1..1000).include?(limit))
39
+ error(":limit should be a positive integer from 1 to 1000, "\
40
+ "#{limit.inspect} given")
41
+ end
42
+
43
+ def error(message)
44
+ raise ArgumentError, message
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -1,41 +1,14 @@
1
1
  require 'ostruct'
2
+ require_relative 'resource_list'
2
3
 
3
4
  module Uploadcare
4
5
  class Api
5
- class FileList < OpenStruct
6
- def initialize api, data
7
- @api = api
6
+ class FileList < ResourceList
7
+ private
8
8
 
9
- unless data["results"].nil?
10
- data["results"].map! do |file|
11
- Uploadcare::Api::File.new @api, file["uuid"], file
12
- end
13
- end
14
-
15
- super data
16
- end
17
-
18
- # Array-like behavior
19
- def [] index
20
- results[index] if defined?(:results)
21
- end
22
-
23
- def to_a
24
- results if defined?(:results)
25
- end
26
-
27
- # List navigation
28
- def next_page
29
- @api.file_list(page+1) unless send(:next).nil?
30
- end
31
-
32
- def go_to index
33
- @api.file_list(index) unless index > pages
34
- end
35
-
36
- def previous_page
37
- @api.file_list(page-1) unless previous.nil?
9
+ def to_resource(api, file_data)
10
+ Uploadcare::Api::File.new(api, file_data['uuid'], file_data)
38
11
  end
39
12
  end
40
13
  end
41
- end
14
+ end
@@ -1,31 +1,14 @@
1
1
  require 'ostruct'
2
+ require_relative 'resource_list'
2
3
 
3
4
  module Uploadcare
4
5
  class Api
5
- class GroupList < OpenStruct
6
- def initialize api, data
7
- @api = api
6
+ class GroupList < ResourceList
7
+ private
8
8
 
9
- unless data["results"].nil?
10
- data["results"].map! do |group|
11
- Uploadcare::Api::Group.new @api, group["id"], group
12
- end
13
- end
14
-
15
- super data
16
- end
17
-
18
- def [] index
19
- results[index] if defined?(:results)
20
- end
21
-
22
- def to_a
23
- results if defined?(:results)
24
- end
25
-
26
- def groups
27
- results if defined?(:results)
9
+ def to_resource(api, group_data)
10
+ Uploadcare::Api::Group.new api, group_data["id"], group_data
28
11
  end
29
12
  end
30
13
  end
31
- end
14
+ end
@@ -0,0 +1,83 @@
1
+ require 'ostruct'
2
+
3
+ module Uploadcare
4
+ class Api
5
+ class ResourceList
6
+ include Enumerable
7
+
8
+ extend Forwardable
9
+ def_delegator :@data, :meta
10
+ def_delegator :@data, :objects
11
+
12
+ attr_reader :options
13
+
14
+ def initialize(api, data, options)
15
+ @api = api
16
+ @data = build_data(data)
17
+ @options = options.dup.freeze
18
+ end
19
+
20
+ def [](index)
21
+ first(index + 1).last
22
+ end
23
+
24
+ def each
25
+ return enum_for(:each) unless block_given?
26
+
27
+ resource_enumerator.each { |object| yield object }
28
+
29
+ self
30
+ end
31
+
32
+ def total
33
+ meta['total']
34
+ end
35
+
36
+ def loaded
37
+ objects.size
38
+ end
39
+
40
+ def fully_loaded?
41
+ meta['next'].nil?
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :api
47
+
48
+ def build_data(data_hash)
49
+ OpenStruct.new(
50
+ meta: data_hash.reject{|k, _| k == 'results'}.freeze,
51
+ objects: data_hash['results'].map{|object| to_resource(api, object)}
52
+ )
53
+ end
54
+
55
+ def get_next_page
56
+ return nil if fully_loaded?
57
+
58
+ next_page = build_data(api.get(@data.meta['next']))
59
+
60
+ @data = OpenStruct.new(
61
+ meta: next_page.meta,
62
+ objects: objects + next_page.objects
63
+ )
64
+
65
+ next_page
66
+ end
67
+
68
+ def to_resource(*args)
69
+ raise NotImplementedError, 'You must define this method in a child class'
70
+ end
71
+
72
+ def resource_enumerator
73
+ Enumerator.new do |yielder|
74
+ objects.each { |obj| yielder << obj }
75
+
76
+ while next_page = get_next_page do
77
+ next_page.objects.each { |obj| yielder << obj }
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -10,14 +10,14 @@ module Uploadcare
10
10
  auth_strategy = Auth.strategy(options)
11
11
 
12
12
  frd.headers['Accept'] = "application/vnd.uploadcare-v#{options[:api_version]}+json"
13
- frd.headers['User-Agent'] = UserAgent.new.call(options)
13
+ frd.headers['User-Agent'] = Uploadcare::user_agent(options)
14
14
 
15
15
  # order of middleware matters!
16
16
 
17
- # url_encoded changes request body and thus should be before
17
+ # :json middleware changes request body and thus should be before
18
18
  # uploadcare_auth which uses it to sign requests when secure auth
19
19
  # strategy is being used
20
- frd.request :url_encoded
20
+ frd.request :json
21
21
  frd.request :uploadcare_auth, auth_strategy
22
22
 
23
23
  frd.response :uploadcare_raise_error
@@ -27,6 +27,28 @@ module Uploadcare
27
27
  frd.adapter :net_http # actually, default adapter, just to be clear
28
28
  end
29
29
  end
30
+
31
+ # NOTE: Faraday doesn't support body in DELETE requests, but
32
+ # Uploadcare API v0.5 requires clients to send array of UUIDs with
33
+ # `DELETE /files/storage/` requests.
34
+ #
35
+ # This is on override of the original Faraday::Connection#delete method.
36
+ #
37
+ # As for now, there are no DELETE requests in Uploadcare REST API
38
+ # which require params to be sent as URI params, so for simplicity
39
+ # this method send all params in a body.
40
+ def delete(url = nil, params = nil, headers = nil)
41
+ run_request(:delete, url, nil, headers) { |request|
42
+ # Original line from Faraday::Connection#delete method
43
+ # request.params.update(params) if params
44
+
45
+ # Monkey patch
46
+ request.body = params if params
47
+
48
+ yield(request) if block_given?
49
+ }
50
+ end
51
+
30
52
  end
31
53
  end
32
54
  end
@@ -9,11 +9,12 @@ module Uploadcare
9
9
  super ssl: { ca_path: ca_path }, url: options[:upload_url_base] do |frd|
10
10
  frd.request :multipart
11
11
  frd.request :url_encoded
12
- frd.adapter :net_http
13
- frd.headers['User-Agent'] = UserAgent.new.call(options)
12
+ frd.headers['User-Agent'] = Uploadcare::user_agent(options)
14
13
 
15
14
  frd.response :uploadcare_raise_error
16
15
  frd.response :uploadcare_parse_json
16
+
17
+ frd.adapter :net_http
17
18
  end
18
19
  end
19
20
  end
@@ -1,3 +1,3 @@
1
1
  module Uploadcare
2
- VERSION = "1.2.2"
2
+ VERSION = '2.0.0'.freeze
3
3
  end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ describe Uploadcare::FileListApi do
4
+ let(:api){ API }
5
+ subject{ api.file_list(limit: 1) }
6
+
7
+ before(:each){ allow(api).to receive(:get){ {'results' => []} } }
8
+
9
+ it 'returns a file list' do
10
+ expect( subject ).to be_a(Uploadcare::Api::FileList)
11
+ end
12
+
13
+ it 'stores options in a file list object' do
14
+ expect( subject.options ).to eq({limit: 1})
15
+ end
16
+
17
+ describe 'validation' do
18
+ it 'passes validation when no options given' do
19
+ expect{ api.file_list }.not_to raise_error
20
+ end
21
+
22
+ it "validates that options don't have unsupported keys" do
23
+ expect{ api.file_list(unknown: 1) }.to raise_error(ArgumentError)
24
+ end
25
+
26
+ it 'validates that :limit is an integer from 1 to 1000' do
27
+ expect{ api.file_list(limit: 1) }.not_to raise_error
28
+ expect{ api.file_list(limit: 395) }.not_to raise_error
29
+ expect{ api.file_list(limit: 1000) }.not_to raise_error
30
+
31
+ expect{ api.file_list(limit: 1.0) }.to raise_error(ArgumentError)
32
+ expect{ api.file_list(limit: -1) }.to raise_error(ArgumentError)
33
+ expect{ api.file_list(limit: 0) }.to raise_error(ArgumentError)
34
+ expect{ api.file_list(limit: 1001) }.to raise_error(ArgumentError)
35
+ expect{ api.file_list(limit: false) }.to raise_error(ArgumentError)
36
+ end
37
+
38
+ it 'validates that :stored is a boolean' do
39
+ expect{ api.file_list(stored: true) }.not_to raise_error
40
+ expect{ api.file_list(stored: false) }.not_to raise_error
41
+
42
+ expect{ api.file_list(stored: 'yes') }.to raise_error(ArgumentError)
43
+ end
44
+
45
+ it 'validates that :removed is a boolean' do
46
+ expect{ api.file_list(removed: true) }.not_to raise_error
47
+ expect{ api.file_list(removed: false) }.not_to raise_error
48
+
49
+ expect{ api.file_list(removed: 'yes') }.to raise_error(ArgumentError)
50
+ end
51
+
52
+ valid_ordering = %w{size -size datetime_uploaded -datetime_uploaded}
53
+ it "validates that :ordering is in [#{valid_ordering.join(', ')}]" do
54
+ valid_ordering.each do |valid_value|
55
+ expect{ api.file_list(ordering: valid_value) }.not_to raise_error
56
+ end
57
+
58
+ expect{ api.file_list(ordering: 'yes') }.to raise_error(ArgumentError)
59
+ end
60
+
61
+ describe 'from' do
62
+ context 'when ordering is "size" or "-size"' do
63
+ let(:opts){ {ordering: ['size', '-size'].sample} }
64
+
65
+ it 'validates that :from is a non-negative integer' do
66
+ valid = [0, 100500]
67
+ valid.each do |value|
68
+ expect{ api.file_list(opts.merge(from: value)) }.not_to raise_error
69
+ end
70
+
71
+ invalid = [-1, 200.0, "string", false]
72
+ invalid.each do |value|
73
+ expect{ api.file_list(opts.merge(from: value)) }.to raise_error(ArgumentError)
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'when ordering is "datetime_uploaded", "-datetime_uploaded" or nil' do
79
+ let(:opts){ {ordering: ['datetime_uploaded', '-datetime_uploaded', nil].sample} }
80
+
81
+ it 'validates that :from.to_s is a iso8601 string' do
82
+ valid = [DateTime.now, DateTime.now.iso8601, "2017-01-01T15"]
83
+ valid.each do |value|
84
+ expect{ api.file_list(opts.merge(from: value)) }.not_to raise_error
85
+ end
86
+
87
+ invalid = [Date.today, Time.now, DateTime.now.rfc2822, "2017-01-01", 123, false]
88
+ invalid.each do |value|
89
+ expect{ api.file_list(opts.merge(from: value)) }.to raise_error(ArgumentError)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe Uploadcare::FileStorageApi do
4
+ let(:api){ API }
5
+ let(:file){ api.file_list(limit: 1).first || api.upload(IMAGE_URL) }
6
+
7
+ shared_examples 'batch action on files' do
8
+ let(:uuids) { ["dc2c175d-a3b5-4435-b4f4-fae77bbe5597", "cea319aa-6e17-4172-8722-8dd7c459a523"] }
9
+ let(:files) { uuids.map { |uuid| Uploadcare::Api::File.new(api, uuid) } }
10
+ let(:api_endpoint) { "/files/storage/" }
11
+
12
+ it 'accepts array of uuids' do
13
+ expect(api).to receive(http_method)
14
+ expect { subject.call(uuids) }.not_to raise_error
15
+ end
16
+
17
+ it 'accepts enumerable containing Uploadcare::Api::File objects' do
18
+ expect(api).to receive(http_method)
19
+ expect { subject.call(files) }.not_to raise_error
20
+ end
21
+
22
+ it 'converts Uploadcare::Api::File-s to uuids' do
23
+ expect(api).to receive(http_method).with(api_endpoint, uuids)
24
+ subject.call(files)
25
+ end
26
+
27
+ context 'when input contains something other than UUIDs or Uploadcare::Api::File-s' do
28
+ it 'raises ArgumentError' do
29
+ ['not-an-uuid', nil, 1].each do |wrong_input_value|
30
+ expect { subject.call([wrong_input_value]) }.to raise_error(ArgumentError)
31
+ end
32
+ end
33
+ end
34
+
35
+ it 'raises ArgumentError if input size is grater then max batch size' do
36
+ stub_const("Uploadcare::FileStorageApi::MAX_BATCH_SIZE", 1)
37
+ expect { subject.call(uuids) }.to raise_error(ArgumentError)
38
+ end
39
+ end
40
+
41
+ describe '#store_files' do
42
+ let(:http_method) { :put }
43
+ subject { ->(objects) { api.store_files(objects) } }
44
+
45
+ it_behaves_like 'batch action on files'
46
+
47
+ describe 'integration test' do
48
+ before { file.tap { |f| wait_until_ready(f) }.delete if file.stored? }
49
+ subject(:store_files) { -> { api.store_files([file]) } }
50
+
51
+ it 'stores files with given uuids' do
52
+ is_expected.to change { file.load!.stored? }.from(false).to(true)
53
+ end
54
+
55
+ it 'returns the API response' do
56
+ expect(store_files.call).to include(
57
+ "status" => "ok",
58
+ "problems" => {},
59
+ "result" => [be_a(Hash)]
60
+ )
61
+ end
62
+ end
63
+ end
64
+
65
+ describe '#delete_files' do
66
+ let(:http_method) { :delete }
67
+ subject { ->(objects) { api.delete_files(objects) } }
68
+
69
+ it_behaves_like 'batch action on files'
70
+
71
+ describe 'integration test' do
72
+ before { file.store if file.deleted? }
73
+ subject(:delete_files) { -> { api.delete_files([file]) } }
74
+
75
+ it 'deletes files with given uuids' do
76
+ is_expected.to change { file.load!.deleted? }.from(false).to(true)
77
+ end
78
+
79
+ it 'returns the API response' do
80
+ expect(delete_files.call).to include(
81
+ "status" => "ok",
82
+ "problems" => {},
83
+ "result" => [be_a(Hash)]
84
+ )
85
+ end
86
+ end
87
+ end
88
+ end