commute 0.2.0.rc.2 → 0.3.0.pre

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 (66) hide show
  1. data/.todo +28 -12
  2. data/README.md +0 -1
  3. data/commute.gemspec +4 -5
  4. data/lib/commute/common/basic_auth.rb +10 -9
  5. data/lib/commute/common/caching.rb +208 -0
  6. data/lib/commute/common/chemicals.rb +47 -24
  7. data/lib/commute/common/eventmachine.rb +68 -0
  8. data/lib/commute/common/synchrony.rb +42 -0
  9. data/lib/commute/common/typhoeus.rb +64 -0
  10. data/lib/commute/core/api.rb +42 -29
  11. data/lib/commute/core/builder.rb +4 -15
  12. data/lib/commute/core/context.rb +156 -15
  13. data/lib/commute/core/http.rb +124 -0
  14. data/lib/commute/core/layer.rb +187 -0
  15. data/lib/commute/core/sequence.rb +83 -132
  16. data/lib/commute/core/stack.rb +63 -72
  17. data/lib/commute/core/status.rb +45 -0
  18. data/lib/commute/core/util/event_emitter.rb +58 -0
  19. data/lib/commute/core/util/path.rb +37 -0
  20. data/lib/commute/core/util/stream.rb +141 -0
  21. data/lib/commute/extensions/crud.rb +88 -0
  22. data/lib/commute/extensions/param.rb +20 -0
  23. data/lib/commute/extensions/url.rb +53 -0
  24. data/lib/commute/version.rb +1 -1
  25. data/spec/commute/common/caching_spec.rb +158 -0
  26. data/spec/commute/common/eventmachine_spec.rb +74 -0
  27. data/spec/commute/common/typhoeus_spec.rb +67 -0
  28. data/spec/commute/core/api_spec.rb +3 -1
  29. data/spec/commute/core/builder_spec.rb +8 -8
  30. data/spec/commute/core/http_spec.rb +39 -0
  31. data/spec/commute/core/layer_spec.rb +81 -0
  32. data/spec/commute/core/sequence_spec.rb +36 -150
  33. data/spec/commute/core/stack_spec.rb +33 -83
  34. data/spec/commute/core/util/event_emitter_spec.rb +35 -0
  35. data/spec/commute/core/util/path_spec.rb +29 -0
  36. data/spec/commute/core/util/stream_spec.rb +90 -0
  37. data/spec/commute/extensions/url_spec.rb +76 -0
  38. data/spec/spec_helper.rb +3 -1
  39. metadata +61 -48
  40. data/examples/gist_api.rb +0 -71
  41. data/examples/highrise_task_api.rb +0 -59
  42. data/examples/pastie_api.rb +0 -18
  43. data/lib/commute/aspects/caching.rb +0 -37
  44. data/lib/commute/aspects/crud.rb +0 -41
  45. data/lib/commute/aspects/pagination.rb +0 -16
  46. data/lib/commute/aspects/url.rb +0 -57
  47. data/lib/commute/common/cache.rb +0 -43
  48. data/lib/commute/common/conditional.rb +0 -27
  49. data/lib/commute/common/em-synchrony_adapter.rb +0 -29
  50. data/lib/commute/common/em_http_request_adapter.rb +0 -57
  51. data/lib/commute/common/typhoeus_adapter.rb +0 -40
  52. data/lib/commute/common/xml.rb +0 -7
  53. data/lib/commute/core/commuter.rb +0 -116
  54. data/lib/commute/core/processors/code_status_processor.rb +0 -40
  55. data/lib/commute/core/processors/hook.rb +0 -14
  56. data/lib/commute/core/processors/request_builder.rb +0 -26
  57. data/lib/commute/core/processors/sequencer.rb +0 -46
  58. data/lib/commute/core/request.rb +0 -58
  59. data/lib/commute/core/response.rb +0 -18
  60. data/spec/commute/aspects/caching_spec.rb +0 -12
  61. data/spec/commute/aspects/url_spec.rb +0 -61
  62. data/spec/commute/core/commuter_spec.rb +0 -64
  63. data/spec/commute/core/processors/code_status_processor_spec.rb +0 -5
  64. data/spec/commute/core/processors/hook_spec.rb +0 -25
  65. data/spec/commute/core/processors/request_builder_spec.rb +0 -25
  66. data/spec/commute/core/processors/sequencer_spec.rb +0 -33
@@ -54,7 +54,7 @@ module Commute
54
54
  end
55
55
 
