clientura 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,143 @@
1
+ require 'clientura/client/request'
2
+ require 'clientura/client/endpoint'
3
+ require 'clientura/client/middleware_function_context'
4
+
5
+ module Clientura
6
+ module Client
7
+ def self.included(klass)
8
+ klass.extend ClassMethods
9
+ klass.include InstanceMethods
10
+ end
11
+
12
+ module ClassMethods
13
+ def registered_endpoints
14
+ @registered_endpoints ||= {}
15
+ end
16
+
17
+ def registered_pipes
18
+ @registered_pipes ||= {}
19
+ end
20
+
21
+ def registered_middleware
22
+ @registered_middleware ||= {}
23
+ end
24
+
25
+ [:get, :post, :put, :patch, :delete].each do |verb|
26
+ define_method verb do |name, path: nil|
27
+ register_endpoint(name, verb: verb, path: path || name.to_s)
28
+ end
29
+ end
30
+
31
+ def register_endpoint(name, verb:, path:)
32
+ registered_endpoints[name] = Endpoint.new verb,
33
+ path,
34
+ [*@middleware_context],
35
+ [*@pipes_context]
36
+
37
+ define_method name do |args = {}|
38
+ call_endpoint name, args
39
+ end
40
+
41
+ define_method "#{name}_promise" do |args = {}|
42
+ Concurrent::Promise.execute { send(name, args) }
43
+ end
44
+ end
45
+
46
+ def pipe(name, callable)
47
+ registered_pipes[name] = callable
48
+ end
49
+
50
+ def middleware(name, callable)
51
+ registered_middleware[name] = callable
52
+ end
53
+
54
+ def pipe_through(*pipes)
55
+ pipes = pipes.map { |o| normalize_mapper o }
56
+
57
+ @pipes_context ||= []
58
+ @pipes_context.push(*pipes)
59
+ yield
60
+ @pipes_context.pop pipes.count
61
+ end
62
+
63
+ def use_middleware(*middleware)
64
+ middleware = middleware.map { |o| normalize_mapper o }
65
+
66
+ @middleware_context ||= []
67
+ @middleware_context.push(*middleware)
68
+ yield
69
+ @middleware_context.pop middleware.count
70
+ end
71
+
72
+ def normalize_mapper(mapper)
73
+ case mapper
74
+ when Array
75
+ name, *config = mapper
76
+ { name: name, config: config }
77
+ else
78
+ { name: mapper, config: [] }
79
+ end
80
+ end
81
+ end
82
+
83
+ module InstanceMethods
84
+ def self.included(klass)
85
+ define_method :registered_endpoints do
86
+ klass.registered_endpoints
87
+ end
88
+
89
+ define_method :registered_pipes do
90
+ klass.registered_pipes
91
+ end
92
+
93
+ define_method :registered_middleware do
94
+ klass.registered_middleware
95
+ end
96
+ end
97
+
98
+ attr_writer :config
99
+
100
+ def config
101
+ @config ||= {}
102
+ end
103
+
104
+ def save_config(args)
105
+ self.config = config.merge args
106
+ end
107
+
108
+ def call_endpoint(name_, args)
109
+ endpoint = registered_endpoints.fetch(name_)
110
+
111
+ path = if endpoint.path.respond_to?(:call)
112
+ endpoint.path.call(args)
113
+ else
114
+ endpoint.path
115
+ end
116
+
117
+ middlewares = endpoint.middleware.map do |name:, config:|
118
+ { callable: registered_middleware.fetch(name), config: config }
119
+ end
120
+
121
+ request = middlewares
122
+ .reduce Request.new do |request_, callable:, config:|
123
+ middleware = MiddlewareFunctionContext.new(request: request_,
124
+ client: self,
125
+ args: args,
126
+ callable: callable,
127
+ config: config)
128
+ middleware.call
129
+ end
130
+
131
+ response = request.send(endpoint.verb, path)
132
+
133
+ endpoints = endpoint.pipes.map do |name:, config:|
134
+ -> (res) { registered_pipes.fetch(name).call(res, *config) }
135
+ end
136
+
137
+ endpoints.reduce response do |response_, pipe|
138
+ pipe.call response_
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,3 @@
1
+ module Clientura
2
+ VERSION = File.read(File.expand_path('../../../VERSION/', __FILE__)).freeze
3
+ end
data/lib/clientura.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'http'
2
+ require 'concurrent'
3
+ require 'concurrent-edge'
4
+ require 'active_support/core_ext/object'
5
+ require 'active_support/json'
6
+
7
+ require 'clientura/version'
8
+ require 'clientura/client'
9
+
10
+ module Clientura
11
+ end
@@ -0,0 +1,204 @@
1
+ describe 'Ability to use this for solving real world problems' do
2
+ # Purpose of this spec is to define some real world use cases in a clean and
3
+ # readable manner to serve as an example of usage.
4
+
5
+ subject { instance }
6
+
7
+ let(:instance) { client_class.new(client_config) }
8
+ let(:client_config) { { uri: 'http://localhost:3001/real_world/' } }
9
+ let(:client_class) do
10
+ Class.new do
11
+ include Clientura::Client
12
+
13
+ middleware :configurable_uri, -> { update(:uri) { client.config[:uri] } }
14
+ middleware :header_with_token, -> { headers AuthToken: client.config[:token] }
15
+ middleware :pass_all_as_query_string, -> { update(:params) { args } }
16
+ middleware :namespace, -> (namespace) { update(:uri) { |uri| URI.join uri, namespace } }
17
+ middleware :bad_middleware, -> { raise 'Some Exception' }
18
+ middleware :slow_middleware, -> { sleep 0.1; self }
19
+
20
+ pipe :body_retriever, -> (res) { res.body.to_s }
21
+ pipe :parser, -> (res, parser) { parser.parse res }
22
+ pipe :answer_header, -> (res) { res.headers['Answer'] }
23
+ pipe :data_retriever, -> (res) { res['data'] }
24
+
25
+ use_middleware :configurable_uri do
26
+ get :root, path: '/'
27
+
28
+ use_middleware :header_with_token do
29
+ get :pass_token
30
+ end
31
+
32
+ pipe_through :body_retriever do
33
+ get :get_some, path: 'some'
34
+ put :put_some, path: 'some'
35
+ patch :patch_some, path: 'some'
36
+ post :post_some, path: 'some'
37
+ delete :delete_some, path: 'some'
38
+
39
+ pipe_through [:parser, JSON] do
40
+ get :parse_response
41
+
42
+ pipe_through :data_retriever do
43
+ get :comments, path: -> (params) { "comments/#{params[:id]}" }
44
+ get :users, path: -> (params) { "users/#{params[:id]}" }
45
+ get :attachments, path: -> (params) { "attachments/#{params[:id]}" }
46
+ end
47
+ end
48
+
49
+ use_middleware :pass_all_as_query_string do
50
+ get :pass_query_string
51
+ end
52
+ end
53
+
54
+ use_middleware [:namespace, 'namespace/'] do
55
+ get :namespaced
56
+ end
57
+
58
+ pipe_through :answer_header do
59
+ get :get_answer_header
60
+ end
61
+ end
62
+
63
+ use_middleware :bad_middleware do
64
+ get :raise_some_exception
65
+ end
66
+
67
+ use_middleware :slow_middleware, :bad_middleware do
68
+ get :slowly_raise_some_exception
69
+ end
70
+
71
+ def initialize(uri:, token: nil)
72
+ save_config uri: uri, token: token
73
+ end
74
+ end
75
+ end
76
+
77
+ describe 'My wish to see this at least working' do
78
+ subject { super().root.status }
79
+
80
+ it { should eq 200 }
81
+ end
82
+
83
+ describe 'My desire to use different verbs' do
84
+ describe 'GET' do
85
+ subject { super().get_some }
86
+
87
+ it { should eq 'get' }
88
+ end
89
+
90
+ describe 'POST' do
91
+ subject { super().post_some }
92
+
93
+ it { should eq 'post' }
94
+ end
95
+
96
+ describe 'PUT' do
97
+ subject { super().put_some }
98
+
99
+ it { should eq 'put' }
100
+ end
101
+
102
+ describe 'PATCH' do
103
+ subject { super().patch_some }
104
+
105
+ it { should eq 'patch' }
106
+ end
107
+
108
+ describe 'DELETE' do
109
+ subject { super().delete_some }
110
+
111
+ it { should eq 'delete' }
112
+ end
113
+ end
114
+
115
+ describe 'My desire to see some exceptions' do
116
+ subject { -> { instance.raise_some_exception } }
117
+
118
+ it { should raise_error 'Some Exception' }
119
+ end
120
+
121
+ describe 'My desire to work with promise' do
122
+ context 'when no errors raised' do
123
+ subject { super().root_promise }
124
+
125
+ it { should be_pending }
126
+
127
+ it 'has correct value' do
128
+ expect(subject.value.status).to eq 200
129
+ end
130
+
131
+ context 'when awaited' do
132
+ before { subject.value }
133
+
134
+ it 'fulfills' do
135
+ expect(subject).to be_fulfilled
136
+ end
137
+ end
138
+ end
139
+
140
+ context 'when error raised' do
141
+ subject { super().slowly_raise_some_exception_promise }
142
+
143
+ it { should be_pending }
144
+
145
+ it 'has nil value' do
146
+ expect(subject.value).to be nil
147
+ end
148
+
149
+ context 'when awaited' do
150
+ before { subject.value }
151
+
152
+ it 'rejects' do
153
+ expect(subject).to be_rejected
154
+ end
155
+
156
+ it 'has correct message' do
157
+ expect(subject.reason.message).to eq 'Some Exception'
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ describe 'My desire to pass some token in header' do
164
+ subject { super().pass_token.status }
165
+
166
+ let(:client_config) { super().merge token: token }
167
+
168
+ context 'when token is bad' do
169
+ let(:token) { 'Bad token!' }
170
+
171
+ it { should eq 403 }
172
+ end
173
+
174
+ context 'when token is good' do
175
+ let(:token) { 'Secret' }
176
+
177
+ it { should eq 200 }
178
+ end
179
+ end
180
+
181
+ describe 'My desire to parse response' do
182
+ subject { super().parse_response }
183
+
184
+ it { should eq 'data' => 'Awesome!' }
185
+ end
186
+
187
+ describe 'My desire to pass query string' do
188
+ subject { super().pass_query_string echo: 'Awesome!' }
189
+
190
+ it { should eq 'Awesome!' }
191
+ end
192
+
193
+ describe 'My desire to get response header' do
194
+ subject { super().get_answer_header }
195
+
196
+ it { should eq 'Awesome!' }
197
+ end
198
+
199
+ describe 'My desire to namespace routes' do
200
+ subject { super().namespaced.status }
201
+
202
+ it { should eq 200 }
203
+ end
204
+ end
@@ -0,0 +1,175 @@
1
+ describe Clientura::Client do
2
+ subject { client }
3
+
4
+ let(:uri) { 'http://localhost:3001' }
5
+ let(:client) { klass.new(uri: uri, token: 'InitializationToken') }
6
+ let(:klass) do
7
+ Class.new do
8
+ include Clientura::Client
9
+
10
+ middleware :static_token, -> (token) { headers(Token: token) }
11
+ middleware :init_token, -> { headers(token: client.config[:token]) }
12
+ middleware :token_passer, -> { headers(token: args[:token]) }
13
+ middleware :send_as_json, -> { update(:json) { args } }
14
+ middleware :pass_as_query, -> { update(:params) { |p| p.merge args } }
15
+ middleware :configurable_uri, lambda {
16
+ update(:uri) { |uri| URI.join client.config[:uri], uri }
17
+ }
18
+
19
+ pipe :body_retriever, -> (res) { res.body.to_s }
20
+ pipe :parser, -> (res, parser) { parser.parse res }
21
+ pipe :data_retriever, -> (res) { res['data'] }
22
+
23
+ use_middleware :configurable_uri do
24
+ pipe_through :body_retriever do
25
+ get :resource_raw, path: 'res'
26
+
27
+ pipe_through [:parser, JSON] do
28
+ get :root, path: '/'
29
+ get :resource, path: 'res'
30
+ get :name
31
+ get :echo_argument, path: -> (params) { "echo_argument/#{params[:argument]}" }
32
+
33
+ use_middleware :pass_as_query do
34
+ get :echo_param
35
+ end
36
+ end
37
+ end
38
+
39
+ pipe_through :body_retriever, [:parser, JSON] do
40
+ use_middleware [:static_token, 'StaticToken'] do
41
+ get :try_static_token, path: 'echo_header'
42
+ end
43
+
44
+ use_middleware [:static_token, 'Token'] do
45
+ get :echo_header_const, path: 'echo_header'
46
+ end
47
+
48
+ use_middleware :token_passer do
49
+ get :try_token_passer, path: 'echo_header'
50
+ get :echo_header
51
+ end
52
+
53
+ use_middleware :init_token do
54
+ get :try_init_token, path: 'echo_header'
55
+ end
56
+
57
+ get :configurable_uri_resource, path: 'res'
58
+
59
+ use_middleware :send_as_json do
60
+ post :send_json, path: 'data'
61
+ end
62
+ end
63
+ end
64
+
65
+ use_middleware :configurable_uri do
66
+ pipe_through :body_retriever, [:parser, JSON], :data_retriever do
67
+ use_middleware :send_as_json do
68
+ post :sum
69
+ end
70
+
71
+ get :comments, path: -> (params) { "comments/#{params[:id]}" }
72
+ get :users, path: -> (params) { "users/#{params[:id]}" }
73
+ get :tags, path: -> (params) { "tags/#{params[:id]}" }
74
+
75
+ use_middleware :pass_as_query do
76
+ get :left_operand
77
+ get :right_operand
78
+ end
79
+ end
80
+ end
81
+
82
+ def initialize(uri:, token:)
83
+ save_config uri: URI.parse(uri), token: token
84
+ end
85
+ end
86
+ end
87
+
88
+ describe '#root' do
89
+ subject { client.root }
90
+
91
+ it { should eq 'data' => 'root' }
92
+ end
93
+
94
+ describe '#resource' do
95
+ subject { client.resource }
96
+
97
+ it { should eq 'data' => 'resource' }
98
+ end
99
+
100
+ describe '#name' do
101
+ subject { client.name }
102
+
103
+ it { should eq 'data' => 'name' }
104
+ end
105
+
106
+ describe '#echo_argument' do
107
+ subject { client.echo_argument argument: 'Argument' }
108
+
109
+ it { should eq 'data' => 'Argument' }
110
+ end
111
+
112
+ describe '#echo_param' do
113
+ subject { client.echo_param param: 'Param' }
114
+
115
+ it { should eq 'data' => 'Param' }
116
+ end
117
+
118
+ describe '#echo_header' do
119
+ subject { client.echo_header token: 'Token' }
120
+
121
+ it { should eq 'data' => 'Token' }
122
+ end
123
+
124
+ describe '#echo_header_const' do
125
+ subject { client.echo_header_const }
126
+
127
+ it { should eq 'data' => 'Token' }
128
+ end
129
+
130
+ describe '#resource_raw' do
131
+ subject { client.resource_raw }
132
+
133
+ it { should eq JSON.dump data: 'resource' }
134
+ end
135
+
136
+ describe 'try_static_token' do
137
+ subject { client.try_static_token }
138
+
139
+ it { should eq 'data' => 'StaticToken' }
140
+ end
141
+
142
+ describe 'try_token_passer' do
143
+ subject { client.try_token_passer token: 'PassedToken' }
144
+
145
+ it { should eq 'data' => 'PassedToken' }
146
+ end
147
+
148
+ describe 'try_init_token' do
149
+ subject { client.try_init_token }
150
+
151
+ it { should eq 'data' => 'InitializationToken' }
152
+ end
153
+
154
+ describe 'configurable_uri_resource' do
155
+ subject { client.configurable_uri_resource }
156
+
157
+ it { should eq 'data' => 'resource' }
158
+ end
159
+
160
+ describe '#send_json' do
161
+ subject { client.send_json json_data: { foo: :bar } }
162
+
163
+ it { should eq 'data' => { 'foo' => 'bar' } }
164
+ end
165
+
166
+ describe '#sum' do
167
+ subject { client.sum sum: sum }
168
+
169
+ let(:sum) { client.left_operand(key: 'Secret') + client.right_operand(key: 'Secret') }
170
+
171
+ it { should eq true }
172
+ end
173
+
174
+ # it('__run_server__', :focus) { binding.pry }
175
+ end
@@ -0,0 +1,9 @@
1
+ # require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Clientura do
4
+ describe Clientura::VERSION do
5
+ subject { described_class }
6
+
7
+ it { should_not be_blank }
8
+ end
9
+ end
@@ -0,0 +1,64 @@
1
+ require 'simplecov'
2
+ require 'codeclimate-test-reporter'
3
+
4
+ module SimpleCov::Configuration
5
+ def clean_filters
6
+ @filters = []
7
+ end
8
+ end
9
+
10
+ SimpleCov.configure do
11
+ clean_filters
12
+ load_profile 'test_frameworks'
13
+ end
14
+
15
+ ENV['COVERAGE'] && SimpleCov.start do
16
+ add_filter '/.rvm/'
17
+ end
18
+
19
+ CodeClimate::TestReporter.start
20
+
21
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
22
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
23
+
24
+ require 'bundler'
25
+ Bundler.require(:test)
26
+ require 'clientura'
27
+
28
+ # Requires supporting files with custom matchers and macros, etc,
29
+ # in ./support/ and its subdirectories.
30
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
31
+
32
+ RSpec.configure do |config|
33
+ # config.expose_current_running_example_as :example
34
+ config.raise_errors_for_deprecations!
35
+ config.run_all_when_everything_filtered = true
36
+ config.filter_run focus: true
37
+ # config.mock_with :rspec
38
+ # config.use_transactional_fixtures = false
39
+
40
+ config.order = :rand
41
+
42
+ config.before(:suite) do |_example|
43
+ pid = Process.fork do
44
+ Rack::Handler::Thin.run TestServer, Port: 3001
45
+ # trap(:INT) { Rack::Handler::WEBrick.shutdown }
46
+ # Rack::Handler::WEBrick.run TestServer.new,
47
+ # Port: 3001,
48
+ # Logger: WEBrick::Log.new('/dev/null'),
49
+ # AccessLog: []
50
+ exit
51
+ end
52
+
53
+ at_exit do
54
+ Process.kill('INT', pid)
55
+ begin
56
+ Process.wait(pid)
57
+ rescue Errno::ECHILD
58
+ # ignore this error...I think it means the child process has already exited.
59
+ end
60
+ end
61
+
62
+ sleep 1
63
+ end
64
+ end