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
data/docs/base_url.md DELETED
@@ -1,38 +0,0 @@
1
- Use `base_url` to define a base url, [operation paths][path] are relative to.
2
-
3
- You can use [`settings`][settings] as the only argument of the declaration block.
4
-
5
- ```ruby
6
- require "evil-client"
7
- require "dry-types"
8
-
9
- class CatsClient < Evil::Client
10
- settings do
11
- option :version, type: Dry::Types["coercible.int"], default: proc { 1 }
12
- end
13
-
14
- base_url do |settings|
15
- "https://cats.example.com/v#{settings.version}"
16
- end
17
-
18
- operation :find_cats do |_settings|
19
- http_method :get
20
- path { "cats" }
21
- end
22
- end
23
- ```
24
-
25
- After a client's instantiation...
26
-
27
- ```ruby
28
- client = CatsClient.new version: 3
29
- ```
30
-
31
- ...the following call will send a request `GET https://example.com/v3/cats`.
32
-
33
- ```ruby
34
- client.operations[:find_cat].call
35
- ```
36
-
37
- [settings]:
38
- [path]:
@@ -1,9 +0,0 @@
1
- Use the `documentation` to provide reference to online docs for the current operation.
2
-
3
- ```ruby
4
- operation :find_cat do |settings| # remember that you have access to settings
5
- documentation "https://cats.example.com/v#{settings.version}/docs/find_cats"
6
- end
7
- ```
8
-
9
- The link will be shown in exceptions risen when either request or response mismatches type constraints.
data/docs/headers.md DELETED
@@ -1,59 +0,0 @@
1
- Use method `headers` to add several headers to a request. The declaration should have a block with several `attributes` describing corresponding headers.
2
-
3
- ```ruby
4
- operation :find_cat do |settings|
5
- headers do
6
- attribute :token if settings.version > 1
7
- attribute :id
8
- end
9
- end
10
- ```
11
-
12
- The syntax of the attribute declaration is exactly the same as of [Evil::Struct][model]. Type constraints and default values are available.
13
-
14
- All values for the headers will be taken from a request options:
15
-
16
- ```ruby
17
- # Sends a request with headers { "id" => 43 }
18
- client.options[:find_cat].call id: 43
19
- ```
20
-
21
- As a rule, you shouldn't define authorization headers in this way. Use [the security method][security] instead.
22
-
23
- Default headers can be declared for every request via anonymous operation. **Notice** that a default headers can be reloaded for specific operation as a whole. New declaration will overwrite all the default set headers instead of merging to them.
24
-
25
- ```ruby
26
- operation do
27
- headers do
28
- attribute :id
29
- end
30
- end
31
-
32
- operation :find_cat do
33
- headers do
34
- attribute :cat_id
35
- end
36
- end
37
-
38
- # later at the runtime the following call
39
- # will send request with { "cat_id" => 4 } header only
40
- client.operations[:find_cat].call id: 1, cat_id: 4
41
- ```
42
-
43
- According to [RFC-2616][rfc-2616], headers are case-insensitive. Inside [middleware][middleware] they are forced to lower case.
44
-
45
- For example, while the following declaration is valid by itself, only value from `Foo` option will be sent to remote server:
46
-
47
- ```ruby
48
- operation do
49
- headers do
50
- attribute :foo
51
- attribute :Foo
52
- end
53
- end
54
- ```
55
-
56
- [rfc-2616]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
57
- [security]:
58
- [model]:
59
- [middleware]:
data/docs/http_method.md DELETED
@@ -1,31 +0,0 @@
1
- Use `http_method` to define it for the current operation.
2
-
3
- ```ruby
4
- operation :find_cat do
5
- http_method :get
6
- end
7
- ```
8
-
9
- As usual, you have access to current settings. This can be useful to make the method dependent from either a version, or other variation of the api.
10
-
11
- ```ruby
12
- operation :find_cat do |settings|
13
- http_method settings.version > 2 ? :post : :get
14
- end
15
- ```
16
-
17
- You can also set a default method for all operations. It can be reloaded later:
18
-
19
- ```ruby
20
- operation do
21
- http_method :get
22
- end
23
-
24
- operation :find_cat do
25
- # sends requests via get
26
- end
27
-
28
- operation :update_cat do
29
- http_method :patch
30
- end
31
- ```
data/docs/model.md DELETED
@@ -1,173 +0,0 @@
1
- Models are simple nested structures, based on [dry-initializer][dry-initializer] and [dry-types][dry-types].
2
-
3
- They are needed to prepare and validate nested bodies and queries, as well as wrap and validate responses.
4
-
5
- # Model Definition
6
-
7
- To define a model create a subclass of `Evil::Struct` and define its attributes.
8
-
9
- ```ruby
10
- class Cat < Evil::Struct
11
- attribute :name, type: Dry::Types["strict.string"], optional: true
12
- attribute :age, type: Dry::Types["coercible.int"], default: proc { 0 }
13
- attribute :color, type: Dry::Types["strict.string"]
14
- end
15
- ```
16
-
17
- The method `attribute` is just an alias of [dry-initializer `option`][dry-initializer]. Because model's constructor takes options only, not params, `param` is reloaded as another alias of `option`. You can use any method you like.
18
-
19
- To initialize an instance send a hash of options to the constructor:
20
-
21
- ```ruby
22
- cat = Cat.new(name: "Navuxodonosor II", age: "15", color: "black")
23
- cat.name # => "Navuxodonosor II"
24
- cat.age # => 15
25
- cat.color # => "black"
26
- ```
27
-
28
- You can build a model from another one (it just returns the object back):
29
-
30
- ```ruby
31
- Cat.new Cat.new(name: "Navuxodonosor II", age: 15, color: "black")
32
- ```
33
-
34
- or from a hash with string keys:
35
-
36
- ```ruby
37
- Cat.new("name" => "Navuxodonosor II", "age" => 15, "color" => "black")
38
- ```
39
-
40
- You can use other (nested) models in type definitions:
41
-
42
- ```ruby
43
- class CatPack < Evil::Struct
44
- attribute :cats, type: Dry::Types["array"].member(Cat)
45
- end
46
-
47
- CatPack.new cats: [{ name: "Navuxodonosor II", age: 15, color: "black" }]
48
- ```
49
-
50
- Models can be converted back to hashes with **symbolic** keys:
51
-
52
- ```ruby
53
- cat = Cat.new(name: "Navuxodonosor II", age: "15", color: "black")
54
- cat.to_h # => { name: "Navuxodonosor II", age: "15", color: "black" }
55
- ```
56
-
57
- The model ignores all options it doesn't know about, and applies constraints to known ones only.
58
-
59
- ```ruby
60
- # Cats don't care about your expectations
61
- cat = Cat.new(name: "Navuxodonosor II", age: "15", color: "black", expectation: "hunting")
62
- cat.to_h # => { name: "Navuxodonosor II", age: "15", color: "black" }
63
- ```
64
-
65
- If all you need is data filtering, just use a shortcut `.[]`:
66
-
67
- ```ruby
68
- Cat[name: "Navuxodonosor II", age: "15", color: "black", expectation: "hunting"]
69
- # => { name: "Navuxodonosor II", age: "15", color: "black" }
70
- ```
71
-
72
- # Model Usage
73
-
74
- You can use models in definitions of request [body][body], [query][query], and [headers][headers]...
75
-
76
- ```ruby
77
- operation :create_cat do
78
- method :post
79
- path { "cats" }
80
- body model: Cat
81
- end
82
- ```
83
-
84
- ...and in [response][response] processing:
85
-
86
- ```ruby
87
- operation :create_cat do
88
- # ...
89
- response 201 do |body:, **|
90
- Cat[JSON.parse(body)]
91
- end
92
- end
93
- ```
94
-
95
- # Distinction of Models from Dry::Struct
96
-
97
- Models are like ~~onions~~ structures, defined in [`dry-struct`][dry-struct]. Both models and structures support hash arguments, type constraints, nested data, and backward hashification via `to_h`. You can check [dry-struct documentation] to make an impression of how it works.
98
-
99
- Nethertheless, there is an important difference between the implementations of nested structures.
100
-
101
- ## Undefined Values vs nils
102
-
103
- The main reason to define gem-specific model is the following. In `Dry::Struct` both the `optional` and `default` properties belong to value type constraint. The gem does not draw the line between attributes that are not set, and those that are set to `nil`.
104
-
105
- To the contrary, in [dry-initializer][dry-initializer] and `Evil::Struct` both `optional` and `default` properties describe not a value type by itself, but its relation to the model. An attribute value can be set to `nil`, or been kept in undefined state.
106
-
107
- Let's see the difference on the example of `StructCat` and `ModelCat`:
108
-
109
- ```ruby
110
- class StructCat < Dry::Struct
111
- attribute :name, type: Dry::Types["strict.string"].optional
112
- attribute :age, type: Dry::Types["coercible.int"].default(0)
113
- end
114
-
115
- class ModelCat < Evil::Struct
116
- attribute :name, type: Dry::Types["strict.string"], optional: true
117
- attribute :age, type: Dry::Types["coercible.int"], default: proc { 0 }
118
- end
119
-
120
- struct_cat = StructCat.new
121
- struct_cat.name # => nil
122
- struct_cat.age # => 0
123
- struct_cat.to_h # => { name: nil, age: 0 }
124
-
125
- model_cat = ModelCat.new
126
- model_cat.name # => #<Dry::Initializer::UNDEFINED>
127
- model_cat.age # => 0
128
- model_cat.to_h # => { age: 0 }
129
-
130
- model_cat = ModelCat.new name: nil
131
- model_cat.name # => nil
132
- model_cat.age # => 0
133
- model_cat.to_h # => { name: nil, age: 0 }
134
- ```
135
-
136
- Notice that in a model hashification ignores undefined attributes. This is important to filter arguments of a request. In PUT/PATCH requests (update server-side data) there is a difference between values not changed, and those that are explicitly reset to `nil`.
137
-
138
- ## Tolerance to Unknown Options
139
-
140
- A model's constructor ignores unknown options, so you can safely sent any ones:
141
-
142
- ```ruby
143
- # Oh, no, this is a cat, not a bat!
144
- model_cat = ModelCat.new name: "Abraham", flying_distance: "5 miles"
145
-
146
- # so we simply ignore flying_distance
147
- model_cat.to_h # => { name: "Abraham", age: 0 }
148
- ```
149
-
150
- This behaviour allows us to slice only necessary arguments for [body][body], [query][query], and [headers][headers] of a request.
151
-
152
- ## Stringified Keys in Constructor
153
-
154
- Ahother difference between structs and models is that models can take hashes with both symbolic and string keys.
155
-
156
- This addition is useful when processing [responses][response]:
157
-
158
- ```ruby
159
- # This works even though JSON#parse returns a hash with string keys
160
- ModelCat.new JSON.parse('{"age":4}')
161
- ```
162
-
163
- ## Equality
164
-
165
- Models, whose methods `to_h` returns equal hashes, are counted as equal.
166
-
167
- [dry-initializer]: http://dry-rb.org/gems/dry-initializer
168
- [dry-struct]: http://dry-rb.org/gems/dry-struct
169
- [dry-types]: http://dry-rb.org/gems/dry-types
170
- [body]:
171
- [headers]:
172
- [query]:
173
- [response]:
data/docs/operation.md DELETED
File without changes
data/docs/overview.md DELETED
File without changes
data/docs/path.md DELETED
@@ -1,48 +0,0 @@
1
- Use `path` to define operation's path that is relative to the [base url][base_url].
2
-
3
- ```ruby
4
- operation :find_cats do
5
- path { "cats" }
6
- end
7
- ```
8
-
9
- Notice that a value should be wrapped into the block. This is necessary to build paths dependent on arguments of the request. The following definition inserts a mandatory id from options:
10
-
11
- ```ruby
12
- operation :find_cat do
13
- path { |id:, **| "cats/#{id}" }
14
- end
15
-
16
- # later at a runtime
17
- client.operations[:find_cat].call id: 98 # sends to "/cats/98"
18
- ```
19
-
20
- As usual, you have access to current settings. This can be useful to add client tokens to paths when necessary:
21
-
22
- ```ruby
23
- operation :find_cats do |settings|
24
- path { "cats/#{settings.token}" }
25
- end
26
- ```
27
-
28
- ## Default Path
29
-
30
- You can set a default path for all operations. Use it to DRY clients whose operations differs not by endpoints, but, for example, by parameters ([query][query], [body][body]) of various requests:
31
-
32
- ```ruby
33
- operation do
34
- path { "cats" }
35
- end
36
-
37
- operation :find_cats do
38
- # sends requests to "/cats"
39
- end
40
-
41
- operation :find_details do
42
- path { "cats/details" } # reloads default setting
43
- end
44
- ```
45
-
46
- [base_url]:
47
- [query]:
48
- [body]:
data/docs/query.md DELETED
@@ -1,99 +0,0 @@
1
- Use `query` to add some data to the request query. The syntax is pretty the same as for [body][body] and [headers][headers].
2
-
3
- ```ruby
4
- operation :find_cat do |settings|
5
- # ...
6
- path { "cats" }
7
- query do
8
- attribute :token, default: proc { settings.token }
9
- attribute :id
10
- end
11
- end
12
-
13
- # Later at the runtime it will send a request to "../cats?id=4&token=foo"
14
- client.operations[:find_cat].call id: 4, token: "foo"
15
- ```
16
-
17
- ## Nested Data Representation
18
-
19
- Nested data are represented in a query following Rails convention:
20
-
21
- ```ruby
22
- client.operations[:find_cat].call id: [{ key: 4 }], token: ["foo"]
23
- # "/cats?id[][key]=4&token[]=foo"
24
- ```
25
-
26
- Non-unicode symbols are encoded as defined in [RFC-3986][rfc-3986]
27
-
28
- ## Model-Based Queries
29
-
30
- Use [models][model] to provide validation of query data:
31
-
32
- ```ruby
33
- class Cat < Evil::Struct
34
- attribute :name, type: Dry::Types["strict.string"], optional: true
35
- attribute :age, type: Dry::Types["strict.int"], default: proc { 0 }
36
- attribute :color, type: Dry::Types["strict.string"]
37
- end
38
- ```
39
-
40
- You can either restrict `type` of an attribute:
41
-
42
- ```ruby
43
- operation :create_cat do
44
- query do
45
- attribute :cat, type: Cat
46
- end
47
- end
48
-
49
- # Later at runtime will send "...?cat[color]=tabby&cat[age]=0"
50
- client.operations[:create_cat].call cat: { color: "tabby" }
51
- ```
52
-
53
- ...or use in for the query as a whole under the `model` key:
54
-
55
- ```ruby
56
- operation :create_cat do
57
- query model: Cat
58
- end
59
-
60
- # Later at runtime will send "...?color=tabby&age=0"
61
- client.operations[:create_cat].call color: "tabby"
62
- ```
63
-
64
- In the last case you can define additional attributes (this redefinition is local, it don't affect a model by itself):
65
-
66
- ```ruby
67
- operation :create_cat do
68
- query model: Cat do
69
- attribute :mood, default: proc { "sleeping" }
70
- end
71
- end
72
-
73
- # Later at runtime will send "...?color=tabby&age=0&mood=sleeping"
74
- client.operations[:create_cat].call color: "tabby"
75
- ```
76
-
77
- **Be careful!** You cannot reload existing attributes (this will cause an exception).
78
-
79
- In operations that update remote data you can skip some attributes (mark them `optional`). If you need to check responses strictly (to require all the necessary attributes), you should provide different models.
80
-
81
- ```ruby
82
- # Requires remote server to return consistent beasts
83
- class Cat
84
- attribute :id, type: Dry::Types["strict.int"].constrained(gt: 0)
85
- attribute :age, type: Dry::Types["strict.int"]
86
- attribute :name, type: Dry::Types["strict.string"]
87
- end
88
-
89
- # Allows updating attributes when necessary
90
- class CatUpdate
91
- attribute :age, type: Dry::Types["coercible.int"], optional: true
92
- attribute :name, type: Dry::Types["coercible.string"], optional: true
93
- end
94
- ```
95
-
96
- [rfc-3986]: https://tools.ietf.org/html/rfc3986
97
- [body]:
98
- [headers]:
99
- [model]: