restfulie 0.9.3 → 1.0.0.beta1

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.
Files changed (78) hide show
  1. data/Gemfile +2 -0
  2. data/Gemfile.lock +2 -2
  3. data/README.textile +4 -2
  4. data/Rakefile +12 -13
  5. data/lib/restfulie/client/base.rb +9 -2
  6. data/lib/restfulie/client/cache/basic.rb +6 -5
  7. data/lib/restfulie/client/cache/http_ext.rb +10 -8
  8. data/lib/restfulie/client/cache/restrictions.rb +1 -6
  9. data/lib/restfulie/client/dsl.rb +66 -0
  10. data/lib/restfulie/client/entry_point.rb +34 -9
  11. data/lib/restfulie/client/ext/atom_ext.rb +4 -2
  12. data/lib/restfulie/client/ext/json_ext.rb +5 -1
  13. data/lib/restfulie/client/feature/base.rb +75 -0
  14. data/lib/restfulie/client/feature/base_request.rb +35 -0
  15. data/lib/restfulie/client/feature/cache.rb +16 -0
  16. data/lib/restfulie/client/feature/enhance_response.rb +12 -0
  17. data/lib/restfulie/client/feature/follow_request.rb +41 -0
  18. data/lib/restfulie/client/feature/history.rb +26 -0
  19. data/lib/restfulie/client/feature/history_request.rb +19 -0
  20. data/lib/restfulie/client/feature/open_search/pattern_matcher.rb +25 -0
  21. data/lib/restfulie/client/feature/open_search.rb +21 -0
  22. data/lib/restfulie/client/feature/serialize_body.rb +32 -0
  23. data/lib/restfulie/client/feature/setup_header.rb +22 -0
  24. data/lib/restfulie/client/feature/throw_error.rb +41 -0
  25. data/lib/restfulie/client/feature/verb.rb +119 -0
  26. data/lib/restfulie/client/feature.rb +5 -0
  27. data/lib/restfulie/client/http/response_holder.rb +26 -6
  28. data/lib/restfulie/client/http.rb +1 -21
  29. data/lib/restfulie/client/master_delegator.rb +31 -0
  30. data/lib/restfulie/client/mikyung/core.rb +5 -4
  31. data/lib/restfulie/client/mikyung/steady_state_walker.rb +1 -1
  32. data/lib/restfulie/client/mikyung.rb +1 -8
  33. data/lib/restfulie/client.rb +3 -1
  34. data/lib/restfulie/common/converter/atom/base.rb +2 -0
  35. data/lib/restfulie/common/converter/form_url_encoded.rb +16 -0
  36. data/lib/restfulie/common/converter/json/base.rb +5 -2
  37. data/lib/restfulie/common/converter/open_search/descriptor.rb +32 -0
  38. data/lib/restfulie/common/converter/open_search.rb +16 -0
  39. data/lib/restfulie/common/converter/xml/base.rb +3 -1
  40. data/lib/restfulie/common/converter/xml/builder.rb +3 -2
  41. data/lib/restfulie/common/converter/xml/helpers.rb +4 -4
  42. data/lib/restfulie/common/converter/xml/link.rb +5 -0
  43. data/lib/restfulie/common/converter/xml/links.rb +1 -5
  44. data/lib/restfulie/common/converter.rb +25 -4
  45. data/lib/restfulie/common/core_ext/hash.rb +6 -0
  46. data/lib/restfulie/common/links.rb +9 -0
  47. data/lib/restfulie/common/representation/atom/base.rb +34 -33
  48. data/lib/restfulie/common/representation/atom/xml.rb +5 -10
  49. data/lib/restfulie/common/representation/generic.rb +0 -12
  50. data/lib/restfulie/common/representation/json/keys_as_methods.rb +2 -0
  51. data/lib/restfulie/common/representation.rb +2 -9
  52. data/lib/restfulie/common.rb +2 -1
  53. data/lib/restfulie/server/action_controller/trait/cacheable.rb +81 -0
  54. data/lib/restfulie/server/action_controller/trait/created.rb +17 -0
  55. data/lib/restfulie/server/action_controller/trait/save_prior_to_create.rb +13 -0
  56. data/lib/restfulie/server/action_controller/trait.rb +9 -0
  57. data/lib/restfulie/server/action_controller.rb +1 -5
  58. data/lib/restfulie/server/action_view/template_handlers/tokamak.rb +1 -1
  59. data/lib/restfulie/server.rb +6 -0
  60. data/lib/restfulie/version.rb +4 -4
  61. data/lib/restfulie.rb +21 -3
  62. metadata +37 -26
  63. data/lib/restfulie/client/ext/xml_ext.rb +0 -4
  64. data/lib/restfulie/client/http/link_request_builder.rb +0 -16
  65. data/lib/restfulie/client/http/request_adapter.rb +0 -213
  66. data/lib/restfulie/client/http/request_builder.rb +0 -114
  67. data/lib/restfulie/client/http/request_builder_executor.rb +0 -24
  68. data/lib/restfulie/client/http/request_executor.rb +0 -17
  69. data/lib/restfulie/client/http/request_follow.rb +0 -42
  70. data/lib/restfulie/client/http/request_follow_executor.rb +0 -10
  71. data/lib/restfulie/client/http/request_history.rb +0 -71
  72. data/lib/restfulie/client/http/request_history_executor.rb +0 -10
  73. data/lib/restfulie/client/http/request_marshaller.rb +0 -129
  74. data/lib/restfulie/client/http/request_marshaller_executor.rb +0 -10
  75. data/lib/restfulie/client/http/response.rb +0 -23
  76. data/lib/restfulie/client/http/response_handler.rb +0 -67
  77. data/lib/restfulie/server/action_controller/cacheable_responder.rb +0 -77
  78. data/lib/restfulie/server/action_controller/created_responder.rb +0 -19
