haveapi 0.3.0 → 0.3.1
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/.gitignore +21 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +277 -0
- data/Rakefile +7 -0
- data/doc/create-client.md +98 -0
- data/doc/index.md +6 -0
- data/doc/protocol.md +355 -0
- data/haveapi.gemspec +30 -0
- data/lib/haveapi/version.rb +1 -1
- metadata +12 -4
- data/lib/haveapi/extensions/resource_prefetch.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4fa0da1f1b314ff7278f68c3f6388579d963b21c
|
4
|
+
data.tar.gz: 7df0e7f2124286d109535b860e53f64c44a06b1c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 945c9596473a913973fc06bc1432a5c8c6cb6b921a780ab420eddea9036b281d81b117aabbaf911e3844c5f0391da728a45e00674a482dcd398ac5e3a02ac961
|
7
|
+
data.tar.gz: dd965f92e0c67a799e053fa9b3f5817e3c1b899127884d81372f3f40872df2751450001d4bfbfc525ed6b1f9d7100dd3f46a1ee78e77ad1ec3e1b7946691a6be
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Jakub Skokan
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
HaveAPI
|
2
|
+
=======
|
3
|
+
A framework for creating self-describing APIs in Ruby.
|
4
|
+
|
5
|
+
Note: HaveAPI is under heavy development. It is not stable, its interface may change.
|
6
|
+
|
7
|
+
## What is a self-describing API?
|
8
|
+
A self-describing API responds to HTTP method `OPTIONS` and returns description
|
9
|
+
of available resources and their actions. The description contains
|
10
|
+
full list of parameters, their labels, text notes, data types, validators
|
11
|
+
and example usage.
|
12
|
+
|
13
|
+
Clients use the self-description to learn how to communicate with the API,
|
14
|
+
which they otherwise know nothing about.
|
15
|
+
|
16
|
+
## Main features
|
17
|
+
- Creates RESTful APIs
|
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 which is available to all clients
|
21
|
+
- Auto-generated online HTML documentation
|
22
|
+
- Generic interface for clients - one client can be used to access all APIs
|
23
|
+
using this framework
|
24
|
+
- Ruby, PHP and JavaScript clients already available
|
25
|
+
- A change in the API is immediately reflected in all clients
|
26
|
+
- Supports API versioning
|
27
|
+
- Ready for ActiveRecord - validators from models are included in the
|
28
|
+
self-description
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
This text might not be complete or up-to-date, as things still often change.
|
32
|
+
Full use of HaveAPI may be seen
|
33
|
+
in [vpsadminapi](https://github.com/vpsfreecz/vpsadminapi), which may serve
|
34
|
+
as an example of how are things meant to be used.
|
35
|
+
|
36
|
+
All resources and actions are represented by classes. They all must be stored
|
37
|
+
in a module, whose name is later given to HaveAPI.
|
38
|
+
|
39
|
+
HaveAPI then searches all classes in that module and constructs your API.
|
40
|
+
|
41
|
+
For the purposes of this document, all resources will be in module `MyAPI`.
|
42
|
+
|
43
|
+
### Example
|
44
|
+
This is a basic example, it does not show all options and functions.
|
45
|
+
|
46
|
+
Let's assume a model:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class User < ActiveRecord::Base
|
50
|
+
validates :login, :full_name, :role, presence: true
|
51
|
+
validates :login, format: {
|
52
|
+
with: /[a-zA-Z\.\-]{3,30}/,
|
53
|
+
message: 'not a valid login'
|
54
|
+
}, uniqueness: true
|
55
|
+
validates :role, inclusion: {
|
56
|
+
in: %w(admin user),
|
57
|
+
message '%{value} is not a valid role'
|
58
|
+
}
|
59
|
+
|
60
|
+
# An example authentication with plain text password
|
61
|
+
def self.authenticate(username, password)
|
62
|
+
u = User.find_by(login: username)
|
63
|
+
|
64
|
+
if u
|
65
|
+
u if u.password == password
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
Resource user might look like this:
|
72
|
+
```ruby
|
73
|
+
module MyAPI
|
74
|
+
class User < HaveAPI::Resource
|
75
|
+
# This resource belongs to version 1.
|
76
|
+
# It is also possible to put resource to multiple versions, e.g. [1, 2]
|
77
|
+
version 1
|
78
|
+
|
79
|
+
# Provide description for this resource
|
80
|
+
desc 'Manage users'
|
81
|
+
|
82
|
+
# ActiveRecord model to load validators from
|
83
|
+
model ::User
|
84
|
+
|
85
|
+
# Require authentication, this is the default
|
86
|
+
auth true
|
87
|
+
|
88
|
+
# Create a named group of shared params, that may be later included
|
89
|
+
# by actions.
|
90
|
+
params(:id) do
|
91
|
+
id :id, label: 'User ID'
|
92
|
+
end
|
93
|
+
|
94
|
+
params(:common) do
|
95
|
+
string :login, label: 'Login', desc: 'Used for authentication'
|
96
|
+
string :full_name, label: 'Full name'
|
97
|
+
string :role, label: 'User role', desc: 'admin or user'
|
98
|
+
end
|
99
|
+
|
100
|
+
# Actions
|
101
|
+
# Module HaveAPI::Actions::Default contains helper classes that define
|
102
|
+
# HTTP methods and routes for generic actions.
|
103
|
+
class Index < HaveAPI::Actions::Default::Index
|
104
|
+
desc 'List all users'
|
105
|
+
|
106
|
+
# There are no input parameters
|
107
|
+
|
108
|
+
# Output parameters
|
109
|
+
output(:object_list) do
|
110
|
+
use :id
|
111
|
+
use :common
|
112
|
+
end
|
113
|
+
|
114
|
+
# Determine if current user can use this action.
|
115
|
+
# allow/deny immediately returns from this block.
|
116
|
+
# Default rule is deny.
|
117
|
+
authorize do |u|
|
118
|
+
allow if u.role == 'admin'
|
119
|
+
deny # deny is implicit, so it may be omitted
|
120
|
+
end
|
121
|
+
|
122
|
+
# Provide example usage
|
123
|
+
example do
|
124
|
+
request({})
|
125
|
+
response({
|
126
|
+
users: [
|
127
|
+
{
|
128
|
+
id: 1,
|
129
|
+
login: 'myuser',
|
130
|
+
full_name: 'My Very Name'
|
131
|
+
}
|
132
|
+
]
|
133
|
+
})
|
134
|
+
comment 'Get a list of all users like this'
|
135
|
+
end
|
136
|
+
|
137
|
+
# Helper method returning a query for all users
|
138
|
+
def query
|
139
|
+
::User.all
|
140
|
+
end
|
141
|
+
|
142
|
+
# This method is called if the request has meta[:count] = true
|
143
|
+
def count
|
144
|
+
query.count
|
145
|
+
end
|
146
|
+
|
147
|
+
# Execute action, return the list
|
148
|
+
def exec
|
149
|
+
query.limit(input[:limit]).offset(input[:offset])
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class Create < HaveAPI::Actions::Default::Create
|
154
|
+
desc 'Create new user'
|
155
|
+
|
156
|
+
input do
|
157
|
+
use :common
|
158
|
+
end
|
159
|
+
|
160
|
+
output do
|
161
|
+
use :id
|
162
|
+
use :common
|
163
|
+
end
|
164
|
+
|
165
|
+
authorize do |u|
|
166
|
+
allow if u.role == 'admin'
|
167
|
+
deny
|
168
|
+
end
|
169
|
+
|
170
|
+
example do
|
171
|
+
request({
|
172
|
+
user: {
|
173
|
+
login: 'anotherlogin',
|
174
|
+
full_name: 'My Very New Name'
|
175
|
+
}
|
176
|
+
})
|
177
|
+
response({
|
178
|
+
user: {
|
179
|
+
id: 2
|
180
|
+
}
|
181
|
+
})
|
182
|
+
comment 'Create new user like this'
|
183
|
+
end
|
184
|
+
|
185
|
+
def exec
|
186
|
+
user = ::User.new(input)
|
187
|
+
|
188
|
+
if user.save
|
189
|
+
ok(user)
|
190
|
+
else
|
191
|
+
error('save failed', user.errors.to_hash)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
```
|
198
|
+
|
199
|
+
### What you get
|
200
|
+
From this piece of code, HaveAPI will generate a self-describing API.
|
201
|
+
It will contain resource `User` with actions `Index` and `Create`,
|
202
|
+
using which you can list existing users and create new ones.
|
203
|
+
|
204
|
+
You can use any of the available clients to work with the API.
|
205
|
+
|
206
|
+
### Run the example
|
207
|
+
```ruby
|
208
|
+
api = HaveAPI::Server.new(MyAPI)
|
209
|
+
|
210
|
+
# Use HTTP basic auth
|
211
|
+
class BasicAuth < HaveAPI::Authentication::Basic::Provider
|
212
|
+
def find_user(request, username, password)
|
213
|
+
User.authenticate(username, password)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
api.use_version(:all)
|
218
|
+
api.set_default_version(1)
|
219
|
+
api.auth_chain << BasicAuth
|
220
|
+
api.mount('/')
|
221
|
+
|
222
|
+
api.start!
|
223
|
+
```
|
224
|
+
|
225
|
+
This should start the application using WEBrick. Check
|
226
|
+
[http://localhost:4567](http://localhost:4567).
|
227
|
+
|
228
|
+
- `GET /` - a list of API versions
|
229
|
+
- `GET /doc` - HaveAPI documentation
|
230
|
+
- `GET /v1/` - documentation for version 1
|
231
|
+
- `OPTIONS /` - description for the whole API
|
232
|
+
- `OPTIONS /v1/` - description for API version 1
|
233
|
+
|
234
|
+
and more.
|
235
|
+
|
236
|
+
### Run with rackup
|
237
|
+
Use the same code as above, only the last line would be
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
run api.app
|
241
|
+
```
|
242
|
+
|
243
|
+
## Authentication
|
244
|
+
HaveAPI defines an interface for creating authentication providers.
|
245
|
+
HTTP basic auth and token providers are built-in.
|
246
|
+
|
247
|
+
Authentication options are self-described. A client can choose what authentication
|
248
|
+
method it understands and wants to use.
|
249
|
+
|
250
|
+
## Authorization
|
251
|
+
HaveAPI provides means for authorizing user access to actions. This process
|
252
|
+
is not self-described.
|
253
|
+
|
254
|
+
If the user is authenticated when requesting self-description, only allowed
|
255
|
+
resources, actions and parameters will be returned.
|
256
|
+
|
257
|
+
## Available clients
|
258
|
+
These clients completely rely on the API description and can be used for all
|
259
|
+
APIs that are using HaveAPI.
|
260
|
+
|
261
|
+
- Ruby client library and CLI: https://github.com/vpsfreecz/haveapi-client
|
262
|
+
- PHP client: https://github.com/vpsfreecz/haveapi-client-php
|
263
|
+
- JavaScript client: https://github.com/vpsfreecz/haveapi-client-js
|
264
|
+
|
265
|
+
or [create your own client](doc/create-client.md).
|
266
|
+
|
267
|
+
## Read more
|
268
|
+
- [Protocol definition](doc/protocol.md)
|
269
|
+
- [How to create a client](doc/create-client.md)
|
270
|
+
|
271
|
+
## Contributing
|
272
|
+
|
273
|
+
1. Fork it ( https://github.com/vpsfreecz/haveapi/fork )
|
274
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
275
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
276
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
277
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# Client definition
|
2
|
+
It is necessary to differentiate between clients for HaveAPI based APIs
|
3
|
+
and clients to work with your API instance. This document describes
|
4
|
+
the former. If you only want to use your API, you should check a list
|
5
|
+
of available clients and pick the one in the right language. Only when
|
6
|
+
there isn't a client already available in the language you want, then
|
7
|
+
continue reading.
|
8
|
+
|
9
|
+
# Design rules
|
10
|
+
The client must completely depend on the API description:
|
11
|
+
|
12
|
+
- it has no assumptions and API-specific code,
|
13
|
+
- it does not know any resources, actions and parameters,
|
14
|
+
- everything the client knows must be found out from the API description.
|
15
|
+
|
16
|
+
All clients should use a similar paradigm, so that it is possible to use
|
17
|
+
clients in different languages in the same way, as far as the language syntax
|
18
|
+
allows. Clients bundled with HaveAPI may serve as a model. A client should
|
19
|
+
use all the advantages and coding styles of the language it is written
|
20
|
+
in (e.g. use objects in object oriented languages).
|
21
|
+
|
22
|
+
A model paradigm (in no particular language):
|
23
|
+
|
24
|
+
// Create client instance
|
25
|
+
api = new HaveAPI.Client("https://your.api.tld")
|
26
|
+
|
27
|
+
// Authenticate
|
28
|
+
api.authenticate("basic", {"user": "yourname", "password": "yourpassword"})
|
29
|
+
|
30
|
+
// Access resources and actions
|
31
|
+
api.<resource>.<action>( { <parameters> } )
|
32
|
+
api.user.new({"name": "Very Name", "password": "donottellanyone"})
|
33
|
+
api.user.list()
|
34
|
+
api.nested.resource.deep.list()
|
35
|
+
|
36
|
+
// Pass IDs to resources
|
37
|
+
api.nested(1).resource(2).deep.list()
|
38
|
+
|
39
|
+
// Object-like access
|
40
|
+
user = api.user.show(1)
|
41
|
+
user.id
|
42
|
+
user.name
|
43
|
+
user.destroy()
|
44
|
+
|
45
|
+
// Logout
|
46
|
+
api.logout()
|
47
|
+
|
48
|
+
# Necessary features to implement
|
49
|
+
A client should implement all of the listed features in order to be useful.
|
50
|
+
|
51
|
+
## Resource tree
|
52
|
+
The client gives access to all listed resources and their actions.
|
53
|
+
|
54
|
+
In scripting languages, resources and actions are usually defined as dynamic
|
55
|
+
properties/objects/methods, depending on the language.
|
56
|
+
|
57
|
+
## Input/output parameters
|
58
|
+
Allow sending input parameters and accessing the response.
|
59
|
+
|
60
|
+
## Input/output formats
|
61
|
+
A client must send appropriate HTTP header `Accept`. As only JSON is by now built-in
|
62
|
+
in HaveAPI, it should send `application/json`.
|
63
|
+
|
64
|
+
## Authentication
|
65
|
+
All authentication methods that are built-in the HaveAPI should be supported
|
66
|
+
if possible. The client should choose suitable authentication method
|
67
|
+
for its purpose and allow the developer to select the authentication method
|
68
|
+
if it makes sense to do so.
|
69
|
+
|
70
|
+
It is a good practise to implement authentication methods as plugins,
|
71
|
+
so that developers may add custom authentication providers easily.
|
72
|
+
|
73
|
+
## Object-like access
|
74
|
+
A client must interpret the API response according to action output layout.
|
75
|
+
Layouts `object` and `object_list` should be handled as object instances,
|
76
|
+
if the language allows it.
|
77
|
+
|
78
|
+
Object instances represent the object fetched from the database. Received
|
79
|
+
parameters are accessed via object attributes/properties. Actions are defined
|
80
|
+
as instance methods. Objects may have associations to other resources which
|
81
|
+
must be made available and be easy to access.
|
82
|
+
|
83
|
+
# Supplemental features
|
84
|
+
Following features are supplemental. It is good to support them,
|
85
|
+
but is not necessary.
|
86
|
+
|
87
|
+
## Client-side validations
|
88
|
+
Client may make use of described validators and validate the input before
|
89
|
+
sending it to the API, to lessen the API load and make it more user-friendly.
|
90
|
+
|
91
|
+
However, as the input is validated on the server anyway, it does not have
|
92
|
+
to be implemented.
|
93
|
+
|
94
|
+
## Metadata channel
|
95
|
+
Metadata channel is currently used to specify what associated resources should
|
96
|
+
be prefetched and whether an object list should contain total number of items.
|
97
|
+
|
98
|
+
Metadata is nothing more than a hash in input parameters under key `_meta`.
|
data/doc/index.md
ADDED
data/doc/protocol.md
ADDED
@@ -0,0 +1,355 @@
|
|
1
|
+
# Protocol definition
|
2
|
+
HaveAPI defines the format for the self-description and URLs where the self-description
|
3
|
+
can be found.
|
4
|
+
|
5
|
+
## Self-description
|
6
|
+
The API is self-describing. It documents itself. Clients use the self-description
|
7
|
+
to work with the API. The Self-description contains access URLs, HTTP methods,
|
8
|
+
input and output parameters and their validators.
|
9
|
+
A part of description is also an example usage and text notes.
|
10
|
+
|
11
|
+
The API responds to ``OPTIONS /``, which returns description of whole
|
12
|
+
API, containing all its versions. To get description only of selected version,
|
13
|
+
use e.g. ``OPTIONS /v1/``.
|
14
|
+
|
15
|
+
Every action also responds to HTTP method ``OPTIONS``,
|
16
|
+
with which you can get description for selected action. To distinguish actions with
|
17
|
+
the same URL, use parameter ``?method=HTTP_METHOD``.
|
18
|
+
|
19
|
+
Thanks to this ability, API changes immediately reflects in all clients without
|
20
|
+
changing a single line of code. A client can also be used on all APIs with compatible
|
21
|
+
self-describing format, without any changes at all.
|
22
|
+
|
23
|
+
## Envelope
|
24
|
+
In addition to output format specified below, every API response
|
25
|
+
is wrapped in an envelope.
|
26
|
+
The envelope reports if action succeeded or failed, provides return value or error
|
27
|
+
messages.
|
28
|
+
|
29
|
+
{
|
30
|
+
"status": true if action succeeded or false if error occured,
|
31
|
+
"response": return value,
|
32
|
+
"message": error message, if status is false,
|
33
|
+
"errors: {
|
34
|
+
"parameter1": ["list", "of", "errors"],
|
35
|
+
"parameter2": ["and", "so", "on"]
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
## Description format
|
40
|
+
In this document, the self-description is encoded in JSON. However, it can
|
41
|
+
be encoded in any of the supported output formats.
|
42
|
+
|
43
|
+
### Version
|
44
|
+
Version is described as:
|
45
|
+
|
46
|
+
{
|
47
|
+
"authentication": {
|
48
|
+
... authentication methods ...
|
49
|
+
},
|
50
|
+
"resources": {
|
51
|
+
... resources ...
|
52
|
+
},
|
53
|
+
"meta": {
|
54
|
+
"namespace": "_meta"
|
55
|
+
},
|
56
|
+
"help": "/<version>/"
|
57
|
+
}
|
58
|
+
|
59
|
+
See appropriate section for detailed description of each section.
|
60
|
+
|
61
|
+
### Authentication
|
62
|
+
HaveAPI defines an interface for implementing custom authentication methods.
|
63
|
+
HTTP basic and token authentication is built-in.
|
64
|
+
|
65
|
+
Authentication methods can be set per API version. They are a part of
|
66
|
+
the self-description, but must be understood by the client.
|
67
|
+
The client can choose whichever available authentication method he prefers.
|
68
|
+
|
69
|
+
#### HTTP basic authentication
|
70
|
+
HTTP basic authentication needs no other configuration, only informs about its presence.
|
71
|
+
|
72
|
+
"basic": {}
|
73
|
+
|
74
|
+
#### Token authentication
|
75
|
+
Token authentication contains a resource ``token``, that is used
|
76
|
+
to acquire and revoke token.
|
77
|
+
|
78
|
+
Token is acquired by action ``request``. The client provides login and password and gets a token
|
79
|
+
that is used afterwards. Token has a validity period, which may also be infinity.
|
80
|
+
|
81
|
+
Token can be revoked by calling the ``revoke`` action.
|
82
|
+
|
83
|
+
"token": {
|
84
|
+
"http_header": "<name of HTTP header to transfer token in, by default X-HaveAPI-Auth-Token>",
|
85
|
+
"query_parameter": "<name of query parameter for token, by default auth_token>",
|
86
|
+
"resources": {
|
87
|
+
"actions": {
|
88
|
+
"request": {
|
89
|
+
...
|
90
|
+
"input": {
|
91
|
+
...
|
92
|
+
"parameters": {
|
93
|
+
"login": ...
|
94
|
+
"password": ...
|
95
|
+
"lifetime": ...
|
96
|
+
"interval": ...
|
97
|
+
},
|
98
|
+
...
|
99
|
+
},
|
100
|
+
"output": {
|
101
|
+
...
|
102
|
+
"parameters": {
|
103
|
+
"token": ...
|
104
|
+
"valid_to": ...
|
105
|
+
},
|
106
|
+
...
|
107
|
+
},
|
108
|
+
...
|
109
|
+
}
|
110
|
+
"revoke": ...
|
111
|
+
}
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
The format for ``resources`` section is the same as for any other resource.
|
116
|
+
|
117
|
+
### Resources
|
118
|
+
Each resource is described as:
|
119
|
+
|
120
|
+
"<resource_name>": {
|
121
|
+
"description": "Some description that explains everything",
|
122
|
+
"actions": {
|
123
|
+
... actions ...
|
124
|
+
},
|
125
|
+
"resources": {
|
126
|
+
... nested resources ...
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
### Actions
|
131
|
+
Every action is described as:
|
132
|
+
|
133
|
+
"<action_name>": {
|
134
|
+
"auth": true|false,
|
135
|
+
"description": "Describe what this action does",
|
136
|
+
"aliases": ["list", "of", "aliases"],
|
137
|
+
"input": {
|
138
|
+
"layout": "layout type",
|
139
|
+
"namespace": "namespace name",
|
140
|
+
"parameters": {
|
141
|
+
... parameters ...
|
142
|
+
}
|
143
|
+
},
|
144
|
+
"output": {
|
145
|
+
"layout": "layout type",
|
146
|
+
"namespace": "namespace name",
|
147
|
+
"parameters": {
|
148
|
+
... parameters ...
|
149
|
+
}
|
150
|
+
},
|
151
|
+
"examples": [
|
152
|
+
... list of examples ...
|
153
|
+
],
|
154
|
+
"meta": ... metadata ...,
|
155
|
+
"url": "URL for this action",
|
156
|
+
"method": "HTTP method to be used",
|
157
|
+
"help": "URL to get this very description of the action"
|
158
|
+
}
|
159
|
+
|
160
|
+
#### Layouts
|
161
|
+
Layout type is specified for input/output parameters. Thanks to the layout type,
|
162
|
+
clients know how to send the request and how to interpret the response.
|
163
|
+
|
164
|
+
Defined layout types:
|
165
|
+
|
166
|
+
- object - mainly the response is to be treated as an instance of a resource
|
167
|
+
- object_list - list of objects
|
168
|
+
- hash - simply a hash of parameters, it is to be treated as such
|
169
|
+
- hash_list - list of hashes
|
170
|
+
|
171
|
+
In client libraries, the ``object`` layout output usually results in returning
|
172
|
+
an object that represents the instance of the resource. The parameters are defined
|
173
|
+
as object properties and the like.
|
174
|
+
|
175
|
+
#### Namespace
|
176
|
+
All input/output parameters are put in a namespace, which is usually
|
177
|
+
the name of the resource.
|
178
|
+
|
179
|
+
For example:
|
180
|
+
|
181
|
+
{
|
182
|
+
"user": {
|
183
|
+
... parameters ...
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
187
|
+
### Parameters
|
188
|
+
There are two parameter types.
|
189
|
+
|
190
|
+
#### Data types
|
191
|
+
The type can be one of:
|
192
|
+
|
193
|
+
- String
|
194
|
+
- Text
|
195
|
+
- Boolean
|
196
|
+
- Integer
|
197
|
+
- Float
|
198
|
+
- Datetime
|
199
|
+
|
200
|
+
"<parameter_name>": {
|
201
|
+
"required": true/false/null,
|
202
|
+
"label": "Label for this parameter",
|
203
|
+
"description": "Describe it's meaning",
|
204
|
+
"type": "<one of the data types>",
|
205
|
+
"choices": a list or a hash of accepted values
|
206
|
+
"validators": ... validators ...,
|
207
|
+
"default": "default value that is used if the parameter is omitted"
|
208
|
+
}
|
209
|
+
|
210
|
+
If the choices are in a list, than it is a list of accepted values.
|
211
|
+
If the choices are in a hash, the keys of that hash are accepted values,
|
212
|
+
values in that hash are to be shown in UI.
|
213
|
+
|
214
|
+
#### Resource association
|
215
|
+
This is used for associations between resources, e.g. car has a wheel.
|
216
|
+
|
217
|
+
"<parameter_name>": {
|
218
|
+
"required": true/false/null,
|
219
|
+
"label": "Label for this parameter",
|
220
|
+
"description": "Describe it's meaning",
|
221
|
+
"type": "Resource",
|
222
|
+
"resource": ["path", "to", "resource"],
|
223
|
+
"value_id": "<name of a parameter that is used as an id>",
|
224
|
+
"value_label": "<name of a parameter that is used as a value>",
|
225
|
+
"value": {
|
226
|
+
"url": "URL to 'show' action of associated resource",
|
227
|
+
"method": "HTTP method to use",
|
228
|
+
"help": "URL to get the associated resource's 'show' description"
|
229
|
+
},
|
230
|
+
"choices": {
|
231
|
+
"url": "URL to action that returns a list of possible associations",
|
232
|
+
"method": "HTTP method to use",
|
233
|
+
"help": "URL to description of the list action"
|
234
|
+
}
|
235
|
+
}
|
236
|
+
|
237
|
+
The _resource_ type also has a different output in action response. It returns
|
238
|
+
a hash containing associated resource ID and its label, so that clients
|
239
|
+
can show the human-friendly label instead of just an ID.
|
240
|
+
|
241
|
+
"<parameter_name>": {
|
242
|
+
"<value of value_id from description>": <resource id>,
|
243
|
+
"<value of value_label from description>": "<label>"
|
244
|
+
}
|
245
|
+
|
246
|
+
### Examples
|
247
|
+
Examples are described in a generic way, so that every client can
|
248
|
+
render them according to its syntax.
|
249
|
+
|
250
|
+
{
|
251
|
+
"title": "A title",
|
252
|
+
"request": {
|
253
|
+
... a hash of request parameters ...
|
254
|
+
},
|
255
|
+
"response": {
|
256
|
+
... a hash of response parameters ...
|
257
|
+
},
|
258
|
+
"comment": "Description of the example"
|
259
|
+
}
|
260
|
+
|
261
|
+
### Metadata
|
262
|
+
Metadata can be global and per-object. Global metadata are sent once for each
|
263
|
+
response, where as per-object are sent with each object that is a part of the
|
264
|
+
response.
|
265
|
+
|
266
|
+
{
|
267
|
+
"global": {
|
268
|
+
"input": ... parameters or null ...,
|
269
|
+
"output: ... parameters or null ...
|
270
|
+
} or null,
|
271
|
+
|
272
|
+
"object": {
|
273
|
+
"input": ... parameters or null ...,
|
274
|
+
"output: ... parameters or null ...
|
275
|
+
} or null,
|
276
|
+
}
|
277
|
+
|
278
|
+
### List API versions
|
279
|
+
Send request ``OPTIONS /?describe=versions``. The description format:
|
280
|
+
|
281
|
+
{
|
282
|
+
"versions": [1, 2, 3, ... list of versions],
|
283
|
+
"default": <which version is default>
|
284
|
+
}
|
285
|
+
|
286
|
+
### Describe default version
|
287
|
+
Send request ``OPTIONS /?describe=default`` the get the description
|
288
|
+
of the default version.
|
289
|
+
|
290
|
+
### Describe the whole API
|
291
|
+
It is possible to get self-description of all versions at once.
|
292
|
+
|
293
|
+
Send request ``OPTIONS /``. The description format:
|
294
|
+
|
295
|
+
{
|
296
|
+
"default_version": <which version is default>,
|
297
|
+
"versions": {
|
298
|
+
"default": ... full version self-description ...,
|
299
|
+
"<version>": ... full version self-description,
|
300
|
+
... all other versions ...
|
301
|
+
}
|
302
|
+
}
|
303
|
+
|
304
|
+
## Authorization
|
305
|
+
Actions may require different levels of authorization. HaveAPI provides means for
|
306
|
+
implementing authorization, but it is not self-described.
|
307
|
+
|
308
|
+
If the user is authenticated when requesting self-description, only allowed
|
309
|
+
resources/actions/parameters will be returned.
|
310
|
+
|
311
|
+
## Input/output formats
|
312
|
+
For now, the only supported input format is JSON.
|
313
|
+
|
314
|
+
Output format can be chosen by a client. However, no other format than JSON is built-in.
|
315
|
+
The output format can be chosen with HTTP header ``Accept``.
|
316
|
+
|
317
|
+
## Request
|
318
|
+
Action URL and HTTP method the client learns from the self-description.
|
319
|
+
|
320
|
+
Example request:
|
321
|
+
|
322
|
+
POST /users HTTP/1.1
|
323
|
+
Content-Type: application/json
|
324
|
+
Accept: application/json
|
325
|
+
Connection: Close
|
326
|
+
|
327
|
+
{
|
328
|
+
"user": {
|
329
|
+
"login": "mylogin",
|
330
|
+
"name": "Very Name",
|
331
|
+
"role": "admin"
|
332
|
+
}
|
333
|
+
}
|
334
|
+
|
335
|
+
## Response
|
336
|
+
Clients know how to interpret the response thanks to the layout type they learn
|
337
|
+
from the self-description.
|
338
|
+
|
339
|
+
Example response to the request above:
|
340
|
+
|
341
|
+
Content-Type: application/json
|
342
|
+
|
343
|
+
{
|
344
|
+
"status": true,
|
345
|
+
"response": {
|
346
|
+
"user": {
|
347
|
+
"id": 1,
|
348
|
+
"login": "mylogin",
|
349
|
+
"name": "Very Name",
|
350
|
+
"role": "admin"
|
351
|
+
}
|
352
|
+
},
|
353
|
+
"message": null,
|
354
|
+
"errors: null
|
355
|
+
}
|
data/haveapi.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'haveapi/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'haveapi'
|
7
|
+
s.version = HaveAPI::VERSION
|
8
|
+
s.date = '2015-08-13'
|
9
|
+
s.summary =
|
10
|
+
s.description = 'Framework for creating self-describing APIs'
|
11
|
+
s.authors = 'Jakub Skokan'
|
12
|
+
s.email = 'jakub.skokan@vpsfree.cz'
|
13
|
+
s.files = `git ls-files -z`.split("\x0")
|
14
|
+
s.license = 'MIT'
|
15
|
+
|
16
|
+
s.required_ruby_version = '~> 2.0'
|
17
|
+
|
18
|
+
s.add_runtime_dependency 'activerecord', '~> 4.1.6'
|
19
|
+
s.add_runtime_dependency 'require_all'
|
20
|
+
s.add_runtime_dependency 'json'
|
21
|
+
s.add_runtime_dependency 'sinatra'
|
22
|
+
s.add_runtime_dependency 'mysql'
|
23
|
+
s.add_runtime_dependency 'sinatra-activerecord'
|
24
|
+
s.add_runtime_dependency 'rake'
|
25
|
+
s.add_runtime_dependency 'github-markdown', '~> 0.6.6'
|
26
|
+
|
27
|
+
s.add_development_dependency 'rspec'
|
28
|
+
s.add_development_dependency 'railties'
|
29
|
+
s.add_development_dependency 'rack-test'
|
30
|
+
end
|
data/lib/haveapi/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: haveapi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jakub Skokan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-08-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -170,6 +170,15 @@ executables: []
|
|
170
170
|
extensions: []
|
171
171
|
extra_rdoc_files: []
|
172
172
|
files:
|
173
|
+
- .gitignore
|
174
|
+
- Gemfile
|
175
|
+
- LICENSE.txt
|
176
|
+
- README.md
|
177
|
+
- Rakefile
|
178
|
+
- doc/create-client.md
|
179
|
+
- doc/index.md
|
180
|
+
- doc/protocol.md
|
181
|
+
- haveapi.gemspec
|
173
182
|
- lib/haveapi.rb
|
174
183
|
- lib/haveapi/action.rb
|
175
184
|
- lib/haveapi/actions/default.rb
|
@@ -186,7 +195,6 @@ files:
|
|
186
195
|
- lib/haveapi/example.rb
|
187
196
|
- lib/haveapi/extensions/action_exceptions.rb
|
188
197
|
- lib/haveapi/extensions/base.rb
|
189
|
-
- lib/haveapi/extensions/resource_prefetch.rb
|
190
198
|
- lib/haveapi/hooks.rb
|
191
199
|
- lib/haveapi/metadata.rb
|
192
200
|
- lib/haveapi/model_adapter.rb
|
@@ -235,7 +243,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
235
243
|
version: '0'
|
236
244
|
requirements: []
|
237
245
|
rubyforge_project:
|
238
|
-
rubygems_version: 2.2.
|
246
|
+
rubygems_version: 2.2.5
|
239
247
|
signing_key:
|
240
248
|
specification_version: 4
|
241
249
|
summary: Framework for creating self-describing APIs
|