web_function 0.6.0 → 0.8.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/README.md +435 -14
- data/lib/web_function/argument.rb +2 -14
- data/lib/web_function/attribute.rb +2 -13
- data/lib/web_function/client.rb +39 -1
- data/lib/web_function/endpoint.rb +7 -15
- data/lib/web_function/object_schema.rb +135 -0
- data/lib/web_function/package.rb +41 -1
- data/lib/web_function/request.rb +1 -0
- data/lib/web_function/type/any.rb +48 -0
- data/lib/web_function/type/array_of.rb +58 -0
- data/lib/web_function/type/base.rb +89 -0
- data/lib/web_function/type/union.rb +54 -0
- data/lib/web_function/type.rb +140 -0
- data/lib/web_function/utils.rb +23 -0
- data/lib/web_function/version.rb +1 -1
- data/lib/web_function.rb +2 -0
- metadata +35 -2
- data/test.rb +0 -43
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4f6b3fc6e012055cb40b8d2e51d4118b0d6d6cff8fdb62abde22790774cf787d
|
|
4
|
+
data.tar.gz: 6f795bf7794369f8fa4699c31f50af7c768b2fbd287069f3d3aca29c2da89174
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 57b90f3d4d8205511ee78a0b963b35a72a535681ddb5cb6d53d942f1429222f55d8e51f48c04ee4278cb75f8766b4f3b805988e5e55c94fa0aacf14d0a803b61
|
|
7
|
+
data.tar.gz: 1c71b7c581ecfd14435c1b57251d10c8bb4199ae82b5c0d1b88e41ff85afcc072faea3b968e6f1441379740de4cd904d65414fb71eb9ac4aff93184178592a8a
|
data/README.md
CHANGED
|
@@ -1,39 +1,460 @@
|
|
|
1
|
-
#
|
|
1
|
+
# web_function
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A [Web Function](https://webfunction.org) client for Ruby.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Web Function is a way to design APIs. There are no verbs and no nested URLs. You
|
|
6
|
+
call an endpoint with a POST request, the path names the action, and the JSON
|
|
7
|
+
body carries the data. This gem lets you call those endpoints from Ruby.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
client = WebFunction::Client.from_package_endpoint("https://api.example.com/package")
|
|
11
|
+
|
|
12
|
+
client.find_user(id: "123")
|
|
13
|
+
# => { "id" => "123", "name" => "Ada" }
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Table of contents
|
|
17
|
+
|
|
18
|
+
- [Why Web Function](#why-web-function)
|
|
19
|
+
- [Installation](#installation)
|
|
20
|
+
- [Quick start](#quick-start)
|
|
21
|
+
- [Clients](#clients)
|
|
22
|
+
- [Calling endpoints](#calling-endpoints)
|
|
23
|
+
- [Authentication](#authentication)
|
|
24
|
+
- [Versioning](#versioning)
|
|
25
|
+
- [Inspecting a package](#inspecting-a-package)
|
|
26
|
+
- [Error handling](#error-handling)
|
|
27
|
+
- [Pipelining](#pipelining)
|
|
28
|
+
- [Custom HTTP client](#custom-http-client)
|
|
29
|
+
- [Low-level requests](#low-level-requests)
|
|
30
|
+
- [Command line tool](#command-line-tool)
|
|
31
|
+
- [Development](#development)
|
|
32
|
+
- [Contributing](#contributing)
|
|
33
|
+
- [License](#license)
|
|
34
|
+
|
|
35
|
+
## Why Web Function
|
|
36
|
+
|
|
37
|
+
A Web Function API has a few simple rules:
|
|
38
|
+
|
|
39
|
+
- Every call is an HTTP POST.
|
|
40
|
+
- The request body is a JSON object.
|
|
41
|
+
- The response body is any JSON value.
|
|
42
|
+
- A `200` status means success and the body is the return value.
|
|
43
|
+
- A `400` status means the request was bad and the body explains why.
|
|
44
|
+
- Any other status is an error you handle yourself.
|
|
45
|
+
|
|
46
|
+
On top of that, an API can publish a **package**. A package is a JSON document
|
|
47
|
+
that lists the endpoints, their arguments, their return types, and their docs.
|
|
48
|
+
This gem reads a package and gives you a client that calls those endpoints as if
|
|
49
|
+
they were Ruby methods.
|
|
50
|
+
|
|
51
|
+
You can read the full specification at [webfunction.org](https://webfunction.org).
|
|
6
52
|
|
|
7
53
|
## Installation
|
|
8
54
|
|
|
9
|
-
|
|
55
|
+
Add the gem to your project:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bundle add web_function
|
|
59
|
+
```
|
|
10
60
|
|
|
11
|
-
|
|
61
|
+
Or install it on its own:
|
|
12
62
|
|
|
13
63
|
```bash
|
|
14
|
-
|
|
64
|
+
gem install web_function
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The gem needs Ruby 3.1 or newer.
|
|
68
|
+
|
|
69
|
+
## Quick start
|
|
70
|
+
|
|
71
|
+
Point a client at a package URL and start calling endpoints:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
require "web_function"
|
|
75
|
+
|
|
76
|
+
client = WebFunction::Client.from_package_endpoint("https://api.example.com/package")
|
|
77
|
+
|
|
78
|
+
# An endpoint named "list-items" becomes the method "list_items".
|
|
79
|
+
items = client.list_items(limit: 10)
|
|
80
|
+
# => [{ "id" => 1 }, { "id" => 2 }]
|
|
81
|
+
|
|
82
|
+
# Pass a bearer token for endpoints that need authentication.
|
|
83
|
+
secure = WebFunction::Client.from_package_endpoint(
|
|
84
|
+
"https://api.example.com/package",
|
|
85
|
+
bearer_auth: "my-token",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
secure.create_item(name: "Notebook")
|
|
89
|
+
# => { "id" => 3, "name" => "Notebook" }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Clients
|
|
93
|
+
|
|
94
|
+
A `WebFunction::Client` wraps a package and turns each endpoint into a method.
|
|
95
|
+
|
|
96
|
+
You usually build a client from a package URL. The gem fetches the package,
|
|
97
|
+
reads its endpoints, and returns a ready client:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
client = WebFunction::Client.from_package_endpoint("https://api.example.com/package")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
If you already have a package in memory, build the client from that instead.
|
|
104
|
+
This avoids the extra request:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
package = WebFunction::Package.from_hash(
|
|
108
|
+
"base_url" => "https://api.example.com/",
|
|
109
|
+
"endpoints" => [
|
|
110
|
+
{ "name" => "list-items" },
|
|
111
|
+
{ "name" => "create-item" },
|
|
112
|
+
],
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
client = WebFunction::Client.from_package(package)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Both builders accept the same options:
|
|
119
|
+
|
|
120
|
+
| Option | Description |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| `bearer_auth` | A bearer token sent with every call. |
|
|
123
|
+
| `version` | A version string sent in the `Api-Version` header. |
|
|
124
|
+
| `pipelined` | When `true`, calls are batched into one request. See [Pipelining](#pipelining). |
|
|
125
|
+
|
|
126
|
+
You can also overwrite these using the `Client#bearer_auth=`, `Client#version=`
|
|
127
|
+
and `Client#pipeline=` attribute writers after having instantiated a client.
|
|
128
|
+
|
|
129
|
+
## Calling endpoints
|
|
130
|
+
|
|
131
|
+
Endpoint names use dashes, like `list-items`. The client exposes them as Ruby
|
|
132
|
+
methods with underscores, like `list_items`. You pass arguments as keywords:
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
client.list_items(limit: 10, offset: 20)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The return value is the parsed JSON response. It can be a hash, an array, a
|
|
139
|
+
string, a number, a boolean, or `nil`.
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
client.get_count # => 42
|
|
143
|
+
client.list_items # => [{ "id" => 1 }]
|
|
144
|
+
client.find_user(id: "1") # => { "id" => "1", "name" => "Ada" }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
If you prefer to call an endpoint by its name, use `call`:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
client.call("list-items", limit: 10)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Calling an endpoint that the package does not define raises `NoMethodError`:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
client.does_not_exist
|
|
157
|
+
# => NoMethodError
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Authentication
|
|
161
|
+
|
|
162
|
+
Some endpoints need a bearer token. Pass it when you build the client and the
|
|
163
|
+
gem adds an `Authorization: Bearer <token>` header to every call:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
client = WebFunction::Client.from_package_endpoint(
|
|
167
|
+
"https://api.example.com/package",
|
|
168
|
+
bearer_auth: "my-token",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
client.list_orders
|
|
15
172
|
```
|
|
16
173
|
|
|
17
|
-
|
|
174
|
+
The gem does not handle login. How you obtain the token is up to you. To find
|
|
175
|
+
out whether an endpoint needs a token, check its `bearer_auth?` flag:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
endpoint = client.package.endpoint("list-orders")
|
|
179
|
+
endpoint.bearer_auth? # => true
|
|
180
|
+
endpoint.capture_bearer? # => false
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Versioning
|
|
184
|
+
|
|
185
|
+
A versioned package selects its version through the `Api-Version` header. Pass a
|
|
186
|
+
version string when you build the client:
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
client = WebFunction::Client.from_package_endpoint(
|
|
190
|
+
"https://api.example.com/package",
|
|
191
|
+
version: "2024-01-01",
|
|
192
|
+
)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
You can ask a package whether it is versioned and which versions it offers:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
package = client.package
|
|
199
|
+
package.versioned? # => true
|
|
200
|
+
package.version # => "2024-01-01"
|
|
201
|
+
package.versions # => ["2023-06-01", "2024-01-01"]
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Inspecting a package
|
|
205
|
+
|
|
206
|
+
A package describes itself. You can read its metadata, walk its endpoints, and
|
|
207
|
+
look at the arguments and outputs of each one. This is useful for building docs
|
|
208
|
+
or for checking a call before you make it.
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
package = client.package
|
|
212
|
+
|
|
213
|
+
package.name # => "Example API"
|
|
214
|
+
package.base_url # => "https://api.example.com/"
|
|
215
|
+
package.docs # => "Markdown documentation for the package."
|
|
216
|
+
package.endpoints # => [#<WebFunction::Endpoint ...>, ...]
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Look up a single endpoint by name. Underscores and dashes both work:
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
endpoint = package.endpoint("find-user")
|
|
223
|
+
# Same as:
|
|
224
|
+
endpoint = package.endpoint(:find_user)
|
|
225
|
+
|
|
226
|
+
endpoint.name # => "find-user"
|
|
227
|
+
endpoint.docs # => "Retrieves user data."
|
|
228
|
+
endpoint.returns # => ["object"]
|
|
229
|
+
endpoint.group # => "Users"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Each endpoint lists the arguments it takes:
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
endpoint.arguments
|
|
236
|
+
# => [#<WebFunction::Argument ...>]
|
|
237
|
+
|
|
238
|
+
id = endpoint.argument("id")
|
|
239
|
+
id.name # => "id"
|
|
240
|
+
id.type # => "string"
|
|
241
|
+
id.required? # => true
|
|
242
|
+
id.optional? # => false
|
|
243
|
+
id.choices # => []
|
|
244
|
+
id.docs # => "Identifier of the user."
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
It also lists the attributes it returns when the return type is an object:
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
name = endpoint.attribute("name")
|
|
251
|
+
name.name # => "name"
|
|
252
|
+
name.type # => "string"
|
|
253
|
+
name.nullable? # => false
|
|
254
|
+
name.values # => []
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
You can call an endpoint object directly once it belongs to a client:
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
endpoint = client.package.endpoint("find-user")
|
|
261
|
+
endpoint.call(id: "123")
|
|
262
|
+
# => { "id" => "123", "name" => "Ada" }
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Error handling
|
|
266
|
+
|
|
267
|
+
Every error this gem raises inherits from `WebFunction::Error`. Each error
|
|
268
|
+
carries a `code` and optional `details`.
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
begin
|
|
272
|
+
client.find_user(id: "missing")
|
|
273
|
+
rescue WebFunction::Error => e
|
|
274
|
+
e.code # => "USER_NOT_FOUND"
|
|
275
|
+
e.message # => "No user with that id."
|
|
276
|
+
e.details # => { "id" => "missing" }
|
|
277
|
+
end
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
These are the error classes:
|
|
281
|
+
|
|
282
|
+
| Class | Raised when |
|
|
283
|
+
| --- | --- |
|
|
284
|
+
| `WebFunction::BadRequestError` | The server replied with status `400`. |
|
|
285
|
+
| `WebFunction::UnexpectedStatusCodeError` | The server replied with a status other than `200` or `400`. |
|
|
286
|
+
| `WebFunction::JsonParseError` | The response body was not valid JSON. |
|
|
287
|
+
| `WebFunction::UnresolvedPromiseError` | A pipeline promise was read before it resolved. |
|
|
288
|
+
|
|
289
|
+
When the server returns a `400`, the body is an error triple. A triple is a
|
|
290
|
+
JSON array with three parts: a code, a message, and details. The gem reads the
|
|
291
|
+
triple and fills in the error:
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
["USER_NOT_FOUND", "No user with that id.", { "id": "missing" }]
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```ruby
|
|
298
|
+
rescue WebFunction::BadRequestError => e
|
|
299
|
+
e.code # => "USER_NOT_FOUND"
|
|
300
|
+
e.message # => "No user with that id."
|
|
301
|
+
e.details # => { "id" => "missing" }
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
If the body is not a triple, the gem still raises `BadRequestError` with the
|
|
305
|
+
code `WFN_BAD_REQUEST_ERROR` and puts the raw body in `details`.
|
|
306
|
+
|
|
307
|
+
An endpoint can document the errors it may return. Read them when the endpoint
|
|
308
|
+
uses the `error_triple` flag:
|
|
309
|
+
|
|
310
|
+
```ruby
|
|
311
|
+
endpoint.errors
|
|
312
|
+
# => [#<WebFunction::DocumentedError code="USER_NOT_FOUND" ...>]
|
|
313
|
+
|
|
314
|
+
error = endpoint.error("USER_NOT_FOUND")
|
|
315
|
+
error.code # => "USER_NOT_FOUND"
|
|
316
|
+
error.docs # => "Returned when no user matches the id."
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
The package can document shared errors too:
|
|
320
|
+
|
|
321
|
+
```ruby
|
|
322
|
+
package.errors
|
|
323
|
+
package.error("RATE_LIMITED")
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Pipelining
|
|
327
|
+
|
|
328
|
+
Pipelining sends several calls in one HTTP request. The server runs them in
|
|
329
|
+
order and you can feed the output of one call into the next. This cuts the
|
|
330
|
+
number of round trips.
|
|
331
|
+
|
|
332
|
+
Build a pipelined client and each call returns a `WebFunction::Promise` instead
|
|
333
|
+
of a value. A promise stands in for a result that does not exist yet:
|
|
334
|
+
|
|
335
|
+
```ruby
|
|
336
|
+
client = WebFunction::Client.from_package_endpoint(
|
|
337
|
+
"https://api.example.com/package",
|
|
338
|
+
pipelined: true,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
user = client.find_user(id: "123") # => a Promise
|
|
342
|
+
order = client.create_order(user_id: user["id"]) # uses the first result
|
|
343
|
+
|
|
344
|
+
order.resolve
|
|
345
|
+
# => { "id" => "order-1", "user_id" => "123" }
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Reading `user["id"]` before the call runs does not return a value. It returns a
|
|
349
|
+
path into the future result. The gem sends that path to the server, and the
|
|
350
|
+
server fills it in when it runs the second call. Calling `resolve` runs the
|
|
351
|
+
whole pipeline and returns the value.
|
|
352
|
+
|
|
353
|
+
Once a pipeline runs, every promise from that batch holds its value:
|
|
354
|
+
|
|
355
|
+
```ruby
|
|
356
|
+
user.resolve # runs the pipeline
|
|
357
|
+
order.value # already available, no extra request
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
You can also drive a pipeline by hand with `WebFunction::Pipeline`. Each step is
|
|
361
|
+
a hash with a `url`, `headers`, and `body`:
|
|
362
|
+
|
|
363
|
+
```ruby
|
|
364
|
+
pipeline = WebFunction::Pipeline.new("https://api.example.com/run-pipeline")
|
|
365
|
+
|
|
366
|
+
pipeline.add_step(url: "https://api.example.com/a", headers: {}, body: {})
|
|
367
|
+
pipeline.add_step(url: "https://api.example.com/b", headers: {}, body: {})
|
|
368
|
+
|
|
369
|
+
pipeline.execute(returns: :all)
|
|
370
|
+
# => [{ "a" => 1 }, { "b" => 2 }]
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The `returns` option controls what comes back:
|
|
374
|
+
|
|
375
|
+
- `:all` returns every step result as an array. This is the default.
|
|
376
|
+
- `:last` returns only the last step result.
|
|
377
|
+
- A JSONPath string returns the value at that path, for example `"$[0].id"`.
|
|
378
|
+
|
|
379
|
+
## Custom HTTP client
|
|
380
|
+
|
|
381
|
+
By default the gem makes requests with [Excon](https://github.com/excon/excon).
|
|
382
|
+
You can swap in any HTTP client by setting `WebFunction::Request.http_client` to
|
|
383
|
+
an object that responds to `call`.
|
|
384
|
+
|
|
385
|
+
The object receives the URL, the headers, and the JSON body. It must return a
|
|
386
|
+
two-element array of the status code and the raw response body:
|
|
387
|
+
|
|
388
|
+
```ruby
|
|
389
|
+
WebFunction::Request.http_client = ->(url, headers, body) do
|
|
390
|
+
response = MyHttp.post(url, headers: headers, body: body)
|
|
391
|
+
[response.status, response.body]
|
|
392
|
+
end
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
This is also handy in tests, where you can return a canned response without
|
|
396
|
+
making a real request:
|
|
397
|
+
|
|
398
|
+
```ruby
|
|
399
|
+
WebFunction::Request.http_client = ->(_url, _headers, _body) do
|
|
400
|
+
[200, JSON.generate({ "id" => "123" })]
|
|
401
|
+
end
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Low-level requests
|
|
405
|
+
|
|
406
|
+
If you do not need a package, you can call a single endpoint URL directly with
|
|
407
|
+
`WebFunction::Request`:
|
|
408
|
+
|
|
409
|
+
```ruby
|
|
410
|
+
WebFunction::Request.execute(
|
|
411
|
+
"https://api.example.com/find-user",
|
|
412
|
+
bearer_auth: "my-token",
|
|
413
|
+
version: "2024-01-01",
|
|
414
|
+
args: { id: "123" },
|
|
415
|
+
)
|
|
416
|
+
# => { "id" => "123", "name" => "Ada" }
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
The request adds the standard headers, posts the JSON body, and parses the
|
|
420
|
+
response. It raises the same errors described in [Error handling](#error-handling).
|
|
421
|
+
|
|
422
|
+
## Command line tool
|
|
423
|
+
|
|
424
|
+
The gem ships with a `wfn` command. Use it to call an endpoint from the shell.
|
|
425
|
+
Arguments go in a JSON string:
|
|
18
426
|
|
|
19
427
|
```bash
|
|
20
|
-
|
|
428
|
+
wfn call https://api.example.com/find-user '{"id":"123"}'
|
|
21
429
|
```
|
|
22
430
|
|
|
23
|
-
|
|
431
|
+
Pass a bearer token with `--auth` and a version with `--version`:
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
wfn call --auth my-token --version 2024-01-01 \
|
|
435
|
+
https://api.example.com/list-orders '{}'
|
|
436
|
+
```
|
|
24
437
|
|
|
25
|
-
|
|
438
|
+
The command prints the response as formatted JSON. On an error it prints the
|
|
439
|
+
code, the message, and the details, then exits with a non-zero status.
|
|
26
440
|
|
|
27
441
|
## Development
|
|
28
442
|
|
|
29
|
-
After
|
|
443
|
+
After you check out the repo, run `bin/setup` to install dependencies. Then run
|
|
444
|
+
`rake test` to run the tests. Run `bin/console` for a prompt where you can
|
|
445
|
+
experiment with the code.
|
|
30
446
|
|
|
31
|
-
To install
|
|
447
|
+
To install the gem on your machine, run `bundle exec rake install`. To release a
|
|
448
|
+
new version, update the version in `lib/web_function/version.rb`, then run
|
|
449
|
+
`bundle exec rake release`. This creates a git tag, pushes the commits and the
|
|
450
|
+
tag, and pushes the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
32
451
|
|
|
33
452
|
## Contributing
|
|
34
453
|
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at
|
|
454
|
+
Bug reports and pull requests are welcome on GitHub at
|
|
455
|
+
[github.com/robinclart/web_function](https://github.com/robinclart/web_function).
|
|
36
456
|
|
|
37
457
|
## License
|
|
38
458
|
|
|
39
|
-
|
|
459
|
+
This gem is open source under the terms of the
|
|
460
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -10,10 +10,9 @@ module WebFunction
|
|
|
10
10
|
class Argument
|
|
11
11
|
include Flaggable
|
|
12
12
|
|
|
13
|
-
def initialize(name:, type:,
|
|
13
|
+
def initialize(name:, type:, group: nil, choices: [], flags: [], docs: nil)
|
|
14
14
|
@name = name
|
|
15
|
-
@type = type
|
|
16
|
-
@hint = hint
|
|
15
|
+
@type = Type.parse(type)
|
|
17
16
|
@group = group
|
|
18
17
|
@choices = choices
|
|
19
18
|
@flags = flags
|
|
@@ -43,7 +42,6 @@ module WebFunction
|
|
|
43
42
|
new(
|
|
44
43
|
name: argument["name"],
|
|
45
44
|
type: argument["type"],
|
|
46
|
-
hint: argument["hint"],
|
|
47
45
|
group: argument["group"],
|
|
48
46
|
choices: [*argument["choices"]],
|
|
49
47
|
flags: Utils.normalize_array_of_strings(argument["flags"]),
|
|
@@ -83,16 +81,6 @@ module WebFunction
|
|
|
83
81
|
#
|
|
84
82
|
attr_reader :type
|
|
85
83
|
|
|
86
|
-
# A hint that further defines what kind of value to expect for an argument.
|
|
87
|
-
#
|
|
88
|
-
# See the [hints section][1] on the Web Function website for the full list of possible hints.
|
|
89
|
-
#
|
|
90
|
-
# @return [String]
|
|
91
|
-
#
|
|
92
|
-
# [1]: https://webfunction.org/package#hints
|
|
93
|
-
#
|
|
94
|
-
attr_reader :hint
|
|
95
|
-
|
|
96
84
|
# A name used to categorize or group similar arguments together. This should be used by documentation tools to
|
|
97
85
|
# organize related arguments.
|
|
98
86
|
#
|
|
@@ -12,10 +12,9 @@ module WebFunction
|
|
|
12
12
|
class Attribute
|
|
13
13
|
include Flaggable
|
|
14
14
|
|
|
15
|
-
def initialize(name:, type:,
|
|
15
|
+
def initialize(name:, type:, values: [], flags: [], docs: nil)
|
|
16
16
|
@name = name
|
|
17
|
-
@type = type
|
|
18
|
-
@hint = hint
|
|
17
|
+
@type = Type.parse(type)
|
|
19
18
|
@values = values
|
|
20
19
|
@flags = flags
|
|
21
20
|
@docs = docs.to_s
|
|
@@ -44,7 +43,6 @@ module WebFunction
|
|
|
44
43
|
new(
|
|
45
44
|
name: attribute["name"],
|
|
46
45
|
type: attribute["type"],
|
|
47
|
-
hint: attribute["hint"],
|
|
48
46
|
values: [*attribute["values"]],
|
|
49
47
|
flags: Utils.normalize_array_of_strings(attribute["flags"]),
|
|
50
48
|
docs: attribute["docs"],
|
|
@@ -88,15 +86,6 @@ module WebFunction
|
|
|
88
86
|
#
|
|
89
87
|
attr_reader :type
|
|
90
88
|
|
|
91
|
-
# A string hinting about the semantics of this attribute. See the [hints section][1] for possible values and
|
|
92
|
-
# documentation tooling guidance.
|
|
93
|
-
#
|
|
94
|
-
# [1]: https://webfunction.org/package#hints
|
|
95
|
-
#
|
|
96
|
-
# @return [String, nil]
|
|
97
|
-
#
|
|
98
|
-
attr_reader :hint
|
|
99
|
-
|
|
100
89
|
# An array specifying the exact, case-sensitive values that may be returned for this attribute. Each value in the
|
|
101
90
|
# values array must conform to the data type specified in the "type" key.
|
|
102
91
|
#
|
data/lib/web_function/client.rb
CHANGED
|
@@ -18,7 +18,7 @@ module WebFunction
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
class << self
|
|
21
|
-
# Creates a new {Client} from an
|
|
21
|
+
# Creates a new {Client} from an endpoint.
|
|
22
22
|
#
|
|
23
23
|
# @param url [String] The URL of the package endpoint
|
|
24
24
|
# @param bearer_auth [String] The bearer authentication token
|
|
@@ -34,6 +34,22 @@ module WebFunction
|
|
|
34
34
|
from_package(package, bearer_auth: bearer_auth, version: version, pipelined: pipelined)
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
# Creates a new {Client} from an url.
|
|
38
|
+
#
|
|
39
|
+
# @param url [String] The URL of the package endpoint
|
|
40
|
+
# @param bearer_auth [String] The bearer authentication token
|
|
41
|
+
# @param version [String] The API version to use
|
|
42
|
+
# @param pipelined [Boolean] Whether to have the client use call pipelining
|
|
43
|
+
#
|
|
44
|
+
# @return [Client]
|
|
45
|
+
#
|
|
46
|
+
def from_url(url, bearer_auth: nil, version: nil, pipelined: false)
|
|
47
|
+
body = ::WebFunction::Utils.get_body_from_url(url, extra_query_params: { api_version: version })
|
|
48
|
+
package = ::WebFunction::Package.from_hash(::JSON.parse(body))
|
|
49
|
+
|
|
50
|
+
from_package(package, bearer_auth: bearer_auth, version: version, pipelined: pipelined)
|
|
51
|
+
end
|
|
52
|
+
|
|
37
53
|
# Creates a new {Client} from a {Package}.
|
|
38
54
|
#
|
|
39
55
|
# @param package [Package] A package
|
|
@@ -95,10 +111,32 @@ module WebFunction
|
|
|
95
111
|
#
|
|
96
112
|
attr_reader :package
|
|
97
113
|
|
|
114
|
+
# The bearer authentication token.
|
|
115
|
+
#
|
|
116
|
+
# @param bearer_auth [String] The bearer authentication token
|
|
117
|
+
#
|
|
118
|
+
attr_writer :bearer_auth
|
|
119
|
+
|
|
120
|
+
# The API version to use.
|
|
121
|
+
#
|
|
122
|
+
# @param version [String] The API version to use
|
|
123
|
+
#
|
|
124
|
+
attr_writer :version
|
|
125
|
+
|
|
126
|
+
# The pipeline to use.
|
|
127
|
+
#
|
|
128
|
+
# @param pipeline [Pipeline] The pipeline to use
|
|
129
|
+
#
|
|
130
|
+
attr_writer :pipeline
|
|
131
|
+
|
|
98
132
|
def methods # :nodoc:
|
|
99
133
|
@endpoints.keys
|
|
100
134
|
end
|
|
101
135
|
|
|
136
|
+
def nil? # :nodoc:
|
|
137
|
+
false
|
|
138
|
+
end
|
|
139
|
+
|
|
102
140
|
def respond_to_missing?(method_name, include_private = false) # :nodoc:
|
|
103
141
|
@endpoints[method_name]
|
|
104
142
|
end
|
|
@@ -25,10 +25,9 @@ module WebFunction
|
|
|
25
25
|
class Endpoint
|
|
26
26
|
include Flaggable
|
|
27
27
|
|
|
28
|
-
def initialize(name:, returns
|
|
28
|
+
def initialize(name:, returns:, flags: [], group: nil, docs: nil, arguments: [], attributes: [], errors: [])
|
|
29
29
|
@name = name
|
|
30
|
-
@returns = returns
|
|
31
|
-
@hints = hints
|
|
30
|
+
@returns = Type.parse(returns)
|
|
32
31
|
@flags = flags
|
|
33
32
|
@group = group
|
|
34
33
|
@docs = docs
|
|
@@ -66,10 +65,13 @@ module WebFunction
|
|
|
66
65
|
return
|
|
67
66
|
end
|
|
68
67
|
|
|
68
|
+
unless endpoint["returns"]
|
|
69
|
+
return
|
|
70
|
+
end
|
|
71
|
+
|
|
69
72
|
new(
|
|
70
73
|
name: endpoint["name"],
|
|
71
|
-
returns:
|
|
72
|
-
hints: Utils.normalize_array_of_strings(endpoint["hints"]),
|
|
74
|
+
returns: endpoint["returns"],
|
|
73
75
|
flags: Utils.normalize_array_of_strings(endpoint["flags"]),
|
|
74
76
|
group: endpoint["group"],
|
|
75
77
|
docs: endpoint["docs"].to_s,
|
|
@@ -119,16 +121,6 @@ module WebFunction
|
|
|
119
121
|
#
|
|
120
122
|
attr_reader :returns
|
|
121
123
|
|
|
122
|
-
# Hints for the endpoint's return value. Each hint's base JSON type matches one of the endpoint's {#returns} types,
|
|
123
|
-
# and at most one hint is supplied per base JSON type. See the [hints section][1] on the Web Function website for
|
|
124
|
-
# the complete list of allowed hints.
|
|
125
|
-
#
|
|
126
|
-
# @return [Array<String>]
|
|
127
|
-
#
|
|
128
|
-
# [1]: https://webfunction.org/package#hints
|
|
129
|
-
#
|
|
130
|
-
attr_reader :hints
|
|
131
|
-
|
|
132
124
|
# A name used to categorize or group similar endpoints together. This should be used by documentation tools to
|
|
133
125
|
# organize related endpoints.
|
|
134
126
|
#
|