data/Gemfile CHANGED
@@ -9,6 +9,8 @@ gem "json_pure"
9
9
  gem "sqlite3-ruby"
10
10
  gem "yard"
11
11
 
12
+ gem "respondie"
13
+
12
14
  if RUBY_VERSION < "1.9"
13
15
  gem "ruby-debug"
14
16
  else
data/Gemfile.lock CHANGED
@@ -72,7 +72,7 @@ GEM
72
72
  thor (~> 0.14.0)
73
73
  rake (0.8.7)
74
74
  rcov (0.9.8)
75
- responders_backport (0.1.2)
75
+ respondie (0.9.0)
76
76
  rspec (2.0.0.beta.19)
77
77
  rspec-core (= 2.0.0.beta.19)
78
78
  rspec-expectations (= 2.0.0.beta.19)
@@ -118,7 +118,7 @@ DEPENDENCIES
118
118
  rack-conneg
119
119
  rails (>= 3.0.0)
120
120
  rcov
121
- responders_backport
121
+ respondie
122
122
  rspec-rails (>= 2.0.0.beta.19)
123
123
  ruby-debug
124
124
  sinatra
data/README.textile CHANGED
@@ -51,7 +51,7 @@ That's it. Restfulie will take care of rendering a valid representation accordin
51
51
  end
52
52
  </pre>
53
53
 
54
- You can go through an entire application by "watching this video":http://guilhermesilveira.wordpress.com or "downloading an example application":http://github.com/caelum/mikyung.
54
+ You can go through an entire application by "watching this video":http://guilhermesilveira.wordpress.com or "downloading an example application contained within the restfulie ":http://github.com/caelum/restfulie/tree/master/full-examples/rest_from_scratch/
55
55
 
56
56
  h2. Simple client example
57
57
 
@@ -70,7 +70,7 @@ h2. Full examples
70
70
 
71
71
  You can view an entire application running Restfulie under *spec/integration/order/server* and *spec/integration/order/client* in this git repository.
72
72
 
73
- "You can also download a full example of a REST based agent and server":http://github.com/caelum/full-restfulie-examples using Restfulie, according to the Rest Architecture Maturity Model.
73
+ "You can also download a full example of a REST based agent and server":http://github.com/caelum/restfulie/tree/master/full-examples/rest_from_scratch/ using Restfulie, according to the Rest Architecture Maturity Model.
74
74
 
75
75
  h2. Documentation
76
76
 
@@ -89,6 +89,8 @@ Execute:
89
89
  gem install restfulie
90
90
  </pre>
