haveapi 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +15 -0
  3. data/CHANGELOG +15 -0
  4. data/README.md +66 -47
  5. data/doc/create-client.md +14 -5
  6. data/doc/json-schema.erb +16 -2
  7. data/doc/protocol.md +25 -3
  8. data/doc/protocol.plantuml +14 -8
  9. data/haveapi.gemspec +4 -2
  10. data/lib/haveapi.rb +5 -3
  11. data/lib/haveapi/action.rb +34 -6
  12. data/lib/haveapi/action_state.rb +92 -0
  13. data/lib/haveapi/authentication/basic/provider.rb +7 -0
  14. data/lib/haveapi/authentication/token/provider.rb +5 -0
  15. data/lib/haveapi/client_example.rb +83 -0
  16. data/lib/haveapi/client_examples/curl.rb +86 -0
  17. data/lib/haveapi/client_examples/fs_client.rb +116 -0
  18. data/lib/haveapi/client_examples/http.rb +91 -0
  19. data/lib/haveapi/client_examples/js_client.rb +149 -0
  20. data/lib/haveapi/client_examples/php_client.rb +122 -0
  21. data/lib/haveapi/client_examples/ruby_cli.rb +117 -0
  22. data/lib/haveapi/client_examples/ruby_client.rb +106 -0
  23. data/lib/haveapi/context.rb +3 -2
  24. data/lib/haveapi/example.rb +29 -2
  25. data/lib/haveapi/extensions/action_exceptions.rb +2 -2
  26. data/lib/haveapi/extensions/base.rb +1 -1
  27. data/lib/haveapi/extensions/exception_mailer.rb +339 -0
  28. data/lib/haveapi/hooks.rb +1 -1
  29. data/lib/haveapi/parameters/typed.rb +5 -3
  30. data/lib/haveapi/public/css/highlight.css +99 -0
  31. data/lib/haveapi/public/doc/protocol.png +0 -0
  32. data/lib/haveapi/public/js/highlight.pack.js +2 -0
  33. data/lib/haveapi/public/js/highlighter.js +9 -0
  34. data/lib/haveapi/public/js/main.js +32 -0
  35. data/lib/haveapi/public/js/nojs-tabs.js +196 -0
  36. data/lib/haveapi/resources/action_state.rb +196 -0
  37. data/lib/haveapi/server.rb +96 -27
  38. data/lib/haveapi/version.rb +2 -2
  39. data/lib/haveapi/views/main_layout.erb +14 -0
  40. data/lib/haveapi/views/version_page.erb +187 -13
  41. data/lib/haveapi/views/version_sidebar.erb +37 -3
  42. metadata +49 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ebf5f1d0fbc768717e2524ba3f39aacd309cfc99
4
- data.tar.gz: fc682dfc13a3233775e4b277f5a28b898c4c39f6
3
+ metadata.gz: 6bd050d4c2bbaf7541cf8f8a353e41c20e4dc6da
4
+ data.tar.gz: 93f458319509dc615b1c1036e3729d16f5d4cd92
5
5
  SHA512:
6
- metadata.gz: 3c6a7e2cf446b7a073abfc3c730d8e66a98bf08c352b436794955908eb81b991935c9e2407e95ce5c2d44d7076089580e5d2ff0aa796dacb19c017da74aca7b4
7
- data.tar.gz: 122cf69cfd635ce62ec712d2d1d1d578cb8d5ddde30baa78b29fe7fc644bcfc1f865e9416b7157612487b3c0d8405f5c0544c3812da77d6fa6d3f02c12207461
6
+ metadata.gz: b5380970887ca813f6de8b6181c8d4dd941d0a84ff3a357b16176f18fb7073e7a8c29799c0af15a56d380f9b7f9a86ef8f66bc0d3b4bede815a7ba4a5f5b419d
7
+ data.tar.gz: a5481d5adb7e8cfae279243cfbc67be05fa56e2f984c6d908b73f207e50c1438bbe2befec0334930fe34ef4423b9473131387bac02084bc7a51f17829d3e9e10
@@ -0,0 +1,15 @@
1
+ # http://editorconfig.org
2
+ root = true
3
+
4
+ [*]
5
+ charset = utf-8
6
+ end_of_line = lf
7
+ trim_trailing_whitespace = true
8
+
9
+ [*.{rb,erb}]
10
+ indent_size = 2
11
+ indent_style = space
12
+
13
+ [*.plantuml]
14
+ indent_size = 2
15
+ indent_style = tab
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
- A framework for creating self-describing APIs in Ruby.
4
-
5
- Note: HaveAPI is in heavy development. It is not stable, its interface may change.
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
- ## Main features
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
- - Handles network communication, input/output formats and parameters
19
- on both server and client, you need only to define resources and actions
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
- using this framework
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
- ## Available clients
265
- These clients completely rely on the API description and can be used for all
266
- APIs that are using HaveAPI.
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)
@@ -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.
@@ -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
- meta: { '$ref' => '#/definitions/action_meta' },
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: {
@@ -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": {
@@ -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 : list
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 {