tinify 0.9.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 +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
|