haveapi 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +12 -0
  3. data/Gemfile +10 -10
  4. data/README.md +19 -12
  5. data/Rakefile +23 -0
  6. data/doc/hooks.erb +35 -0
  7. data/doc/index.md +1 -0
  8. data/doc/json-schema.erb +369 -0
  9. data/doc/protocol.md +178 -38
  10. data/doc/protocol.plantuml +220 -0
  11. data/haveapi.gemspec +6 -10
  12. data/lib/haveapi/action.rb +35 -6
  13. data/lib/haveapi/api.rb +22 -5
  14. data/lib/haveapi/common.rb +7 -0
  15. data/lib/haveapi/exceptions.rb +7 -0
  16. data/lib/haveapi/hooks.rb +19 -8
  17. data/lib/haveapi/model_adapters/active_record.rb +58 -19
  18. data/lib/haveapi/output_formatter.rb +8 -5
  19. data/lib/haveapi/output_formatters/json.rb +6 -1
  20. data/lib/haveapi/params/param.rb +33 -39
  21. data/lib/haveapi/params/resource.rb +20 -0
  22. data/lib/haveapi/params.rb +26 -4
  23. data/lib/haveapi/public/doc/protocol.png +0 -0
  24. data/lib/haveapi/resource.rb +2 -7
  25. data/lib/haveapi/server.rb +87 -26
  26. data/lib/haveapi/tasks/hooks.rb +3 -0
  27. data/lib/haveapi/tasks/yard.rb +12 -0
  28. data/lib/haveapi/validator.rb +134 -0
  29. data/lib/haveapi/validator_chain.rb +99 -0
  30. data/lib/haveapi/validators/acceptance.rb +38 -0
  31. data/lib/haveapi/validators/confirmation.rb +46 -0
  32. data/lib/haveapi/validators/custom.rb +21 -0
  33. data/lib/haveapi/validators/exclusion.rb +38 -0
  34. data/lib/haveapi/validators/format.rb +42 -0
  35. data/lib/haveapi/validators/inclusion.rb +42 -0
  36. data/lib/haveapi/validators/length.rb +71 -0
  37. data/lib/haveapi/validators/numericality.rb +104 -0
  38. data/lib/haveapi/validators/presence.rb +40 -0
  39. data/lib/haveapi/version.rb +2 -1
  40. data/lib/haveapi/views/doc_sidebars/json-schema.erb +7 -0
  41. data/lib/haveapi/views/main_layout.erb +11 -0
  42. data/lib/haveapi/views/version_page.erb +26 -3
  43. data/lib/haveapi.rb +7 -4
  44. metadata +45 -66
data/doc/protocol.md CHANGED
@@ -2,7 +2,7 @@
2
2
  HaveAPI defines the format for the self-description and URLs where the self-description
3
3
  can be found.
4
4
 
5
- ## Self-description
5
+ # Self-description
6
6
  The API is self-describing. It documents itself. Clients use the self-description
7
7
  to work with the API. The Self-description contains access URLs, HTTP methods,
8
8
  input and output parameters and their validators.
@@ -20,7 +20,19 @@ Thanks to this ability, API changes immediately reflects in all clients without
20
20
  changing a single line of code. A client can also be used on all APIs with compatible
21
21
  self-describing format, without any changes at all.
22
22
 
23
- ## Envelope
23
+ # Protocol versioning
24
+ Protocol version defines the form and contents of
25
+
26
+ - the envelope,
27
+ - API description,
28
+ - data transfers.
29
+
30
+ Protocol version is in the form `<major>.<minor>`. `major` is incremented
31
+ whenever a change is made to the protocol that breaks backward compatibility.
32
+ When backward compatibility is kept and only some new features are added to the
33
+ protocol, `major` stays and `minor` is incremented.
34
+
35
+ # Envelope
24
36
  In addition to output format specified below, every API response
25
37
  is wrapped in an envelope.
26
38
  The envelope reports if action succeeded or failed, provides return value or error
@@ -36,12 +48,23 @@ messages.
36
48
  }
37
49
  }
38
50
 