91
91
 
92
+ For use in Rails 2.3, make sure to require the responders_backport gem in addition to the restfulie gem, either in environment.rb or in the Gemfile.
93
+
92
94
  h2. Building the project
93
95
 
94
96
  If you want to build the project and run its tests, remember to install all (client and server) required gem.
data/Rakefile CHANGED
@@ -95,23 +95,22 @@ namespace :test do
95
95
  FakeServer.start_server_and_run_spec "full-examples/rest_from_scratch/part_3"
96
96
  end
97
97
 
98
- task :all => ["spec","integration"]
98
+ task :sinatra do
99
+ FakeServer.start_sinatra do
100
+ puts "Press something to quit"
101
+ gets
102
+ end
103
+ end
99
104
 
100
- # namespace :rcov do
101
- # Spec::Rake::SpecTask.new('rcov') do |t|
102
- # t.spec_opts = %w(-fs --color)
103
- # t.spec_files = FileList['spec/units/**/*_spec.rb']
104
- # t.rcov = true
105
- # t.rcov_opts = ["-e", "/Library*", "-e", "~/.rvm", "-e", "spec", "-i", "bin"]
106
- # end
107
- # desc 'Run coverage test with fake server'
108
- # task :run do
109
- # start_server_and_invoke_test('test:rcov:rcov')
110
- # end
111
- # end
105
+ task :all => ["spec","integration"]
112
106
 
113
107
  end
114
108
 
109
+ RSpec::Core::RakeTask.new(:spec) do |t|
110
+ # t.spec_files = FileList['spec_*.rb']
111
+ t.spec_opts = ['--colour', '--format progress']
112
+ end
113
+
115
114
  Rake::GemPackageTask.new(spec) do |pkg|
116
115
  pkg.gem_spec = spec
117
116
  end
@@ -1,7 +1,14 @@
1
1
  module Restfulie
2
2
  module Client#:nodoc
3
3
  module Base
4
- include HTTP::RequestMarshaller
4
+
5
+ def method_missing(sym, *args, &block)
6
+ if @base_position.respond_to?(sym)
7
+ @base_position.send sym, *args, &block
8
+ else
9
+ super(sym, *args, &block)
10
+ end
11
+ end
5
12
 
6
13
  def self.included(base)#:nodoc
7
14
  base.extend(self)
@@ -15,7 +22,7 @@ module Restfulie
15
22
  def configure
16
23
  configuration = EntryPoint.configuration_of(resource_name)
17
24
  raise "Undefined configuration for #{resource_name}" unless configuration
18
- at(configuration.entry_point)
25
+ @base_position = Restfulie.at(configuration.entry_point)
19
26
  configuration.representations.each do |representation_name,representation|
20
27
  register_representation(representation_name,representation)
21
28
  end
@@ -14,10 +14,10 @@ module Restfulie::Client::Cache
14
14
  response
15
15
  end
16
16
 
17
- def get(key, request, verb)
17
+ def get(key, request)
18
18
 
19
19
  # debugger
20
- response = cache_get(key, request, verb)
20
+ response = cache_get(key, request)
21
21
  return nil if response.nil?
22
22
 
23
23
  if response.has_expired_cache?
@@ -47,13 +47,14 @@ module Restfulie::Client::Cache
47
47
  cache.write(key, values)
48
48
  end
49
49
 
50
- def cache_get(key, req, verb)
50
+ def cache_get(key, req)
51
51
  return nil unless cache.exist?(key)
52
52
  found = cache.read(key).find do |cached|
53
53
  old_req = cached.first
54
54
  old_response = cached.last
55
- if old_response.vary_headers_for(old_req) == old_response.vary_headers_for(req) &&
56
- old_response.method == verb
55
+
56
+ headers_match = old_response.vary_headers_for(old_req) == old_response.vary_headers_for(req)
57
+ if headers_match && old_response.verb == req.verb
57
58
  old_response
58
59
  else
59
60
  false
@@ -59,13 +59,13 @@ module Restfulie::Client::HTTP::ResponseCacheCheck
59
59
 
60
60
  def has_expired_cache?
61
61
  return true if headers['date'].nil?
