voorhees 0.2.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,9 @@
1
+ module Voorhees
2
+ class Error < ::StandardError; end
3
+ class ParameterMissingError < Error; end
4
+ class NotFoundError < Error; end
5
+ class TimeoutError < Error; end
6
+ class UnavailableError < Error; end
7
+ class ParseError < Error; end
8
+ class NotResourceError < Error; end
9
+ end
@@ -0,0 +1,5 @@
1
+ module Voorhees
2
+ def self.debug(message)
3
+ Voorhees::Config[:logger].debug("VOORHEES: #{message}")
4
+ end
5
+ end
@@ -0,0 +1,146 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+
4
+ begin
5
+ require 'system_timer'
6
+ VoorheesTimer = SystemTimer
7
+ rescue LoadError
8
+ require 'timeout'
9
+ VoorheesTimer = Timeout
10
+ end
11
+
12
+ module Voorhees
13
+
14
+ class Request
15
+
16
+ attr_accessor :timeout, :retries, :path,
17
+ :required, :defaults, :parameters,
18
+ :base_uri, :http_method, :hierarchy
19
+
20
+ def initialize(caller_class=nil)
21
+ @caller_class = caller_class
22
+ end
23
+
24
+ def path=(uri)
25
+ @path = URI.parse(uri)
26
+ end
27
+
28
+ def uri
29
+ u = path.relative? ? URI.parse("#{base_uri}#{path}") : path
30
+ if query = query_string(u)
31
+ u.query = query
32
+ end
33
+ u
34
+ end
35
+
36
+ def base_uri
37
+ @base_uri || Voorhees::Config[:base_uri]
38
+ end
39
+
40
+ def defaults
41
+ @defaults || Voorhees::Config[:defaults]
42
+ end
43
+
44
+ def parameters
45
+ (defaults || {}).merge(@parameters || {})
46
+ end
47
+
48
+ def timeout
49
+ @timeout || Voorhees::Config[:timeout]
50
+ end
51
+
52
+ def retries
53
+ @retries || Voorhees::Config[:retries]
54
+ end
55
+
56
+ def http_method
57
+ @http_method || Voorhees::Config[:http_method]
58
+ end
59
+
60
+ def perform
61
+ setup_request
62
+ build_response(perform_actual_request)
63
+ end
64
+
65
+ private
66
+
67
+ def setup_request
68
+ @http = Net::HTTP.new(uri.host, uri.port)
69
+ @req = http_method.new(uri.path)
70
+
71
+ @req.form_data = if Voorhees::Config[:post_json]
72
+ { Voorhees::Config[:post_json_parameter] => parameters.to_json }
73
+ else
74
+ parameters
75
+ end
76
+
77
+ @http.open_timeout = timeout
78
+ @http.read_timeout = timeout
79
+ end
80
+
81
+ def perform_actual_request
82
+ retries_left = retries
83
+
84
+ Voorhees.debug("Performing #{http_method} request for #{uri.to_s}")
85
+
86
+ begin
87
+ retries_left -= 1
88
+
89
+ response = VoorheesTimer.timeout(timeout) do
90
+ @http.start do |connection|
91
+ connection.request(@req)
92
+ end
93
+ end
94
+
95
+ rescue Timeout::Error
96
+ if retries_left >= 0
97
+ Voorhees.debug("Retrying due to Timeout::Error (#{uri.to_s})")
98
+ retry
99
+ end
100
+
101
+ Voorhees.debug("Request failed due to Timeout::Error (#{uri.to_s})")
102
+ raise Voorhees::TimeoutError.new
103
+
104
+ rescue Errno::ECONNREFUSED
105
+ if retries_left >= 0
106
+ Voorhees.debug("Retrying due to Errno::ECONNREFUSED (#{uri.to_s})")
107
+ sleep(1)
108
+ retry
109
+ end
110
+
111
+ Voorhees.debug("Request failed due to Errno::ECONREFUSED (#{uri.to_s})")
112
+ raise Voorhees::UnavailableError.new
113
+
114
+ end
115
+
116
+ if response.is_a?(Net::HTTPNotFound)
117
+ Voorhees.debug("Request failed due to Net::HTTPNotFound (#{uri.to_s})")
118
+ raise Voorhees::NotFoundError.new
119
+ end
120
+
121
+ response
122
+ end
123
+
124
+ def query_string(uri)
125
+ return if post?
126
+ query_string_parts = []
127
+ query_string_parts << uri.query unless uri.query.blank?
128
+ query_string_parts += parameters.collect{|k,v| "#{k}=#{v}" } unless parameters.empty?
129
+ query_string_parts.size > 0 ? query_string_parts.join('&') : nil
130
+ end
131
+
132
+ def build_response(response)
133
+ Voorhees::Config[:response_class].new(response.body, @caller_class, @hierarchy)
134
+ end
135
+
136
+ def validate
137
+ raise Voorhees::ParameterMissingError if @required && !@required.all?{|x| @parameters.keys.include?(x) }
138
+ end
139
+
140
+ def post?
141
+ http_method == Net::HTTP::Post
142
+ end
143
+
144
+ end
145
+
146
+ end
@@ -0,0 +1,141 @@
1
+ module Voorhees
2
+
3
+ module Resource
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ base.send :include, InstanceMethods
8
+
9
+ base.instance_eval do
10
+ attr_accessor :raw_json, :json_hierarchy
11
+ undef_method :id
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ def new_from_json(json, hierarchy=nil)
17
+ obj = self.new
18
+ obj.raw_json = json
19
+ obj.json_hierarchy = hierarchy
20
+ obj
21
+ end
22
+
23
+ def json_service(name, request_options={})
24
+ klass = request_options.delete(:class) || self
25
+ (class << self; self; end).instance_eval do
26
+ define_method name do |*args|
27
+ params = args[0]
28
+ json_request(:class => klass) do |r|
29
+ r.parameters = params if params.is_a?(Hash)
30
+ request_options.each do |option, value|
31
+ r.send("#{option}=", value)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def json_request(options={})
39
+ request = Voorhees::Request.new(options[:class] || self)
40
+ yield request
41
+ response = request.perform
42
+
43
+ case options[:returning]
44
+ when :raw
45
+ response.body
46
+ when :json
47
+ response.json
48
+ when :response
49
+ response
50
+ else
51
+ response.to_objects
52
+ end
53
+ end
54
+ end
55
+
56
+ module InstanceMethods
57
+
58
+ def json_attributes
59
+ @json_attributes ||= @raw_json.keys.collect{|x| x.underscore.to_sym}
60
+ end
61
+
62
+ def json_request(options={})
63
+ self.class.json_request(options) do |r|
64
+ yield r
65
+ end
66
+ end
67
+
68
+ def method_missing(*args)
69
+ method_name = args[0]
70
+ if json_attributes.include?(method_name)
71
+ value = value_from_json(method_name)
72
+ build_methods(method_name, value)
73
+ value
74
+ elsif method_name.to_s =~ /(.+)=$/ && json_attributes.include?($1.to_sym)
75
+ build_methods($1, args[1])
76
+ else
77
+ super
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def value_from_json(method_name)
84
+ item = raw_json[method_name.to_s] || raw_json[method_name.to_s.camelize(:lower)]
85
+
86
+ sub_hierarchy = nil
87
+ if json_hierarchy && hierarchy = json_hierarchy[method_name]
88
+ if hierarchy.is_a?(Array)
89
+ klass = hierarchy[0]
90
+ sub_hierarchy = hierarchy[1]
91
+ else
92
+ klass = hierarchy
93
+ end
94
+
95
+ klass = Object.const_get(klass.to_s.pluralize.classify) if klass.is_a?(Symbol)
96
+ end
97
+
98
+ if item.is_a?(Array)
99
+ build_collection_from_json(method_name, item, klass, sub_hierarchy)
100
+ else
101
+ build_item(item, klass, sub_hierarchy)
102
+ end
103
+ end
104
+
105
+ def build_methods(name, value)
106
+ self.instance_variable_set("@#{name}".to_sym, value)
107
+
108
+ instance_eval "
109
+ def #{name}
110
+ @#{name} ||= value_from_json(:#{name})
111
+ end
112
+
113
+ def #{name}=(val)
114
+ @#{name} = val
115
+ end
116
+ "
117
+ end
118
+
119
+ def build_item(json, klass, hierarchy)
120
+ if klass
121
+ raise Voorhees::NotResourceError.new unless klass.respond_to?(:new_from_json)
122
+ klass.new_from_json(json, hierarchy)
123
+ else
124
+ json
125
+ end
126
+ end
127
+
128
+ def build_collection_from_json(name, json, klass, hierarchy)
129
+ klass ||= Object.const_get(name.to_s.classify)
130
+ json.collect do |item|
131
+ klass.new_from_json(json, hierarchy)
132
+ end
133
+ rescue NameError
134
+ json
135
+ end
136
+
137
+ end
138
+
139
+ end
140
+
141
+ end
@@ -0,0 +1,36 @@
1
+ module Voorhees
2
+
3
+ class Response
4
+
5
+ attr_reader :body, :klass
6
+
7
+ def initialize(body, klass=nil, hierarchy=nil)
8
+ @body = body
9
+ @hierarchy = hierarchy
10
+ @klass = klass
11
+ end
12
+
13
+ def json
14
+ @json ||= JSON.parse(@body)
15
+ rescue JSON::ParserError
16
+ Voorhees.debug("Parsing JSON failed.\nFirst 500 chars of body:\n#{response.body[0...500]}")
17
+ raise Voorhees::ParseError
18
+ end
19
+
20
+ def to_objects
21
+ return unless @klass
22
+
23
+ raise Voorhees::NotResourceError.new unless @klass.respond_to?(:new_from_json)
24
+
25
+ if json.is_a?(Array)
26
+ json.collect do |item|
27
+ @klass.new_from_json(item, @hierarchy)
28
+ end
29
+ else
30
+ @klass.new_from_json(json, @hierarchy)
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,144 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Voorhees::Config do
4
+
5
+ before :each do
6
+
7
+ Voorhees::Config.clear
8
+ Voorhees::Config.setup do |c|
9
+ c[:one] = 1
10
+ c[:two] = 2
11
+ end
12
+
13
+ end
14
+
15
+ describe "logger" do
16
+
17
+ before :each do
18
+ Object.send(:remove_const, :RAILS_DEFAULT_LOGGER) if defined?(RAILS_DEFAULT_LOGGER)
19
+ end
20
+
21
+ it "should default to RAILS_DEFAULT_LOGGER if defined" do
22
+ RAILS_DEFAULT_LOGGER = "something"
23
+ Voorhees::Config.reset
24
+ Voorhees::Config.logger.should == "something"
25
+ end
26
+
27
+ it "should default to a Logger if RAILS_DEFAULT_LOGGER is not defined" do
28
+ Voorhees::Config.reset
29
+ Voorhees::Config.logger.should be_a(Logger)
30
+ end
31
+
32
+ end
33
+
34
+ describe "configuration" do
35
+
36
+ it "should return the configuration hash" do
37
+ Voorhees::Config.configuration.should == {:one => 1, :two => 2}
38
+ end
39
+
40
+ end
41
+
42
+ describe "[]" do
43
+
44
+ it "should return the config option matching the key" do
45
+ Voorhees::Config[:one].should == 1
46
+ end
47
+
48
+ it "should return nil if the key doesn't exist" do
49
+ Voorhees::Config[:monkey].should be_nil
50
+ end
51
+
52
+ end
53
+
54
+ describe "[]=" do
55
+
56
+ it "should set the config option for the key" do
57
+ lambda{
58
+ Voorhees::Config[:banana] = :yellow
59
+ }.should change(Voorhees::Config, :banana).from(nil).to(:yellow)
60
+ end
61
+
62
+ end
63
+
64
+ describe "delete" do
65
+
66
+ it "should delete the config option for the key" do
67
+ lambda{
68
+ Voorhees::Config.delete(:one)
69
+ }.should change(Voorhees::Config, :one).from(1).to(nil)
70
+ end
71
+
72
+ it "should leave the config the same if the key doesn't exist" do
73
+ lambda{
74
+ Voorhees::Config.delete(:test)
75
+ }.should_not change(Voorhees::Config, :configuration)
76
+ end
77
+
78
+ end
79
+
80
+ describe "fetch" do
81
+
82
+ it "should return the config option matching the key if it exists" do
83
+ Voorhees::Config.fetch(:one, 100).should == 1
84
+ end
85
+
86
+ it "should return the config default if the key doesn't exist" do
87
+ Voorhees::Config.fetch(:other, 100).should == 100
88
+ end
89
+
90
+ end
91
+
92
+ describe "to_hash" do
93
+
94
+ it "should return a hash of the configuration" do
95
+ Voorhees::Config.to_hash.should == {:one => 1, :two => 2}
96
+ end
97
+
98
+ end
99
+
100
+ describe "setup" do
101
+
102
+ it "should yield self" do
103
+ Voorhees::Config.setup do |c|
104
+ c.should == Voorhees::Config
105
+ end
106
+ end
107
+
108
+ it "should let you set items on the configuration object as a hash" do
109
+ lambda{
110
+ Voorhees::Config.setup do |c|
111
+ c[:bananas] = 100
112
+ end
113
+ }.should change(Voorhees::Config, :bananas).from(nil).to(100)
114
+ end
115
+
116
+ it "should let you set items on the configuration object as a method" do
117
+ lambda{
118
+ Voorhees::Config.setup do |c|
119
+ c.monkeys = 100
120
+ end
121
+ }.should change(Voorhees::Config, :monkeys).from(nil).to(100)
122
+ end
123
+
124
+ end
125
+
126
+ describe "calling a missing method" do
127
+
128
+ it "should retreive the config if the method matches a key" do
129
+ Voorhees::Config.one.should == 1
130
+ end
131
+
132
+ it "should retreive nil if the method doesn't match a key" do
133
+ Voorhees::Config.moo.should be_nil
134
+ end
135
+
136
+ it "should set the value of the config item matching the method name if it's an assignment" do
137
+ lambda{
138
+ Voorhees::Config.trees = 3
139
+ }.should change(Voorhees::Config, :trees).from(nil).to(3)
140
+ end
141
+
142
+ end
143
+
144
+ end