haveapi 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 {
|