rat_pack_swagger 0.1.0 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e40eeb8f6f982d76bec3166a5083fb9594077446
4
- data.tar.gz: e6a5eeed42020edc342c618c8a4b7195be54df8e
3
+ metadata.gz: 5fdeaee701efde21c026b00ab2dd9c0adb7d75cb
4
+ data.tar.gz: e7eab1bb0d62b5434b6f6daa5f1485cf2c564c92
5
5
  SHA512:
6
- metadata.gz: 8fc08f942f3e40ede0714b0351a849d558c9be8c5c99647dd058b15c51782a11327ac0f5c5f4e7a7ab552553c4ecbcf6f9dede07e49cdeda48330584792e64ec
7
- data.tar.gz: 587bae87402c87cf695387f9432f842ba028a08f478b2f40d3c8fc850824679bc1d36c03ab573a1f950b88dcb547ba0ac455cb4ff73bc6be9b6cb3932ec8989e
6
+ metadata.gz: 5637b41810a20347aa6c8bda4aab77f56afa10d515ad528776d42c2d9cd5e31d4b09df6d6b96af7808276366320e193dc9894fa0d6dc3eb4362c9a17cbaa18bb
7
+ data.tar.gz: 46d1c23c71f96ff53a052b7d5de52a2749dee97268ae0ecb78417914ee84beeed8dbb37d6ff162e8fcc217264918a2a42402167c79e28351cc4767ce47c19dc3
@@ -1,3 +1,3 @@
1
1
  module RatPackSwagger
2
- VERSION = "0.1.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -1,100 +1,316 @@
1
1
  require 'sinatra/base'
2
+ require 'json'
2
3
 
3
- class SwaggerInfo
4
- def initialize(&block)
5
- instance_eval &block
4
+ class SwaggerObject
5
+ instance_methods.each do |m|
6
+ unless m =~ /^__/ || [:inspect, :instance_eval, :object_id].include?(m)
7
+ undef_method m
8
+ end
6
9
  end
7
10
 
8
- def title(app_title)
9
- @title = app_title
11
+ def initialize(*args, **kwargs, &block)
12
+ if args.count > 0 && (!kwargs.empty? || block_given?)
13
+ raise "Cannot give both unnamed arguments AND named arguments or block to Swagger parameter '#{m}'."
14
+ elsif block_given?
15
+ @obj = kwargs unless kwargs.empty?
16
+ instance_eval &block
17
+ elsif !kwargs.empty?
18
+ @obj = kwargs
19
+ elsif args.count > 0
20
+ @obj = [*args]
21
+ else
22
+ raise "Cannot create SwaggerObject with no arguments."
23
+ end
10
24
  end
11
25
 
12
- def description(app_description)
13
- @description = app_description
26
+ def add(*args, **kwargs)
27
+ @obj ||= []
28
+ if !@obj.is_a?(Array)
29
+ raise "Swagger object must be an array to append data '#{item}'"
30
+ elsif args.count > 0
31
+ @obj << [*args, kwargs]
32
+ else
33
+ @obj << kwargs
34
+ end
14
35
  end
15
36
 
16
- def version(app_version)
17
- @version = app_version
37
+ def method_missing(m, *args, **kwargs, &block)
38
+ @obj ||= {}
39
+ if block_given?
40
+ @obj[m] = SwaggerObject.new(**kwargs, &block).get
41
+ elsif !kwargs.empty?
42
+ @obj[m] = SwaggerObject.new(**kwargs).get
43
+ elsif args.count > 1
44
+ @obj[m] = [*args]
45
+ elsif args.count == 1
46
+ @obj[m] = args[0]
47
+ else
48
+ raise "Cannot give zero arguments to Swagger key '#{m}'"
49
+ end
18
50
  end
19
51
 
20
- def contact(app_contact)
21
- @contact = app_contact
52
+ def get
53
+ @obj
22
54
  end
55
+ end
23
56
 
