apipie-dsl 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +485 -0
  4. data/app/controllers/apipie_dsl/apipie_dsls_controller.rb +190 -0
  5. data/app/helpers/apipie_dsl_helper.rb +110 -0
  6. data/app/public/apipie_dsl/javascripts/apipie_dsl.js +6 -0
  7. data/app/public/apipie_dsl/javascripts/bundled/bootstrap-collapse.js +138 -0
  8. data/app/public/apipie_dsl/javascripts/bundled/bootstrap.js +1726 -0
  9. data/app/public/apipie_dsl/javascripts/bundled/jquery.js +5 -0
  10. data/app/public/apipie_dsl/javascripts/bundled/prettify.js +28 -0
  11. data/app/public/apipie_dsl/stylesheets/application.css +7 -0
  12. data/app/public/apipie_dsl/stylesheets/bundled/bootstrap-responsive.min.css +12 -0
  13. data/app/public/apipie_dsl/stylesheets/bundled/bootstrap.min.css +689 -0
  14. data/app/public/apipie_dsl/stylesheets/bundled/prettify.css +30 -0
  15. data/app/views/apipie_dsl/apipie_dsls/_index_class_meth.erb +11 -0
  16. data/app/views/apipie_dsl/apipie_dsls/_index_class_prop.erb +13 -0
  17. data/app/views/apipie_dsl/apipie_dsls/_languages.erb +6 -0
  18. data/app/views/apipie_dsl/apipie_dsls/_metadata.erb +1 -0
  19. data/app/views/apipie_dsl/apipie_dsls/_method.erb +34 -0
  20. data/app/views/apipie_dsl/apipie_dsls/_method_detail.erb +58 -0
  21. data/app/views/apipie_dsl/apipie_dsls/_params.html.erb +47 -0
  22. data/app/views/apipie_dsl/apipie_dsls/_params_plain.html.erb +19 -0
  23. data/app/views/apipie_dsl/apipie_dsls/_property.erb +24 -0
  24. data/app/views/apipie_dsl/apipie_dsls/_property_detail.erb +35 -0
  25. data/app/views/apipie_dsl/apipie_dsls/_raises.html.erb +23 -0
  26. data/app/views/apipie_dsl/apipie_dsls/_returns.html.erb +53 -0
  27. data/app/views/apipie_dsl/apipie_dsls/apipie_dsl_404.html.erb +17 -0
  28. data/app/views/apipie_dsl/apipie_dsls/class.html.erb +75 -0
  29. data/app/views/apipie_dsl/apipie_dsls/custom_help.html.erb +10 -0
  30. data/app/views/apipie_dsl/apipie_dsls/getting_started.html.erb +4 -0
  31. data/app/views/apipie_dsl/apipie_dsls/index.html.erb +72 -0
  32. data/app/views/apipie_dsl/apipie_dsls/method.html.erb +52 -0
  33. data/app/views/apipie_dsl/apipie_dsls/plain.html.erb +116 -0
  34. data/app/views/apipie_dsl/apipie_dsls/static.html.erb +158 -0
  35. data/app/views/layouts/apipie_dsl/apipie_dsl.html.erb +26 -0
  36. data/lib/apipie-dsl.rb +3 -0
  37. data/lib/apipie_dsl.rb +28 -0
  38. data/lib/apipie_dsl/Rakefile +6 -0
  39. data/lib/apipie_dsl/apipie_dsl_module.rb +51 -0
  40. data/lib/apipie_dsl/application.rb +321 -0
  41. data/lib/apipie_dsl/class_description.rb +107 -0
  42. data/lib/apipie_dsl/configuration.rb +87 -0
  43. data/lib/apipie_dsl/dsl.rb +596 -0
  44. data/lib/apipie_dsl/errors.rb +68 -0
  45. data/lib/apipie_dsl/exception_description.rb +39 -0
  46. data/lib/apipie_dsl/markup.rb +41 -0
  47. data/lib/apipie_dsl/method_description.rb +112 -0
  48. data/lib/apipie_dsl/parameter_description.rb +152 -0
  49. data/lib/apipie_dsl/railtie.rb +15 -0
  50. data/lib/apipie_dsl/return_description.rb +71 -0
  51. data/lib/apipie_dsl/routing.rb +17 -0
  52. data/lib/apipie_dsl/see_description.rb +35 -0
  53. data/lib/apipie_dsl/static_dispatcher.rb +70 -0
  54. data/lib/apipie_dsl/tag_list_description.rb +11 -0
  55. data/lib/apipie_dsl/tasks/apipie_dsl/cache.rake +43 -0
  56. data/lib/apipie_dsl/tasks/apipie_dsl/static.rake +43 -0
  57. data/lib/apipie_dsl/tasks/apipie_dsl/static_json.rake +29 -0
  58. data/lib/apipie_dsl/tasks_utils.rb +137 -0
  59. data/lib/apipie_dsl/utils.rb +83 -0
  60. data/lib/apipie_dsl/validator.rb +479 -0
  61. data/lib/apipie_dsl/version.rb +5 -0
  62. data/lib/generators/apipie_dsl/install/install_generator.rb +21 -0
  63. data/lib/generators/apipie_dsl/install/templates/initializer.rb.erb +9 -0
  64. data/lib/generators/apipie_dsl/views_generator.rb +14 -0
  65. data/test/test_helper.rb +6 -0
  66. metadata +220 -0
