@@ -20,6 +20,385 @@ module TLAW
20
20
# documentation structure considered to be most informative.
21
21
#
22
22
module DSL
23
+ # @!method base(url)
24
+ # Allows to set entire API base URL, all endpoints and namespaces
25
+ # pathes are calculated relative to it.
26
+ #
27
+ # **Works for:** API
28
+ #
29
+ # @param url [String]
30
+
31
+ # @!method desc(text)
32
+ # Allows to set description string for your API object. It can
33
+ # be multiline, and TLAW will automatically un-indent excessive
34
+ # indentations:
35
+ #
36
+ # ```ruby
37
+ # # ...several levels of indents while you create a definition
38
+ # desc %Q{
39
+ # This is some endpoint.
40
+ # And it works!
41
+ # }
42
+ #
43
+ # # ...but when you are using it...
44
+ # p my_api.endpoints[:endpoint].describe
45
+ # # This is some endpoint.
46
+ # # And it works!
47
+ # # ....
48
+ # ```
49
+ #
50
+ # **Works for:** API, namespace, endpoint
51
+ #
52
+ # @param text [String]
53
+
54
+ # @!method docs(link)
55
+ # Allows to add link to documentation as a separate line to
56
+ # object description. Just to be semantic :)
57
+ #
58
+ # ```ruby
59
+ # # you do something like
60
+ # desc "That's my endpoint"
61
+ #
62
+ # docs "http://docs.example.com/my/endpoint"
63
+ #
64
+ # # ...and then somewhere...
65
+ # p my_api.endpoints[:endpoint].describe
66
+ # # That is my endpoint.
67
+ # #
68
+ # # Docs: http://docs.example.com/my/endpoint
69
+ # # ....
70
+ # ```
71
+ #
72
+ # **Works for:** API, namespace, endpoint
73
+ #
74
+ # @param link [String]
75
+
76
+ # @!method param(name, type = nil, keyword: true, required: false, **opts)
77
+ # Defines parameter for current API (global), namespace or endpoint.
78
+ #
79
+ # Param defnition defines several things:
80
+ #
81
+ # * how method definition to call this namespace/endpoint would
82
+ # look like: whether the parameter is keyword or regular argument,
83
+ # whether it is required and what is default value otherwise;
84
+ # * how parameter is processed: converted and validated from passed
85
+ # value;
86
+ # * how param is sent to target API: how it will be called in
87
+ # the query string and formatted on call.
88
+ #
89
+ # Note also those things about params:
90
+ #
91
+ # * as described in {#namespace} and {#endpoint}, setting path template
92
+ # will implicitly set params. You can rewrite this on implicit
93
+ # param call, for ex:
94
+ #
95
+ # ```ruby
96
+ # endpoint :foo, '/foo/{bar}'
97
+ # # call-sequence would be foo(bar = nil)
98
+ #
99
+ # # But you can make it back keyword:
100
+ # endpoint :foo, '/foo/{bar}' do
101
+ # param :bar, keyword: true, default: 'test'
102
+ # end
103
+ # # call-sequence now is foo(bar: 'test')
104
+ #
105
+ # # Or make it strictly required
106
+ # endpoint :foo, '/foo/{bar}/{baz}' do
107
+ # param :bar, required: true
108
+ # param :baz, keyword: true, required: true
109
+ # end
110
+ # # call-sequence now is foo(bar, baz:)
111
+ # ```
112
+ #
113
+ # * param of outer namespace are passed to API on call from inner
114
+ # namespaces and endpoints, for ex:
115
+ #
116
+ # ```ruby
117
+ # namespace :city do
118
+ # param :city_name
119
+ #
120
+ # namespace :population do
121
+ # endpoint :by_year, '/year/{year}'
122
+ # end
123
+ # end
124
+ #
125
+ # # real call:
126
+ # api.city('London').population.by_year(2015)
127
+ # # Will get http://api.example.com/city/year/2015?city_name=London
128
+ # ```
129
+ #
130
+ # **Works for:** API, namespace, endpoint
131
+ #
132
+ # @param name [Symbol] Parameter name
133
+ # @param type [Class, Symbol] Expected parameter type. Could by
134
+ # some class (then parameter would be checked for being instance
135
+ # of this class or it would be `ArgumentError`), or duck type
136
+ # (method name that parameter value should respond to).
137
+ # @param keyword [true, false] Whether the param will go as a
138
+ # keyword param to method definition.
139
+ # @param required [true, false] Whether this param is required.
140
+ # It will be considered on method definition.
141
+ # @param opts [Hash] Options
142
+ # @option opts [Symbol] :field What the field would be called in
143
+ # API query string (it would be `name` by default).
144
+ # @option opts [#to_proc] :format How to format this option before
145
+ # including into URL. By default, it is just `.to_s`.
146
+ # @option opts [String] :desc Params::Base description. You could do it
147
+ # multiline and with indents, like {#desc}.
148
+ # @option opts :default Default value for this param. Would be
149
+ # rendered in method definition and then passed to target API
150
+ # _(TODO: in future, there also would be "invisible" params,
151
+ # that are just passed to target, always the same, as well as
152
+ # params that aren't passed at all if user gave default value.)_
153
+ # @option opts [Hash, Array] :enum Whether parameter only accepts
154
+ # enumerated values. Two forms are accepted:
155
+ #
156
+ # ```ruby
157
+ # # array form
158
+ # param :units, enum: %i[us metric britain]
159
+ # # parameter accepts only :us, :metric, :britain values, and
160
+ # # passes them to target API as is
161
+ #
162
+ # # hash "accepted => passed" form
163
+ # param :compact, enum: {true => 'gzip', false => nil}
164
+ # # parameter accepts true or false, on true passes "compact=gzip",
165
+ # # on false passes nothing.
166
+ # ```
167
+
168
+ # @!method namespace(name, path = nil, &block)
169
+ # Defines new namespace or updates existing one.
170
+ #
171
+ # {Namespace} has two roles:
172
+ #
173
+ # * on Ruby API, defines how you access to the final endpoint,
174
+ # like `api.namespace1.namespace2(some_param).endpoint(...)`
175
+ # * on calling API, it adds its path to entire URL.
176
+ #
177
+ # **NB:** If you call `namespace(:something)` and it was already defined,
178
+ # current definition will be added to existing one (but it can't
179
+ # change path of existing one, which is reasonable).
180
+ #
181
+ # **Works for:** API, namespace
182
+ #
183
+ # @param name [Symbol] Name of the method by which namespace would
184
+ # be accessible.
185
+ # @param path [String] Path to add to API inside this namespace.
186
+ # When not provided, considered to be `/<name>`. When provided,
187
+ # taken literally (no slashes or other symbols added). Note, that
188
+ # you can use `/../` in path, redesigning someone else's APIs on
189
+ # the fly. Also, you can use [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570.txt)
190
+ # URL templates to mark params going straightly into URI.
191
+ #
192
+ # Some examples:
193
+ #
194
+ # ```ruby
195
+ # # assuming API base url is http://api.example.com
196
+ #
197
+ # namespace :foo
198
+ # # method would be foo(), API URL would be http://api.example.com/foo
199
+ #
200
+ # namespace :bar, '/foo/bar'
201
+ # # metod would be bar(), API URL http://api.example.com/foo/bar
202
+ #
203
+ # namespace :baz, ''
204
+ # # method baz(), API URL same as base: useful for gathering into
205
+ # # quazi-namespace from several unrelated endpoints.
206
+ #
207
+ # namespace :quux, '/foo/quux/{id}'
208
+ # # method quux(id = nil), API URL http://api.example.com/foo/quux/123
209
+ # # ...where 123 is what you've passed as id
210
+ # ```
211
+ # @param block Definition of current namespace params, and
212
+ # namespaces and endpoints inside current.
213
+ # Note that by defining params inside this block, you can change
214
+ # namespace's method call sequence.
215
+ #
216
+ # For example:
217
+ #
218
+ # ```ruby
219
+ # namespace :foo
220
+ # # call-sequence: foo()
221
+ #
222
+ # namespace :foo do
223
+ # param :bar
224
+ # end
225
+ # # call-sequence: foo(bar: nil)
226
+ #
227
+ # namespace :foo do
228
+ # param :bar, required: true, keyword: false
229
+ # param :baz, required: true
230
+ # end
231
+ # # call-sequence: foo(bar, baz:)
232
+ # ```
233
+ #
234
+ # ...and so on. See also {#param} for understanding what you
235
+ # can change here.
236
+ #
237
+
238
+ # @!method endpoint(name, path = nil, **opts, &block)
239
+ # Defines new endpoint or updates existing one.
240
+ #
241
+ # {Endpoint} is the thing doing the real work: providing Ruby API
242
+ # method to really call target API.
243
+ #
244
+ # **NB:** If you call `endpoint(:something)` and it was already defined,
245
+ # current definition will be added to existing one (but it can't
246
+ # change path of existing one, which is reasonable).
247
+ #
248
+ # **Works for:** API, namespace
249
+ #
250
+ # @param name [Symbol] Name of the method by which endpoint would
251
+ # be accessible.
252
+ # @param path [String] Path to call API from this endpoint.
253
+ # When not provided, considered to be `/<name>`. When provided,
254
+ # taken literally (no slashes or other symbols added). Note, that
255
+ # you can use `/../` in path, redesigning someone else's APIs on
256
+ # the fly. Also, you can use [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570.txt)
257
+ # URL templates to mark params going straightly into URI.
258
+ #
259
+ # Look at {#namespace} for examples, idea is the same.
260
+ #
261
+ # @param opts [Hash] Some options, currently only `:xml`.
262
+ # @option opts [true, false] :xml Whether endpoint's response should
263
+ # be parsed as XML (JSON otherwise & by default). Parsing in this
264
+ # case is performed with [crack](https://github.com/jnunemaker/crack),
265
+ # producing the hash, to which all other rules of post-processing
266
+ # are applied.
267
+ # @param block Definition of endpoint's params and docs.
268
+ # Note that by defining params inside this block, you can change
269
+ # endpoints's method call sequence.
270
+ #
271
+ # For example:
272
+ #
273
+ # ```ruby
274
+ # endpoint :foo
275
+ # # call-sequence: foo()
276
+ #
277
+ # endpoint :foo do
278
+ # param :bar
279
+ # end
280
+ # # call-sequence: foo(bar: nil)
281
+ #
282
+ # endpoint :foo do
283
+ # param :bar, required: true, keyword: false
284
+ # param :baz, required: true
285
+ # end
286
+ # # call-sequence: foo(bar, baz:)
287
+ # ```
288
+ #
289
+ # ...and so on. See also {#param} for understanding what you
290
+ # can change here.
291
+
292
+ # @!method process(key = nil, &block)
293
+ # Sets post-processors for response.
294
+ #
295
+ # There are also {#process_replace} (for replacing entire
296
+ # response with something else) and {#process_items} (for
297
+ # post-processing each item of sub-array).
298
+ #
299
+ # Notes:
300
+ #
301
+ # * you can set any number of post-processors of any kind, and they
302
+ # will be applied in exactly the same order they are set;
303
+ # * you can set post-processors in parent namespace (or for entire
304
+ # API), in this case post-processors of _outer_ namespace are
305
+ # always applied before inner ones. That allow you to define some
306
+ # generic parsing/rewriting on API level, then more specific
307
+ # key postprocessors on endpoints;
308
+ # * hashes are flattened again after _each_ post-processor, so if
309
+ # for some `key` you'll return `{count: 1, continue: false}`,
310
+ # response hash will immediately have
311
+ # `{"key.count" => 1, "key.continue" => false}`.
312
+ #
313
+ # @overload process(&block)
314
+ # Sets post-processor for whole response. Note, that in this case
315
+ # _return_ value of block is ignored, it is expected that your
316
+ # block will receive response and modify it inplace, like this:
317
+ #
318
+ # ```ruby
319
+ # process do |response|
320
+ # response['coord'] = Geo::Coord.new(response['lat'], response['lng'])
321
+ # end
322
+ # ```
323
+ # If you need to replace entire response with something else,
324
+ # see {#process_replace}
325
+ #
326
+ # @overload process(key, &block)
327
+ # Sets post-processor for one response key. Post-processor is
328
+ # called only if key exists in the response, and value by this
329
+ # key is replaced with post-processor's response.
330
+ #
331
+ # Note, that if `block` returns `nil`, key will be removed completely.
332
+ #
333
+ # Usage:
334
+ #
335
+ # ```ruby
336
+ # process('date') { |val| Date.parse(val) }
337
+ # # or, btw, just
338
+ # process('date', &Date.method(:parse))
339
+ # ```
340
+ #
341
+ # @param key [String]
342
+
343
+ # @!method process_items(key, &block)
344
+ # Sets post-processors for each items of array, being at `key` (if
345
+ # the key is present in response, and if its value is array of
346
+ # hashes).
347
+ #
348
+ # Inside `block` you can use {#process} method as described
349
+ # above (but all of its actions will be related only to current
350
+ # item of array).
351
+ #
352
+ # Example:
353
+ #
354
+ # Considering API response like:
355
+ #
356
+ # ```json
357
+ # {
358
+ # "meta": {"count": 100},
359
+ # "data": [
360
+ # {"timestamp": "2016-05-01", "value": "10", "dummy": "foo"},
361
+ # {"timestamp": "2016-05-02", "value": "13", "dummy": "bar"}
362
+ # ]
363
+ # }
364
+ # ```
365
+ # ...you can define postprocessing like this:
366
+ #
367
+ # ```ruby
368
+ # process_items 'data' do
369
+ # process 'timestamp', &Date.method(:parse)
370
+ # process 'value', &:to_i
371
+ # process('dummy'){nil} # will be removed
372
+ # end
373
+ # ```
374
+ #
375
+ # See also {#process} for some generic explanation of post-processing.
376
+ #
377
+ # @param key [String]
378
+
379
+ # @!method process_replace(&block)
380
+ # Just like {#process} for entire response, but _replaces_
381
+ # it with what block returns.
382
+ #
383
+ # Real-life usage: WorldBank API typically returns responses this
384
+ # way:
385
+ #
386
+ # ```json
387
+ # [
388
+ # {"count": 100, "page": 1},
389
+ # {"some_data_variable": [{}, {}, {}]}
390
+ # ]
391
+ # ```
392
+ # ...e.g. metadata and real response as two items in array, not
393
+ # two keys in hash. We can easily fix this:
394
+ #
395
+ # ```ruby
396
+ # process_replace do |response|
397
+ # {meta: response.first, data: response.last}
398
+ # end
399
+ # ```
400
+ #
401
+ # See also {#process} for some generic explanation of post-processing.
23
402
end
24
403
end
25
404
0 commit comments