39
- ## Description format
51
+ Responses for `OPTIONS` requests also send a protocol version in the envelope:
52
+
53
+ "version": <version>
54
+
55
+ # Description format
40
56
  In this document, the self-description is encoded in JSON. However, it can
41
57
  be encoded in any of the supported output formats.
42
58
 
43
- ### Version
44
- Version is described as:
59
+ ## API version
60
+
61
+ <a href="/doc/protocol.png">
62
+ <img src="/doc/protocol.png"
63
+ alt="Diagram representing the structure of an API version"
64
+ width="600">
65
+ </a>
66
+
67
+ API version is described as:
45
68
 
46
69
  {
47
70
  "authentication": {
@@ -50,15 +73,15 @@ Version is described as:
50
73
  "resources": {
51
74
  ... resources ...
52
75
  },
53
- "meta": {
54
- "namespace": "_meta"
55
- },
76
+ "meta": {
77
+ "namespace": "_meta"
78
+ },
56
79
  "help": "/<version>/"
57
80
  }
58
81
 
59
82
  See appropriate section for detailed description of each section.
60
83
 
61
- ### Authentication
84
+ ## Authentication
62
85
  HaveAPI defines an interface for implementing custom authentication methods.
63
86
  HTTP basic and token authentication is built-in.
64
87
 
@@ -66,12 +89,12 @@ Authentication methods can be set per API version. They are a part of
66
89
  the self-description, but must be understood by the client.
67
90
  The client can choose whichever available authentication method he prefers.
68
91
 
69
- #### HTTP basic authentication
92
+ ### HTTP basic authentication
70
93
  HTTP basic authentication needs no other configuration, only informs about its presence.
71
94
 
72
95
  "basic": {}
73
96
 
74
- #### Token authentication
97
+ ### Token authentication
75
98
  Token authentication contains a resource ``token``, that is used
76
99
  to acquire and revoke token.
77
100
 
@@ -114,7 +137,7 @@ Token can be revoked by calling the ``revoke`` action.
114
137
 
115
138
  The format for ``resources`` section is the same as for any other resource.
116
139
 
117
- ### Resources
140
+ ## Resources
118
141
  Each resource is described as:
119
142
 
120
143
  "<resource_name>": {
@@ -127,7 +150,7 @@ Each resource is described as:
127
150
  }
128
151
  }
129
152
 
130
- ### Actions
153
+ ## Actions
131
154
  Every action is described as:
132
155
 
133
156
  "<action_name>": {
@@ -157,7 +180,7 @@ Every action is described as:
157
180
  "help": "URL to get this very description of the action"
158
181
  }
159
182
 
160
- #### Layouts
183
+ ### Layouts
161
184
  Layout type is specified for input/output parameters. Thanks to the layout type,
162
185
  clients know how to send the request and how to interpret the response.
163
186
 
@@ -172,7 +195,7 @@ In client libraries, the ``object`` layout output usually results in returning
172
195
  an object that represents the instance of the resource. The parameters are defined
173
196
  as object properties and the like.
174
197
 
175
- #### Namespace
198
+ ### Namespace
176
199
  All input/output parameters are put in a namespace, which is usually
177
200
  the name of the resource.
178
201
 
@@ -184,10 +207,10 @@ For example:
184
207
  }
185
208
  }
186
209
 
187
- ### Parameters
210
+ ## Parameters
188
211
  There are two parameter types.
189
212
 
190
- #### Data types
213
+ ### Data types
191
214
  The type can be one of:
192
215
 
193
216
  - String
@@ -202,16 +225,133 @@ The type can be one of:
202
225
  "label": "Label for this parameter",
203
226
  "description": "Describe it's meaning",
204
227
  "type": "<one of the data types>",
205
- "choices": a list or a hash of accepted values
206
228
  "validators": ... validators ...,
207
229
  "default": "default value that is used if the parameter is omitted"
208
230
  }
