evil-client 0.3.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +0 -11
- data/.gitignore +1 -0
- data/.rspec +0 -1
- data/.rubocop.yml +22 -19
- data/.travis.yml +1 -0
- data/CHANGELOG.md +251 -6
- data/LICENSE.txt +3 -1
- data/README.md +47 -81
- data/docs/helpers/body.md +93 -0
- data/docs/helpers/connection.md +19 -0
- data/docs/helpers/headers.md +72 -0
- data/docs/helpers/http_method.md +39 -0
- data/docs/helpers/let.md +14 -0
- data/docs/helpers/logger.md +24 -0
- data/docs/helpers/middleware.md +56 -0
- data/docs/helpers/operation.md +103 -0
- data/docs/helpers/option.md +50 -0
- data/docs/helpers/path.md +37 -0
- data/docs/helpers/query.md +59 -0
- data/docs/helpers/response.md +40 -0
- data/docs/helpers/scope.md +121 -0
- data/docs/helpers/security.md +102 -0
- data/docs/helpers/validate.md +68 -0
- data/docs/index.md +70 -78
- data/docs/license.md +5 -1
- data/docs/rspec.md +96 -0
- data/evil-client.gemspec +10 -8
- data/lib/evil/client.rb +126 -72
- data/lib/evil/client/builder.rb +47 -0
- data/lib/evil/client/builder/operation.rb +40 -0
- data/lib/evil/client/builder/scope.rb +31 -0
- data/lib/evil/client/chaining.rb +17 -0
- data/lib/evil/client/connection.rb +60 -20
- data/lib/evil/client/container.rb +66 -0
- data/lib/evil/client/container/operation.rb +23 -0
- data/lib/evil/client/container/scope.rb +28 -0
- data/lib/evil/client/exceptions/definition_error.rb +15 -0
- data/lib/evil/client/exceptions/name_error.rb +32 -0
- data/lib/evil/client/exceptions/response_error.rb +42 -0
- data/lib/evil/client/exceptions/type_error.rb +29 -0
- data/lib/evil/client/exceptions/validation_error.rb +27 -0
- data/lib/evil/client/formatter.rb +49 -0
- data/lib/evil/client/formatter/form.rb +45 -0
- data/lib/evil/client/formatter/multipart.rb +33 -0
- data/lib/evil/client/formatter/part.rb +66 -0
- data/lib/evil/client/formatter/text.rb +21 -0
- data/lib/evil/client/resolver.rb +84 -0
- data/lib/evil/client/resolver/body.rb +22 -0
- data/lib/evil/client/resolver/format.rb +30 -0
- data/lib/evil/client/resolver/headers.rb +46 -0
- data/lib/evil/client/resolver/http_method.rb +34 -0
- data/lib/evil/client/resolver/middleware.rb +36 -0
- data/lib/evil/client/resolver/query.rb +39 -0
- data/lib/evil/client/resolver/request.rb +96 -0
- data/lib/evil/client/resolver/response.rb +26 -0
- data/lib/evil/client/resolver/security.rb +113 -0
- data/lib/evil/client/resolver/uri.rb +35 -0
- data/lib/evil/client/rspec.rb +127 -0
- data/lib/evil/client/schema.rb +105 -0
- data/lib/evil/client/schema/operation.rb +177 -0
- data/lib/evil/client/schema/scope.rb +73 -0
- data/lib/evil/client/settings.rb +172 -0
- data/lib/evil/client/settings/validator.rb +64 -0
- data/mkdocs.yml +21 -15
- data/spec/features/custom_connection_spec.rb +17 -0
- data/spec/features/operation/middleware_spec.rb +50 -0
- data/spec/features/operation/options_spec.rb +71 -0
- data/spec/features/operation/request_spec.rb +94 -0
- data/spec/features/operation/response_spec.rb +48 -0
- data/spec/features/scope/options_spec.rb +52 -0
- data/spec/fixtures/locales/en.yml +16 -0
- data/spec/fixtures/test_client.rb +76 -0
- data/spec/spec_helper.rb +18 -6
- data/spec/support/fixtures_helper.rb +7 -0
- data/spec/unit/builder/operation_spec.rb +90 -0
- data/spec/unit/builder/scope_spec.rb +84 -0
- data/spec/unit/client_spec.rb +137 -0
- data/spec/unit/connection_spec.rb +78 -0
- data/spec/unit/container/operation_spec.rb +81 -0
- data/spec/unit/container/scope_spec.rb +61 -0
- data/spec/unit/container_spec.rb +107 -0
- data/spec/unit/exceptions/definition_error_spec.rb +15 -0
- data/spec/unit/exceptions/name_error_spec.rb +77 -0
- data/spec/unit/exceptions/response_error_spec.rb +22 -0
- data/spec/unit/exceptions/type_error_spec.rb +71 -0
- data/spec/unit/exceptions/validation_error_spec.rb +13 -0
- data/spec/unit/formatter/form_spec.rb +27 -0
- data/spec/unit/formatter/multipart_spec.rb +23 -0
- data/spec/unit/formatter/part_spec.rb +49 -0
- data/spec/unit/formatter/text_spec.rb +37 -0
- data/spec/unit/formatter_spec.rb +46 -0
- data/spec/unit/resolver/body_spec.rb +65 -0
- data/spec/unit/resolver/format_spec.rb +66 -0
- data/spec/unit/resolver/headers_spec.rb +93 -0
- data/spec/unit/resolver/http_method_spec.rb +67 -0
- data/spec/unit/resolver/middleware_spec.rb +83 -0
- data/spec/unit/resolver/query_spec.rb +85 -0
- data/spec/unit/resolver/request_spec.rb +121 -0
- data/spec/unit/resolver/response_spec.rb +64 -0
- data/spec/unit/resolver/security_spec.rb +156 -0
- data/spec/unit/resolver/uri_spec.rb +117 -0
- data/spec/unit/rspec_spec.rb +342 -0
- data/spec/unit/schema/operation_spec.rb +309 -0
- data/spec/unit/schema/scope_spec.rb +110 -0
- data/spec/unit/schema_spec.rb +157 -0
- data/spec/unit/settings/validator_spec.rb +128 -0
- data/spec/unit/settings_spec.rb +248 -0
- metadata +192 -135
- data/docs/base_url.md +0 -38
- data/docs/documentation.md +0 -9
- data/docs/headers.md +0 -59
- data/docs/http_method.md +0 -31
- data/docs/model.md +0 -173
- data/docs/operation.md +0 -0
- data/docs/overview.md +0 -0
- data/docs/path.md +0 -48
- data/docs/query.md +0 -99
- data/docs/responses.md +0 -66
- data/docs/security.md +0 -102
- data/docs/settings.md +0 -32
- data/lib/evil/client/connection/net_http.rb +0 -57
- data/lib/evil/client/dsl.rb +0 -127
- data/lib/evil/client/dsl/base.rb +0 -26
- data/lib/evil/client/dsl/files.rb +0 -37
- data/lib/evil/client/dsl/headers.rb +0 -16
- data/lib/evil/client/dsl/http_method.rb +0 -24
- data/lib/evil/client/dsl/operation.rb +0 -91
- data/lib/evil/client/dsl/operations.rb +0 -41
- data/lib/evil/client/dsl/path.rb +0 -25
- data/lib/evil/client/dsl/query.rb +0 -16
- data/lib/evil/client/dsl/response.rb +0 -61
- data/lib/evil/client/dsl/responses.rb +0 -29
- data/lib/evil/client/dsl/scope.rb +0 -27
- data/lib/evil/client/dsl/security.rb +0 -57
- data/lib/evil/client/dsl/verifier.rb +0 -35
- data/lib/evil/client/middleware.rb +0 -81
- data/lib/evil/client/middleware/base.rb +0 -11
- data/lib/evil/client/middleware/merge_security.rb +0 -20
- data/lib/evil/client/middleware/normalize_headers.rb +0 -17
- data/lib/evil/client/middleware/stringify_form.rb +0 -40
- data/lib/evil/client/middleware/stringify_json.rb +0 -19
- data/lib/evil/client/middleware/stringify_multipart.rb +0 -36
- data/lib/evil/client/middleware/stringify_multipart/part.rb +0 -36
- data/lib/evil/client/middleware/stringify_query.rb +0 -35
- data/lib/evil/client/operation.rb +0 -34
- data/lib/evil/client/operation/request.rb +0 -26
- data/lib/evil/client/operation/response.rb +0 -39
- data/lib/evil/client/operation/response_error.rb +0 -13
- data/lib/evil/client/operation/unexpected_response_error.rb +0 -19
- data/spec/features/instantiation_spec.rb +0 -68
- data/spec/features/middleware_spec.rb +0 -79
- data/spec/features/operation_with_documentation_spec.rb +0 -41
- data/spec/features/operation_with_files_spec.rb +0 -40
- data/spec/features/operation_with_form_body_spec.rb +0 -158
- data/spec/features/operation_with_headers_spec.rb +0 -99
- data/spec/features/operation_with_http_method_spec.rb +0 -45
- data/spec/features/operation_with_json_body_spec.rb +0 -156
- data/spec/features/operation_with_nested_responses_spec.rb +0 -95
- data/spec/features/operation_with_path_spec.rb +0 -47
- data/spec/features/operation_with_query_spec.rb +0 -84
- data/spec/features/operation_with_security_spec.rb +0 -228
- data/spec/features/scoping_spec.rb +0 -48
- data/spec/support/test_client.rb +0 -15
- data/spec/unit/evil/client/connection/net_http_spec.rb +0 -38
- data/spec/unit/evil/client/dsl/files_spec.rb +0 -37
- data/spec/unit/evil/client/dsl/operation_spec.rb +0 -374
- data/spec/unit/evil/client/dsl/operations_spec.rb +0 -29
- data/spec/unit/evil/client/dsl/scope_spec.rb +0 -32
- data/spec/unit/evil/client/dsl/security_spec.rb +0 -135
- data/spec/unit/evil/client/middleware/merge_security_spec.rb +0 -32
- data/spec/unit/evil/client/middleware/normalize_headers_spec.rb +0 -17
- data/spec/unit/evil/client/middleware/stringify_form_spec.rb +0 -63
- data/spec/unit/evil/client/middleware/stringify_json_spec.rb +0 -61
- data/spec/unit/evil/client/middleware/stringify_multipart/part_spec.rb +0 -59
- data/spec/unit/evil/client/middleware/stringify_multipart_spec.rb +0 -62
- data/spec/unit/evil/client/middleware/stringify_query_spec.rb +0 -40
- data/spec/unit/evil/client/middleware_spec.rb +0 -46
- data/spec/unit/evil/client/operation/request_spec.rb +0 -49
- data/spec/unit/evil/client/operation/response_spec.rb +0 -63
@@ -0,0 +1,93 @@
|
|
1
|
+
Use helpers `body` and `format` to define content of http message, and how it should be formatted.
|
2
|
+
|
3
|
+
We support several formats, namely: `:json` (default), `:text`, `:form`, `:yaml`, and `:multipart`.
|
4
|
+
|
5
|
+
## Plain Formats
|
6
|
+
|
7
|
+
With default `:json` format, the body should be a hash:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class CatsAPI < Evil::Client
|
11
|
+
format { :json }
|
12
|
+
# ...
|
13
|
+
scope :cats do
|
14
|
+
# ...
|
15
|
+
operation :create do
|
16
|
+
option :species
|
17
|
+
option :name
|
18
|
+
|
19
|
+
body { { species: species, name: name } }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
Before sending to the stack of [middleware], it will be dumped:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
CatsAPI.cats.create species: "Acinonyx jubatus", name: "Cheetah"
|
29
|
+
# sends a request with a body: '{"species":"Acinonyx jubatus","name":"Cheetah"}'
|
30
|
+
# and a header "Content-Type": "application/json"
|
31
|
+
```
|
32
|
+
|
33
|
+
The same content, but formatted as `:yaml` will send another body and header:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class CatsAPI < Evil::Client
|
37
|
+
format { :yaml }
|
38
|
+
end
|
39
|
+
|
40
|
+
CatsAPI.cats.create species: "Acinonyx jubatus", name: "Cheetah"
|
41
|
+
# sends a request with a body: "---\n:species: Acinonyx jubatus\n:name: Cheetah\n'
|
42
|
+
# and a header "Content-Type": "application/yaml"
|
43
|
+
```
|
44
|
+
|
45
|
+
The `:form` format will make body url encoded:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class CatsAPI < Evil::Client
|
49
|
+
format { :form }
|
50
|
+
end
|
51
|
+
|
52
|
+
CatsAPI.cats.create species: "Acinonyx jubatus", name: "Cheetah"
|
53
|
+
# sends a request with a body: "species=Acinonyx jubatus&name=Cheetah"
|
54
|
+
# and a header "Content-Type": "application/x-www-form-urlencoded"
|
55
|
+
```
|
56
|
+
|
57
|
+
The `:text` just stringifies it as is:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
class CatsAPI < Evil::Client
|
61
|
+
format { :text }
|
62
|
+
end
|
63
|
+
|
64
|
+
CatsAPI.cats.create species: "Acinonyx jubatus", name: "Cheetah"
|
65
|
+
# sends a request with a body: '{:species=>"Acionyx jubatus",:name=>"Cheetah"}'
|
66
|
+
# and a header "Content-Type": "text/plain"
|
67
|
+
```
|
68
|
+
|
69
|
+
This format doesn't require the body to be hash. It can be anything.
|
70
|
+
|
71
|
+
## Multipart Formats
|
72
|
+
|
73
|
+
When you need sending files you should select the `:multipart` format. This time the whole body is formatted as `form/multipart`.
|
74
|
+
Depending on its type (hash, file, StringIO), the content will be formatted correspondingly. Arrays are treated as several parts of the body. All other objects will be stringified.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
class CatsAPI < Evil::Client
|
78
|
+
# ...
|
79
|
+
scope :cats do
|
80
|
+
# ...
|
81
|
+
operation :upload do
|
82
|
+
option :files, method(:Array)
|
83
|
+
|
84
|
+
format { :multipart }
|
85
|
+
body { files }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
CatsAPI.cats.upload files: [StringIO.new("Cheetah"), StrinIO.new("Lion")]
|
91
|
+
```
|
92
|
+
|
93
|
+
[middleware]:
|
@@ -0,0 +1,19 @@
|
|
1
|
+
By default, Evil Client uses [Net::HTTP][net-http]-based connection to send requests.
|
2
|
+
|
3
|
+
If you need another user agent, change it to you client via connection writer. The connection can be an object with method `#call` that takes [rack environment][rack-env] (from stack of middleware) and returns [rack response][rack-response] back.
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
conn = Object.new do
|
7
|
+
def self.call(env)
|
8
|
+
[200, {"Content-Type"=>"application/json"}, ['{"age":7}']]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class CatsClient < Evil::Client
|
13
|
+
connection = conn
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
[net-http]: http://ruby-doc.org/stdlib-2.4.1/libdoc/net/http/rdoc/Net/HTTP.html
|
18
|
+
[rack-env]: http://www.rubydoc.info/github/rack/rack/file/SPEC#The_Environment
|
19
|
+
[rack-response]: http://www.rubydoc.info/github/rack/rack/file/SPEC#The_Response
|
@@ -0,0 +1,72 @@
|
|
1
|
+
Use method `headers` to add several headers to a request
|
2
|
+
|
3
|
+
```ruby
|
4
|
+
class CatsClient < Evil::Client
|
5
|
+
operation :fetch do
|
6
|
+
option :id
|
7
|
+
option :language
|
8
|
+
|
9
|
+
headers { { "Accept-Language" => language } }
|
10
|
+
# ...
|
11
|
+
end
|
12
|
+
end
|
13
|
+
```
|
14
|
+
|
15
|
+
Remember that you can define header values as a flat array instead of the string. Inside an array all the values are counted as srings.
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
headers { "Language" => ["ru_RU", "charset=utf-8"] }
|
19
|
+
```
|
20
|
+
|
21
|
+
You can define headers bit-by-bit starting from a root scope:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class CatsClient < Evil::Client
|
25
|
+
option :charset, default: -> { "utf-8" }
|
26
|
+
headers { { "Accept-Charset" => charset } }
|
27
|
+
|
28
|
+
operation :fetch do
|
29
|
+
option :id
|
30
|
+
option :language
|
31
|
+
|
32
|
+
headers { { "Accept-Language" => language } }
|
33
|
+
# ...
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
CatsClient.new(charset: "ascii-1251").fetch(id: 3, language: "en_US")
|
38
|
+
# This would send a request with the headers:
|
39
|
+
# { "Accept-Charset" => "ascii-1251", "Accept-Language" => "en_US" }
|
40
|
+
```
|
41
|
+
|
42
|
+
When you redefine some of root options, this redefinition can affects headers:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
CatsClient.new(charset: "ascii-1251")
|
46
|
+
.fetch(id: 3, language: "en_US", charset: "utf-16")
|
47
|
+
|
48
|
+
# This would send a request with the headers:
|
49
|
+
# { "Accept-Charset" => "utf-16", "Accept-Language" => "en_US" }
|
50
|
+
```
|
51
|
+
|
52
|
+
As a rule, you shouldn't define authorization headers in this way. Use [the security helper][security] instead.
|
53
|
+
|
54
|
+
**Remember** that eventual collection of request headers is also affected by [security][security] (sets `Authentication`), and [format][format] (sets `Content-Type`) helpers. You can add request headers via [middleware] as well. Finally, the [connection] adds some headers (like `User-Agent`) by its own.
|
55
|
+
|
56
|
+
When you define both headers and security settings at the same time, the priority will be given to security. This isn't depend on where (root scope or its sub-scopes) security and headers parts are defined. Security settings will always be written over the same headers.
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
class CatsAPI < Evil::Client
|
60
|
+
security { key_auth :Authentication, "Bar" }
|
61
|
+
|
62
|
+
scope :cats do
|
63
|
+
headers { { Authentication: "Foo" } }
|
64
|
+
# will set "Authentication" => "Bar" (not "Foo")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
[security]:
|
70
|
+
[format]:
|
71
|
+
[middleware]:
|
72
|
+
[connection]:
|
@@ -0,0 +1,39 @@
|
|
1
|
+
Define [http method] for sending a request using the `http_method` helper.
|
2
|
+
|
3
|
+
```ruby
|
4
|
+
operation :fetch do
|
5
|
+
http_method :get
|
6
|
+
# ...
|
7
|
+
end
|
8
|
+
```
|
9
|
+
|
10
|
+
As usual, you have access to current options. This can be useful to make the method dependent from either a version, or another variation of API.
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
operation :fetch do
|
14
|
+
# ...
|
15
|
+
option :version, proc(&:to_i)
|
16
|
+
|
17
|
+
http_method { version > 2 ? :post : :get }
|
18
|
+
# ...
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
The definition can be reloaded at any level of scoping.
|
23
|
+
|
24
|
+
Following [RFC 7231], we support only valid methods (they could be set as case-insensitive stringified object):
|
25
|
+
|
26
|
+
- GET
|
27
|
+
- POST
|
28
|
+
- PUT
|
29
|
+
- PATCH
|
30
|
+
- DELETE
|
31
|
+
- OPTIONS
|
32
|
+
- HEAD
|
33
|
+
- TRACE
|
34
|
+
- CONNECT
|
35
|
+
|
36
|
+
Setting http method to another value, or missing it, will cause an exception.
|
37
|
+
|
38
|
+
[http method]:
|
39
|
+
[RFC 7231]: https://tools.ietf.org/html/rfc7231
|
data/docs/helpers/let.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Use `let` helper to add virtial memoized attributes to the scope/operation.
|
2
|
+
|
3
|
+
```ruby
|
4
|
+
class CatsAPI < Evil::Client
|
5
|
+
option :version, default: proc { 0 }
|
6
|
+
option :release, default: proc { 1 }
|
7
|
+
|
8
|
+
let(:full_version) { [version, release].join(".") } # "0.1" by default
|
9
|
+
end
|
10
|
+
```
|
11
|
+
|
12
|
+
These virtual attributes are available inside all block declarations, including [validations].
|
13
|
+
|
14
|
+
[validations]:
|
@@ -0,0 +1,24 @@
|
|
1
|
+
You can assign a logger to any instance of operation/scope.
|
2
|
+
|
3
|
+
As a rule you would do this assignment to the initialized client:
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
client = CatsClient.new(token: "foobar")
|
7
|
+
log = StringIO.new
|
8
|
+
logger = Logger.new log
|
9
|
+
```
|
10
|
+
|
11
|
+
The client will publish debag messages to the log every time it sets instance of scope/operation schema, or initializes them with some options. Requests and responses are logged at the info level.
|
12
|
+
|
13
|
+
Later you can check the log:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
client.cats.fetch id: 83
|
17
|
+
|
18
|
+
log.string
|
19
|
+
# D, [2017-07-30T22:23:50.262734 #23474] DEBUG -- CatsApi.cats: initializing with options {}...
|
20
|
+
# D, [2017-07-30T22:23:50.263240 #23474] DEBUG -- #<CatsApi.cats:0x000000034d2710 @token="foobar"> initialized
|
21
|
+
# D, [2017-07-30T22:23:50.262734 #23474] DEBUG -- CatsApi.cats.fetch: initializing with options {"token"=>"foobar", id"=>83}...
|
22
|
+
# D, [2017-07-30T22:23:50.263240 #23474] DEBUG -- #<CatsApi.cats.fetch:0x000000034d2840 @token="foobar", "id"=83> initialized
|
23
|
+
# ...
|
24
|
+
```
|
@@ -0,0 +1,56 @@
|
|
1
|
+
Evil client supports stack of rack-compatible middleware which is specific for every scope and operation.
|
2
|
+
|
3
|
+
As usual, every middleware is just an object wrapped around some application. It should:
|
4
|
+
|
5
|
+
- initialize with the only parameter `app` for the rest of stack, wrapped around connection,
|
6
|
+
- define the only instance method `#call` which takes a [rack environment], and returns a [rack response].
|
7
|
+
|
8
|
+
The example of valid middleware which adds the tag "FOO" to the request header:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
class Foo
|
12
|
+
def call(env)
|
13
|
+
env["HTTP_Variables"]["Tag"] = "FOO"
|
14
|
+
@app.call env
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def initialize(app)
|
20
|
+
@app = app
|
21
|
+
end
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
Use the `middleware` helper method to add a specific class to a stack. You can do this at any level of nesting:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class CatsAPI < Evil::Client
|
29
|
+
# ...
|
30
|
+
middleware Foo
|
31
|
+
|
32
|
+
scope :cats do
|
33
|
+
option :version, proc(&:to_i)
|
34
|
+
# ...
|
35
|
+
middleware { version < 2 ? Bar : [Bar, Baz] }
|
36
|
+
|
37
|
+
operation :fetch do
|
38
|
+
middleware { Qux }
|
39
|
+
# ...
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Depending on version, this will send rack request to either
|
45
|
+
# [Foo, Bar, Qux, Evil::Client::Connection] (versions 1-)
|
46
|
+
# [Foo, Bar, Baz, Qux, Evil::Client::Connection] (versions 2+)
|
47
|
+
#
|
48
|
+
# The response will be processed in the reverse order
|
49
|
+
```
|
50
|
+
|
51
|
+
You can place the same class to the stack several times.
|
52
|
+
|
53
|
+
The order of definitions is important. The request is processed by middleware in the order from root to operation, and the response will be processed in reverse order -- from operation to the root.
|
54
|
+
|
55
|
+
[rack environment]: http://www.rubydoc.info/github/rack/rack/file/SPEC#The_Environment
|
56
|
+
[rack response]: http://www.rubydoc.info/github/rack/rack/file/SPEC#The_Response
|
@@ -0,0 +1,103 @@
|
|
1
|
+
Operation defines a specific request to a remote API with some helpers to process its responses.
|
2
|
+
|
3
|
+
To specify an operation, you should define the following parts:
|
4
|
+
|
5
|
+
- [options] of the request sender
|
6
|
+
- http(s) [path] to the remote server
|
7
|
+
- [http method] for sending requests
|
8
|
+
- [security] definitions describing what credentials the request should carry
|
9
|
+
- http [headers] to include into the request (along with security-related ones)
|
10
|
+
- request [query] to be added to the request path (along with security-related)
|
11
|
+
- a [content][body] of the request and a [format][body] describing the content should be formatted
|
12
|
+
- a set of [middleware] that should be added to the remote [connection]
|
13
|
+
- a set of expected [responses] with corresponding handlers
|
14
|
+
|
15
|
+
An operation is specified with a unique name inside the corresponding [scope].
|
16
|
+
|
17
|
+
You can add it to the root of the client, or to any subscope:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
class CatsClient < Evil::Client
|
21
|
+
# Returns current information about the client
|
22
|
+
operation :info do
|
23
|
+
# ...
|
24
|
+
end
|
25
|
+
|
26
|
+
scope :cats do
|
27
|
+
# ...
|
28
|
+
|
29
|
+
# Fetches information about a specific cat
|
30
|
+
operation :fetch do
|
31
|
+
# ...
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
Operation-specific definitions should be made inside the block. They will affect only this operation:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class CatsClient < Evil::Client
|
41
|
+
# ...
|
42
|
+
scope :cats do
|
43
|
+
# ...
|
44
|
+
operation :fetch do
|
45
|
+
option :id
|
46
|
+
|
47
|
+
path { id }
|
48
|
+
http_method :get
|
49
|
+
response 200
|
50
|
+
response(400, 422) { |(status, *)| raise "#{status}: Wrong request" }
|
51
|
+
response(404) { raise "404: Not found" }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
57
|
+
Besides operation-specific settings, you can add same definitions for a scope. This definitions are shared by all operations of the scope and its sub-scopes on any level of nesting. Any sub-scope or operation can reload this shared definitions, or update it with those of its own.
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
class CatsClient < Evil::Client
|
61
|
+
# ...
|
62
|
+
scope :cats do
|
63
|
+
path { "cats" } # relative to root scope
|
64
|
+
http_method :get
|
65
|
+
response 200
|
66
|
+
response(400, 422) { |(status, *)| raise "#{status}: Wrong request" }
|
67
|
+
response(404) { raise "404: Not found" }
|
68
|
+
|
69
|
+
operation :fetch do
|
70
|
+
option :id
|
71
|
+
path { id } # relative to upper scope
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
The user of custom client sends a request by invoking some operation by name on a corresponding scope.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
client = CatsClient.new
|
81
|
+
cats = client.cats # scope for the `fetch` operaton
|
82
|
+
|
83
|
+
cats.fetch id: 44 # sends request and returns a processed response
|
84
|
+
```
|
85
|
+
|
86
|
+
Alternatively you can initialize the operation first, and call it later:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
operation = cats.operations[:fetch].new(id: 44)
|
90
|
+
operation.call
|
91
|
+
```
|
92
|
+
|
93
|
+
[options]:
|
94
|
+
[path]:
|
95
|
+
[http method]:
|
96
|
+
[security]:
|
97
|
+
[headers]:
|
98
|
+
[query]:
|
99
|
+
[body]:
|
100
|
+
[middleware]:
|
101
|
+
[connection]:
|
102
|
+
[responses]:
|
103
|
+
[scope]:
|
@@ -0,0 +1,50 @@
|
|
1
|
+
Use the `option` helper to add options to some scope or operation.
|
2
|
+
|
3
|
+
```ruby
|
4
|
+
class CatsClient < Evil::Client
|
5
|
+
# Define options for the client's initializer
|
6
|
+
option :domain, proc(&:to_s)
|
7
|
+
option :user, proc(&:to_s)
|
8
|
+
option :password, proc(&:to_s), optional: true
|
9
|
+
option :token, proc(&:to_s), optional: true
|
10
|
+
# ...
|
11
|
+
|
12
|
+
scope :cats do
|
13
|
+
# Scope-specific options
|
14
|
+
option :version, default: proc { 1 }
|
15
|
+
|
16
|
+
# Operation-specific options
|
17
|
+
operation :fetch do
|
18
|
+
option :id, proc(&:to_i)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
The helper is taken from [dry-initializer] gem, so you can see [the gem's documentation][dry-initializer-docs] for the details of its syntax. Notice, that evil-client doesn't support positional arguments (aka `param`-s).
|
25
|
+
|
26
|
+
All declarations made at a root scope, or any ancestor scope, are available at the nested levels.
|
27
|
+
Options assigned during client/scope/operation instantiation are accumulated:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
client = CatsClient.new domain: "wild", user: "andy"
|
31
|
+
client.options # => { domain: "wild", user: "andy" }
|
32
|
+
client.domain # => "wild"
|
33
|
+
client.user # => "andy"
|
34
|
+
|
35
|
+
cats = client.cats(version: 3)
|
36
|
+
cats.options # => { domain: "wild", user: "andy", version: 3 }
|
37
|
+
|
38
|
+
fetch = cats.operations[:fetch].new(id: 7)
|
39
|
+
fetch.options # => { domain: "wild", user: "andy", version: 3, id: 7 }
|
40
|
+
```
|
41
|
+
|
42
|
+
You can define any assigned option at any level of nesting:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
fetch = cats.operations[:fetch].new(id: 7, domain: "domestic")
|
46
|
+
fetch.options # => { domain: "domestic", user: "andy", version: 3, id: 7 }
|
47
|
+
```
|
48
|
+
|
49
|
+
[dry-initializer] https://github.com/dry-rb/dry-initializer
|
50
|
+
[dry-initializer-docs]: http://dry-rb.org/gems/dry-initializer/
|