tinify 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +15 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +29 -0
- data/LICENSE +21 -0
- data/README.md +30 -0
- data/Rakefile +8 -0
- data/lib/data/cacert.pem +3894 -0
- data/lib/tinify.rb +45 -0
- data/lib/tinify/client.rb +52 -0
- data/lib/tinify/error.rb +35 -0
- data/lib/tinify/result.rb +32 -0
- data/lib/tinify/source.rb +40 -0
- data/lib/tinify/version.rb +3 -0
- data/test/examples/dummy.png +0 -0
- data/test/helper.rb +28 -0
- data/test/tinify_client_test.rb +193 -0
- data/test/tinify_result_test.rb +44 -0
- data/test/tinify_source_test.rb +123 -0
- data/test/tinify_test.rb +96 -0
- data/tinify.gemspec +27 -0
- metadata +141 -0
data/lib/tinify.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require "tinify/version"
|
2
|
+
require "tinify/error"
|
3
|
+
|
4
|
+
require "tinify/client"
|
5
|
+
require "tinify/result"
|
6
|
+
require "tinify/source"
|
7
|
+
|
8
|
+
require "thread"
|
9
|
+
|
10
|
+
module Tinify
|
11
|
+
class << self
|
12
|
+
attr_accessor :key
|
13
|
+
attr_accessor :app_identifier
|
14
|
+
attr_accessor :compression_count
|
15
|
+
|
16
|
+
def from_file(path)
|
17
|
+
Source.from_file(path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def from_buffer(string)
|
21
|
+
Source.from_buffer(string)
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate!
|
25
|
+
client.request(:post, "/shrink")
|
26
|
+
rescue ClientError
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset!
|
31
|
+
@key = nil
|
32
|
+
@client = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
@@mutex = Mutex.new
|
36
|
+
|
37
|
+
def client
|
38
|
+
raise AccountError.new("Provide an API key with Tinify.key = ...") unless @key
|
39
|
+
return @client if @client
|
40
|
+
@@mutex.synchronize do
|
41
|
+
@client ||= Client.new(@key, @app_identifier).freeze
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require "httpclient"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module Tinify
|
5
|
+
class Client
|
6
|
+
API_ENDPOINT = "https://api.tinify.com".freeze
|
7
|
+
USER_AGENT = "Tinify/#{VERSION} Ruby/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}".freeze
|
8
|
+
CA_BUNDLE = File.expand_path("../../data/cacert.pem", __FILE__).freeze
|
9
|
+
|
10
|
+
def initialize(key, app_identifier = nil)
|
11
|
+
@client = HTTPClient.new
|
12
|
+
@client.base_url = API_ENDPOINT
|
13
|
+
@client.default_header = { user_agent: [USER_AGENT, app_identifier].compact.join(" ") }
|
14
|
+
|
15
|
+
@client.force_basic_auth = true
|
16
|
+
@client.set_auth("/", "api", key)
|
17
|
+
|
18
|
+
@client.ssl_config.clear_cert_store
|
19
|
+
@client.ssl_config.add_trust_ca(CA_BUNDLE)
|
20
|
+
end
|
21
|
+
|
22
|
+
def request(method, url, body = nil, header = {})
|
23
|
+
if Hash === body && !body.empty?
|
24
|
+
body = JSON.generate(body)
|
25
|
+
header["Content-Type"] = "application/json"
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
response = @client.request(method, url, body: body, header: header)
|
30
|
+
rescue HTTPClient::TimeoutError => err
|
31
|
+
raise ConnectionError.new("Timeout while connecting")
|
32
|
+
rescue StandardError => err
|
33
|
+
raise ConnectionError.new("Error while connecting: #{err.message}")
|
34
|
+
end
|
35
|
+
|
36
|
+
if count = response.headers["Compression-Count"]
|
37
|
+
Tinify.compression_count = count.to_i
|
38
|
+
end
|
39
|
+
|
40
|
+
if response.ok?
|
41
|
+
response
|
42
|
+
else
|
43
|
+
details = begin
|
44
|
+
JSON.parse(response.body)
|
45
|
+
rescue StandardError => err
|
46
|
+
{ "message" => "Error while parsing response: #{err.message}", "error" => "ParseError" }
|
47
|
+
end
|
48
|
+
raise Error.create(details["message"], details["error"], response.status)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/tinify/error.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Tinify
|
2
|
+
class Error < StandardError
|
3
|
+
class << self
|
4
|
+
def create(message, type, status)
|
5
|
+
klass = case status
|
6
|
+
when 401, 429 then AccountError
|
7
|
+
when 400..499 then ClientError
|
8
|
+
when 500..599 then ServerError
|
9
|
+
else Error
|
10
|
+
end
|
11
|
+
|
12
|
+
message = "No message was provided" if message.to_s.empty?
|
13
|
+
klass.new(message, type, status)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(message, type = self.class.name.split("::").last, status = nil)
|
18
|
+
@message, @type, @status = message, type, status
|
19
|
+
end
|
20
|
+
|
21
|
+
def message
|
22
|
+
if @status
|
23
|
+
"#{@message} (HTTP #{@status}/#{@type})"
|
24
|
+
else
|
25
|
+
"#{@message}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
alias_method :to_s, :message
|
29
|
+
end
|
30
|
+
|
31
|
+
class AccountError < Error; end
|
32
|
+
class ClientError < Error; end
|
33
|
+
class ServerError < Error; end
|
34
|
+
class ConnectionError < Error; end
|
35
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Tinify
|
2
|
+
class Result
|
3
|
+
attr_reader :data
|
4
|
+
|
5
|
+
def initialize(meta, data)
|
6
|
+
@meta, @data = meta.freeze, data.freeze
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_file(path)
|
10
|
+
File.open(path, "wb") { |file| file.write(data) }
|
11
|
+
end
|
12
|
+
|
13
|
+
alias_method :to_buffer, :data
|
14
|
+
|
15
|
+
def width
|
16
|
+
@meta["Image-Width"].to_i
|
17
|
+
end
|
18
|
+
|
19
|
+
def height
|
20
|
+
@meta["Image-Height"].to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
def size
|
24
|
+
@meta["Content-Length"].to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def media_type
|
28
|
+
@meta["Content-Type"]
|
29
|
+
end
|
30
|
+
alias_method :content_type, :media_type
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Tinify
|
2
|
+
class Source
|
3
|
+
class << self
|
4
|
+
def from_file(path)
|
5
|
+
from_buffer(File.open(path, "rb") { |file| file.read })
|
6
|
+
end
|
7
|
+
|
8
|
+
def from_buffer(string)
|
9
|
+
response = Tinify.client.request(:post, "/shrink", string)
|
10
|
+
new(response.headers["Location"]).freeze
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(url, commands = {})
|
15
|
+
@url, @commands = url.freeze, commands.freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
def resize(options)
|
19
|
+
self.class.new(@url, @commands.merge(resize: options))
|
20
|
+
end
|
21
|
+
|
22
|
+
def store(options)
|
23
|
+
response = Tinify.client.request(:post, @url, @commands.merge(store: options))
|
24
|
+
Result.new(response.headers, response.body).freeze
|
25
|
+
end
|
26
|
+
|
27
|
+
def result
|
28
|
+
response = Tinify.client.request(:get, @url, @commands)
|
29
|
+
Result.new(response.headers, response.body).freeze
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_file(path)
|
33
|
+
result.to_file(path)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_buffer
|
37
|
+
result.to_buffer
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
File without changes
|
data/test/helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "webmock/minitest"
|
3
|
+
|
4
|
+
require "tinify"
|
5
|
+
|
6
|
+
module TestHelpers
|
7
|
+
def before_setup
|
8
|
+
Tinify.reset!
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def assert_raise_with_message(message)
|
13
|
+
err = nil
|
14
|
+
begin
|
15
|
+
yield
|
16
|
+
rescue => err
|
17
|
+
end
|
18
|
+
if message.is_a?(Regexp)
|
19
|
+
assert_match(message, err.message)
|
20
|
+
else
|
21
|
+
assert_equal(message, err.message)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class MiniTest::Spec
|
27
|
+
include TestHelpers
|
28
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require File.expand_path("../helper", __FILE__)
|
2
|
+
|
3
|
+
describe Tinify::Client do
|
4
|
+
subject do
|
5
|
+
Tinify::Client.new("key")
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "request" do
|
9
|
+
describe "when valid" do
|
10
|
+
before do
|
11
|
+
stub_request(:get, "https://api:key@api.tinify.com").to_return(
|
12
|
+
status: 200,
|
13
|
+
headers: { "Compression-Count" => "12" }
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should issue request" do
|
18
|
+
subject.request(:get, "/")
|
19
|
+
assert_requested :get, "https://api:key@api.tinify.com",
|
20
|
+
headers: { "Authorization" => "Basic " + ["api:key"].pack("m").chomp }
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should issue request with json body" do
|
24
|
+
subject.request(:get, "/", { hello: "world" })
|
25
|
+
assert_requested :get, "https://api:key@api.tinify.com",
|
26
|
+
headers: { "Content-Type" => "application/json" },
|
27
|
+
body: '{"hello":"world"}'
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should issue request with user agent" do
|
31
|
+
subject.request(:get, "/")
|
32
|
+
assert_requested :get, "https://api:key@api.tinify.com",
|
33
|
+
headers: { "User-Agent" => Tinify::Client::USER_AGENT }
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should update compression count" do
|
37
|
+
subject.request(:get, "/")
|
38
|
+
assert_equal 12, Tinify.compression_count
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "with app id" do
|
42
|
+
subject do
|
43
|
+
Tinify::Client.new("key", "TestApp/0.1")
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should issue request with user agent" do
|
47
|
+
subject.request(:get, "/")
|
48
|
+
assert_requested :get, "https://api:key@api.tinify.com",
|
49
|
+
headers: { "User-Agent" => "#{Tinify::Client::USER_AGENT} TestApp/0.1" }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "with timeout" do
|
55
|
+
before do
|
56
|
+
stub_request(:get, "https://api:key@api.tinify.com").to_timeout
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should raise connection error" do
|
60
|
+
assert_raises Tinify::ConnectionError do
|
61
|
+
subject.request(:get, "/")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should raise error with message" do
|
66
|
+
assert_raise_with_message "Timeout while connecting" do
|
67
|
+
subject.request(:get, "/")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "with socket error" do
|
73
|
+
before do
|
74
|
+
stub_request(:get, "https://api:key@api.tinify.com").to_raise(SocketError.new("nodename nor servname provided"))
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should raise error" do
|
78
|
+
assert_raises Tinify::ConnectionError do
|
79
|
+
subject.request(:get, "/")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should raise error with message" do
|
84
|
+
assert_raise_with_message "Error while connecting: nodename nor servname provided" do
|
85
|
+
subject.request(:get, "/")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "with unexpected error" do
|
91
|
+
before do
|
92
|
+
stub_request(:get, "https://api:key@api.tinify.com").to_raise("some error")
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should raise error" do
|
96
|
+
assert_raises Tinify::ConnectionError do
|
97
|
+
subject.request(:get, "/")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should raise error with message" do
|
102
|
+
assert_raise_with_message "Error while connecting: some error" do
|
103
|
+
subject.request(:get, "/")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "with server error" do
|
109
|
+
before do
|
110
|
+
stub_request(:get, "https://api:key@api.tinify.com").to_return(
|
111
|
+
status: 584,
|
112
|
+
body: '{"error":"InternalServerError","message":"Oops!"}'
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should raise server error" do
|
117
|
+
assert_raises Tinify::ServerError do
|
118
|
+
subject.request(:get, "/")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should raise error with message" do
|
123
|
+
assert_raise_with_message "Oops! (HTTP 584/InternalServerError)" do
|
124
|
+
subject.request(:get, "/")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
describe "with bad server response" do
|
131
|
+
before do
|
132
|
+
stub_request(:get, "https://api:key@api.tinify.com").to_return(
|
133
|
+
status: 543,
|
134
|
+
body: '<!-- this is not json -->'
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should raise server error" do
|
139
|
+
assert_raises Tinify::ServerError do
|
140
|
+
subject.request(:get, "/")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should raise error with message" do
|
145
|
+
assert_raise_with_message %r{Error while parsing response: .*unexpected token at '<!-- this is not json -->' \(HTTP 543/ParseError\)} do
|
146
|
+
subject.request(:get, "/")
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "with client error" do
|
152
|
+
before do
|
153
|
+
stub_request(:get, "https://api:key@api.tinify.com").to_return(
|
154
|
+
status: 492,
|
155
|
+
body: '{"error":"BadRequest","message":"Oops!"}'
|
156
|
+
)
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should raise client error" do
|
160
|
+
assert_raises Tinify::ClientError do
|
161
|
+
subject.request(:get, "/")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should raise error with message" do
|
166
|
+
assert_raise_with_message "Oops! (HTTP 492/BadRequest)" do
|
167
|
+
subject.request(:get, "/")
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "with bad credentials" do
|
173
|
+
before do
|
174
|
+
stub_request(:get, "https://api:key@api.tinify.com").to_return(
|
175
|
+
status: 401,
|
176
|
+
body: '{"error":"Unauthorized","message":"Oops!"}'
|
177
|
+
)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should raise account error" do
|
181
|
+
assert_raises Tinify::AccountError do
|
182
|
+
subject.request(:get, "/")
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should raise error with message" do
|
187
|
+
assert_raise_with_message "Oops! (HTTP 401/Unauthorized)" do
|
188
|
+
subject.request(:get, "/")
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path("../helper", __FILE__)
|
2
|
+
|
3
|
+
describe Tinify::Result do
|
4
|
+
describe "with meta and data" do
|
5
|
+
subject do
|
6
|
+
Tinify::Result.new({
|
7
|
+
"Image-Width" => "100",
|
8
|
+
"Image-Height" => "60",
|
9
|
+
"Content-Length" => "450",
|
10
|
+
"Content-Type" => "image/png",
|
11
|
+
}, "image data")
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "width" do
|
15
|
+
it "should return image width" do
|
16
|
+
assert_equal 100, subject.width
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "height" do
|
21
|
+
it "should return image height" do
|
22
|
+
assert_equal 60, subject.height
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "size" do
|
27
|
+
it "should return content length" do
|
28
|
+
assert_equal 450, subject.size
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "content_type" do
|
33
|
+
it "should return mime type" do
|
34
|
+
assert_equal "image/png", subject.content_type
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "data" do
|
39
|
+
it "should return image data" do
|
40
|
+
assert_equal "image data", subject.data
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|