209
231
 
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,
232
+
233
+ #### Validators
234
+ Every parameter has its own validators. Any of the following validators
235
+ may be present. Input value must pass through all validators in order
236
+ to be considered valid.
237
+
238
+ ##### Acceptance
239
+ Used when a parameter must have one specific value.
240
+
241
+ "accept": {
242
+ "value": <value to accept>,
243
+ "message": "has to be <value>"
244
+ }
245
+
246
+ ##### Presence
247
+ The parameter must be present. If `empty` is `false`, leading and trailing
248
+ whitespace is stripped before the check.
249
+
250
+ "present": {
251
+ empty: true/false,
252
+ message: "must be present"
253
+ }
254
+
255
+ ##### Confirmation
256
+ Used to confirm that two parameters have either the same value or not have the
257
+ same value. The former can be used e.g. to verify that passwords are same
258
+ and the latter to give two different e-mail addresses.
259
+
260
+ "confirm": {
261
+ "equal": true/false,
262
+ "parameter": <parameter_name>,
263
+ "message": "must (or must not) be the same as <parameter_name>"
264
+ }
265
+
266
+ ##### Inclusion
267
+ The parameter can contain only one of given options.
268
+
269
+ "include": {
270
+ "values": ["list", "of", "allowed", "values"],
271
+ "message": "%{value} cannot be used"
272
+ }
273
+
274
+ If the `values` are a list, than it is a list of accepted values.
275
+ If the `values` are a hash, the keys of that hash are accepted values,
212
276
  values in that hash are to be shown in UI.
213
277
 
214
- #### Resource association
278
+ "include": {
279
+ "values": {
280
+ "one": "Fancy one",
281
+ "two": "Fancy two"
282
+ },
283
+ "message": "%{value} cannot be used"
284
+ }
285
+
286
+ ##### Exclusion
287
+ The parameter can be set to anything except values listed here.
288
+
289
+ "exclude": {
290
+ "values": ["list", "of", "excluded", "values"],
291
+ "message": "%{value} cannot be used"
292
+ }
293
+
294
+ ##### Specific format
295
+ If `match` is true, the parameter must pass given regular expression.
296
+ Otherwise it must not pass the regular expression.
297
+
298
+ "format": {
299
+ "rx": "regular expression",
300
+ "match": true/false,
301
+ "description": "human-readable description of the regular expression",
302
+ "message": "%{value} is not in a valid format"
303
+ }
304
+
305
+ ##### Length
306
+ Useful only for `String` and `Text` parameters. Checks the length of given string.
307
+ It may check either
308
+
309
+ - minimum
310
+ - maximum
311
+ - minimum and maximum
312
+ - constant length
313
+
314
+ The length validator must therefore contain one or more checks, but cannot
315
+ contain both min/max and equality.
316
+
317
+ Length range:
318
+
319
+ "length": {
320
+ "min": 0,
321
+ "max": 99,
322
+ "message": "length has to be in range <0,99>"
323
+ }
324
+
325
+ Constant length:
326
+
327
+ "length": {
328
+ "equals": 10,
329
+ "message": "length has to be 10"
330
+ }
331
+
332
+ ##### Numericality
333
+ Numericality implies that the parameter must be a number, i.e. `Integer`, `Float`
334
+ or `String` containing only digits. It can check that the number is in a specified
335
+ range and can provide a step. The validator can contain one or more of these conditions.
336
+
337
+ "number": {
338
+ "min": 0,
339
+ "max": 99,
340
+ "step": 3,
341
+ "mod": 3,
342
+ "even": true/false,
343
+ "odd": true/false
344
+ }
345
+
346
+ ##### Custom validation
347
+ Custom validation cannot be documented by the API. The developer may or may not
348
+ provide information that some non-documented validation takes place. The documentation
349
+ contains only the description of the validations that may be shown to the user,
350
+ but is not evaluated client-side, only server-side.
351
+
352
+ "custom": "description of custom validation"
353
+
354
+ ### Resource association
215
355
  This is used for associations between resources, e.g. car has a wheel.
216
356
 
217
357
  "<parameter_name>": {
@@ -243,7 +383,7 @@ can show the human-friendly label instead of just an ID.
243
383
  "<value of value_label from description>": "<label>"
244
384
  }
245
385
 
246
- ### Examples
386
+ ## Examples
247
387
  Examples are described in a generic way, so that every client can
248
388
  render them according to its syntax.
249
389
 
@@ -258,24 +398,24 @@ render them according to its syntax.
258
398
  "comment": "Description of the example"
259
399
  }
260
400
 
261
- ### Metadata
401
+ ## Metadata
262
402
  Metadata can be global and per-object. Global metadata are sent once for each
263
403
  response, where as per-object are sent with each object that is a part of the
264
404
  response.
265
405
 
266
406
  {
267
407
  "global": {
268
- "input": ... parameters or null ...,
269
- "output: ... parameters or null ...
270
- } or null,
408
+ "input": ... parameters or null ...,
409
+ "output: ... parameters or null ...
410
+ } or null,
271
411
 
272
- "object": {
273
- "input": ... parameters or null ...,
274
- "output: ... parameters or null ...
275
- } or null,
412
+ "object": {
413
+ "input": ... parameters or null ...,
414
+ "output: ... parameters or null ...
415
+ } or null,
276
416
  }
277
417
 
278
- ### List API versions
418
+ ## List API versions
279
419
  Send request ``OPTIONS /?describe=versions``. The description format:
280
420
 
281
421
  {
@@ -283,11 +423,11 @@ Send request ``OPTIONS /?describe=versions``. The description format:
283
423
  "default": <which version is default>
284
424
  }
285
425
 
286
- ### Describe default version
426
+ ## Describe default version
287
427
  Send request ``OPTIONS /?describe=default`` the get the description
288
428
  of the default version.
289
429
 
290
- ### Describe the whole API
430
+ ## Describe the whole API
291
431
  It is possible to get self-description of all versions at once.
292
432
 
293
433
  Send request ``OPTIONS /``. The description format:
@@ -301,20 +441,20 @@ Send request ``OPTIONS /``. The description format:
301
441
  }
302
442
  }
303
443
 
304
- ## Authorization
444
+ # Authorization
305
445
  Actions may require different levels of authorization. HaveAPI provides means for
306
446
  implementing authorization, but it is not self-described.
307
447
 
308
448
  If the user is authenticated when requesting self-description, only allowed
309
449
  resources/actions/parameters will be returned.
310
450
 
311
- ## Input/output formats
451
+ # Input/output formats
312
452
  For now, the only supported input format is JSON.
313
453
 
314
454
  Output format can be chosen by a client. However, no other format than JSON is built-in.
315
455
  The output format can be chosen with HTTP header ``Accept``.
316
456
 
317
- ## Request
457
+ # Request
318
458
  Action URL and HTTP method the client learns from the self-description.
319
459
 
320
460
  Example request:
@@ -332,7 +472,7 @@ Example request:
332
472
  }
333
473
  }
334
474
 
335
- ## Response
475
+ # Response
336
476
  Clients know how to interpret the response thanks to the layout type they learn
337
477
  from the self-description.
338
478
 
@@ -349,7 +489,7 @@ Example response to the request above:
349
489
  "name": "Very Name",
350
490
  "role": "admin"
351
491
  }
352
- },
492
+ },
353
493
  "message": null,
354
494
  "errors: null
355
495
  }