56
56
  # Public: Adds logical parameters to the context.
57
- # Logocal parameters whose names match the id of a
57
+ # Logical parameters whose names match the id of a
58
58
  # layer, get passed to that layer as options.
59
59
  #
60
60
  # When a parameter is set to nil, it is excluded
@@ -188,7 +188,7 @@ module Commute
188
188
  end
189
189
 
190
190
  # Same as `transform` but removes all existing transformations for these dependencies.
191
- def transform! *dependencies, &transformation
191
+ def transform! name, *dependencies, &transformation
192
192
  end
193
193
 
194
194
  # Public: Used to alter the stack or one of its sequences.
@@ -233,19 +233,8 @@ module Commute
233
233
  end
234
234
  end
235
235
 
236
- # TODO
237
- def limit max
238
- with limit: max
239
- end
240
-
241
- # TODO
242
- def offset index
243
- with offset: index
244
- end
245
-
246
- # TODO
247
- def order field, direction = :asc
248
- with order: { field: field, direction: direction }
236
+ def body body
237
+ with! body: body
249
238
  end
250
239
 
251
240
  private
@@ -2,14 +2,61 @@ require 'forwardable'
2
2
 
3
3
  require 'commute/core/builder'
4
4
  require 'commute/core/stack'
5
- require 'commute/core/builder'
5
+ require 'commute/core/http'
6
6
 
7
+ # A context is a snapshot of a builder. It holds partial
8
+ # information on how to build and execute a request.
9
+ #
10
+ # This is:
11
+ # * A stack
12
+ # * Logical parameters
13
+ # * Transformation that transform logical parameters into request parameters
14
+ # * Disables (for skipping layers)
15
+ #
16
+ # When a context holds enough information to execute a request, you
17
+ # can execute it in 6 different ways (depending on your adapter):
18
+ #
19
+ # 1) Run Synchronously.
20
+ # paste, status[, response] = pastes.find(1).run
21
+ # pasted, status[, response] = api.create.run("Some paste")
22
+ # # OR
23
+ # pasted, status[, response] = api.put.one.run("Some paste")
24
+ #
25
+ # 2) Run Synchronously unsafe
26
+ # paste = pasted.find(1).run!
27
+ #
28
+ # 3) Run Asynchronously.
29
+ # pastes.find(1).run { |paste[, status[, response]]| ... }
30
+ #
31
+ # 4) Stream (down)
32
+ # pastes.all.stream { |response, status|
33
+ # # Received a response.
34
+ # response.on(:data) { |paste| ... }
35
+ # response.on(:end) { ... }
36
+ # }
37
+ #
38
+ # 5) Stream unsafe (down)
39
+ # pastes.all.stream! { |paste|
40
+ # }
41
+ #
42
+ # 6) The full treatment (up and downstreaming)
43
+ # request = pastes.all.request { |response, status|
44
+ # }
45
+ # request.write ...
46
+ # request.end
47
+ #
7
48
  module Commute
8
49
  class Context
9
50
  include Buildable
10
51
 
52
+ # Public: All information needed to build and execute a request.
11
53
  attr_reader :stack, :parameters, :transformations, :disables
12
54
 
55
+ # Internal: Creates a new context. A developer never has to call
56
+ # this method manually.
57
+ #
58
+ # It sets up a snapshot of a builder and freezes all information.
59
+ #
13
60
  def initialize stack = nil, parameters = {}, transformations = [], \
14
61
  disables = [], builder_class = Builder
15
62
  @stack = stack.freeze
@@ -19,32 +66,111 @@ module Commute
19
66
  @builder_class = builder_class
20
67
  end
21
68
 
69
+ # Public: Get a logical parameter.
70
+ #
71
+ # key - The key of the logical parameter.
72
+ #
73
+ # Returns the value of the logical parameter.
22
74
  def [] key
23
75
  @parameters[key]
24
76
  end
25
77
 
26
- # Public: Creates a new context, resetting all transformations.
78
+ # Public: Check if a layer is enabled.
27
79
  #
80
+ # name - The name of the layer in question.
81
+ #
82
+ # Returns `true` if the layer is enabled in the current context.
83
+ def enabled? name
84
+ !disables.include?(name)
85
+ end
86
+
87
+ # Internal: Creates a new context, resetting all transformations.
28
88
  def reset
89
+ self.class.new stack, parameters, [], disables, builder_class
29
90
  end
30
91
 
