swd 0.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.
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