@@ -0,0 +1,220 @@
1
+ @startuml
2
+
3
+ class Version {
4
+ help : String
5
+ }
6
+
7
+ class Meta {
8
+ namespace : String
9
+ }
10
+
11
+ abstract class Authentication {
12
+
13
+ }
14
+
15
+ class Basic {
16
+
17
+ }
18
+
19
+ class Token {
20
+ http_header : String
21
+ query_parameter : String
22
+ }
23
+
24
+ class Resource {
25
+ name : String
26
+ description : String
27
+ }
28
+
29
+ class Action {
30
+ name : String
31
+ auth : Bool
32
+ description : String
33
+ aliases : list
34
+ url : String
35
+ method : String
36
+ help : String
37
+ }
38
+
39
+ class Example {
40
+ title : String
41
+ request : hash
42
+ response : hash
43
+ comment : String
44
+ }
45
+
46
+ class Parameters {
47
+ layout : String
48
+ namespace : String
49
+ }
50
+
51
+ abstract class Parameter {
52
+ required : Bool
53
+ label : String
54
+ description : String
55
+ type : String
56
+ }
57
+
58
+ class InputParameter {
59
+ default : any
60
+ }
61
+
62
+ class OutputParameter {
63
+
64
+ }
65
+
66
+ abstract class ResourceParameter {
67
+ value_id : String
68
+ value_label : String
69
+ }
70
+
71
+ class TypedInputParameter {
72
+
73
+ }
74
+
75
+ class ResourceInputParameter {
76
+ }
77
+
78
+ class TypedOutputParameter {
79
+
80
+ }
81
+
82
+ class ResourceOutputParameter {
83
+ }
84
+
85
+ class ResourceLink {
86
+ url : String
87
+ method : String
88
+ help : String
89
+ }
90
+
91
+
92
+ class ActionMeta {
93
+ }
94
+
95
+ abstract class Validator {
96
+ message : String
97
+ }
98
+
99
+ class AcceptanceValidator {
100
+ value : any
101
+ }
102
+
103
+ class ConfirmationValidator {
104
+ equal : Bool
105
+ }
106
+
107
+ class CustomValidator {
108
+ description : String
109
+ }
110
+
111
+ class ExclusionValidator {
112
+ values : list
113
+ }
114
+
115
+ class FormatValidator {
116
+ rx : RegExp
117
+ match : Bool
118
+ description : String
119
+ }
120
+
121
+ abstract class InclusionValidator {
122
+
123
+ }
124
+
125
+ class ArrayInclusionValidator {
126
+ values : list
127
+ }
128
+
129
+ class HashInclusionValidator {
130
+ values : hash
131
+ }
132
+
133
+ abstract class LengthValidator {
134
+
135
+ }
136
+
137
+ class EqualLengthValidator {
138
+ equals : Integer
139
+ }
140
+
141
+ class RangeLengthValidator {
142
+ min : Integer
143
+ max : Integer
144
+ }
145
+
146
+ class NumericalityValidator {
147
+ min : Number
148
+ max : Number
149
+ step : Number
150
+ mod : Integer
151
+ odd : Bool
152
+ even : Bool
153
+ }
154
+
155
+ class PresenceValidator {
156
+ empty : Bool
157
+ }
158
+
159
+ Version -- Meta
160
+ Version *-- Authentication
161
+
162
+ Authentication <|-- Basic
163
+ Authentication <|-- Token
164
+
165
+ Token *-- Resource
166
+
167
+ Version *-- Resource
168
+
169
+ Resource *-- Resource
170
+ Resource *-- Action
171
+
172
+ Action *-- Example
173
+ Action -- Parameters : input
174
+ Action -- Parameters : output
175
+ Action -- ActionMeta : object
176
+ Action -- ActionMeta : global
177
+
178
+ ActionMeta -- Parameters : input
179
+ ActionMeta -- Parameters : output
180
+
181
+ Parameters *-- Parameter
182
+
183
+ Parameter <|-- InputParameter
184
+ Parameter <|-- OutputParameter
185
+ Parameter <|-- ResourceParameter
186
+
187
+ ResourceParameter <|-- ResourceInputParameter
188
+ ResourceParameter <|-- ResourceOutputParameter
189
+ ResourceParameter -- Resource : associated with
190
+
191
+ InputParameter <|-- TypedInputParameter
192
+ InputParameter <|-- ResourceInputParameter
193
+
194
+ TypedInputParameter *-- Validator
195
+
196
+ Validator <|-- AcceptanceValidator
197
+ Validator <|-- ConfirmationValidator
198
+ ConfirmationValidator -- InputParameter : confirms
199
+ Validator <|-- CustomValidator
200
+ Validator <|-- ExclusionValidator
201
+ Validator <|-- FormatValidator
202
+ Validator <|-- InclusionValidator
203
+ InclusionValidator <|-- ArrayInclusionValidator
204
+ InclusionValidator <|-- HashInclusionValidator
205
+ Validator <|-- LengthValidator
206
+ LengthValidator <|-- EqualLengthValidator
207
+ LengthValidator <|-- RangeLengthValidator
208
+ Validator <|-- NumericalityValidator
209
+ Validator <|-- PresenceValidator
210
+
211
+ OutputParameter <|-- TypedOutputParameter
212
+ OutputParameter <|-- ResourceOutputParameter
213
+
214
+ ResourceInputParameter -- ResourceLink : value
215
+ ResourceInputParameter -- ResourceLink : choices
216
+
217
+ ResourceOutputParameter -- ResourceLink : value
218
+ ResourceOutputParameter -- ResourceLink : choices
219
+
220
+ @enduml
data/haveapi.gemspec CHANGED
@@ -5,7 +5,7 @@ require 'haveapi/version'
5
5
  Gem::Specification.new do |s|