24
- def to_hash
25
- {
26
- title: @title,
27
- description: @description,
28
- version: @version,
29
- contact: {
30
- name: @contact[:name],
31
- email: @contact[:email]
57
+ module RatPackSwagger
58
+ module DefinitionClass
59
+ # makes sure @definition is initialized
60
+ def definition
61
+ @definition ||= {
62
+ type: 'object',
63
+ required: [],
64
+ properties: {}
32
65
  }
33
- }
66
+ @definition
67
+ end
68
+
69
+ # Class declaration API
70
+ def properties(&block)
71
+ definition[:properties].merge!(SwaggerObject.new(&block).get)
72
+ # create top-level property accessors for instance-like usage
73
+ definition[:properties].keys.each do |k|
74
+ self.send(:attr_accessor, k)
75
+ end
76
+ end
77
+ def required(*args)
78
+ definition[:required].concat([*args]).uniq!
79
+ end
80
+ end
81
+
82
+ module Definition
83
+ def self.included mod
84
+ mod.extend DefinitionClass
85
+ end
86
+
87
+ def definition
88
+ self.class.definition
89
+ end
90
+
91
+ # Instance API
92
+ def validate
93
+ validate_object(definition, to_h(false))
94
+ return self
95
+ end
96
+ def from_h(h)
97
+ properties = definition[:properties]
98
+ h.each do |k,v|
99
+ k = k.to_sym
100
+ setter = "#{k}="
101
+ if properties.keys.include?(k)
102
+ # if property type references another class, instantiate it and use hash data to populate it
103
+ if properties[k][:$ref]
104
+ send(setter, properties[k][:$ref].new.from_h(v))
105
+ # if property type is an ARRAY that references another class, instantiate and use hash data to populate them
106
+ elsif properties[k][:type].to_sym == :array && properties[k][:items][:$ref]
107
+ send(setter, v.map{|_| properties[k][:items][:$ref].new.from_h(_) })
108
+ else
109
+ send(setter, v)
110
+ end
111
+ end
112
+ end
113
+ return self
114
+ end
115
+ def to_h(recur = true)
116
+ h = {}
117
+ definition[:properties].keys.each do |p|
118
+ val = send(p)
119
+ puts val
120
+ if recur
121
+ if val.is_a? Array
122
+ h[p] = val.map{|v| v.is_a?(Definition) ? v.to_h : v}
123
+ elsif val.is_a?(Definition)
124
+ h[p] = val.to_h
125
+ else
126
+ h[p] = val
127
+ end
128
+ else
129
+ h[p] = val
130
+ end
131
+ end
132
+ return h
133
+ end
134
+
135
+ # Validation
136
+ def validate_object(object_definition, data)
137
+ check_requireds(object_definition, data)
138
+ check_object_types(object_definition, data)
139
+ end
140
+ def check_requireds(object_definition, data)
141
+ object_definition[:properties].keys.each do |k|
142
+ if object_definition[:required].include?(k) && data[k].nil?
143
+ raise "Missing required property #{k}"
144
+ end
145
+ end
146
+ end
147
+ def check_object_types(object_definition, data)
148
+ data.each do |k,v|
149
+ property = object_definition[:properties][k]
150
+
151
+ # verify 'type' if set
152
+ type = property[:type]
153
+ if type
154
+ case type
155
+ when :string
156
+ raise "Property #{k} must be a string, not a #{v.class}" unless [String, Symbol].include?(v.class)
157
+ when :number
158
+ raise "Property #{k} must be a number, not a #{v.class}" unless v.is_a?(Numeric)
159
+ when :integer
160
+ if v.is_a?(Numeric)
161
+ raise "Property #{k} must be an integer. Value is #{v}" unless v % 1 == 0
162
+ else
163
+ raise "Property #{k} must be an integer, not a #{v.class}" unless v.is_a?(Numeric)
164
+ end
165
+ when :boolean
166
+ raise "Property #{k} must be a string, not a #{v.class}" unless [FalseClass, TrueClass].include?(v.class)
167
+ when :array
168
+ raise "Property #{k} must be an array, not a #{v.class}" unless v.is_a?(Array)
169
+ when :object
170
+ validate_object(property, v)
171
+ else
172
+ raise "Unknown property type '#{type}'"
173
+ end
174
+ end
175
+
176
+ # verify type if a ref to another definition class
177
+ ref = property[:$ref]
178
+ if ref
179
+ raise "Property #{k} should be a #{ref}, not a #{v.class}" unless ref.to_s == v.class.name
180
+ end
181
+
182
+ # verify enum
183
+ enum = property[:enum]
184
+ if enum
185
+ raise "Enum for property #{k} must be an array, not a #{enum.class}" unless enum.is_a?(Array)
186
+ raise "Invalid enum value (#{v}) for property #{k}. Valid enum values are #{enum}" unless enum.include?(v) || enum.include?(v.to_sym)
187
+ end
188
+
189
+ # verify mins and maxes
190
+ min = property[:minimum]
191
+ if min
192
+ raise "Property #{k} value (#{v}) is less than the property minimum (#{min})" unless v >= min
193
+ end
194
+ max = property[:maximum]
195
+ if min
196
+ raise "Property #{k} value (#{v}) is less than the property maximum (#{max})" unless v <= max
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ def transform_hash_values(obj, &block)
204
+ if obj.is_a? Hash
205
+ obj.each do |k,v|
206
+ if v.is_a?(Hash) || v.is_a?(Array)
207
+ obj[k] = transform_hash_values(v, &block)
208
+ else
209
+ obj[k] = yield(k,v)
210
+ end
211
+ end
212
+ elsif obj.is_a? Array
213
+ obj.each_with_index do |e,i|
214
+ if e.is_a?(Hash) || e.is_a?(Array)
215
+ obj[i] = transform_hash_values(e, &block)
216
+ end
217
+ end
218
+ else
219
+ raise "Argument must be a Hash or an Array."
34
220
  end
221
+ obj
35
222
  end
36
223
 
37
224
  module Sinatra
38
225
  module RatPackSwagger
39
- def swagger(version)
226
+ def swagger(*args, **kwargs, &block)
40
227
  @@doc ||= {}
41
- @@doc["swagger"] = version
228
+ if args.count.zero?
229
+ # assume passing data into method call
230
+ @@doc.merge!(SwaggerObject.new(**kwargs, &block).get)
231
+ else
232
+ # assume single argument is filename of existing json
233
+ @@doc.merge!(::JSON.parse(File.read(args[0])))
234
+ end
42
235
  end
43
236
 
44
- def info(&block)
45
- @@doc ||= {}
46
- @@doc["info"] = SwaggerInfo.new(&block).to_hash
237
+ def description(d)
238
+ @@description = d
47
239
  end
48
240
 
49
- def desc(description)
50
- @@desc = description
241
+ def param(**kwargs, &block)
242
+ @@parameters ||= []
243
+ @@parameters << SwaggerObject.new(**kwargs, &block).get
51
244
  end
52
245
 
53
- def param(opts)
54
- puts "in param"
55
- @@parameters ||= []
56
- puts opts
57
- @@parameters << opts
58
- end
59
-
60
- def add_swagger_route(app)
61
- app.get "/v2/swagger.json" do
62
- content_type "application/json"
63
- @@doc.to_json
246
+ def tags(*args)
247
+ @@tags ||= []
248
+ args.each{|a| @@tags << a}
249
+ end
250
+
251
+ def summary(s)
252
+ @@summary = s
253
+ end
254
+
255
+ def response(http_status_code, **kwargs, &block)
256
+ @@responses ||= {}
257
+ @@responses[http_status_code] = SwaggerObject.new(**kwargs, &block).get
258
+ end
259
+
260
+ def definitions(*constants)
261
+ @@doc[:definitions] ||= {}
262
+ constants.each do |constant|
263
+ if Module === constant
264
+ constant.constants.each do |c|
265
+ klass = constant.const_get(c)
266
+ if Class === klass
267
+ @@doc[:definitions][c] = klass.definition
268
+ end
269
+ end
270
+ else
271
+ @@doc[:definitions][constant.to_s.rpartition('::').last] = constant.definition
272
+ end
64
273
  end
65
274
  end
66
275
 
276
+ def self.registered(app)
277
+ app.get '/v2/swagger.json' do
278
+ content_type 'application/json'
279
+ response['Access-Control-Allow-Origin'] = '*'
280
+ response['Access-Control-Allow-Headers'] = 'Content-Type, api-key, Authorization'
281
+ response['Access-Control-Allow-Methods'] = 'GET, POST'
282
+ doc = transform_hash_values(@@doc) do |k,v|
283
+ k.to_s == '$ref' ? "\#/definitions/#{v.to_s.rpartition('::').last}" : v
284
+ end
285
+ doc.to_json
286
+ end
287
+ @@doc = {}
288
+ end
289
+
67
290
  def self.route_added(verb, path, block)
68
- return if path == "/v2/swagger.json"
69
- return unless ["GET", "POST", "PUT", "DELETE"].include?(verb)
291
+ return if path == '/v2/swagger.json'
292
+ return unless ['GET', 'POST', 'PUT', 'DELETE'].include?(verb)
70
293
 
71
- @@doc ||= {}
72
- @@doc["paths"] ||= {}
73
-
74
- @@doc["paths"][path] = {
75
- verb.downcase => {
76
- "description" => @@desc,
77
- "produces" => [ "application/json" ],
78
- "parameters" => @@parameters.map { |p|
79
- {
80
- "name" => p[:name],
81
- "in" => p[:type],
82
- "description" => p[:desc],
83
- "required" => p[:required] == true,
84
- "type" => p[:type]
85
- }
86
- },
87
- "responses" => {
88
- "200" => {
89
- "description" => @@desc
90
- }
91
- }
92
- }
294
+ @@doc['paths'] ||= {}
295
+ @@description ||= ''
296
+ @@tags ||= []
297
+ @@summary ||= ''
298
+ @@responses ||= {}
299
+
300
+ @@doc['paths'][path] ||= {}
301
+ @@doc['paths'][path][verb.downcase] = {
302
+ tags: @@tags,
303
+ description: @@description,
304
+ summary: @@summary,
305
+ parameters: @@parameters,
306
+ responses: @@responses
93
307
  }
94
308
 
95
309
  @@parameters = []
310
+ @@description = nil
311
+ @@tags = nil
312
+ @@summary = nil
313
+ @@responses = nil
96
314
  end
97
315
  end
98
-
99
- register RatPackSwagger
100
316
  end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rat_pack_swagger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Hildebrand
8
+ - Mike Schreiber
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2015-06-26 00:00:00.000000000 Z
12
+ date: 2015-07-15 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: sinatra