31
- # Returns the
32
- def run &on_complete
33
- context = on_complete ? with(on_complete: on_complete) : self
34
- commuter = Commuter.new context
35
- @stack.call commuter
36
- commuter.get
92
+ # Public: Down and Upstream any content (safely).
93
+ # The Full Treatment.
94
+ #
95
+ # Yields Layer Response and a status.
96
+ # Returns a Layer Request.
97
+ #
98
+ def request &on_response
99
+ @stack.call self.build, &on_response
37
100
  end
38
101
 
39
- def run! *args, &block
40
- result = self.run(*args, &block)
41
- if result.kind_of?(Array)
42
- result.first
43
- else
44
- result
102
+ # Public: Downstream any remote content (safely).
103
+ #
104
+ # body - Optional body to send with the request.
105
+ #
106
+ # Yields a Layer Response and a status.
107
+ # Returns Nothing.
108
+ #
109
+ def stream body = self[:body], &on_response
110
+ self.request(&on_response).tap do |request|
111
+ request.write body if body
112
+ request.end
45
113
  end
114
+ # Return Nothing.
115
+ nil
46
116
  end
47
117
 
118
+ # Public: Downstream any remote content (unsafely).
119
+ #
120
+ # body - Optional body to send with the request.
121
+ #
122
+ # Yields for every chunk of body/result streamed.
123
+ # Returns Nothing.
124
+ #
125
+ def stream! body = self[:body], &on_data
126
+ self.stream do |response, status|
127
+ response.on :end, &on_data
128
+ end
129
+ # Return Nothing.
130
+ nil
131
+ end
132
+
133
+ # Public: Run (a)synchronously, only returns if data is fully
134
+ # received. Direct return only possible with synchronous adapter.
135
+ #
136
+ # body - Optional body to send with the request.
137
+ #
138
+ # Yields the buffered data, status and response. (sync)
139
+ # Returns the buffered data, status and response. (sync/async)
140
+ #
141
+ def run body = self[:body], &on_result
142
+ # Temprary result variable in this scope.
143
+ result, status, response = nil, nil, nil
144
+ # Stream the response.
145
+ self.stream(body) do |_response, _status|
146
+ # Set status.
147
+ status, response = _status, _response
148
+ # Buffer all data in result.
149
+ response.once(:data) { |_result| result = _result }
150
+ # Call on_result in the end.
151
+ response.on(:end) do
152
+ on_result.call result, status, response if on_result
153
+ end
154
+ end
155
+ # Also just return the result.
156
+ return result, status, response
157
+ end
158
+
159
+ # Public: Run synchronously unsafe.
160
+ # Only returns data when fully buffered, without a status.
161
+ #
162
+ # body - Optional body to send with the request.
163
+ #
164
+ # Returns the buffered data.
165
+ #
166
+ def run! body = self[:body]
167
+ return self.run(body).first
168
+ end
169
+
170
+ # Internal: Methods not defined on Context trigger
171
+ # the creation of a builder.
172
+ #
173
+ # The method is then forwarded to the builder.
48
174
  def method_missing method, *args, &block
49
175
  b = builder
50
176
  if b.respond_to?(method)
@@ -54,8 +180,23 @@ module Commute
54
180
  end
55
181
  end
56
182
 
57
- private
183
+ protected
58
184
 
185
+ # Internal: Build a Http Request using context transformations.
186
+ #
187
+ def build
188
+ # Create a new request.
189
+ http_request = Http::Request.new(self)
190
+ # Build the request using context transformations.
191
+ @transformations.each do |t|
192
+ t.call http_request, self
193
+ end
194
+ # Return the http request.
195
+ http_request
196
+ end
197
+
198
+ # Internal: Creates a new Builder for this context.
199
+ #
59
200
  def builder
60
201
  @builder_class.new self
61
202
  end
