swd 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/swd.rb ADDED
@@ -0,0 +1,16 @@
1
+ module SWD
2
+ def self.cache=(cache)
3
+ @@cache = cache
4
+ end
5
+ def self.cache
6
+ @@cache
7
+ end
8
+ def self.discover!(attributes = {})
9
+ Resource.new(attributes).discover!
10
+ end
11
+ end
12
+
13
+ require 'swd/cache'
14
+ require 'swd/exception'
15
+ require 'swd/resource'
16
+ require 'swd/response'
data/lib/swd/cache.rb ADDED
@@ -0,0 +1,8 @@
1
+ module SWD
2
+ class Cache
3
+ def self.fetch(cache_key, options = {})
4
+ yield
5
+ end
6
+ end
7
+ self.cache = Cache
8
+ end
@@ -0,0 +1,36 @@
1
+ module SWD
2
+ class Exception < StandardError; end
3
+
4
+ class HttpError < Exception
5
+ attr_accessor :status, :response
6
+ def initialize(status, message = nil, response = nil)
7
+ super message
8
+ @status = status
9
+ @response = response
10
+ end
11
+ end
12
+
13
+ class BadRequest < HttpError
14
+ def initialize(message = nil, response = nil)
15
+ super 400, message, response
16
+ end
17
+ end
18
+
19
+ class Unauthorized < HttpError
20
+ def initialize(message = nil, response = nil)
21
+ super 401, message, response
22
+ end
23
+ end
24
+
25
+ class Forbidden < HttpError
26
+ def initialize(message = nil, response = nil)
27
+ super 403, message, response
28
+ end
29
+ end
30
+
31
+ class NotFound < HttpError
32
+ def initialize(message = nil, response = nil)
33
+ super 404, message, response
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,79 @@
1
+ require 'digest/md5'
2
+ require 'json'
3
+ require 'httpclient'
4
+ require 'active_support/core_ext'
5
+ require 'attr_required'
6
+
7
+ module SWD
8
+ class Resource
9
+ include AttrRequired
10
+ attr_required :principal, :service, :host, :path
11
+
12
+ class ContentExpired < Exception; end
13
+
14
+ def initialize(attributes = {})
15
+ required_attributes.each do |key|
16
+ self.send "#{key}=", attributes[key]
17
+ end
18
+ @path ||= '/.well-known/simple-web-discovery'
19
+ @cache_options = attributes[:cache] || {}
20
+ attr_missing!
21
+ end
22
+
23
+ def discover!
24
+ SWD.cache.fetch(cache_key, @cache_options) do
25
+ handle_response do
26
+ HTTPClient.get endpoint.to_s
27
+ end
28
+ end
29
+ end
30
+
31
+ def endpoint
32
+ URI::HTTPS.build [nil, host, 443, path, {
33
+ :principal => principal,
34
+ :service => service
35
+ }.to_query, nil]
36
+ end
37
+
38
+ private
39
+
40
+ def handle_response
41
+ res = yield
42
+ case res.status
43
+ when 200
44
+ res = JSON.parse(res.body).with_indifferent_access
45
+ if redirect = res[:SWD_service_redirect]
46
+ redirect_to redirect[:location], redirect[:expires]
47
+ else
48
+ Response.new res
49
+ end
50
+ when 400
51
+ raise BadRequest.new(res)
52
+ when 401
53
+ raise Unauthorized.new(res)
54
+ when 403
55
+ raise Forbidden.new(res)
56
+ when 404
57
+ raise NotFound.new(res)
58
+ else
59
+ raise HttpError.new(res.code, res)
60
+ end
61
+ end
62
+
63
+ def redirect_to(location, expires)
64
+ uri = URI.parse(location)
65
+ @host, @path = uri.host, uri.path
66
+ raise ContentExpired if expires && expires.to_i < Time.now.utc.to_i
67
+ discover!
68
+ end
69
+
70
+ def cache_key
71
+ md5 = Digest::MD5.hexdigest [
72
+ principal,
73
+ service,
74
+ host
75
+ ].join(' ')
76
+ "swd:resource:#{md5}"
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,11 @@
1
+ module SWD
2
+ class Response
3
+ attr_accessor :locations, :location, :raw
4
+
5
+ def initialize(hash)
6
+ @locations = hash[:locations]
7
+ @location = @locations.first
8
+ @raw = hash
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,38 @@
1
+ require 'webmock/rspec'
2
+
3
+ module WebMockHelper
4
+ def mock_json(endpoint, response_file, options = {})
5
+ endpoint = endpoint.to_s
6
+ stub_request(:get, endpoint).with(
7
+ request_for(options)
8
+ ).to_return(
9
+ response_for(response_file, options)
10
+ )
11
+ yield
12
+ a_request(:get, endpoint).with(
13
+ request_for(options)
14
+ ).should have_been_made.once
15
+ end
16
+
17
+ private
18
+
19
+ def request_for(options = {})
20
+ request = {}
21
+ if options[:query]
22
+ request[:query] = options[:query]
23
+ end
24
+ request
25
+ end
26
+
27
+ def response_for(response_file, options = {})
28
+ response = {}
29
+ response[:body] = File.new(File.join(File.dirname(__FILE__), '../mock_json', "#{response_file}.json"))
30
+ if options[:status]
31
+ response[:status] = options[:status]
32
+ end
33
+ response
34
+ end
35
+ end
36
+
37
+ include WebMockHelper
38
+ WebMock.disable_net_connect!
File without changes
@@ -0,0 +1,5 @@
1
+ {
2
+ "SWD_service_redirect": {
3
+ "location": "https://swd.proseware.com/swd_server"
4
+ }
5
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "SWD_service_redirect": {
3
+ "location": "https://swd.proseware.com/swd_server",
4
+ "expires": 1300752001
5
+ }
6
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "locations": ["http://calendars.proseware.com/calendars/joseph"]
3
+ }
@@ -0,0 +1,4 @@
1
+ require 'rspec'
2
+ require 'swd'
3
+
4
+ require 'helpers/webmock_helper'
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe SWD::HttpError do
4
+ subject do
5
+ SWD::HttpError.new 400, 'Bad Request', HTTP::Message.new_response('')
6
+ end
7
+
8
+ its(:status) { should == 400 }
9
+ its(:message) { should == 'Bad Request' }
10
+ its(:response) { should be_a HTTP::Message }
11
+ end
12
+
13
+ describe SWD::BadRequest do
14
+ its(:status) { should == 400 }
15
+ its(:message) { should == 'SWD::BadRequest' }
16
+ end
17
+
18
+ describe SWD::Unauthorized do
19
+ its(:status) { should == 401 }
20
+ its(:message) { should == 'SWD::Unauthorized' }
21
+ end
22
+
23
+ describe SWD::Forbidden do
24
+ its(:status) { should == 403 }
25
+ its(:message) { should == 'SWD::Forbidden' }
26
+ end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ describe SWD::Resource do
4
+ subject { resource }
5
+ let(:resource) { SWD::Resource.new attributes }
6
+ let(:attributes) do
7
+ {
8
+ :principal => 'mailto:joe@example.com',
9
+ :service => 'urn:adatum.com:calendar',
10
+ :host => 'example.com'
11
+ }
12
+ end
13
+
14
+ its(:path) { should == '/.well-known/simple-web-discovery' }
15
+
16
+ [:principal, :service, :host].each do |key|
17
+ it "should require #{key}" do
18
+ expect do
19
+ SWD::Resource.new attributes.merge(key => nil)
20
+ end.should raise_error AttrRequired::AttrMissing
21
+ end
22
+ end
23
+
24
+ describe '#discover!' do
25
+ context 'when succeeded' do
26
+ it 'should return SWD::Response' do
27
+ mock_json resource.endpoint, 'success' do
28
+ res = resource.discover!
29
+ res.should be_a SWD::Response
30
+ res.locations == ['http://calendars.proseware.com/calendars/joseph']
31
+ res.location == 'http://calendars.proseware.com/calendars/joseph'
32
+ res.raw == {
33
+ 'locations' => ['http://calendars.proseware.com/calendars/joseph']
34
+ }
35
+ end
36
+ end
37
+ end
38
+
39
+ context 'when redirected' do
40
+ it 'should follow redirect' do
41
+ resource.should_receive(:redirect_to).with(
42
+ 'https://swd.proseware.com/swd_server', nil
43
+ )
44
+ mock_json resource.endpoint, 'redirect' do
45
+ resource.discover!
46
+ end
47
+ end
48
+
49
+ context 'when expired' do
50
+ it 'should return SWD::Response' do
51
+ mock_json resource.endpoint, 'redirect_expired' do
52
+ expect { res = resource.discover! }.should raise_error SWD::Resource::ContentExpired
53
+ end
54
+ end
55
+ end
56
+
57
+ context 'otherwise' do
58
+ it 'should return SWD::Response' do
59
+ mock_json resource.endpoint, 'redirect' do
60
+ mock_json 'https://swd.proseware.com/swd_server', 'success', :query => {
61
+ :principal => 'mailto:joe@example.com',
62
+ :service => 'urn:adatum.com:calendar'
63
+ } do
64
+ res = resource.discover!
65
+ res.should be_a SWD::Response
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ context 'when bad request' do
73
+ it 'should raise SWD::BadRequest' do
74
+ mock_json resource.endpoint, 'blank', :status => 400 do
75
+ expect { res = resource.discover! }.should raise_error SWD::BadRequest
76
+ end
77
+ end
78
+ end
79
+
80
+ context 'when unauthorized' do
81
+ it 'should raise SWD::Unauthorized' do
82
+ mock_json resource.endpoint, 'blank', :status => 401 do
83
+ expect { res = resource.discover! }.should raise_error SWD::Unauthorized
84
+ end
85
+ end
86
+ end
87
+
88
+ context 'when forbidden' do
89
+ it 'should raise SWD::Forbidden' do
90
+ mock_json resource.endpoint, 'blank', :status => 403 do
91
+ expect { res = resource.discover! }.should raise_error SWD::Forbidden
92
+ end
93
+ end
94
+ end
95
+
96
+ context 'when not found' do
97
+ it 'should raise SWD::NotFound' do
98
+ mock_json resource.endpoint, 'blank', :status => 404 do
99
+ expect { res = resource.discover! }.should raise_error SWD::NotFound
100
+ end
101
+ end
102
+ end
103
+
104
+ context 'when other error happened' do
105
+ it 'should raise SWD::HttpError' do
106
+ mock_json resource.endpoint, 'blank', :status => 500 do
107
+ expect { res = resource.discover! }.should raise_error SWD::HttpError
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
data/spec/swd_spec.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe SWD do
4
+ its(:cache) { should == SWD::Cache }
5
+
6
+ describe '#discover!' do
7
+ it 'should return SWD::Response' do
8
+ mock_json "https://example.com/.well-known/simple-web-discovery", 'success', :query => {
9
+ :principal => 'mailto:joe@example.com',
10
+ :service => 'urn:adatum.com:calendar'
11
+ } do
12
+ SWD.discover!(
13
+ :principal => 'mailto:joe@example.com',
14
+ :service => 'urn:adatum.com:calendar',
15
+ :host => 'example.com'
16
+ ).should be_a SWD::Response
17
+ end
18
+ end
19
+ end
20
+ end
data/swd.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "swd"
3
+ s.version = File.read("VERSION")
4
+ s.authors = ["nov matake"]
5
+ s.email = ["nov@matake.jp"]
6
+ s.homepage = "https://github.com/nov/swd"
7
+ s.summary = %q{SWD (Simple Web Discovery) Client Library}
8
+ s.description = %q{SWD (Simple Web Discovery) Client Library}
9
+ s.files = `git ls-files`.split("\n")
10
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
11
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ s.require_paths = ["lib"]
13
+ s.add_runtime_dependency "json", ">= 1.4.3"
14
+ s.add_runtime_dependency "httpclient", ">= 2.2.1"
15
+ s.add_runtime_dependency "activesupport", ">= 3"
16
+ s.add_runtime_dependency "i18n"
17
+ s.add_runtime_dependency "attr_required", ">= 0.0.3"
18
+ s.add_development_dependency "rake", ">= 0.8"
19
+ s.add_development_dependency "rcov", ">= 0.9"
20
+ s.add_development_dependency "rspec", ">= 2"
21
+ s.add_development_dependency "webmock", ">= 1.6.2"
22
+ end
metadata ADDED
@@ -0,0 +1,238 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: swd
3
+ version: !ruby/object:Gem::Version
4
+ hash: 31
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 0
10
+ version: 0.0.0
11
+ platform: ruby
12
+ authors:
13
+ - nov matake
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-19 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: json
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 1
29
+ segments:
30
+ - 1
31
+ - 4
32
+ - 3
33
+ version: 1.4.3
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: httpclient
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 5
45
+ segments:
46
+ - 2
47
+ - 2
48
+ - 1
49
+ version: 2.2.1
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: activesupport
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ hash: 5
61
+ segments:
62
+ - 3
63
+ version: "3"
64
+ type: :runtime
65
+ version_requirements: *id003
66
+ - !ruby/object:Gem::Dependency
67
+ name: i18n
68
+ prerelease: false
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ type: :runtime
79
+ version_requirements: *id004
80
+ - !ruby/object:Gem::Dependency
81
+ name: attr_required
82
+ prerelease: false
83
+ requirement: &id005 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 25
89
+ segments:
90
+ - 0
91
+ - 0
92
+ - 3
93
+ version: 0.0.3
94
+ type: :runtime
95
+ version_requirements: *id005
96
+ - !ruby/object:Gem::Dependency
97
+ name: rake
98
+ prerelease: false
99
+ requirement: &id006 !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ hash: 27
105
+ segments:
106
+ - 0
107
+ - 8
108
+ version: "0.8"
109
+ type: :development
110
+ version_requirements: *id006
111
+ - !ruby/object:Gem::Dependency
112
+ name: rcov
113
+ prerelease: false
114
+ requirement: &id007 !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ hash: 25
120
+ segments:
121
+ - 0
122
+ - 9
123
+ version: "0.9"
124
+ type: :development
125
+ version_requirements: *id007
126
+ - !ruby/object:Gem::Dependency
127
+ name: rspec
128
+ prerelease: false
129
+ requirement: &id008 !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ hash: 7
135
+ segments:
136
+ - 2
137
+ version: "2"
138
+ type: :development
139
+ version_requirements: *id008
140
+ - !ruby/object:Gem::Dependency
141
+ name: webmock
142
+ prerelease: false
143
+ requirement: &id009 !ruby/object:Gem::Requirement
144
+ none: false
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ hash: 11
149
+ segments:
150
+ - 1
151
+ - 6
152
+ - 2
153
+ version: 1.6.2
154
+ type: :development
155
+ version_requirements: *id009
156
+ description: SWD (Simple Web Discovery) Client Library
157
+ email:
158
+ - nov@matake.jp
159
+ executables: []
160
+
161
+ extensions: []
162
+
163
+ extra_rdoc_files: []
164
+
165
+ files:
166
+ - .gitignore
167
+ - Gemfile
168
+ - Rakefile
169
+ - VERSION
170
+ - coverage/index.html
171
+ - coverage/jquery-1.3.2.min.js
172
+ - coverage/jquery.tablesorter.min.js
173
+ - coverage/lib-swd-cache_rb.html
174
+ - coverage/lib-swd-exception_rb.html
175
+ - coverage/lib-swd-resource_rb.html
176
+ - coverage/lib-swd-response_rb.html
177
+ - coverage/lib-swd_rb.html
178
+ - coverage/print.css
179
+ - coverage/rcov.js
180
+ - coverage/screen.css
181
+ - lib/swd.rb
182
+ - lib/swd/cache.rb
183
+ - lib/swd/exception.rb
184
+ - lib/swd/resource.rb
185
+ - lib/swd/response.rb
186
+ - spec/helpers/webmock_helper.rb
187
+ - spec/mock_json/blank.json
188
+ - spec/mock_json/redirect.json
189
+ - spec/mock_json/redirect_expired.json
190
+ - spec/mock_json/success.json
191
+ - spec/spec_helper.rb
192
+ - spec/swd/exception_spec.rb
193
+ - spec/swd/resource_spec.rb
194
+ - spec/swd_spec.rb
195
+ - swd.gemspec
196
+ homepage: https://github.com/nov/swd
197
+ licenses: []
198
+
199
+ post_install_message:
200
+ rdoc_options: []
201
+
202
+ require_paths:
203
+ - lib
204
+ required_ruby_version: !ruby/object:Gem::Requirement
205
+ none: false
206
+ requirements:
207
+ - - ">="
208
+ - !ruby/object:Gem::Version
209
+ hash: 3
210
+ segments:
211
+ - 0
212
+ version: "0"
213
+ required_rubygems_version: !ruby/object:Gem::Requirement
214
+ none: false
215
+ requirements:
216
+ - - ">="
217
+ - !ruby/object:Gem::Version
218
+ hash: 3
219
+ segments:
220
+ - 0
221
+ version: "0"
222
+ requirements: []
223
+
224
+ rubyforge_project:
225
+ rubygems_version: 1.8.5
226
+ signing_key:
227
+ specification_version: 3
228
+ summary: SWD (Simple Web Discovery) Client Library
229
+ test_files:
230
+ - spec/helpers/webmock_helper.rb
231
+ - spec/mock_json/blank.json
232
+ - spec/mock_json/redirect.json
233
+ - spec/mock_json/redirect_expired.json
234
+ - spec/mock_json/success.json
235
+ - spec/spec_helper.rb
236
+ - spec/swd/exception_spec.rb
237
+ - spec/swd/resource_spec.rb
238
+ - spec/swd_spec.rb