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.
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Tinify
2
+ VERSION = "0.9.0"
3
+ end
File without changes
@@ -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