transport 1.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.
@@ -0,0 +1,61 @@
1
+ require 'cgi'
2
+
3
+ module Transport
4
+
5
+ # Common transport layer for http transfers.
6
+ class HTTP
7
+
8
+ # Builder for the transport layer requests.
9
+ class RequestBuilder
10
+
11
+ # Serializer for transport http parameters.
12
+ class ParameterSerializer
13
+
14
+ attr_reader :result
15
+
16
+ def initialize(parameters = nil)
17
+ @parameters = parameters || { }
18
+ end
19
+
20
+ def perform
21
+ quote_parameters
22
+ serialize_parameters
23
+ end
24
+
25
+ private
26
+
27
+ def quote_parameters
28
+ @quoted_parameters = { }
29
+ @parameters.each do |key, value|
30
+ encoded_key = CGI.escape(key.to_s)
31
+ @quoted_parameters[encoded_key] = self.class.escape value
32
+ end
33
+ end
34
+
35
+ def serialize_parameters
36
+ @result = if @parameters.nil? || @parameters.empty?
37
+ nil
38
+ else
39
+ @quoted_parameters.collect do |key, value|
40
+ self.class.pair key, value
41
+ end.join("&")
42
+ end
43
+ end
44
+
45
+ def self.escape(value)
46
+ value.is_a?(Array) ? value.map{ |element| CGI.escape element } : CGI.escape(value)
47
+ end
48
+
49
+ def self.pair(key, value)
50
+ value.is_a?(Array) ?
51
+ value.map{ |element| "#{key}=#{element}" }.join("&") :
52
+ "#{key}=#{value}"
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,37 @@
1
+ require File.join(File.dirname(__FILE__), "common")
2
+ require File.join(File.dirname(__FILE__), "http")
3
+
4
+ module Transport
5
+
6
+ # Transport layer for json transfers.
7
+ class JSON
8
+ include Common
9
+
10
+ autoload :RequestBuilder, File.join(File.dirname(__FILE__), "json", "request_builder")
11
+ autoload :ResponseParser, File.join(File.dirname(__FILE__), "json", "response_parser")
12
+
13
+ # ParserError will be raised if the response parser couldn't process the http response.
14
+ class ParserError < StandardError; end
15
+
16
+ def perform
17
+ perform_http_transport
18
+ parse_response
19
+ end
20
+
21
+ private
22
+
23
+ def perform_http_transport
24
+ http_transport = HTTP.new @uri, @request, @options
25
+ http_transport.perform
26
+ @http_response = http_transport.response
27
+ end
28
+
29
+ def parse_response
30
+ response_parser = ResponseParser.new @http_response
31
+ response_parser.perform
32
+ @response = response_parser.result
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,56 @@
1
+ require 'json'
2
+
3
+ module Transport
4
+
5
+ # Transport layer for json transfers.
6
+ class JSON
7
+
8
+ # Builder for the json transport layer requests.
9
+ class RequestBuilder
10
+ include Common::RequestBuilder
11
+
12
+ attr_reader :request
13
+
14
+ def perform
15
+ set_headers
16
+ convert_parameters_to_json
17
+ convert_body_to_json
18
+ build_http_request
19
+ end
20
+
21
+ private
22
+
23
+ def set_headers
24
+ headers = @options[:headers] || { }
25
+ headers.merge! "Accept" => "application/json"
26
+ headers.merge! "Content-Type" => "application/json" if @options[:body]
27
+ @options[:headers] = headers
28
+ end
29
+
30
+ def convert_parameters_to_json
31
+ return unless @options[:encode_parameters]
32
+ parameters = @options[:parameters]
33
+ if parameters
34
+ parameters.each do |key, value|
35
+ parameters[key] = value.to_json if value.respond_to?(:to_json)
36
+ end
37
+ @options[:parameters] = parameters
38
+ end
39
+ end
40
+
41
+ def convert_body_to_json
42
+ body = @options[:body]
43
+ @options[:body] = body.to_json if body
44
+ end
45
+
46
+ def build_http_request
47
+ http_request_builder = HTTP::RequestBuilder.new @http_method, @url, @options
48
+ http_request_builder.perform
49
+ @request = http_request_builder.request
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,33 @@
1
+ require 'json'
2
+
3
+ module Transport
4
+
5
+ # Transport layer for json transfers.
6
+ class JSON
7
+
8
+ # Parser for the json response.
9
+ class ResponseParser
10
+
11
+ attr_reader :result
12
+
13
+ def initialize(http_response)
14
+ @http_response = http_response
15
+ end
16
+
17
+ def perform
18
+ parse_response_body
19
+ end
20
+
21
+ private
22
+
23
+ def parse_response_body
24
+ @result = @http_response && @http_response.strip != "" ? ::JSON.parse(@http_response) : nil
25
+ rescue ::JSON::ParserError => error
26
+ raise ParserError, error.to_s
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,10 @@
1
+
2
+ module Transport
3
+
4
+ module Spec
5
+
6
+ autoload :Faker, File.join(File.dirname(__FILE__), "spec", "faker")
7
+
8
+ end
9
+
10
+ end
@@ -0,0 +1,75 @@
1
+ require 'yaml'
2
+ require 'rspec'
3
+
4
+ module Transport
5
+
6
+ module Spec
7
+
8
+ # Spec helper class to fake http and json request.
9
+ class Faker
10
+
11
+ TRANSPORT_CLASSES = [
12
+ Transport::HTTP,
13
+ Transport::JSON
14
+ ].freeze unless defined?(TRANSPORT_CLASSES)
15
+
16
+ # This error is raised if no fake request is found for the call.
17
+ class NoFakeRequestError < StandardError; end
18
+
19
+ def initialize(fakes)
20
+ @fakes = fakes
21
+ end
22
+
23
+ def stub_requests!
24
+ TRANSPORT_CLASSES.each do |transport_class|
25
+ stub_requests_for_transport! transport_class
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def stub_requests_for_transport!(transport_class)
32
+ transport_class.stub(:request).and_return do |*arguments|
33
+ request *arguments
34
+ end
35
+ end
36
+
37
+ def request(http_method, url, options = { })
38
+ parameters, headers, expected_status_code = options.values_at :parameters, :headers, :expected_status_code
39
+ fake = find_fake :http_method => http_method, :url => url, :parameters => parameters, :headers => headers
40
+ response = fake[:response]
41
+ check_status_code response, expected_status_code
42
+ response[:body].dup
43
+ end
44
+
45
+ def check_status_code(response, expected_status_code)
46
+ response_code, response_body = response.values_at :code, :body
47
+ raise Transport::UnexpectedStatusCodeError.new(
48
+ response_code.to_i,
49
+ response_body
50
+ ) if expected_status_code && expected_status_code.to_s != response_code
51
+ end
52
+
53
+ def find_fake(options)
54
+ fake = @fakes.detect{ |fake| self.class.fake_match? fake, options }
55
+ raise NoFakeRequestError, "no fake request found for [#{options[:http_method]} #{options[:url]} #{options[:parameters].inspect} #{options[:headers].inspect}]" unless fake
56
+ fake
57
+ end
58
+
59
+ def self.fake_match?(fake, options)
60
+ fake[:http_method].to_s == options[:http_method].to_s &&
61
+ fake[:url].to_s == options[:url].to_s &&
62
+ fake[:parameters] == options[:parameters] &&
63
+ fake[:headers] == options[:headers]
64
+ end
65
+
66
+ def self.fake!(filename)
67
+ faker = new YAML.load_file(filename)
68
+ faker.stub_requests!
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -0,0 +1,21 @@
1
+
2
+ module Transport
3
+
4
+ # The UnexpectedStatusCodeError is raised if the :expected_status_code option is given to
5
+ # the :request method and the responded status code is different from the expected one.
6
+ class UnexpectedStatusCodeError < StandardError
7
+
8
+ attr_reader :status_code
9
+ attr_reader :message
10
+
11
+ def initialize(status_code, message = nil)
12
+ @status_code, @message = status_code, message
13
+ end
14
+
15
+ def to_s
16
+ "#{super} received status code #{self.status_code}" + (@message ? " [#{@message}]" : "")
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,20 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
+ require 'logger'
3
+
4
+ describe "fetching the google start page" do
5
+
6
+ it "should return some html" do
7
+ response = Transport::HTTP.request :get, "http://www.google.de"
8
+ response.should_not be_nil
9
+ end
10
+
11
+ end
12
+
13
+ describe "fetch a search query result from google" do
14
+
15
+ it "should return some html" do
16
+ response = Transport::HTTP.request :get, "http://www.google.de/search", :parameters => { "q" => "transport" }
17
+ response.should_not be_nil
18
+ end
19
+
20
+ end
@@ -0,0 +1,10 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
+
3
+ describe "fetching the delicious bookmarks" do
4
+
5
+ it "should return some json" do
6
+ response = Transport::JSON.request :get, "http://feeds.delicious.com/v2/json", :logger => Logger.new(STDOUT)
7
+ response.should be_instance_of(Array)
8
+ end
9
+
10
+ end
@@ -0,0 +1,61 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper"))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "lib", "transport", "common", "request_builder"))
3
+
4
+ describe Transport::Common::RequestBuilder do
5
+
6
+ before :each do
7
+ @klass = Class.new
8
+ @klass.send :include, described_class
9
+ end
10
+
11
+ describe "initialize" do
12
+
13
+ before :each do
14
+ @object = @klass.new :test_http_method, "http://host:1234/test", :test_options
15
+ end
16
+
17
+ it "should set the http_method" do
18
+ @object.http_method.should == :test_http_method
19
+ end
20
+
21
+ it "should set the url" do
22
+ @object.url.should == "http://host:1234/test"
23
+ end
24
+
25
+ it "should set the options" do
26
+ @object.options.should == :test_options
27
+ end
28
+
29
+ it "should set the uri" do
30
+ @object.uri.host.should == "host"
31
+ @object.uri.port.should == 1234
32
+ end
33
+
34
+ end
35
+
36
+ describe "build" do
37
+
38
+ before :each do
39
+ @uri = Object.new
40
+ @request = Object.new
41
+ @object = mock described_class, :perform => nil, :uri => @uri, :request => @request
42
+ @klass.stub(:new).and_return(@object)
43
+ end
44
+
45
+ it "should initialize the request builder" do
46
+ @klass.should_receive(:new).with(:test_argument).and_return(@object)
47
+ @klass.build :test_argument
48
+ end
49
+
50
+ it "should perform the build" do
51
+ @object.should_receive(:perform)
52
+ @klass.build
53
+ end
54
+
55
+ it "should return the uri and the request" do
56
+ @klass.build.should == [ @uri, @request ]
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,68 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "transport", "common"))
3
+
4
+ describe Transport::Common do
5
+
6
+ before :each do
7
+ @klass = Class.new
8
+ @klass.send :include, described_class
9
+ end
10
+
11
+ describe "initialize" do
12
+
13
+ before :each do
14
+ @object = @klass.new :test_uri, :test_request, :test_options
15
+ end
16
+
17
+ it "should set the uri" do
18
+ @object.uri.should == :test_uri
19
+ end
20
+
21
+ it "should set the request" do
22
+ @object.request.should == :test_request
23
+ end
24
+
25
+ it "should set the options" do
26
+ @object.options.should == :test_options
27
+ end
28
+
29
+ end
30
+
31
+ describe "request" do
32
+
33
+ before :each do
34
+ @uri = Object.new
35
+ @request = Object.new
36
+
37
+ @klass::RequestBuilder.stub(:build).and_return([ @uri, @request ])
38
+
39
+ @object = mock described_class, :perform => nil, :response => :test_response
40
+ @klass.stub(:new).and_return(@object)
41
+ end
42
+
43
+ def do_request
44
+ @klass.request :test_http_method, :test_url, :test_options
45
+ end
46
+
47
+ it "should build the request" do
48
+ @klass::RequestBuilder.should_receive(:build).with(:test_http_method, :test_url, :test_options).and_return([ @uri, @request ])
49
+ do_request
50
+ end
51
+
52
+ it "should initialize the transport" do
53
+ @klass.should_receive(:new).with(@uri, @request, :test_options).and_return(@object)
54
+ do_request
55
+ end
56
+
57
+ it "should perform the transport" do
58
+ @object.should_receive(:perform)
59
+ do_request
60
+ end
61
+
62
+ it "should return the response" do
63
+ do_request.should == :test_response
64
+ end
65
+
66
+ end
67
+
68
+ end