@@ -0,0 +1,124 @@
1
+ require 'uri'
2
+
3
+ require 'commute/core/status'
4
+
5
+ module Commute
6
+
7
+ # Collection of model classes associated with Http.
8
+ module Http
9
+
10
+ # Public: Status that derives its ok status from a given
11
+ # Http status code.
12
+ #
13
+ class Status < Commute::Status
14
+ attr_reader :code
15
+
16
+ # Public: Creates a new Http Status.
17
+ #
18
+ # code - Http Status code.
19
+ # error - Optional error details (Can be anything).
20
+ #
21
+ def initialize code, error = nil
22
+ super !(400..599).include?(code)
23
+ @code = code
24
+ end
25
+
26
+ def not_modified?
27
+ @code == 304
28
+ end
29
+ end
30
+
31
+ # Public: A Http connection to a host/port with a certain protocol.
32
+ class Connection
33
+
34
+ # Public: Uri of the host (containing scheme, host and port).
35
+ attr_reader :uri
36
+ end
37
+
38
+ # Public: A Http Request.
39
+ class Request
40
+
41
+ # Public: The context in which the Http Request is made.
42
+ attr_reader :context
43
+
44
+ # Public: Uri of EATS (Everything after the slash).
45
+ attr_accessor :uri
46
+
47
+ # Public: Url parameters (Hash).
48
+ attr_accessor :query
49
+ alias :params :query
50
+ alias :params= :query=
51
+
52
+ # Public: The HTTP Method of the request (Symbol).
53
+ # Mostly :get, :post, :put, :patch, :delete
54
+ attr_accessor :method
55
+
56
+ # Public: Request headers.
57
+ attr_accessor :headers
58
+
59
+ # Public: Optional connection associated with this request.
60
+ attr_reader :connection
61
+
62
+ # Public: Initializes a new Http Request with some default values.
63
+ #
64
+ # context - The context in which the Http Request is made.
65
+ #
66
+ def initialize context
67
+ @context = context
68
+ @uri = URI::Generic.build path: '/'
69
+ @headers = {}
70
+ @query = {}
71
+ @method = :get
72
+ end
73
+
74
+ # Public: Getting the Http method.
75
+ #
76
+ # Overload the default reader method for overloading
77
+ # of Object#method.
78
+ #
79
+ def method name = nil
80
+ name.nil? ? @method : super(name)
81
+ end
82
+
83
+ # Public: Get the URI of the Request.
84
+ #
85
+ # Override the default reader method for consistency
86
+ # with the query setter method on Request.
87
+ # We first merge the query string into the URI before
88
+ # returning it.
89
+ #
90
+ def uri
91
+ @uri.query = URI.encode_www_form_component( \
92
+ URI.encode_www_form(self.query)
93
+ ) if !self.query.empty?
94
+ @uri
95
+ end
96
+ end
97
+
98
+ # Public: An Http Response.
99
+ class Response
100
+
101
+ # Public: The request responsible for this response.
102
+ attr_accessor :request
103
+
104
+ # Public: Http Response code.
105
+ attr_accessor :code
106
+
107
+ # Public: Request headers.
108
+ attr_accessor :headers
109
+
110
+ # Public: Initializes a new Http Response with a reference Request.
111
+ #
112
+ # request - The request responsible for this response.
113
+ #
114
+ def initialize request
115
+ @request = request
116
+ @headers = {}
117
+ end
118
+
119
+ def etag
120
+ headers['ETag']
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,187 @@
1
+ require 'commute/core/util/event_emitter'
2
+ require 'commute/core/util/stream'
3
+
4
+ module Commute
5
+
6
+ # Internal: A Layer is one processing piece in a commute stack.
7
+ #
8
+ # A layer acts as a mini in-process server that takes its own
9
+ # requests (Layer::Request) and issues its own requests (to the
10
+ # next layer in the stack). Therefor implementing a layer
11
+ # gives you alot of rope to work with.
12
+ #
13
+ # Think of layers as seperate network components that call
14
+ # each other in a russian-doll model with the adapter as
15
+ # the most inner doll.
16
+ #
17
+ # The Layer requests and response are only for communication
18
+ # between the layers, they carry the real http requests
19
+ # and responses (access at Request#http and Response#http).
20
+ #
21
+ # The Spec for a layer is documented hereafter.
22
+ #
23
+ # A layer implements a call method that takes a router, request
24
+ # and some options from the context of that request
25
+ # meant for the layer.
26
+ #
27
+ # The router is a the router that a connects all layers of
28
+ # stack needed for this request.
29
+ #
30
+ # Take a simple layer that does not call the next layer,
31
+ # but just echoes all that is sent to it.
32
+ #
33
+ # def call router, request, options = {
34
+ # # Respond to the request right away.
35
+ # response = request.respond Response.new, Status.ok
36
+ # # Echo all data.
37
+ # request.on(:data) do |chunk|
38
+ # response.write chunk
39
+ # end
40
+ # # When the request ends, we also have no use anymore.
41
+ # request.on(:end) { response.end }
42
+ # end
43
+ #
44
+ # Sidenote: We could also have written
45
+ #
46
+ # def call router, request, options = {}
47
+ # # Respond to the request right away.
48
+ # response = request.respond Response.new
49
+ # # Connect the incoming stream with the outgoing stream.
50
+ # request.pipe response
51
+ # end
52
+ #
53
+ # A bit more complex example that shows the entire spec
54
+ # is a layer does nothing but two-way forwarding (short with piping).
55
+ #
56
+ # def call router, request, options = {}
57
+ # # Forward the request to the next layer.
58
+ # proxy_request = router.call request.http do |response, status|
59
+ # proxy_response = request.respond response.http, status
60
+ # response.pipe proxy_response
61
+ # end
62
+ #
63
+ # request.pipe proxy_request
64
+ # end
65
+ #
66
+ class Layer
67
+
68
+ # Internal: Base class for Request and Response.
69
+ # Includes some modules by default.
70
+ class Communication
71
+ include EventEmitter
72
+ include Stream
73
+
74
+ attr_reader :http
75
+
76
+ # Internal: Creates a new Request/Response.
77
+ #
78
+ # http - Underlaying http Request/Response.
79
+ #
80
+ def initialize http
81
+ super()
82
+ @http = http
83
+ end
84
+ end
85
+
86
+ # Public: An inter-Layer Request.
87
+ #
88
+ # Includes EventEmitter to listen for responses, data and request end.
89
+ # Includes Stream for stream-uploading.
90
+ #
91
+ class Request < Communication
92
+
93
+ # Public: Respond to a Request using a Http Response.
94
+ # The http response the gets wrapped in a Layer Response.
95
+ #
96
+ # http - Http Response for this Request.
97
+ # status - Any status indications for the Response.
98
+ #
99
+ # Yields the Layer response and ends the response thereafter
100
+ # if a block is given.
101
+ #
102
+ # Returns the Layer Response.
103
+ def respond http, status, &lifecycle
104
+ Response.new(http).tap do |response|
105
+ emit :response, response, status
106
+ if block_given?
107
+ lifecycle.call response
108
+ response.end
109
+ end
110
+ end
111
+ end
112
+
113
+ # Public: Returns a responder for this request.
114
+ #
115
+ # A Responder will automatically handle a response
116
+ # from a layer and use it to respond to this request.
117
+ #
118
+ # It will pipe all data from the response to the response
119
+ # for this request.
120
+ #
121
+ # Returns a block that responds to this request.
122
+ def responder
123
+ proc do |response, status|
124
+ response.pipe self.respond(response.http, status)
125
+ end
126
+ end
127
+ end
128
+
129
+ # Public: An inter-Layer Response.
130
+ #
131
+ # Includes EventEmitter to listen for data and response end.
132
+ # Includes Stream for downloading data in chunks.
133
+ #
134
+ class Response < Communication
135
+ end
136
+
137
+ # Internal: Name of the Layer.
138
+ attr_reader :name
139
+
140
+ # Internal: Creates a new Layer.
141
+ #
142
+ # callable - Object with Layer logics that responds to #call.
143
+ # name - Name of the Layer (optional).
144
+ #
145
+ def initialize callable, name = nil
146
+ @name = name \
147
+ || callable.class.instance_variable_get(:@name) \
148
+ || (callable.respond_to?(:name) && callable.name)
149
+ @callable = callable
150
+ end
151
+
152
+ # Internal: Call the Layer with options fetched out of the context
153
+ # using the name of the Layer.
154
+ #
155
+ # For example, when you add a Layer to a sequence and name it auth.
156
+ # And when you then call `with auth: { key: 'secret'}` on your
157
+ # builder, `key: 'secret' will be passed as options to the Layer.
158
+ #
159
+ # router - Path of connected Layers.
160
+ # request - Request issued for this Layer.
161
+ #
162
+ # Returns Nothing
163
+ def call router, request, options = {}
164
+ @callable.call router, request, request.http.context[@name]
165
+ nil
166
+ end
167
+
168
+ # Internal: Checks if this Layer has to be called in a certain context.
169
+ #
170
+ # context - The context the Layer is called in.
171
+ #
172
+ # Returns true if the Layer has to be called.
173
+ def callable? context
174
+ !@callable.nil? && context.enabled?(name)
175
+ end
176
+
177
+ # Internal: Two layers are equal if there callables are equal.
178
+ def == layer
179
+ self.callable == layer.callable
180
+ end
181
+
182
+ protected
183
+
184
+ # Internal: The #call object where the logic of this Layer resides.
185
+ attr_reader :callable
186
+ end
187
+ end