Skip to content

Commit 0ac46d8

Browse files
committed
Moved DSL comments to module
1 parent f9e584c commit 0ac46d8

File tree

2 files changed

+379
-381
lines changed

2 files changed

+379
-381
lines changed

lib/tlaw/dsl.rb

Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,385 @@ module TLAW
2020
# documentation structure considered to be most informative.
2121
#
2222
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.
23402
end
24403
end
25404

0 commit comments

Comments
 (0)