62
- max_time = Time.rfc2822(headers['date']) + cache_max_age.seconds
62
+ max_time = Time.rfc2822(headers['date'][0]) + cache_max_age.seconds
63
63
  Time.now > max_time
64
64
  end
65
65
 
66
66
  # checks if the header's max-age is available and no no-store if available.
67
67
  def may_cache?
68
- may_cache_field?(headers['cache-control'])
68
+ may_cache_method? && may_cache_field?(headers['cache-control'])
69
69
  end
70
70
 
71
71
  # Returns whether this cache control field allows caching
@@ -86,16 +86,13 @@ module Restfulie::Client::HTTP::ResponseCacheCheck
86
86
  max_age_header = value_for(field, /^max-age=(\d+)/)
87
87
  return false if max_age_header.nil?
88
88
 
89
- if value_for(field, /^no-store/)
90
- false
91
- else
92
- true
93
- end
89
+ return !value_for(field, /^no-store/)
94
90
  end
95
91
 
96
92
  # extracts the header value for an specific expression, which can be located at the start or in the middle
97
93
  # of the expression
98
94
  def value_for(value, expression)
95
+ value = value[0] if value.kind_of? Array
99
96
  value.split(",").find { |obj| obj.strip =~ expression }
100
97
  end
101
98
 
@@ -113,9 +110,14 @@ module Restfulie::Client::HTTP::ResponseCacheCheck
113
110
  end
114
111
  end
115
112
 
113
+ private
114
+ def may_cache_method?
115
+ verb == :get || verb == :post
116
+ end
116
117
  end
117
118
 
118
- class Restfulie::Client::HTTP::Response
119
+ class Net::HTTPResponse
119
120
  include Restfulie::Client::HTTP::ResponseStatus
120
121
  include Restfulie::Client::HTTP::ResponseCacheCheck
122
+
121
123
  end
@@ -4,12 +4,7 @@ module Restfulie::Client::Cache
4
4
 
5
5
  # checks whether this request verb and its cache headers allow caching
6
6
  def may_cache?(response)
7
- may_cache_method?(response.method) && response.may_cache?
8
- end
9
-
10
- # only Post and Get requests are cacheable so far
11
- def may_cache_method?(verb)
12
- verb == :get || verb == :post
7
+ response && response.may_cache?
13
8
  end
14
9
 
15
10
  end
@@ -0,0 +1,66 @@
1
+ module Restfulie::Client
2
+ class Dsl
3
+
4
+ def initialize
5
+ @requests = []
6
+ trait :base
7
+ trait :verb
8
+ request :base_request
9
+ request :setup_header
10
+ request :serialize_body
11
+ request :enhance_response
12
+ # request :cache
13
+ request :follow_request
14
+ end
15
+
16
+ def request(what)
17
+ req = "Restfulie::Client::Feature::#{what.to_s.classify}".constantize
18
+ @requests << req
19
+ self
20
+ end
21
+
22
+ def trait(sym)
23
+ t = "Restfulie::Client::Feature::#{sym.to_s.classify}".constantize
24
+ self.extend t
25
+ self
26
+ end
27
+
28
+ def method_missing(sym, *args)
29
+ if Restfulie::Client::Feature.const_defined? sym.to_s.classify
30
+ loaded = true
31
+ trait sym
32
+ end
33
+ if Restfulie::Client::Feature.const_defined? "#{sym.to_s.classify}Request"
34
+ loaded = true
35
+ request "#{sym.to_s}Request"
36
+ end
37
+ if loaded
38
+ self
39
+ else
40
+ super sym, *args
41
+ end
42
+ end
43
+
44
+ def request_flow(env = {})
45
+ Parser.new(@requests).continue(self, nil, env)
46
+ end
47
+
48
+ end
49
+
50
+ class Parser
51
+
52
+ def initialize(stack)
53
+ @stack = stack.dup
54
+ end
55
+
56
+ def continue(request, response, env)
57
+ current = @stack.pop
58
+ if current.nil?
59
+ return response
60
+ end
61
+ filter = current.new
62
+ filter.execute(self, request, response, env)
63
+ end
64
+
65
+ end
66
+ end
@@ -1,14 +1,8 @@
1
+
1
2
  module Restfulie
2
3
  module Client#:nodoc