@@ -0,0 +1,479 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApipieDSL
4
+ module Validator
5
+ class Lazy
6
+ def initialize(param_description, argument, options, block)
7
+ @param_description = param_description
8
+ @argument = argument
9
+ @options = options
10
+ @block = block
11
+ end
12
+
13
+ def build
14
+ # TODO support for plain Ruby
15
+ return unless defined? Rails
16
+
17
+ BaseValidator.find(@param_description, @argument.constantize, @options, @block)
18
+ end
19
+ end
20
+ # To create a new validator, inherit from ApipieDSL::Validator::BaseValidator
21
+ # and implement class method 'build' and instance method 'validate'
22
+ class BaseValidator
23
+ attr_reader :param_description
24
+
25
+ def initialize(param_description)
26
+ @param_description = param_description
27
+ end
28
+
29
+ def self.build(_param_description, _argument, _options, &_block)
30
+ raise NotImplementedError
31
+ end
32
+
33
+ def validate(_value)
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def description
38
+ raise NotImplementedError
39
+ end
40
+
41
+ def inspected_fields
42
+ [:param_description]
43
+ end
44
+
45
+ def inspect
46
+ string = "#<#{self.class.name}:#{object_id} "
47
+ fields = inspected_fields.map { |field| "#{field}: #{send(field)}" }
48
+ string << fields.join(', ') << '>'
49
+ end
50
+
51
+ def self.inherited(subclass)
52
+ @validators ||= []
53
+ @validators.unshift(subclass)
54
+ end
55
+
56
+ def self.find(param_description, argument, options, block)
57
+ @validators.each do |type|
58
+ validator = type.build(param_description, argument, options, block)
59
+ return validator if validator
60
+ end
61
+ nil
62
+ end
63
+
64
+ def valid?(value)
65
+ return true if validate(value)
66
+
67
+ raise ParamInvalid.new(@param_description.name, value, description)
68
+ end
69
+
70
+ def to_s
71
+ description
72
+ end
73
+
74
+ def docs
75
+ raise NotImplementedError
76
+ end
77
+
78
+ def expected_type
79
+ 'string'
80
+ end
81
+
82
+ def sub_params
83
+ nil
84
+ end
85
+
86
+ def merge_with(other_validator)
87
+ return self if self == other_validator
88
+
89
+ raise NotImplementedError, "Don't know how to merge #{inspect} with #{other_validator.inspect}"
90
+ end
91
+
92
+ def ==(other)
93
+ return false unless self.class == other.class
94
+
95
+ param_description == other.param_description
96
+ end
97
+ end
98
+
99
+ class TypeValidator < BaseValidator
100
+ def initialize(param_description, argument)
101
+ super(param_description)
102
+ @type = argument
103
+ end
104
+
105
+ def self.build(param_description, argument, _options, block)
106
+ return unless argument.is_a?(::Class)
107
+ return if argument == Hash && !block.nil?
108
+
109
+ new(param_description, argument)
110
+ end
111
+
112
+ def validate(value)
113
+ return false if value.nil?
114
+
115
+ value.is_a?(@type)
116
+ end
117
+
118
+ def description
119
+ "Must be a #{@type}"
120
+ end
121
+
122
+ def expected_type
123
+ if @type.ancestors.include?(Hash)
124
+ 'hash'
125
+ elsif @type.ancestors.include?(Array)
126
+ 'array'
127
+ elsif @type.ancestors.include?(Numeric)
128
+ 'numeric'
129
+ else
130
+ 'string'
131
+ end
132
+ end
133
+ end
134
+
135
+ class RegexpValidator < BaseValidator
136
+ def initialize(param_description, argument)
137
+ super(param_description)
138
+ @regexp = argument
139
+ end
140
+
141
+ def self.build(param_description, argument, _options, _block)
142
+ new(param_description, argument) if argument.is_a?(Regexp)
143
+ end
144
+
145
+ def validate(value)
146
+ value =~ @regexp
147
+ end
148
+
149
+ def description
150
+ "Must match regular expression <code>/#{@regexp.source}/</code>."
151
+ end
152
+
153
+ def expected_type
154
+ 'regexp'
155
+ end
156
+ end
157
+
158
+ # Arguments value must be one of given in array
159
+ class EnumValidator < BaseValidator
160
+ def initialize(param_description, argument)
161
+ super(param_description)
162
+ @array = argument
163
+ end
164
+
165
+ def self.build(param_description, argument, _options, _block)
166
+ new(param_description, argument) if argument.is_a?(Array)
167
+ end
168
+
169
+ def validate(value)
170
+ @array.include?(value)
171
+ end
172
+
173
+ def values
174
+ @array
175
+ end
176
+
177
+ def description
178
+ string = @array.map { |value| "<code>#{value}</code>" }.join(', ')
179
+ "Must be one of: #{string}."
180
+ end
181
+ end
182
+
183
+ class ArrayValidator < BaseValidator
184
+ def initialize(param_description, argument, options = {})
185
+ super(param_description)
186
+ @type = argument
187
+ @items_type = options[:of]
188
+ @items_enum = options[:in]
189
+ end
190
+
191
+ def self.build(param_description, argument, options, block)
192
+ return if argument != Array || block.is_a?(Proc)
193
+
194
+ new(param_description, argument, options)
195
+ end
196
+
197
+ def validate(values)
198
+ return false unless process_value(values).respond_to?(:each) &&
199
+ !process_value(values).is_a?(String)
200
+
201
+ process_value(values).all? { |v| validate_item(v) }
202
+ end
203
+
204
+ def process_value(values)
205
+ values || []
206
+ end
207
+
208
+ def description
209
+ "Must be an array of #{items_type}"
210
+ end
211
+
212
+ def expected_type
213
+ 'array'
214
+ end
215
+
216
+ private
217
+
218
+ def validate_item(value)
219
+ valid_type?(value) && valid_value?(value)
220
+ end
221
+
222
+ def valid_type?(value)
223
+ return true unless @items_type
224
+
225
+ item_validator = BaseValidator.find(nil, @items_type, nil, nil)
226
+
227
+ if item_validator
228
+ item_validator.valid?(value)
229
+ else
230
+ value.is_a?(@items_type)
231
+ end
232
+ end
233
+
234
+ def items_enum
235
+ @items_enum = Array(@items_enum.call) if @items_enum.is_a?(Proc)
236
+ @items_enum
237
+ end
238
+
239
+ def valid_value?(value)
240
+ if items_enum
241
+ items_enum.include?(value)
242
+ else
243
+ true
244
+ end
245
+ end
246
+
247
+ def items_type
248
+ return items_enum.inspect if items_enum
249
+
250
+ @items_type || 'any type'
251
+ end
252
+ end
253
+
254
+ class ArrayClassValidator < BaseValidator
255
+ def initialize(param_description, argument)
256
+ super(param_description)
257
+ @array = argument
258
+ end
259
+
260
+ def validate(value)
261
+ @array.include?(value.class)
262
+ end
263
+
264
+ def self.build(param_description, argument, _options, block)
265
+ return if !argument.is_a?(Array) || argument.first.class != ::Class || block.is_a?(Proc)
266
+
267
+ new(param_description, argument)
268
+ end
269
+
270
+ def description
271
+ "Must be one of: #{@array.join(', ')}."
272
+ end
273
+ end
274
+
275
+ class ProcValidator < BaseValidator
276
+ def initialize(param_description, argument)
277
+ super(param_description)
278
+ @proc = argument
279
+ end
280
+
281
+ def validate(value)
282
+ # The proc should return true if value is valid
283
+ # Otherwise it should return a string
284
+ !(@help = @proc.call(value)).is_a?(String)
285
+ end
286
+
287
+ def self.build(param_description, argument, _options, _block)
288
+ return if !argument.is_a?(Proc) || argument.arity != 1
289
+
290
+ new(param_description, argument)
291
+ end
292
+
293
+ def description
294
+ @help
295
+ end
296
+ end
297
+
298
+ class HashValidator < BaseValidator
299
+ include ApipieDSL::Base
300
+ include ApipieDSL::Parameter
301
+ include ApipieDSL::Klass
302
+
303
+ def initialize(param_description, argument, param_group)
304
+ super(param_description)
305
+ @param_group = param_group
306
+ instance_exec(&argument)
307
+ prepare_hash_params
308
+ end
309
+
310
+ def self.build(param_description, argument, options, block)
311
+ return if argument != Hash || !block.is_a?(Proc) || block.arity.positive?
312
+
313
+ new(param_description, block, options[:param_group])
314
+ end
315
+
316
+ def sub_params
317
+ @sub_params ||= dsl_data[:params].map do |args|
318
+ options = args.find { |arg| arg.is_a?(Hash) }
319
+ options[:parent] = param_description
320
+ ApipieDSL::ParameterDescription.from_dsl_data(param_description.method_description, args)
321
+ end
322
+ end
323
+
324
+ def validate(value)
325
+ return false unless value.is_a?(Hash)
326
+
327
+ @hash_params&.each do |name, param|
328
+ if ApipieDSL.configuration.validate_value?
329
+ param.validate(value[name]) if value.key?(name)
330
+ end
331
+ end
332
+ true
333
+ end
334
+
335
+ def description
336
+ 'Must be a Hash'
337
+ end
338
+
339
+ def expected_type
340
+ 'hash'
341
+ end
342
+
343
+ def default_param_group_scope
344
+ @param_group && @param_group[:scope]
345
+ end
346
+
347
+ def merge_with(other_validator)
348
+ if other_validator.is_a?(HashValidator)
349
+ @sub_params = ApipieDSL::ParameterDescription.unify(sub_params + other_validator.sub_params)
350
+ prepare_hash_params
351
+ else
352
+ super
353
+ end
354
+ end
355
+
356
+ private
357
+
358
+ def prepare_hash_params
359
+ @hash_params = sub_params.each_with_object({}) do |param, hash|
360
+ hash.update(param.name.to_sym => param)
361
+ end
362
+ end
363
+ end
364
+
365
+ class DecimalValidator < BaseValidator
366
+ def self.build(param_description, argument, _options, _block)
367
+ return if argument != :decimal
368
+
369
+ new(param_description)
370
+ end
371
+
372
+ def validate(value)
373
+ value.to_s =~ /\A^[-+]?[0-9]+([,.][0-9]+)?\Z$/
374
+ end
375
+
376
+ def description
377
+ 'Must be a decimal number'
378
+ end
379
+ end
380
+
381
+ class NumberValidator < BaseValidator
382
+ def self.build(param_description, argument, _options, _block)
383
+ return if argument != :number
384
+
385
+ new(param_description)
386
+ end
387
+
388
+ def validate(value)
389
+ value.to_s =~ /\A(0|[1-9]\d*)\Z$/
390
+ end
391
+
392
+ def description
393
+ 'Must be a number'
394
+ end
395
+
396
+ def expected_type
397
+ 'numeric'
398
+ end
399
+ end
400
+
401
+ class BooleanValidator < BaseValidator
402
+ def self.build(param_description, argument, _options, _block)
403
+ return unless %i[bool boolean].include?(argument)
404
+
405
+ new(param_description)
406
+ end
407
+
408
+ def validate(value)
409
+ %w[true false 1 0].include?(value.to_s)
410
+ end
411
+
412
+ def description
413
+ string = %w[true false 1 0].map { |value| "<code>#{value}</code>" }.join(', ')
414
+ "Must be one of: #{string}"
415
+ end
416
+
417
+ def expected_type
418
+ 'boolean'
419
+ end
420
+ end
421
+
422
+ class RestValidator < BaseValidator
423
+ def self.build(param_description, argument, _options, _block)
424
+ return unless %i[rest list splat].include?(argument)
425
+
426
+ new(param_description)
427
+ end
428
+
429
+ def validate(_value)
430
+ # In *rest param we don't care about passed values.
431
+ true
432
+ end
433
+
434
+ def description
435
+ 'Must be a list of values'
436
+ end
437
+
438
+ def expected_type
439
+ 'list'
440
+ end
441
+ end
442
+
443
+ class NestedValidator < BaseValidator
444
+ def initialize(param_description, argument, param_group)
445
+ super(param_description)
446
+ @validator = HashValidator.new(param_description, argument, param_group)
447
+ @type = argument
448
+ end
449
+
450
+ def self.build(param_description, argument, options, block)
451
+ return if argument != Array || !block.is_a?(Proc) || block.arity.positive?
452
+
453
+ new(param_description, block, options[:param_group])
454
+ end
455
+
456
+ def validate(value)
457
+ value ||= []
458
+ return false if value.class != Array
459
+
460
+ value.each do |child|
461
+ return false unless @validator.validate(child)
462
+ end
463
+ true
464
+ end
465
+
466
+ def expected_type
467
+ 'array'
468
+ end
469
+
470
+ def description
471
+ 'Must be an Array of nested elements'
472
+ end
473
+
474
+ def sub_params
475
+ @validator.sub_params
476
+ end
477
+ end
478
+ end
479
+ end