6
6
  s.name = 'haveapi'
7
7
  s.version = HaveAPI::VERSION
8
- s.date = '2015-08-13'
8
+ s.date = '2016-01-20'
9
9
  s.summary =
10
10
  s.description = 'Framework for creating self-describing APIs'
11
11
  s.authors = 'Jakub Skokan'
@@ -15,16 +15,12 @@ Gem::Specification.new do |s|
15
15
 
16
16
  s.required_ruby_version = '~> 2.0'
17
17
 
18
- s.add_runtime_dependency 'activerecord', '~> 4.1.6'
19
18
  s.add_runtime_dependency 'require_all'
20
19
  s.add_runtime_dependency 'json'
21
- s.add_runtime_dependency 'sinatra'
22
- s.add_runtime_dependency 'mysql'
23
- s.add_runtime_dependency 'sinatra-activerecord'
20
+ s.add_runtime_dependency 'sinatra', '~> 1.4.6'
21
+ s.add_runtime_dependency 'tilt', '~> 1.4.1'
22
+ s.add_runtime_dependency 'redcarpet', '~> 3.3.4'
24
23
  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'
24
+ s.add_runtime_dependency 'github-markdown', '~> 0.6.9'
25
+ s.add_runtime_dependency 'nesty', '~> 1.0.2'
30
26
  end
@@ -11,7 +11,16 @@ module HaveAPI
11
11
 
12
12
  include Hookable
13
13
 
14
- has_hook :exec_exception
14
+ has_hook :exec_exception,
15
+ desc: 'Called when unhandled exceptions occurs during Action.exec',
16
+ args: {
17
+ action: 'HaveAPI::Action instance',
18
+ exception: 'exception instance',
19
+ },
20
+ ret: {
21
+ status: 'true or false, indicating whether error should be reported',
22
+ message: 'error message sent to the user',
23
+ }
15
24
 
16
25
  attr_reader :message, :errors, :version
17
26
  attr_accessor :flags
@@ -65,10 +74,14 @@ module HaveAPI
65
74
  def initialize
66
75
  return if @initialized
67
76
 
68
- input.exec
69
- model_adapter(input.layout).load_validators(model, input) if model
77
+ check_build("#{self}.input") do
78
+ input.exec
79
+ model_adapter(input.layout).load_validators(model, input) if model
80
+ end
70
81
 
71
- output.exec
82
+ check_build("#{self}.output") do
83
+ output.exec
84
+ end
72
85
 
73
86
  model_adapter(input.layout).used_by(:input, self)
74
87
  model_adapter(output.layout).used_by(:output, self)
@@ -76,14 +89,30 @@ module HaveAPI
76
89
  if @meta
77
90
  @meta.each_value do |m|
78
91
  next unless m
79
- m.input && m.input.exec
80
- m.output && m.output.exec
92
+
93
+ check_build("#{self}.meta.input") do
94
+ m.input && m.input.exec
95
+ end
96
+
97
+ check_build("#{self}.meta.output") do
98
+ m.output && m.output.exec
99
+ end
81
100
  end
82
101
  end
83
102
 
84
103
  @initialized = true
85
104
  end
86
105
 
106
+ def validate_build
107
+ check_build("#{self}.input") do
108
+ input.validate_build
109
+ end
110
+
111
+ check_build("#{self}.output") do
112
+ output.validate_build
113
+ end
114
+ end
115
+
87
116
  def model_adapter(layout)
88
117
  ModelAdapter.for(layout, resource.model)
89
118
  end