3
- module EntryPoint
4
- include HTTP::RequestMarshaller
5
- include HTTP::RequestFollow
6
- extend self
7
-
8
- def self.at(uri)
9
- Object.new.send(:extend, EntryPoint).at(uri)
10
- end
11
-
4
+
5
+ module HTTP::RecipeModule
12
6
  def recipe(converter_sym, options={}, &block)
13
7
  raise 'Undefined block' unless block_given?
14
8
  converter = "Restfulie::Common::Converter::#{converter_sym.to_s.camelize}".constantize
@@ -32,5 +26,36 @@ module Restfulie
32
26
  end
33
27
  end
34
28
  end
29
+
30
+ class HTTP::Recipe < MasterDelegator
31
+
32
+ def initialize(requester)
33
+ @requester = requester
34
+ @resources_configurations = {}
35
+ end
36
+
37
+ include Restfulie::Client::HTTP::RecipeModule
38
+
39
+ end
40
+
41
+ class EntryPoint
42
+
43
+ @resources_configurations = {}
44
+ extend Restfulie::Client::HTTP::RecipeModule
45
+
46
+ def initialize(requester)
47
+ @requester = requester
48
+ end
49
+
50
+ def self.at(uri)
51
+ Restfulie.using {
52
+ recipe
53
+ follow_link
54
+ request_marshaller
55
+ verb_request
56
+ }.at(uri)
57
+ end
58
+
59
+ end
35
60
  end
36
61
  end
@@ -1,10 +1,12 @@
1
- # inject new behavior in Atom instances to enable easily access to link relationships.
1
+ # Atom links now can be followed
2
2
  module Restfulie
3
3
  module Common
4
4
  module Representation
5
5
  module Atom
6
6
  class Link
7
- include Restfulie::Client::HTTP::LinkRequestBuilder
7
+ def follow
8
+ Restfulie.at(href).as(type)
9
+ end
8
10
  end
9
11
  end
10
12
  end
@@ -4,7 +4,11 @@ module Restfulie
4
4
  module Representation
5
5
  class Json
6
6
  class Link
7
- include Restfulie::Client::HTTP::LinkRequestBuilder
7
+ def follow
8
+ r = Restfulie.at(href)
9
+ r = r.as(type) if type
10
+ r
11
+ end
8
12
  end
9
13
  end
10
14
  end
