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.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +0 -11
  3. data/.gitignore +1 -0
  4. data/.rspec +0 -1
  5. data/.rubocop.yml +22 -19
  6. data/.travis.yml +1 -0
  7. data/CHANGELOG.md +251 -6
  8. data/LICENSE.txt +3 -1
  9. data/README.md +47 -81
  10. data/docs/helpers/body.md +93 -0
  11. data/docs/helpers/connection.md +19 -0
  12. data/docs/helpers/headers.md +72 -0
  13. data/docs/helpers/http_method.md +39 -0
  14. data/docs/helpers/let.md +14 -0
  15. data/docs/helpers/logger.md +24 -0
  16. data/docs/helpers/middleware.md +56 -0
  17. data/docs/helpers/operation.md +103 -0
  18. data/docs/helpers/option.md +50 -0
  19. data/docs/helpers/path.md +37 -0
  20. data/docs/helpers/query.md +59 -0
  21. data/docs/helpers/response.md +40 -0
  22. data/docs/helpers/scope.md +121 -0
  23. data/docs/helpers/security.md +102 -0
  24. data/docs/helpers/validate.md +68 -0
  25. data/docs/index.md +70 -78
  26. data/docs/license.md +5 -1
  27. data/docs/rspec.md +96 -0
  28. data/evil-client.gemspec +10 -8
  29. data/lib/evil/client.rb +126 -72
  30. data/lib/evil/client/builder.rb +47 -0
  31. data/lib/evil/client/builder/operation.rb +40 -0
  32. data/lib/evil/client/builder/scope.rb +31 -0
  33. data/lib/evil/client/chaining.rb +17 -0
  34. data/lib/evil/client/connection.rb +60 -20
  35. data/lib/evil/client/container.rb +66 -0
  36. data/lib/evil/client/container/operation.rb +23 -0
  37. data/lib/evil/client/container/scope.rb +28 -0
  38. data/lib/evil/client/exceptions/definition_error.rb +15 -0
  39. data/lib/evil/client/exceptions/name_error.rb +32 -0
  40. data/lib/evil/client/exceptions/response_error.rb +42 -0
  41. data/lib/evil/client/exceptions/type_error.rb +29 -0
  42. data/lib/evil/client/exceptions/validation_error.rb +27 -0
  43. data/lib/evil/client/formatter.rb +49 -0
  44. data/lib/evil/client/formatter/form.rb +45 -0
  45. data/lib/evil/client/formatter/multipart.rb +33 -0
  46. data/lib/evil/client/formatter/part.rb +66 -0
  47. data/lib/evil/client/formatter/text.rb +21 -0
  48. data/lib/evil/client/resolver.rb +84 -0
  49. data/lib/evil/client/resolver/body.rb +22 -0
  50. data/lib/evil/client/resolver/format.rb +30 -0
  51. data/lib/evil/client/resolver/headers.rb +46 -0
  52. data/lib/evil/client/resolver/http_method.rb +34 -0
  53. data/lib/evil/client/resolver/middleware.rb +36 -0
  54. data/lib/evil/client/resolver/query.rb +39 -0
  55. data/lib/evil/client/resolver/request.rb +96 -0
  56. data/lib/evil/client/resolver/response.rb +26 -0
  57. data/lib/evil/client/resolver/security.rb +113 -0
  58. data/lib/evil/client/resolver/uri.rb +35 -0
  59. data/lib/evil/client/rspec.rb +127 -0
  60. data/lib/evil/client/schema.rb +105 -0
  61. data/lib/evil/client/schema/operation.rb +177 -0
  62. data/lib/evil/client/schema/scope.rb +73 -0
  63. data/lib/evil/client/settings.rb +172 -0
  64. data/lib/evil/client/settings/validator.rb +64 -0
  65. data/mkdocs.yml +21 -15
  66. data/spec/features/custom_connection_spec.rb +17 -0
  67. data/spec/features/operation/middleware_spec.rb +50 -0
  68. data/spec/features/operation/options_spec.rb +71 -0
  69. data/spec/features/operation/request_spec.rb +94 -0
  70. data/spec/features/operation/response_spec.rb +48 -0
  71. data/spec/features/scope/options_spec.rb +52 -0
  72. data/spec/fixtures/locales/en.yml +16 -0
  73. data/spec/fixtures/test_client.rb +76 -0
  74. data/spec/spec_helper.rb +18 -6
  75. data/spec/support/fixtures_helper.rb +7 -0
  76. data/spec/unit/builder/operation_spec.rb +90 -0
  77. data/spec/unit/builder/scope_spec.rb +84 -0
  78. data/spec/unit/client_spec.rb +137 -0
  79. data/spec/unit/connection_spec.rb +78 -0
  80. data/spec/unit/container/operation_spec.rb +81 -0
  81. data/spec/unit/container/scope_spec.rb +61 -0
  82. data/spec/unit/container_spec.rb +107 -0
  83. data/spec/unit/exceptions/definition_error_spec.rb +15 -0
  84. data/spec/unit/exceptions/name_error_spec.rb +77 -0
  85. data/spec/unit/exceptions/response_error_spec.rb +22 -0
  86. data/spec/unit/exceptions/type_error_spec.rb +71 -0
  87. data/spec/unit/exceptions/validation_error_spec.rb +13 -0
  88. data/spec/unit/formatter/form_spec.rb +27 -0
  89. data/spec/unit/formatter/multipart_spec.rb +23 -0
  90. data/spec/unit/formatter/part_spec.rb +49 -0
  91. data/spec/unit/formatter/text_spec.rb +37 -0
  92. data/spec/unit/formatter_spec.rb +46 -0
  93. data/spec/unit/resolver/body_spec.rb +65 -0
  94. data/spec/unit/resolver/format_spec.rb +66 -0
  95. data/spec/unit/resolver/headers_spec.rb +93 -0
  96. data/spec/unit/resolver/http_method_spec.rb +67 -0
  97. data/spec/unit/resolver/middleware_spec.rb +83 -0
  98. data/spec/unit/resolver/query_spec.rb +85 -0
  99. data/spec/unit/resolver/request_spec.rb +121 -0
  100. data/spec/unit/resolver/response_spec.rb +64 -0
  101. data/spec/unit/resolver/security_spec.rb +156 -0
  102. data/spec/unit/resolver/uri_spec.rb +117 -0
  103. data/spec/unit/rspec_spec.rb +342 -0
  104. data/spec/unit/schema/operation_spec.rb +309 -0
  105. data/spec/unit/schema/scope_spec.rb +110 -0
  106. data/spec/unit/schema_spec.rb +157 -0
  107. data/spec/unit/settings/validator_spec.rb +128 -0
  108. data/spec/unit/settings_spec.rb +248 -0
  109. metadata +192 -135
  110. data/docs/base_url.md +0 -38
  111. data/docs/documentation.md +0 -9
  112. data/docs/headers.md +0 -59
  113. data/docs/http_method.md +0 -31
  114. data/docs/model.md +0 -173
  115. data/docs/operation.md +0 -0
  116. data/docs/overview.md +0 -0
  117. data/docs/path.md +0 -48
  118. data/docs/query.md +0 -99
  119. data/docs/responses.md +0 -66
  120. data/docs/security.md +0 -102
  121. data/docs/settings.md +0 -32
  122. data/lib/evil/client/connection/net_http.rb +0 -57
  123. data/lib/evil/client/dsl.rb +0 -127
  124. data/lib/evil/client/dsl/base.rb +0 -26
  125. data/lib/evil/client/dsl/files.rb +0 -37
  126. data/lib/evil/client/dsl/headers.rb +0 -16
  127. data/lib/evil/client/dsl/http_method.rb +0 -24
  128. data/lib/evil/client/dsl/operation.rb +0 -91
  129. data/lib/evil/client/dsl/operations.rb +0 -41
  130. data/lib/evil/client/dsl/path.rb +0 -25
  131. data/lib/evil/client/dsl/query.rb +0 -16
  132. data/lib/evil/client/dsl/response.rb +0 -61
  133. data/lib/evil/client/dsl/responses.rb +0 -29
  134. data/lib/evil/client/dsl/scope.rb +0 -27
  135. data/lib/evil/client/dsl/security.rb +0 -57
  136. data/lib/evil/client/dsl/verifier.rb +0 -35
  137. data/lib/evil/client/middleware.rb +0 -81
  138. data/lib/evil/client/middleware/base.rb +0 -11
  139. data/lib/evil/client/middleware/merge_security.rb +0 -20
  140. data/lib/evil/client/middleware/normalize_headers.rb +0 -17
  141. data/lib/evil/client/middleware/stringify_form.rb +0 -40
  142. data/lib/evil/client/middleware/stringify_json.rb +0 -19
  143. data/lib/evil/client/middleware/stringify_multipart.rb +0 -36
  144. data/lib/evil/client/middleware/stringify_multipart/part.rb +0 -36
  145. data/lib/evil/client/middleware/stringify_query.rb +0 -35
  146. data/lib/evil/client/operation.rb +0 -34
  147. data/lib/evil/client/operation/request.rb +0 -26
  148. data/lib/evil/client/operation/response.rb +0 -39
  149. data/lib/evil/client/operation/response_error.rb +0 -13
  150. data/lib/evil/client/operation/unexpected_response_error.rb +0 -19
  151. data/spec/features/instantiation_spec.rb +0 -68
  152. data/spec/features/middleware_spec.rb +0 -79
  153. data/spec/features/operation_with_documentation_spec.rb +0 -41
  154. data/spec/features/operation_with_files_spec.rb +0 -40
  155. data/spec/features/operation_with_form_body_spec.rb +0 -158
  156. data/spec/features/operation_with_headers_spec.rb +0 -99
  157. data/spec/features/operation_with_http_method_spec.rb +0 -45
  158. data/spec/features/operation_with_json_body_spec.rb +0 -156
  159. data/spec/features/operation_with_nested_responses_spec.rb +0 -95
  160. data/spec/features/operation_with_path_spec.rb +0 -47
  161. data/spec/features/operation_with_query_spec.rb +0 -84
  162. data/spec/features/operation_with_security_spec.rb +0 -228
  163. data/spec/features/scoping_spec.rb +0 -48
  164. data/spec/support/test_client.rb +0 -15
  165. data/spec/unit/evil/client/connection/net_http_spec.rb +0 -38
  166. data/spec/unit/evil/client/dsl/files_spec.rb +0 -37
  167. data/spec/unit/evil/client/dsl/operation_spec.rb +0 -374
  168. data/spec/unit/evil/client/dsl/operations_spec.rb +0 -29
  169. data/spec/unit/evil/client/dsl/scope_spec.rb +0 -32
  170. data/spec/unit/evil/client/dsl/security_spec.rb +0 -135
  171. data/spec/unit/evil/client/middleware/merge_security_spec.rb +0 -32
  172. data/spec/unit/evil/client/middleware/normalize_headers_spec.rb +0 -17
  173. data/spec/unit/evil/client/middleware/stringify_form_spec.rb +0 -63
  174. data/spec/unit/evil/client/middleware/stringify_json_spec.rb +0 -61
  175. data/spec/unit/evil/client/middleware/stringify_multipart/part_spec.rb +0 -59
  176. data/spec/unit/evil/client/middleware/stringify_multipart_spec.rb +0 -62
  177. data/spec/unit/evil/client/middleware/stringify_query_spec.rb +0 -40
  178. data/spec/unit/evil/client/middleware_spec.rb +0 -46
  179. data/spec/unit/evil/client/operation/request_spec.rb +0 -49
  180. 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
@@ -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/