haveapi 0.6.0 → 0.7.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/.editorconfig +15 -0
- data/CHANGELOG +15 -0
- data/README.md +66 -47
- data/doc/create-client.md +14 -5
- data/doc/json-schema.erb +16 -2
- data/doc/protocol.md +25 -3
- data/doc/protocol.plantuml +14 -8
- data/haveapi.gemspec +4 -2
- data/lib/haveapi.rb +5 -3
- data/lib/haveapi/action.rb +34 -6
- data/lib/haveapi/action_state.rb +92 -0
- data/lib/haveapi/authentication/basic/provider.rb +7 -0
- data/lib/haveapi/authentication/token/provider.rb +5 -0
- data/lib/haveapi/client_example.rb +83 -0
- data/lib/haveapi/client_examples/curl.rb +86 -0
- data/lib/haveapi/client_examples/fs_client.rb +116 -0
- data/lib/haveapi/client_examples/http.rb +91 -0
- data/lib/haveapi/client_examples/js_client.rb +149 -0
- data/lib/haveapi/client_examples/php_client.rb +122 -0
- data/lib/haveapi/client_examples/ruby_cli.rb +117 -0
- data/lib/haveapi/client_examples/ruby_client.rb +106 -0
- data/lib/haveapi/context.rb +3 -2
- data/lib/haveapi/example.rb +29 -2
- data/lib/haveapi/extensions/action_exceptions.rb +2 -2
- data/lib/haveapi/extensions/base.rb +1 -1
- data/lib/haveapi/extensions/exception_mailer.rb +339 -0
- data/lib/haveapi/hooks.rb +1 -1
- data/lib/haveapi/parameters/typed.rb +5 -3
- data/lib/haveapi/public/css/highlight.css +99 -0
- data/lib/haveapi/public/doc/protocol.png +0 -0
- data/lib/haveapi/public/js/highlight.pack.js +2 -0
- data/lib/haveapi/public/js/highlighter.js +9 -0
- data/lib/haveapi/public/js/main.js +32 -0
- data/lib/haveapi/public/js/nojs-tabs.js +196 -0
- data/lib/haveapi/resources/action_state.rb +196 -0
- data/lib/haveapi/server.rb +96 -27
- data/lib/haveapi/version.rb +2 -2
- data/lib/haveapi/views/main_layout.erb +14 -0
- data/lib/haveapi/views/version_page.erb +187 -13
- data/lib/haveapi/views/version_sidebar.erb +37 -3
- metadata +49 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6bd050d4c2bbaf7541cf8f8a353e41c20e4dc6da
|
4
|
+
data.tar.gz: 93f458319509dc615b1c1036e3729d16f5d4cd92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5380970887ca813f6de8b6181c8d4dd941d0a84ff3a357b16176f18fb7073e7a8c29799c0af15a56d380f9b7f9a86ef8f66bc0d3b4bede815a7ba4a5f5b419d
|
7
|
+
data.tar.gz: a5481d5adb7e8cfae279243cfbc67be05fa56e2f984c6d908b73f207e50c1438bbe2befec0334930fe34ef4423b9473131387bac02084bc7a51f17829d3e9e10
|
data/.editorconfig
ADDED
data/CHANGELOG
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
* Thu Nov 24 2016 - version 0.7.0
|
2
|
+
- Blocking actions
|
3
|
+
- Attempt to authenticate even for actions that don't require auth
|
4
|
+
- Authentication methods have documented text descriptions
|
5
|
+
- Examples can contain URL parameters, message, errors and HTTP status code
|
6
|
+
- Examples are rendered in HaveAPI client languages, showing a snippet of
|
7
|
+
code generated from the example
|
8
|
+
- Allow APIs to set HTTP status code
|
9
|
+
- New hook HaveAPI::Server.description_exception
|
10
|
+
- Online doc: link to prebuilt haveapi-webui at webui.haveapi.org
|
11
|
+
- Print errors and exceptions in development mode
|
12
|
+
- New extension ExceptionMailer
|
13
|
+
- Fixed HaveAPI::Hooks.stop
|
14
|
+
- Use DateTime.iso8601 to parse dates
|
15
|
+
|
1
16
|
* Tue Oct 18 2016 - version 0.6.0
|
2
17
|
- Token authentication: fix output of action renew
|
3
18
|
- ActiveRecord: fix parsing of includes
|
data/README.md
CHANGED
@@ -1,29 +1,60 @@
|
|
1
1
|
HaveAPI
|
2
2
|
=======
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
HaveAPI defines a protocol for self-describing RESTful APIs. This repository contains
|
4
|
+
documentation of said protocol and a reference implementation in Ruby, in the form of
|
5
|
+
a framework for building APIs.
|
6
6
|
|
7
7
|
## What is a self-describing API?
|
8
8
|
A self-describing API responds to HTTP method `OPTIONS` and returns description
|
9
9
|
of available resources and their actions. The description contains
|
10
|
-
full list of parameters, their labels, text notes, data types, validators
|
10
|
+
a full list of parameters, their labels, text notes, data types, validators
|
11
11
|
and example usage.
|
12
12
|
|
13
13
|
Clients use the self-description to learn how to communicate with the API,
|
14
14
|
which they otherwise know nothing about.
|
15
15
|
|
16
|
-
##
|
16
|
+
## Motivation
|
17
|
+
Whenever you create an API server, you need to implement clients in various programming
|
18
|
+
languages to work with it. Even if you make all APIs similar using e.g. REST or SOAP,
|
19
|
+
you still need clients to know what resources and actions the API has, what are their
|
20
|
+
parameters, and so on.
|
21
|
+
|
22
|
+
When your API speaks the HaveAPI protocol, you can use pre-created clients that will
|
23
|
+
know how to work with it. You can do this by using this framework to handle all
|
24
|
+
HaveAPI-protocol stuff for you, or you can implement the [protocol](doc/protocol.md)
|
25
|
+
on your own.
|
26
|
+
|
27
|
+
At the moment, the following clients are available:
|
28
|
+
|
29
|
+
- Ruby client library and CLI: https://github.com/vpsfreecz/haveapi-client
|
30
|
+
- PHP client: https://github.com/vpsfreecz/haveapi-client-php
|
31
|
+
- JavaScript client: https://github.com/vpsfreecz/haveapi-client-js
|
32
|
+
|
33
|
+
If there isn't a client in the language you need, you can [create it](doc/create-client.md).
|
34
|
+
|
35
|
+
Complex applications can be built on top of these basic clients, e.g.:
|
36
|
+
|
37
|
+
- [haveapi-webui](https://github.com/vpsfreecz/haveapi-webui), a generic web administration
|
38
|
+
for HaveAPI-based APIs
|
39
|
+
- [haveapi-fs](https://github.com/vpsfreecz/haveapi-fs), a FUSE based filesystem that can
|
40
|
+
mount any HaveAPI-based API
|
41
|
+
|
42
|
+
## Protocol features
|
17
43
|
- Creates RESTful APIs usable even with simple HTTP client, should it be needed
|
18
|
-
-
|
19
|
-
|
20
|
-
- By writing the code you get the documentation for free, it is available to all clients
|
21
|
-
- Auto-generated online HTML documentation
|
44
|
+
- A change in the API is immediately reflected in all clients when they re-download the
|
45
|
+
documentation
|
22
46
|
- Generic interface for clients - one client can be used to access all APIs
|
23
|
-
|
24
|
-
- Ruby (with CLI), PHP and JavaScript (with web administration) clients already available
|
25
|
-
- A change in the API is immediately reflected in all clients
|
47
|
+
that implement this protocol
|
26
48
|
- Supports API versioning
|
49
|
+
- Standardised authentication methods
|
50
|
+
- Defines action input/output parameters and their validators
|
51
|
+
- Clients can monitor progress of long-running actions
|
52
|
+
|
53
|
+
## Server framework features
|
54
|
+
- Handles network communication, authentication, authorization, input/output formats
|
55
|
+
and parameters, you need only to define resources and actions
|
56
|
+
- By writing the code you get the documentation for free
|
57
|
+
- Auto-generated online HTML documentation
|
27
58
|
- ORM integration
|
28
59
|
- ActiveRecord
|
29
60
|
- integrated with controller
|
@@ -60,7 +91,7 @@ class User < ActiveRecord::Base
|
|
60
91
|
in: %w(admin user),
|
61
92
|
message '%{value} is not a valid role'
|
62
93
|
}
|
63
|
-
|
94
|
+
|
64
95
|
# An example authentication with plain text password
|
65
96
|
def self.authenticate(username, password)
|
66
97
|
u = User.find_by(login: username)
|
@@ -79,42 +110,42 @@ module MyAPI
|
|
79
110
|
# This resource belongs to version 1.
|
80
111
|
# It is also possible to put resource to multiple versions, e.g. [1, 2]
|
81
112
|
version 1
|
82
|
-
|
113
|
+
|
83
114
|
# Provide description for this resource
|
84
115
|
desc 'Manage users'
|
85
|
-
|
116
|
+
|
86
117
|
# ActiveRecord model to load validators from
|
87
118
|
model ::User
|
88
|
-
|
119
|
+
|
89
120
|
# Require authentication, this is the default
|
90
121
|
auth true
|
91
|
-
|
122
|
+
|
92
123
|
# Create a named group of shared params, that may be later included
|
93
124
|
# by actions.
|
94
125
|
params(:id) do
|
95
126
|
id :id, label: 'User ID'
|
96
127
|
end
|
97
|
-
|
128
|
+
|
98
129
|
params(:common) do
|
99
130
|
string :login, label: 'Login', desc: 'Used for authentication'
|
100
131
|
string :full_name, label: 'Full name'
|
101
132
|
string :role, label: 'User role', desc: 'admin or user'
|
102
133
|
end
|
103
|
-
|
134
|
+
|
104
135
|
# Actions
|
105
136
|
# Module HaveAPI::Actions::Default contains helper classes that define
|
106
137
|
# HTTP methods and routes for generic actions.
|
107
138
|
class Index < HaveAPI::Actions::Default::Index
|
108
139
|
desc 'List all users'
|
109
|
-
|
140
|
+
|
110
141
|
# There are no input parameters
|
111
|
-
|
142
|
+
|
112
143
|
# Output parameters
|
113
144
|
output(:object_list) do
|
114
145
|
use :id
|
115
146
|
use :common
|
116
147
|
end
|
117
|
-
|
148
|
+
|
118
149
|
# Determine if current user can use this action.
|
119
150
|
# allow/deny immediately returns from this block.
|
120
151
|
# Default rule is deny.
|
@@ -122,7 +153,7 @@ module MyAPI
|
|
122
153
|
allow if u.role == 'admin'
|
123
154
|
deny # deny is implicit, so it may be omitted
|
124
155
|
end
|
125
|
-
|
156
|
+
|
126
157
|
# Provide example usage
|
127
158
|
example do
|
128
159
|
request({})
|
@@ -147,30 +178,30 @@ module MyAPI
|
|
147
178
|
def count
|
148
179
|
query.count
|
149
180
|
end
|
150
|
-
|
181
|
+
|
151
182
|
# Execute action, return the list
|
152
183
|
def exec
|
153
184
|
query.limit(input[:limit]).offset(input[:offset])
|
154
185
|
end
|
155
186
|
end
|
156
|
-
|
187
|
+
|
157
188
|
class Create < HaveAPI::Actions::Default::Create
|
158
189
|
desc 'Create new user'
|
159
|
-
|
190
|
+
|
160
191
|
input do
|
161
192
|
use :common
|
162
193
|
end
|
163
|
-
|
194
|
+
|
164
195
|
output do
|
165
196
|
use :id
|
166
197
|
use :common
|
167
198
|
end
|
168
|
-
|
199
|
+
|
169
200
|
authorize do |u|
|
170
201
|
allow if u.role == 'admin'
|
171
202
|
deny
|
172
203
|
end
|
173
|
-
|
204
|
+
|
174
205
|
example do
|
175
206
|
request({
|
176
207
|
user: {
|
@@ -185,10 +216,10 @@ module MyAPI
|
|
185
216
|
})
|
186
217
|
comment 'Create new user like this'
|
187
218
|
end
|
188
|
-
|
219
|
+
|
189
220
|
def exec
|
190
221
|
user = ::User.new(input)
|
191
|
-
|
222
|
+
|
192
223
|
if user.save
|
193
224
|
ok(user)
|
194
225
|
else
|
@@ -261,22 +292,10 @@ resources, actions and parameters will be returned.
|
|
261
292
|
Because validators are a part of the API's documentation, the clients can
|
262
293
|
perform client-side validation before the data is sent to the API server.
|
263
294
|
|
264
|
-
##
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
- Ruby client library and CLI: https://github.com/vpsfreecz/haveapi-client
|
269
|
-
- PHP client: https://github.com/vpsfreecz/haveapi-client-php
|
270
|
-
- JavaScript client: https://github.com/vpsfreecz/haveapi-client-js
|
271
|
-
|
272
|
-
or [create your own client](doc/create-client.md).
|
273
|
-
|
274
|
-
Complex applications can be built on top of these basic clients, e.g.:
|
275
|
-
|
276
|
-
- [haveapi-webui](https://github.com/vpsfreecz/haveapi-webui), a generic web administration
|
277
|
-
for HaveAPI-based APIs
|
278
|
-
- [haveapi-fs](https://github.com/vpsfreecz/haveapi-fs), a FUSE based filesystem that can
|
279
|
-
mount any HaveAPI-based API
|
295
|
+
## Blocking actions
|
296
|
+
Blocking mode is for actions whose execution is not immediate but takes an unspecified
|
297
|
+
amount of time. HaveAPI protocol allows clients to monitor progress of such actions
|
298
|
+
or cancel their execution.
|
280
299
|
|
281
300
|
## Read more
|
282
301
|
- [Protocol definition](doc/protocol.md)
|
data/doc/create-client.md
CHANGED
@@ -23,25 +23,25 @@ A model paradigm (in no particular language):
|
|
23
23
|
|
24
24
|
// Create client instance
|
25
25
|
api = new HaveAPI.Client("https://your.api.tld")
|
26
|
-
|
26
|
+
|
27
27
|
// Authenticate
|
28
28
|
api.authenticate("basic", {"user": "yourname", "password": "yourpassword"})
|
29
|
-
|
29
|
+
|
30
30
|
// Access resources and actions
|
31
31
|
api.<resource>.<action>( { <parameters> } )
|
32
32
|
api.user.new({"name": "Very Name", "password": "donottellanyone"})
|
33
33
|
api.user.list()
|
34
34
|
api.nested.resource.deep.list()
|
35
|
-
|
35
|
+
|
36
36
|
// Pass IDs to resources
|
37
37
|
api.nested(1).resource(2).deep.list()
|
38
|
-
|
38
|
+
|
39
39
|
// Object-like access
|
40
40
|
user = api.user.show(1)
|
41
41
|
user.id
|
42
42
|
user.name
|
43
43
|
user.destroy()
|
44
|
-
|
44
|
+
|
45
45
|
// Logout
|
46
46
|
api.logout()
|
47
47
|
|
@@ -96,3 +96,12 @@ Metadata channel is currently used to specify what associated resources should
|
|
96
96
|
be prefetched and whether an object list should contain total number of items.
|
97
97
|
|
98
98
|
Metadata is nothing more than a hash in input parameters under key `_meta`.
|
99
|
+
|
100
|
+
## Blocking actions
|
101
|
+
Useful for APIs with long-running actions. Clients can check state of such actions
|
102
|
+
using resource `ActionState`. Because this resource is automatically present in all
|
103
|
+
APIs that use blocking actions, client libraries expose this resource to the developer
|
104
|
+
just as any other resource in the API.
|
105
|
+
|
106
|
+
However, you may wish to integrate it in your client and provide ways for the action
|
107
|
+
call to block/accept callbacks.
|
data/doc/json-schema.erb
CHANGED
@@ -81,15 +81,29 @@ DEFINITIONS = {
|
|
81
81
|
type: :array,
|
82
82
|
items: { type: :string }
|
83
83
|
},
|
84
|
+
blocking: { type: :boolean },
|
84
85
|
input: { '$ref' => '#/definitions/input_parameters' },
|
85
86
|
output: { '$ref' => '#/definitions/output_parameters' },
|
86
|
-
|
87
|
+
meta: { '$ref' => '#/definitions/action_meta' },
|
87
88
|
examples: {
|
88
89
|
type: :object,
|
89
90
|
properties: {
|
90
91
|
title: { type: :string },
|
92
|
+
url_params: { type: :array, items: { type: :integer } },
|
91
93
|
request: { type: :object },
|
92
94
|
response: { type: :object },
|
95
|
+
status: { type: :boolean },
|
96
|
+
message: { type: :string },
|
97
|
+
errors: {
|
98
|
+
type: :object,
|
99
|
+
patternProperties: {
|
100
|
+
'^[a-z_]+$' => {
|
101
|
+
type: :array,
|
102
|
+
items: { type: :string },
|
103
|
+
}
|
104
|
+
}
|
105
|
+
},
|
106
|
+
http_status: { type: :integer },
|
93
107
|
comment: { type: :string },
|
94
108
|
}
|
95
109
|
},
|
@@ -99,7 +113,7 @@ DEFINITIONS = {
|
|
99
113
|
}
|
100
114
|
}
|
101
115
|
}
|
102
|
-
|
116
|
+
|
103
117
|
},
|
104
118
|
|
105
119
|
input_parameters: {
|
data/doc/protocol.md
CHANGED
@@ -157,6 +157,7 @@ Every action is described as:
|
|
157
157
|
"auth": true|false,
|
158
158
|
"description": "Describe what this action does",
|
159
159
|
"aliases": ["list", "of", "aliases"],
|
160
|
+
"blocking": true|false,
|
160
161
|
"input": {
|
161
162
|
"layout": "layout type",
|
162
163
|
"namespace": "namespace name",
|
@@ -389,12 +390,19 @@ render them according to its syntax.
|
|
389
390
|
|
390
391
|
{
|
391
392
|
"title": "A title",
|
393
|
+
"url_params: [ ... array of integers ... ],
|
392
394
|
"request": {
|
393
395
|
... a hash of request parameters ...
|
394
396
|
},
|
395
397
|
"response": {
|
396
398
|
... a hash of response parameters ...
|
397
399
|
},
|
400
|
+
"status": true/false,
|
401
|
+
"message": "Message explaining an error if it occurs",
|
402
|
+
"errors": {
|
403
|
+
"<parameter>": [ ... array of errors ... ],
|
404
|
+
},
|
405
|
+
http_status: 200,
|
398
406
|
"comment": "Description of the example"
|
399
407
|
}
|
400
408
|
|
@@ -408,13 +416,27 @@ response.
|
|
408
416
|
"input": ... parameters or null ...,
|
409
417
|
"output: ... parameters or null ...
|
410
418
|
} or null,
|
411
|
-
|
419
|
+
|
412
420
|
"object": {
|
413
421
|
"input": ... parameters or null ...,
|
414
422
|
"output: ... parameters or null ...
|
415
423
|
} or null,
|
416
424
|
}
|
417
425
|
|
426
|
+
## Blocking mode
|
427
|
+
Blocking mode is for actions whose execution takes a long time. Clients can monitor
|
428
|
+
progress of such actions and even cancel their execution.
|
429
|
+
|
430
|
+
The blocking action returns its result as usual, but provides a unique identifier using
|
431
|
+
which clients can check its status via the `ActionState` resource. The state identifier
|
432
|
+
is passed through global output metadata in a parameter called `action_state_id`.
|
433
|
+
Resource `ActionState` is a part of the API's documentation and is the standard interface
|
434
|
+
for browsing and manipulating currently active blocking actions.
|
435
|
+
|
436
|
+
An action is blocking if parameter `blocking` in its description is `true` and if
|
437
|
+
`action_state_id` is present in its metadata after each call. This allows actions
|
438
|
+
to be blocking only when needed.
|
439
|
+
|
418
440
|
## List API versions
|
419
441
|
Send request ``OPTIONS /?describe=versions``. The description format:
|
420
442
|
|
@@ -463,7 +485,7 @@ Example request:
|
|
463
485
|
Content-Type: application/json
|
464
486
|
Accept: application/json
|
465
487
|
Connection: Close
|
466
|
-
|
488
|
+
|
467
489
|
{
|
468
490
|
"user": {
|
469
491
|
"login": "mylogin",
|
@@ -479,7 +501,7 @@ from the self-description.
|
|
479
501
|
Example response to the request above:
|
480
502
|
|
481
503
|
Content-Type: application/json
|
482
|
-
|
504
|
+
|
483
505
|
{
|
484
506
|
"status": true,
|
485
507
|
"response": {
|
data/doc/protocol.plantuml
CHANGED
@@ -9,14 +9,15 @@ class Meta {
|
|
9
9
|
}
|
10
10
|
|
11
11
|
abstract class Authentication {
|
12
|
-
|
12
|
+
|
13
13
|
}
|
14
14
|
|
15
15
|
class Basic {
|
16
|
-
|
16
|
+
description : String
|
17
17
|
}
|
18
18
|
|
19
19
|
class Token {
|
20
|
+
description : String
|
20
21
|
http_header : String
|
21
22
|
query_parameter : String
|
22
23
|
}
|
@@ -30,7 +31,8 @@ class Action {
|
|
30
31
|
name : String
|
31
32
|
auth : Bool
|
32
33
|
description : String
|
33
|
-
aliases :
|
34
|
+
aliases : Array<String>
|
35
|
+
blocking : Bool
|
34
36
|
url : String
|
35
37
|
method : String
|
36
38
|
help : String
|
@@ -38,8 +40,12 @@ class Action {
|
|
38
40
|
|
39
41
|
class Example {
|
40
42
|
title : String
|
43
|
+
url_params : Array<Integer>
|
41
44
|
request : hash
|
42
45
|
response : hash
|
46
|
+
status : Bool
|
47
|
+
message : String
|
48
|
+
http_status : Integer
|
43
49
|
comment : String
|
44
50
|
}
|
45
51
|
|
@@ -60,7 +66,7 @@ class InputParameter {
|
|
60
66
|
}
|
61
67
|
|
62
68
|
class OutputParameter {
|
63
|
-
|
69
|
+
|
64
70
|
}
|
65
71
|
|
66
72
|
abstract class ResourceParameter {
|
@@ -69,14 +75,14 @@ abstract class ResourceParameter {
|
|
69
75
|
}
|
70
76
|
|
71
77
|
class TypedInputParameter {
|
72
|
-
|
78
|
+
|
73
79
|
}
|
74
80
|
|
75
81
|
class ResourceInputParameter {
|
76
82
|
}
|
77
83
|
|
78
84
|
class TypedOutputParameter {
|
79
|
-
|
85
|
+
|
80
86
|
}
|
81
87
|
|
82
88
|
class ResourceOutputParameter {
|
@@ -119,7 +125,7 @@ class FormatValidator {
|
|
119
125
|
}
|
120
126
|
|
121
127
|
abstract class InclusionValidator {
|
122
|
-
|
128
|
+
|
123
129
|
}
|
124
130
|
|
125
131
|
class ArrayInclusionValidator {
|
@@ -131,7 +137,7 @@ class HashInclusionValidator {
|
|
131
137
|
}
|
132
138
|
|
133
139
|
abstract class LengthValidator {
|
134
|
-
|
140
|
+
|
135
141
|
}
|
136
142
|
|
137
143
|
class EqualLengthValidator {
|