@@ -0,0 +1,75 @@
1
+ module Restfulie::Client::Feature
2
+ module Base
3
+
4
+ attr_reader :default_headers, :cookies, :verb, :host
5
+ attr_writer :headers
6
+
7
+ #Set host
8
+ def at(url)
9
+ if self.host.nil?
10
+ self.host= url
11
+ else
12
+ self.host= self.host + url
13
+ end
14
+ self
15
+ end
16
+
17
+ #Set Content-Type and Accept headers
18
+ def as(content_type)
19
+ headers['Content-Type'] = content_type
20
+ accepts(content_type)
21
+ end
22
+
23
+ #Set Accept headers
24
+ def accepts(content_type)
25
+ headers['Accept'] = content_type
26
+ self
27
+ end
28
+
29
+ # Merge internal header
30
+ #
31
+ # * <tt>headers (e.g. {'Cache-control' => 'no-cache'})</tt>
32
+ #
33
+ def with(headers)
34
+ headers.merge!(headers)
35
+ self
36
+ end
37
+
38
+ # Path (e.g. http://restfulie.com/posts => /posts)
39
+ def path
40
+ host.path + (host.query.nil? ? "" : "?#{host.query}")
41
+ end
42
+
43
+ def host=(host)
44
+ if host.is_a?(::URI)
45
+ @host = host
46
+ else
47
+ @host = ::URI.parse(host)
48
+ end
49
+ end
50
+
51
+ def default_headers
52
+ @default_headers ||= {}
53
+ end
54
+
55
+ def headers
56
+ @headers ||= {}
57
+ end
58
+
59
+ def http_to_s(method, path, *args)
60
+ result = ["#{method.to_s.upcase} #{path}"]
61
+
62
+ arguments = args.dup
63
+ headers = arguments.extract_options!
64
+
65
+ if [:post, :put].include?(method)
66
+ body = arguments.shift
67
+ end
68
+
69
+ result << headers.collect { |key, value| "#{key}: #{value}" }.join("\n")
70
+
71
+ (result + [body ? (body.inspect + "\n") : nil]).compact.join("\n") << "\n"
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,35 @@
1
+ class Restfulie::Client::Feature::BaseRequest
2
+
3
+ def execute(flow, request, response, env)
4
+ request!(request.verb, request.host, request.path, request, flow, env)
5
+ end
6
+
7
+ # Executes a request against your server and return a response instance.
8
+ # * <tt>method: :get,:post,:delete,:head,:put</tt>
9
+ # * <tt>path: '/posts'</tt>
10
+ # * <tt>args: payload: 'some text' and/or headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}</tt>
11
+ def request!(method, host, path, request, flow, env)
12
+
13
+ ::Restfulie::Common::Logger.logger.info(request.http_to_s(method, path, [request.headers])) if ::Restfulie::Common::Logger.logger
14
+ begin
15
+ http_request = get_connection_provider(host)
16
+
17
+ if env[:body]
18
+ response = http_request.send(method, path, env[:body], request.headers)
19
+ else
20
+ response = http_request.send(method, path, request.headers)
21
+ end
22
+
23
+ rescue Exception => e
24
+ response = e
25
+ end
26
+
27
+ flow.continue(request, response, env)
28
+
29
+ end
30
+
31
+ def get_connection_provider(host)
32
+ @connection ||= ::Net::HTTP.new(host.host, host.port)
33
+ end
34
+
35
+ end
@@ -0,0 +1,16 @@
1
+ class Restfulie::Client::Feature::Cache
2
+
3
+ def execute(flow, request, response, env)
4
+ found = Restfulie::Client.cache_provider.get([request.host, request.path], request)
5
+ return found if found
6
+
7
+ resp = flow.continue(request, response, env)
8
+ if resp.kind_of?(Exception)
9
+ resp
10
+ else
11
+ Restfulie::Client.cache_provider.put([request.host, request.path], request, resp)
12
+ resp
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,12 @@
1
+ module Restfulie::Client::Feature
2
+ class EnhanceResponse
3
+ def execute(flow, request, response, env)
4
+ resp = flow.continue(request, response, env)
5
+ unless resp.kind_of? ::Restfulie::Client::HTTP::ResponseHolder
6
+ resp.extend(::Restfulie::Client::HTTP::ResponseHolder)
7
+ resp.results_from request, resp
8
+ end
9
+ resp
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,41 @@
1
+ # ==== FollowLink follow new location of a document usually with response codes 201,301,302,303 and 307. You can also configure other codes.
2
+ #
3
+ # ==== Example:
4
+ # @executor = ::Restfulie::Client::HTTP::FollowLinkExecutor.new("http://restfulie.com") #this class includes FollowLink module.
5
+ # @executor.at('/custom/songs').accepts('application/atom+xml').follow(201).post!("custom").code
6
+ class Restfulie::Client::Feature::FollowRequest
7
+
8
+ def follow(code = nil)
9
+ unless code.nil? or follow_codes.include?(code)
10
+ follow_codes << code
11
+ end
12
+ self
13
+ end
14
+
15
+ def execute(flow, request, response, env)
16
+ resp = flow.continue(request, response, env)
17
+ if !resp.respond_to?(:code)
18
+ return resp
19
+ end
20
+ code = resp.code.to_i
21
+ if follow_codes.include?(code)
22
+ if code==201 && !resp.body.empty?
23
+ resp
24
+ else
25
+ location = resp.response.headers['location'] || resp.response.headers['Location']
26
+ raise Error::AutoFollowWithoutLocationError.new(request, resp) unless location
27
+ # use the first location available
28
+ location = location[0]
29
+ Restfulie.at(location).accepts(request.headers['Accept']).get
30
+ end
31
+ else
32
+ resp
33
+ end
34
+ end
35
+
36
+ protected
37
+
38
+ def follow_codes
39
+ @follow_codes ||= [201,301,302,303,307]
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ module Restfulie::Client::Feature::History
2
+
3
+ def snapshots
4
+ @snapshots ||= []
5
+ end
6
+
7
+ def max_to_remind
8
+ 10
9
+ end
10
+
11
+ def history(number)
12
+ snapshots[snapshots.size + number] || raise("Undefined snapshot for #{number}, only containing #{@snapshots.size} snapshots.")
13
+ end
14
+
15
+ def make_snapshot(request)
16
+ snapshots.shift if snapshot_full?
17
+ snapshots << request
18
+ end
19
+
20
+ private
21
+
22
+ def snapshot_full?
23
+ snapshots.size >= max_to_remind
24
+ end
25
+
26
+ end
@@ -0,0 +1,19 @@
1
+ # ==== RequestHistory
2
+ # Uses RequestBuilder and remind previous requests
3
+ #
4
+ # ==== Example:
5
+ #
6
+ # @executor = ::Restfulie::Client::HTTP::RequestHistoryExecutor.new("http://restfulie.com") #this class includes RequestHistory module.
7
+ # @executor.at('/posts').as('application/xml').accepts('application/atom+xml').with('Accept-Language' => 'en').get.code #=> 200 #first request
8
+ # @executor.at('/blogs').as('application/xml').accepts('application/atom+xml').with('Accept-Language' => 'en').get.code #=> 200 #second request
9
+ # @executor.request_history!(0) #doing first request
10
+ #
11
+ class Restfulie::Client::Feature::HistoryRequest
12
+
13
+ def execute(flow, request, response, env)
14
+ resp = flow.continue(request, response, env)
15
+ request.make_snapshot(request)
16
+ resp
17
+ end
18
+
19
+ end
@@ -0,0 +1,25 @@
1
+ module Restfulie::Client::Feature::OpenSearch
2
+
3
+ class PatternMatcher
4
+
5
+ def match(params, pattern)
6
+ params = params.collect do |key, value|
7
+ [key, value]
8
+ end
9
+ pattern = params.inject(pattern) do |pattern, p|
10
+ what = "{#{p[0]}}"
11
+ if pattern[what]
12
+ pattern[what]= "#{p[1]}"
13
+ end
14
+ what = "{#{p[0]}?}"
15
+ if pattern[what]
16
+ pattern[what]= "#{p[1]}"
17
+ end
18
+ pattern
19
+ end
20
+ pattern.gsub(/\{[^\?]*\?\}/,"")
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,21 @@
1
+ module Restfulie::Client::Feature
2
+ module OpenSearch
3
+ autoload :PatternMatcher, 'restfulie/client/feature/open_search/pattern_matcher'
4
+ end
5
+ end
6
+
7
+ module Restfulie::Client::Feature::OpenSearch
8
+
9
+ def search(params)
10
+ at ("?" + PatternMatcher.new.match(params, params_pattern))
11
+ get
12
+ end
13
+
14
+ attr_reader :params_pattern
15
+
16
+ def with_pattern(params_pattern)
17
+ @params_pattern = params_pattern
18
+ self
19
+ end
20
+
21
+ end
@@ -0,0 +1,32 @@
1
+ class Restfulie::Client::Feature::SerializeBody
2
+
3
+ def execute(flow, request, response, env)
4
+
5
+ if should_have_payload?(request.verb)
6
+
7
+ payload = env[:body]
8
+ if payload && !(payload.kind_of?(String) && payload.empty?)
9
+ type = request.headers['Content-Type']
10
+ raise Restfulie::Common::Error::RestfulieError, "Missing content type related to the data to be submitted" unless type
11
+
12
+ marshaller = Restfulie::Common::Converter.content_type_for(type)
13
+ raise Restfulie::Common::Error::RestfulieError, "Missing content type for #{type} related to the data to be submitted" unless marshaller
14
+
15
+ rel = request.respond_to?(:rel) ? request.rel : ""
16
+ env[:body] = marshaller.marshal(payload, { :rel => rel, :recipe => env[:recipe] })
17
+ end
18
+
19
+ end
20
+
21
+ flow.continue(request, response, env)
22
+ end
23
+
24
+ protected
25
+
26
+ PAYLOAD_METHODS = {:put=>true,:post=>true,:patch=>true}
27
+
28
+ def should_have_payload?(method)
29
+ PAYLOAD_METHODS[method]
30
+ end
31
+
32
+ end