evil-client 0.3.3 → 1.0.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.
- 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/
|