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.
- checksums.yaml +5 -5
- data/.gitignore +1 -1
- data/.rspec +1 -0
- data/.travis.yml +19 -5
- data/CHANGELOG.md +12 -30
- data/README.md +149 -72
- data/Rakefile +1 -1
- data/UPGRADE_NOTES.md +36 -0
- data/lib/uploadcare.rb +16 -22
- data/lib/uploadcare/api.rb +3 -1
- data/lib/uploadcare/api/file_list_api.rb +15 -4
- data/lib/uploadcare/api/file_storage_api.rb +34 -0
- data/lib/uploadcare/api/group_list_api.rb +13 -4
- data/lib/uploadcare/api/raw_api.rb +10 -16
- data/lib/uploadcare/api/uploading_api.rb +45 -84
- data/lib/uploadcare/api/uploading_api/upload_params.rb +72 -0
- data/lib/uploadcare/api/validators/file_list_options_validator.rb +73 -0
- data/lib/uploadcare/api/validators/group_list_options_validator.rb +49 -0
- data/lib/uploadcare/resources/file_list.rb +6 -33
- data/lib/uploadcare/resources/group_list.rb +6 -23
- data/lib/uploadcare/resources/resource_list.rb +83 -0
- data/lib/uploadcare/rest/connections/api_connection.rb +25 -3
- data/lib/uploadcare/rest/connections/upload_connection.rb +3 -2
- data/lib/uploadcare/version.rb +1 -1
- data/spec/api/file_list_api_spec.rb +95 -0
- data/spec/api/file_storage_api_spec.rb +88 -0
- data/spec/api/group_list_api_spec.rb +59 -0
- data/spec/api/raw_api_spec.rb +12 -12
- data/spec/api/uploading_api/upload_params_spec.rb +99 -0
- data/spec/api/uploading_api_spec.rb +59 -0
- data/spec/resources/file_list_spec.rb +13 -52
- data/spec/resources/file_spec.rb +6 -3
- data/spec/resources/group_list_spec.rb +15 -20
- data/spec/rest/api_connection_spec.rb +1 -1
- data/spec/shared/resource_list.rb +188 -0
- data/spec/spec_helper.rb +11 -1
- data/spec/uploadcare_spec.rb +9 -32
- data/spec/utils/parser_spec.rb +34 -36
- data/uploadcare-ruby.gemspec +7 -6
- metadata +52 -37
- data/lib/uploadcare/utils/user_agent.rb +0 -44
- data/spec/uploading/uploading_multiple_spec.rb +0 -43
- data/spec/uploading/uploading_spec.rb +0 -40
- 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 <
|
6
|
-
|
7
|
-
@api = api
|
6
|
+
class FileList < ResourceList
|
7
|
+
private
|
8
8
|
|
9
|
-
|
10
|
-
|
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 <
|
6
|
-
|
7
|
-
@api = api
|
6
|
+
class GroupList < ResourceList
|
7
|
+
private
|
8
8
|
|
9
|
-
|
10
|
-
|
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'] =
|
13
|
+
frd.headers['User-Agent'] = Uploadcare::user_agent(options)
|
14
14
|
|
15
15
|
# order of middleware matters!
|
16
16
|
|
17
|
-
#
|
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 :
|
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.
|
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
|
data/lib/uploadcare/version.rb
CHANGED
@@ -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
|